I am only receiving partial output from an object Groovy - java

I have an object in Groovy and in this object there appears to be either a map. I am trying to get the "value" rather than the key from this objects map. How do I go about doing something like this?
This is the output I am receiving in my console the first is the object and the 2nd is the output for the property fromValues...
<com.atlassian.jira.issue.changehistory.ChangeHistoryItem#b791639b id=10130 changeGroupId=10113 userKey=Charley field=status projectId=10000 issueId=10217 issueKey=ICB-128 created=2019-03-12 14:19:22.0 nextChangeCreated=292278994-08-17 02:12:55.807 fromValues=[10003:To Do] toValues=[3:In Progress]>
[runner.ScriptRunnerImpl]: 10003
Here is my code below that I am working with so far in attempt to get this information: (Just so you understand the context... I am pulling an issue from Jira software and extracting the history of that particular issue.)
IssueManager issueManager = ComponentAccessor.getIssueManager();
def issue = issueManager.getIssueObject("ICB-128");
def changeHistoryManager = ComponentAccessor.getChangeHistoryManager()
def changeItems = changeHistoryManager.getAllChangeItems(issue)
//def changeItems = changeHistoryManager.getChangeHistoriesSince(issue, since)
changeItems.eachWithIndex { item, index ->
//log.warn(changeItems.dump())
def last_change = changeItems[index]
log.warn(last_change.dump())
def text = "LAST MODIFIED FIELD:"+ last_change["field"] + "; FROM VALUE:"+ last_change["fromValue"] + "; TO VALUE:"+ last_change["toValue"]
if (last_change["fromValue"])
{
log.warn(last_change["fromValue"])
}
}
Edit 1: I have corrected for what #vahid suggested and now have both values returned how I want it. I have tried numerous ways to call and retrieve specifically the value for that map and nothing has worked (.get or .value or even .key)... Any suggestions?
Edit 2: Turns out this extracted field is now considered as a type "hashmap"

Related

modify underlying result/value of async object

I am using Kotlin in a webserver app and I have a line of code as follows:
.onComplete { jsonResult: AsyncResult<JsonObject>? ->
Now what I want to do is change the underlying JsonObject wrapped in the AsyncResult, so that it is going to be reflected further downstream.
var res: JsonObject? = jsonResult?.result()
if (res != null) {
if (res.getInteger("files_uploaded") > 0) {
res.put("URL", "Some URL")
}
}
I was then imagining to update the underlying JSON object in the result but not sure how to do that.
please take note that single quotes are missing and ` appear as \` because the code formatting. I tried to leave what seemed least confusing...
You should be able to make changes in the conditional statement
if (res !=null) {
res being the JsonObject:
console.log(res);
would show you what's in there. You may need to use
let resXmodifiedX = JSON.parse(res);
One approach is to write a function and pass res to that function which you can do if it is in the console.log(res).
Some notes on what's below:
place the function somewhere consistent maybe at the bottom of the file...
objects often have multiple levels res.person.name, res.contact.email, or whatever...
use multiple for loops:
let level = res[key]; for(child in level) {
you don't need to do this if you know exactly what object attributes you need to update.
you can set the value directly but you always want to test for it before trying to set it to avoid errors that stop execution.
let toBe = toBe =>`${toBe}`;
let update = (res)?toBe(update(res)):toBe('not Found');
This option is really only if you know for sure that data will be there and you can't proceed without it. Which is not uncommon but also not how JSON is designed to be used.
The code below is a concise way to make some simple changes but may not be an ideal solution. To use it xModify(res) replaces console.log(res) above.
function xModify(x) {
let resXmodifiedX = JSON.parse(x);
let res = resXmodifiedX;
for (key in res) {
res[key] = key=='name'? \`My change ${res[key]}\`: key=='other'? \`My Change ${res[key]}\`:res[key];
resXmodifiedX = JSON.stringify(res);
return resXmodifiedX;
}
That will update res.name and res.other otherwise res[key] is unchanged. If you do not need to parse res change let res = xModifiedx; to let res = x; remove the first line and change the last two lines to return res;
function xModify(x) {
let res = x;
for (key in res) {
res[key] = key=='name'? \`My change ${res[key]}\`: key=='other'? \`My Change ${res[key]}\`:res[key];
return res;
}
If your data is numeric which is not generally the case in a web server response scenario this is a terrible approach. Because it is probably a string I used the template variable as a way to easily add a complex pattern in place of a string. My change ${res[key]} not a real world example. Any valid JS code can go in the ${ } (template variable). I've been defaulting to the first pattern more and more.
let me = (bestCase)?`${'the best version'} of myself`:`${'someone'} I'm ok with`;

Extract Object Data into usable format

I am a real newbie so go easy on me and my terminology, I am still learning!
I have a Backendless database I would like to show in my app.
I have successfully connected it to my Android Studio app, queried it and returned the data in the following method:
Backendless.Data.of( "database" ).find( queryBuilder, new AsyncCallback>(){public void handleResponse(List'<'Map'>'response ){
The narrative on the Backendless SDK says "the "response" object is a collection of java.util.Map objects"
I then used an iterator:
Iterator itr = response.iterator();
And a while loop to 'get' the object:
Object element = itr.next();
I am happy up until this point, the next step is to extract the useful data from element.
I have tried many options of but the only one I have working is element.toString() and use various methods to pick out what I want. This seems so inefficient I thought I would ask the experts for a better option!?
Your question is rather about working with Java Map interface. So I'd advice you to look into its documentation and maybe some tutorials on this topic.
As to your Backendless question, it looks like you got the request part right. Here is the extended example from the docs, which shows you how to retrieve the object fields:
Backendless.Persistence.of( "Contact" ).find( new AsyncCallback<List<Map<String, Object>>>(){
#Override
public void handleResponse( List<Map<String, Object>> foundContacts )
{
Iterator<Map<String, Object>> contactsIterator = foundContacts.iterator();
while( contactsIterator.hasNext() )
{
Map<String, Object> contact = contactsIterator.next();
String name = (String) contact.get( "name" ); // in case you have STRING field 'name' in Backendless database
Integer age = (Integer) contact.get( "age" ); // in case you have INT field 'age' in Backendless database
// etc.
}
}
#Override
public void handleFault( BackendlessFault fault )
{
System.out.err( "Failed find: " + fault );
}
});
As you may see, the main concern is to retrieve a Map instead of Object from the response List.
And also your question would be more useful with code samples of what you tried and maybe direct link to the docs you used as an example.

How to implement this property file?

I have this piece of data (this is just one part of one line of the whole file):
000000055555444444******4444 YY
I implemented this CSV config file to be able to read each part of the data and parse it:
128-12,140-22,YY
The first pair (128-12) represent at what position in the line to start reading and then the amount of characters to read, that first pair is for the account number.
The second pair if for the card number.
And the thir parameter is for the registry name.
Anyways, what I do is String.split(","), and then assign the array[0] as the account number and so on.
But I want to change that CSV config file to a Property file, but I'm not sure of how to implement that solution, if I use a Properties file I'd have to add a bunch of if/then in order to properly map my values, here's what I'm thinking of doing:
Property cfg = new Property();
cfg.put("fieldName", "accountNumber");
cfg.put("startPosition", "128");
cfg.put("length", "12");
But I'd have to say if("fieldName".equals("accountNumber")) then assign accountNumber; is there a way to implement this in such a way that I could avoid implementing all this decisions? right now with my solution I don't have to use ifs, I only say accountNumber = array[0]; and that's it, but I don't think that's a good solution and I think that using Property would be more elegant or efficient
EDIT:
This probably needs some more clarification, this data I'm showing is part of a parsing program that I'm currently doing for a client; the data holds information for many many of their customers and I have to parse a huge mess of data that I receive from them, into something more readable in order to convert it to a PDF file, so far the program is under production but I'm trying to refactor it a little bit. All the customer's information is saved into different Registry classes, each class having it's own set of fields with unique information, lets say that this is what RegistryYY would look like:
class RegistryYY extends Registry{
String name;
String lastName;
PhysicalAddress address;
public RegistryYY(String dataFromFile) {
}
}
I want to implement the Property solution, because in that way, I could make the Property for parsing the file, or interpreting the data correctly to be owned by each Registry class, I mean, a Registry should know what data it needs from the data received from the file right?, I think that if I do it that way, I could make each Registry an Observer and they would decide if the current line read from the file belongs to them by checking the registry name stored in the current line and then they'd return an initialized Registry to the calling object which only cares about receiving and storing a Registry class.
EDIT 2:
I created this function to return the value stored in each line's position:
public static String getField(String fieldParams, String rawData){
// splits the field
String[] fields = fieldParams.split("-");
int fieldStart = Integer.parseInt(fields[0]); // get initial position of the field
int fieldLen = Integer.parseInt(fields[1]); // get length of field
// gets field value
String fieldValue = FieldParser.getStringValue(rawData, fieldStart, fieldLen);
return fieldValue;
}
Which works with the CSV file, I'd like to change the implementation to work with the Property file instead.
Is there any reason why you need to have the record layout exposed to the outside world ? does it need to be configurable ?
I think your proposed approached of using the Property file is better than your current approach of using the CSV file since it is more descriptive and meaningful. I would just add a "type" attribute to your Property definition as well to enforce your conversion i.e. for Numeric/String/Date/Boolean.
I wouldnt use an "if" statement to process your property file. You can load all the properties into an Array at the beginning and then iterate around the array for each line of your data file and process that section accordingly something like pseudo code below,
for each loop of data-file{
SomeClass myClass = myClassBuilder(data-file-line)
}
myClassBuilder SomeClass (String data-file-line){
Map<column, value> result = new HashMap<>
for each attribute of property-file-list{
switch attribute_type {
Integer:
result.put(fieldname, makeInteger(data-file-line, property_attribute)
Date:
result.put(fieldname, makeDate(data-file-line, property_attribute)
Boolean :
result.put(fieldname, makeBoolean(data-file-line, property_attribute)
String :
result.put(fieldname, makeBoolean(data-file-line, property_attribute)
------- etc
}
}
return new SomeClass(result)
}
}
If your record layout doesnt need to be configurable then you could do all the conversion inside your Java application only and not even use a Property file.
If you could get your data in XML format then you could use the JAXB framework and simply have your data definition in an XML file.
First of all, thanks to the guys who helped me, #robbie70, #RC. and #VinceEmigh.
I used YAML to parse a file called "test.yml" with the following information in it:
statement:
- fieldName: accountNumber
startPosition: 128
length: 12
- fieldName: cardNumber
startPosition: 140
length: 22
- fieldName: registryName
startPosition: 162
length: 2
This is what I made:
// Start of main
String fileValue = "0222000000002222F 00000000000111110001000000099999444444******4444 YY";
YamlReader reader = new YamlReader(new FileReader("test.yml"));
Object object = reader.read();
System.out.println(object);
Map map = (Map) object;
List list = (List) map.get("statement");
for(int i = 0; i < list.size(); i++) {
Map map2 = (Map) list.get(i);
System.out.println("Value: " + foo(map2, fileValue));
}
}
// End of main
public static String foo(Map map, String source) {
int startPos = Integer.parseInt((String) map.get("startPosition"));
int length = Integer.parseInt((String) map.get("length"));
return getField(startPos, length, source);
}
public static String getField(int start, int length, String source) {
return source.substring(start, start+length);
}
It correctly displays the output:
Value: 000000099999
Value: 444444******4444
Value: YY
I know that maybe the config file has some lists and other unnecessary values and what nots, and that maybe the program needs a little improvement, but I think that I can take it from here and implement what I had in mind.
EDIT:
I made this other one, using Apache Commons, this is what I have in the configuration property file:
#properties defining the statement file
#properties for account number
statement.accountNumber.startPosition = 128
statement.accountNumber.length = 12
statement.account.rules = ${statement.accountNumber.startPosition} ${statement.accountNumber.length}
#properties for card number
statement.cardNumber.startPosition = 140
statement.cardNumber.length = 22
statement.card.rules = ${statement.cardNumber.startPosition} ${statement.cardNumber.length}
#properties for registry name
statement.registryName.startPosition = 162
statement.registryName.length = 2
statement.registry.rules = ${statement.registryName.startPosition} ${statement.registryName.length}
And this is how I read it:
// Inside Main
String valorLeido = "0713000000007451D 00000000000111110001000000099999444444******4444 YY";
Parameters params = new Parameters();
FileBasedConfigurationBuilder<FileBasedConfiguration> builder =
new FileBasedConfigurationBuilder<FileBasedConfiguration>(PropertiesConfiguration.class)
.configure(params.properties()
.setFileName("config.properties"));
try {
Configuration config = builder.getConfiguration();
Iterator<String> keys = config.getKeys();
String account = getValue(getRules(config, "statement.account.rules"), valorLeido);
String cardNumber = getValue(getRules(config, "statement.card.rules"), valorLeido);
String registryName = getValue(getRules(config, "statement.registry.rules"), valorLeido);
} catch (org.apache.commons.configuration2.ex.ConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// End of Main
public static String getRules(Configuration config, String rules) {
return config.getString(rules);
}
public static String getValue(String rules, String source) {
String[] tokens = rules.split(" ");
int startPos = Integer.parseInt(tokens[0]);
int length = Integer.parseInt(tokens[1]);
return getField(startPos, length, source);
}
I'm not entirely sure, I think that with the YAML file it looks simpler, but I really like the control I get with the Apache Commons Config, since I can pass around the Configuration object to each registry, and the registry knows what "rules" it wants to get, let's say that the Registry class only cares about "statement.registry.rules" and that's it, with the YAML option I'm not entirely sure of how to do that yet, maybe I'll need to experiment with both options a little bit more, but I like where this is going.
PS:
That weird value I used in fileValue is what I'm dealing with, now add nearly 1,000 characters to the length of the line and you'll understand why I want to have a config file for parsing it (don't ask why....clients be crazy)

Java Multiple Sets within a Map

I am reading from a CSV file which contains data about Hills, all the reading etc works fine but now I want to create a map which associates the "County Name" (A column header in the file) with all the hills that have the county name in it.
I am along the right path, because my code works for the first countyName, but the problem is when my for loop If statement goes to the else statement, I want it to technically create another set (clearing the previous values) and assigning the new hill data with the county name
My output is:
{Perth and Kinross=[16,Knock of Crieff,Perth and Kinross,279.0,56.389329,-3.826973, 3,Creag Uchdag,Perth and Kinross,879.0,56.465278,-4.098107]}
So it has all the hills with "Perth and Kinross" in it. So now my next county name is "Stirling" for example so the map should end up like
{Perth and Kinross=[16,Knock of Crieff,Perth and Kinross,279.0,56.389329,-3.826973, 3,Creag Uchdag,Perth and Kinross,879.0,56.465278,-4.098107], Stirling=[7,Meall Buidhe,Stirling,719.0,56.419004,-4.308645]}
What I'm unsure about is how to create another set without clearing the values stored for Perth and Kinross
My code is:
Map<String, Set<Hill>> hillsByCounty = new HashMap<>();
if (h.getCounty().equals(countyName)) {
hillsByCounty.get(countyName);
currentSet.add(h);
hillsByCounty.put(countyName, currentSet);
} else {
countyName = h.getCounty();
currentSet.clear();
currentSet.add(h);
}
}
return hillsByCounty;
}
Where exactly is it going wrong. I have a feeling it is the clear function but i'm not sure how else i would do this.
My current code prints out:
{Perth and Kinross=[7,Meall Buidhe,Stirling,719.0,56.419004,-4.308645], Stirling=[7,Meall Buidhe,Stirling,719.0,56.419004,-4.308645]}
As its overwriting the set. What would be my workaround?
You reuse the same set. So if your county changes, you clear the set and fill it with the next county. Instead you should create a new set. As long as you reuse the same set, well, you reuse the same set, empty it and fill something else in it. hillsByCounty.put(...) does not clone the set, but just stores a reference.
The value for each entry of the map is the same set so every modification on one set will also appear at the other entries.
You can check for an existing set in the loop body:
for (Hill h : hills) {
Set<Hill> currentSet = hillsByCountry.get(h.getCountry());
if (currentSet == null) {
currentSet = new HashSet<>();
hillsByCountry.put(h.getCountry(), currentSet);
}
currentSet.add(h);
}

Updating pre-existing documents in mongoDB java driver when you've changed document structure

I've got a database of playerdata that has some pre-existing fields from previous versions of the program. Example out-dated document:
{
"playername": "foo"
}
but a player document generated under the new version would look like this:
{
"playername": "bar",
"playercurrency": 20
}
the issue is that if I try to query playercurrency on foo I get a NullPointerException because playercurrency doesn't exist for foo. I want to add the playercurrency field to foo without disturbing any other data that could be stored in foo. I've tried some code using $exists Example:
players.updateOne(new Document("playername", "foo"), new Document("$exists", new Document("playername", "")));
players.updateOne(new Document("playername", "foo"), new Document("$exists", new Document("playercurrency", 20)));
My thought is that it updates only playercurrency because it doesn't exist and it would leave playername alone becuase it exists. I might be using exists horribly wrong, and if so please do let me know because this is one of my first MongoDB projects and I would like to learn as much as I possibly can.
Do you have to do this with java? Whenever I add a new field that I want to be required I just use the command line to migrate all existing documents. This will loop through all players that don't have a playercurrency and set it to 0 (change to whatever default you want):
db.players.find({playercurrency:null}).forEach(function(player) {
player.playercurrency = 0; // or whatever default value
db.players.save(player);
});
This will result in you having the following documents:
{
"playername" : "foo",
"playercurrency" : 0
}
{
"playername" : "bar",
"playercurrency" : 20
}
So I know that it is normally frowned upon on answering your own question, but nobody really posted what I ended up doing I would like to take this time to thank #Mark Watson for answering and ultimately guiding me to finding my answer.
Since checking if a certain field is null doesn't work in the MongoDB Java Driver I needed to find a different way to know when something is primed for an update. So after a little bit of research I stumbled upon this question which helped me come up with this code:
private static void updateValue(final String name, final Object defaultValue, final UUID key) {
if (!exists(name, key)) {
FindIterable iterable = players.find(new Document("_id", key));
iterable.forEach(new Block<Document>() {
#Override
public void apply(Document document) {
players.updateOne(new Document("_id", key), new Document("$set", new Document(name, defaultValue)));
}
});
}
}
private static boolean exists(String name, UUID key) {
Document query = new Document(name, new Document("$exists", true)).append("_id", key);
return players.count(query) == 1;
}
Obviously this is a little specialized to what I wanted to do, but with little revisions it can be easliy changed to work with anything you might need. Make sure to replace players with your Collection object.

Categories

Resources