iterate, find and update value in nested Map<String, ?> - java

I have a YML file, which I parse to Map using yamlBeans library.
I don't know how deep the nested map goes.
for example:
key1:
key2: value1
key3:
key4: value2
key5: value3
I need to find a specific value in this map, update it, and write the map back to YML file (which I know how to do).
This is my code for updating the value, and it's working.
However, this is only iterating twice through the nested map, and I need it to iterate it for as long as needed:
static void updateYmlContent(Map<String, ?> ymlMap, String value, String... keys) {
boolean found = false;
for (Map.Entry entry : ymlMap.entrySet()) {
if (entry.getKey().equals(keys[0])) {
found = true;
for (Map.Entry subEntry : ((Map<?, ?>) entry.getValue()).entrySet()) {
if (subEntry.getKey().equals(keys[1])) {
subEntry.setValue(value);
break;
} else {
throwKeyNotFoundException(keys[1]);
}
}
break;
}
}
if (!found) {
throwKeyNotFoundException(keys[0]);
}
}

Use recursion and a depth counter to drop through each level of the map.
I didn't compile this, so it probably needs a little tweaking, but here's the basic idea:
static void updateYmlContent(Map<String, ?> ymlMap, String value, String... keys) {
int depth = 0;
findAndReplaceContent(ymlMap, value, keys, depth);
}
static void findAndReplaceContent(Map map, .......) {
if (map.containsKey(keys[depth]))
{
if (depth == keys.length - 1)
{
// found it
map.put(keys[depth], value);
// done
}
else
{
findAndReplaceContent(map.get(keys[depth]), value, keys, depth+1);
}
}
else
{
// throw key not found
}
}

If ymlMap is mutable, it should be of type Map<String, Object> (ideally), i belive you have checked it already.
#SuppressWarnings("unchecked")
static void updateYmlContent(Map<String, ?> ymlMap, String value, String... keys)
{
for (int i = 0, lastIndex = keys.length - 1; i <= lastIndex; i++)
{
String key = keys[i];
Object v = ymlMap.get(key);
if (v == null) // Assumed value is never null, if key exists
throw new /* KeyNotFound */ RuntimeException("Key '" + key + "' not found");
if (i < lastIndex)
ymlMap = (Map<String, Object>) v;
else
((Map<String, String>) ymlMap).put(key, value);
}
}

You can do it via one for loop, please see the example:
private static void updateYmlContent(Map<String, Object> map, String newValue, String... keys) {
for (int i = 0; i < keys.length; i++) {
if (i + 1 == keys.length) {
map.put(keys[i], newValue);
return;
}
if (map.get(keys[i]) instanceof Map) {
map = (Map<String, Object>) map.get(keys[i]);
} else {
throw new RuntimeException();
}
}
throw new RuntimeException();
}
Also please see how it is used:
public static void main(String[] keys) throws Exception {
Map<String, Object> ymlMap = new HashMap<>();
Map<Object, Object> nested1 = new HashMap<>();
Map<Object, Object> nested2 = new HashMap<>();
nested2.put("key3", "oldvalue1");
nested2.put("key4", "oldvalue2");
nested1.put("key2", nested2);
ymlMap.put("key1", nested1);
updateYmlContent(ymlMap, "new", "key1", "key2", "key3");
}

Related

How to transform this piece of code using Java Stream

In order to defend against XSS attacks, I wrote a class that extends from HttpServletRequestWrapper and overrides the getParameterValues method, the code is shown below:
#Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> parameters = super.getParameterMap();
LinkedHashMap<String, String[]> map = new LinkedHashMap<>();
if (parameters != null) {
for (String key : parameters.keySet()) {
String[] values = parameters.get(key);
for (int i = 0; i < values.length; i++) {
String value = values[i];
if (!StrUtil.hasEmpty(value)) {
value = HtmlUtil.filter(value);
}
values[i] = value;
}
map.put(key, values);
}
}
return map;
}
I'm wondering if this piece of code could be transformed using Java Stream because I see a lot of if condition judgement and for loop.
Methods:StrUtil.hasEmpty and HtmlUtil.filter are come from here: Hutool
Any suggestions to improve the performance of this code are welcome.
Yes you can make elegent solution with streams plus map / filter but it will be re-allocating the memory footprint of the request data creating many new for objects for Map, N * Map.Entry, N * String[], and filtered strings and other intermediate steps.
Alternatively consider simplifying the logic of your existing loop just to fix each String[] value in place and return the existing map:
public Map<String, String[]> getParameterMap() {
Map<String, String[]> parameters = super.getParameterMap();
if (parameters != null) {
for (String[] values : parameters.values()) {
for (int i = 0; i < values.length; i++) {
String value = values[i];
if (!StrUtil.hasEmpty(value)) {
values[i] = HtmlUtil.filter(value);
}
}
}
}
return parameters;
}
#Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> parameters = super.getParameterMap();
LinkedHashMap<String, String[]> map = new LinkedHashMap<>();
if (parameters != null) {
map = parameters.entrySet().stream().collect(Collectors.toMap(
Map.Entry::getKey,
v -> Arrays.stream(v.getValue())
.filter(val -> !StrUtil.hasEmpty(val))
.map(HtmlUtil::filter).collect(Collectors.toList()).toArray(new String[0]),
(x, y) -> y, LinkedHashMap::new
));
}
return map;
}

Custom implementation of a Map collection in Java

I'm trying to create an implementation of a Map collection that stores a pair of key and value items.
The error occurs during runtime when I try to register a key-value pair and hit this line.
EntryNode<K, V> mapEntry = mapEntryList[mapSize];
I'm running out of ideas on what could be the issue, any help is appreciated. Thanks.
//Driver class to test output
public class Driver{
public static void main(String[] args) {
MyMap<String, String> mapInstance = new MyMap<String, String>();
myMap.register("Key1", "Value");
System.out.println(myMap.get("Key1"));
}
}
public class MyMap<K, V> implements MapInterface<K, V>{
private EntryNode<K, V>[] mapEntryList;
private int mapSize = 0;
public MyMap(){
}
public MyMap(int capacity){
this.mapEntryList = new EntryNode[mapSize];
}
//static
public class EntryNode<K, V>{
K keyElement;
V valueElement;
EntryNode<K, V> nextMapEntry;
public EntryNode(K keyElement, V valueElement, EntryNode<K, V> nextMapEntry) {
this.keyElement = keyElement;
this.valueElement = valueElement;
this.nextMapEntry = nextMapEntry;
}
public K getKey() {
return keyElement;
}
public V getValue() {
return valueElement;
}
public EntryNode<K, V> getNextMapEntry() {
return nextMapEntry;
}
public final V setNewValue(V newValueElement) {
V oldValueElement = valueElement;
valueElement = newValueElement;
return oldValueElement;
}
public String toString() {
return "{" + keyElement + ", " + valueElement + "}";
}
}
public int size() {
return mapSize;
}
public V get(K keyElement) {
EntryNode<K, V> mapEntry = mapEntryList[mapSize];
int count = mapSize;
boolean entryPresent = false;
EntryNode tempNode = firstEntry;
while (!entryPresent && mapEntry != null) {
if (keyElement == mapEntry.keyElement) {
entryPresent = true;
return mapEntry.valueElement;
}
else {
mapEntry = mapEntry.nextMapEntry;
}
}
return null;
}
public void register(K newKeyElement, V newValueElement) {
EntryNode<K, V> newEntry = new EntryNode(newKeyElement, newValueElement, null);
EntryNode<K, V> mapEntry = mapEntryList[mapSize];
boolean entryPresent = false;
while (!entryPresent && mapEntry != null) {
if (newKeyElement == mapEntry.keyElement) {
entryPresent = true;
break;
}
else {
if(mapEntry.nextMapEntry == null){
break;
}
mapEntry = mapEntry.nextMapEntry;
}
}
if(!entryPresent){
mapEntry.nextMapEntry = newEntry;
mapSize++;
}
}
public void remove(K removeKeyElement) {
EntryNode previousEntry = null;
EntryNode<K, V> mapEntry = mapEntryList[mapSize];
while (mapEntry != null) {
if (removeKeyElement == mapEntry.keyElement) {
mapEntry.keyElement = null;
mapEntry.valueElement = null;
previousEntry.nextMapEntry = mapEntry.nextMapEntry;
break;
}
else {
previousEntry = mapEntry;
mapEntry = mapEntry.nextMapEntry;
}
}
}
private int getEntriesSize() {
return mapEntryList.length;
}
public String toString() {
StringBuilder stringOutput = new StringBuilder();
for (EntryNode entry : mapEntryList) {
stringOutput.append("[");
while (entry != null) {
stringOutput.append(entry);
if (entry.nextMapEntry != null) {
stringOutput.append(", ");
}
entry = entry.nextMapEntry;
}
stringOutput.append("]");
}
return "{" + stringOutput.toString() + "}";
}
}
Let's say I make a new map, and then call .get() on it.
MyMap<Integer, String> m = new MyMap<>();
m.get(5);
This constructor (the no-args one) means that mapEntryList is never set, which means it defaults to null. Thus, when the get is invoked, the first thing your get method does is dereference the mapEntryList field (the foo[idx] construct dereferences foo). Dereferencing a null value means: NullPointerException is thrown. Clearly the intent of your code is to return null if the key is not in the map, so, that part is broken.
Alternatively, I'll go with:
MyMap<Integer, String> m = new MyMap<>(10);
m.get(5);
This time, I call the second constructor. This constructor takes the 'capacity' value, tosses it in the trash, and makes a 0-sized array. Then, get is invoked, and your code runs:
mapEntry = mapEntryList[mapSize];
This cannot work; you cannot get anything out of a 0-length array. In fact, if you write:
int[] a = new int[5];
a[5];
(as in, you make a new array of size X and then ask for the element with index X), you always get an IndexOutOfBoundsException: in java all arrays are 0-indexed. a[0] is the first element, and new int[1] makes a 1-size int array, so doing a[1] (asking for the second element) on a 1-size array is not possible (it only has one element).
There are about 50 other issues with this code, you really need to take it one small step at a time and debug this code: Instead of just looking at all this code and going: Uh, it doesn't work - you need to debug it:
Write some code, then run it. As you run it, 'mentally run it': Take pen and paper if you have to, walk through the code line by line and figure out what each line should be doing, by hand. Then check what you think should happen vs. what actually happens, either by using a debugger or adding a ton of System.out statements if you have to. There where the code does something different from what you thought? You found a bug. Probably the first in quite a large list of them. Fix it, and keep going until the bugs are gone.

Java - Converting Defined Intergers to Name Value

Not sure how to phrase that but I have this:
In a
public class DefinedValues{
public static final int CommandGroupLength = 0x00000001;
}
I want a way of getting the String "CommandGroupLength" from value 0x00000001;
Is that possible?
do you want to access the name of the variable that has the value 0x00000001? than this is not possible:
public class DefinedValues {
public static final int CommandGroupLength = 0x00000001;
}
with Java8 it is technically possible to at least get the name of a variable via reflection, see Java Reflection: How to get the name of a variable?
you can achieve the same thing much easier with a Map, which contains Key-Value-Pairs.
Map<String,Integer> myMap= new HashMap<>();
myMap.put("CommandGroupLength", 0x00000001);
then you write a function which searches in the entrySet of the Map for all keys that have that value, since it is not ensured there is only one, it'll need to return a Collection or Array or something similar.
here's my code:
public static void main(String[] args) {
Map<String,Integer> myMap = new HashMap<>();
myMap.put("CommandGroupLength", 0x00000001);
myMap.put("testA", 5);
myMap.put("testB", 12);
myMap.put("testC", 42);
System.out.println("Searching for value 0x00000001 in myMap");
Set<String> searchResults = findKeyByValue(myMap, 0x00000001);
System.out.println("I found the following keys:");
boolean isFirst = true;
for(String result : searchResults) {
if(isFirst)
isFirst = false;
else
System.out.printf(", ");
System.out.printf("%s", result);
}
}
public static Set<String> findKeyByValue(Map<String, Integer> map, Integer value) {
Set<String> result = new HashSet<>();
if(value != null) {
Set<Entry<String, Integer>> entrySet = map.entrySet();
for(Entry<String, Integer> entry : entrySet) {
if(value.equals(entry.getValue())) {
result.add(entry.getKey());
}
}
}
return result;
}

TreeMap<String, Integer> object's get method return null value

import java.util.*;
public class Sort {
static class ValueComparator implements Comparator<String> {
Map<String, Integer> base;
ValueComparator(Map<String, Integer> base) {
this.base = base;
}
#Override
public int compare(String a, String b) {
if (base.get(a) >= base.get(b)) {
return 1;
} else {
return -1;
}
}
}
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<String, Integer>();
ValueComparator vc = new ValueComparator(map);
TreeMap<String, Integer> sorted = new TreeMap<String, Integer>(vc);
map.put("A", 1);
map.put("B", 2);
sorted.putAll(map);
for (String key : sorted.keySet()) {
System.out.println(key + " : " + sorted.get(key)); // why null values here?
}
System.out.println(sorted.values()); // But we do have non-null values here!
}
}
Output:
A : null
B : null
[1, 2]
BUILD SUCCESSFUL (total time: 0 seconds)
I wonder why we get null values at the first commented line while we do have non-null values as demonstrated by the second commented line.
Edit: #null's version seems not working. I've changed my code as follows:
public int compare(String a, String b) {
if (a.equals(b)) return 0;
if (base.get(a) >= base.get(b)) {
return 1;
} else return -1;
}
It seems to work but I'm not sure.
My guess is that your ValueComparator.compare() method never returns 0, indicating equality, causing the Map.get() method to not find matches.
Change your compare to in this way
public int compare(String a, String b) {
if (base.get(a) > base.get(b)) {
return 1;
}else if(base.get(a) == base.get(b)){
return 0;
}
return -1;
}
Even with your Comparator which is definitely broken the program will work if you change it as
for (Map.Entry e : sorted.entrySet()) {
System.out.println(e.getKey() + " : " + e.getValue());
}

Java HashMap losing value on loop iteration

i'm attempting to reorder an List of Maps in alphabetical order. i can see that the "name" String gets filled out with the appropriate value, but groupDataCopy is never updated. as far as i know, using the new operator and calling "put" will place the value in the Map. but I can see that on the following iteration, the ArrayList contains:
{name = null}
i don't know why i'm losing values in my Map List. here is the code:
private void sortByName() {
List<Map<String, String>> groupDataCopy = new ArrayList<Map<String, String>>();
List<List<Map<String, String>>> childDataCopy = new ArrayList<List<Map<String, String>>>();
int groupPos = 0;
int nextNamePos = 0;
String name = null;
while(groupPos<groupData.size()) {
//main loop
int groupDataComparison = 0;
name = null;
while(groupDataComparison<groupData.size()) {
//comparison traversal for group
if(!groupDataCopy.isEmpty()) { //if groupDataCopy has data
if(groupDataCopy.get(groupDataCopy.size()-1).get("name").compareTo(groupData.get(groupDataComparison).get("name")) > 0) { //if the last index of groupDataCopy is alphabetically after (or equal to) last chosen name
if(name==null || groupData.get(groupDataComparison).get("name").compareTo(name) < 0) {
name = groupData.get(groupDataComparison).get("name");
nextNamePos = groupDataComparison;
}
}
} else {
if(name==null || groupData.get(groupDataComparison).get("name").compareTo(name) < 0) {
name = groupData.get(groupDataComparison).get("name");
nextNamePos = groupDataComparison;
}
}
groupDataComparison++;
}
groupDataCopy.add(new HashMap<String, String>());
groupDataCopy.get(groupPos).put("name", name);
childDataCopy.add(new ArrayList<Map<String, String>>());
for(Map<String, String> data : childData.get(nextNamePos)) {
childDataCopy.get(groupPos).add(data);
}
groupPos++;
}
groupData = groupDataCopy;
childData = childDataCopy;
}
Comparator<Map<String, String> comparator = new Comparator<Map<String, String>()
{
public int compare(Map<String, String> o1, Map<String, String> o2)
{
return o1.get("name").compartTo(o2.get("name");
}
}
Collections.sort(groupData, comparator);
Try creating a Comparator that will let you use Collections.sort:
Something like:
Comparator<Map<String, String> comp = new Comparator<Map<String, String>()
{
public int compare(Map<String, String> o1, Map<String, String> o2)
{
//write code to compare values
}
}
After which you can simply do:
Collections.sort(groupData, comp);

Categories