Is there some "native" format in java that will allow me to write up a data file that I can then easily load into a Map<String, Collection<String>>? I would rather do this without having to code any significant parsing. This would be to initialize an application-scoped bean, so it is only run once at startup.
I realize I can use JSON and then use something like GSON to load the data, but that seems like overkill for something that would appear to be simple. If I had closures, I could potentially create it as a property file, use a specific separator for my collection, and have the closure parse on the separator, but that doesn't seem very elegant, and most importantly isn't available in Java 8.
Similarly, I can load as a simple properties file and then copy over to a Map parsing the individual property values, but again seems a little "hacky".
I could use XML and then import the XML, but that is way overkill and would rather have a simple text format that is easily understood.
I am not against using something from Apache Commons or Guava, etc, but haven't found anything straightforward yet.
My opinion is that JSON is lightweight and simple, it's in-memory representation is a map of maps or arrays, seems to suit the job. This would read your entire file to a multimap (uses Jackson):
ObjectMapper mapper = new ObjectMapper();
Map<String, List<String>> map = mapper.readValue(inputStream,
new TypeReference<Map<String, Object>>() {});
Give SimpleXML a shot. it's ridiculously easy to use.
#Default
Map<String, Collection<String>> somethingToPersist;
....
Prersister persister = new Persister();
File file = new File("/file_path");
persister.write(somethingToPersist,file)
If you're OK with a leading tab having special meaning, I guess I would format my file like:
key1
value1-a
value1-b
key2
value2-a
key3
value3-a
value3-b
value3-c
Where a leading tab means the string is a value (not a key).
Then the parser:
public class MapBuilder
{
private final Map<String, Collection<String>> map;
private Collection<String> col;
public MapBuilder()
{
map = new LinkedHashMap<String, Collection<String>>();
}
public Map<String, Collection<String>> getMap()
{
return map;
}
public void addLine(String line)
{
if (line.length() > 0)
{
if (line.charAt(0) == '\t')
{
if (col == null)
{
throw new IllegalArgumentException(
"No key specified");
}
col.add(line.substring(1));
}
else
{
col = new ArrayList<String>();
map.put(line, col);
}
}
}
public static Map<String, Collection<String>> getMap(File file,
Charset charset) throws IOException
{
MapBuilder mb = new MapBuilder();
BufferedReader br = new BufferedReader(new InputStreamReader(
new FileInputStream(file), charset));
try
{
String line;
while ((line = br.readLine()) != null)
{
mb.addLine(line);
}
return mb.getMap();
}
finally
{
try
{
br.close();
}
catch (Throwable t)
{
t.printStackTrace();
}
}
}
}
Usage:
Map<String, Collection<String>> myMap = MapBuilder.getMap(
new File("C:/myfile.txt"), Charset.forName("US-ASCII"));
Related
I am trying to convert below code into stream. It's not that difficult but I am not sure how to handle null values in stream. I did checked out Optinal.ofNullable method but quite not sure if I have to use it 2-3 times to get the correct stream code. I can use the below code for now, but I wish to learn doing it in stream. Please help me learn.
Map<String, Map<String, List<String>>> fileTypeMapping = new HashMap<>();
Map<String, List<String>> map = new HashMap<>();
map.put("codec", new ArrayList(Arrays.asList("ext1", "ext2")));
fileTypeMapping.put("image", map);
String fileType = "Image";
String codec = "Codec";
String extension = "ext2";
boolean exist = false;
if(fileType != null) {
Map<String, List<String>> codecMap = fileTypeMapping.get(fileType.toLowerCase());
if(codecMap != null) {
List<String> list = codecMap.get(codec.toLowerCase());
if (list != null) {
exist = list.contains(extension.toLowerCase());
}
}
}
System.out.print(exist);
Seems not related to Stream API, just use Map.getOrDefault to get rid of null return.
import java.util.*;
public class NullableMapGet {
public static void main(String[] args) {
Map<String, Map<String, List<String>>> fileTypeMapping = new HashMap<>();
Map<String, List<String>> map = new HashMap<>();
map.put("codec", new ArrayList(Arrays.asList("ext1", "ext2")));
fileTypeMapping.put("image", map);
String fileType = "Image";
String codec = "Codec";
String extension = "ext2";
boolean exist = false;
if (fileType == null || codec == null || extension == null) {
exist = false;
} else {
exist = fileTypeMapping
.getOrDefault(fileType.toLowerCase(), Collections.emptyMap())
.getOrDefault(codec.toLowerCase(), Collections.emptyList())
.contains(extension.toLowerCase());
}
System.out.print(exist);
}
}
I'm new to Java and I would like to read this file content using Java:
Filename Type Size Used Priority
/dev/mapper/VolGroup00-LogVol01 partition 524280 0 -1
/dev/mapper/VolGroup00-LogVol02 partition 324280 0 -1
Can you show me some working example with Java 8?
This is the code so far:
private static HashMap<String, HashMap<String, Long>> totalSwap() throws FileNotFoundException, IOException
{
File file = new File("/proc/swaps");
if (!file.exists())
{
System.err.println("/proc/swaps did not exist!");
return null;
}
else if (file.isDirectory())
{
System.err.println("/proc/swaps is a directory, not a file.");
return null;
}
Pattern pattern = Pattern.compile("([\\/A-Za-z0-9]+)[\\s]+([a-z]+)[\\s]+([0-9]+)[\\s]+([0-9]+)[\\s]+([\\-0-9]+).*");
BufferedReader reader = new BufferedReader(new FileReader("/proc/swaps"));
String s = reader.readLine();
while (s != null)
{
Matcher matcher = pattern.matcher(s);
if (matcher.matches())
{
HashMap<String, Long> usageData2 = new HashMap<>();
usageData2.put("allSwap", Long.parseLong(matcher.group(3)));
usageData2.put("utilizedSwap", Long.parseLong(matcher.group(4)));
data.put("First", usageData2);
}
s = reader.readLine();
}
reader.close();
return data;
}
I don't know how to read the FileName column. Finally I would like to get this result:
HashMap</dev/mapper/VolGroup00-LogVol01, HashMap<Size, 524280>
HashMap<Used, 0>>
HashMap</dev/mapper/VolGroup00-LogVol02, HashMap<Size, 334220>
HashMap<Used, 0>>
Can you help to solve this problem?
It may be better to split using a tab delimeter, if i remember correctly, linux is outputting using the tab character.
I have had to improvise with youre code but it should be easy to plug your code back in.
See my example below:
private static HashMap<String, HashMap<String, Long>> totalSwap()
{
HashMap<String, HashMap<String, Long>> data = new HashMap<String, HashMap<String, Long>>();
Pattern pattern = Pattern.compile("([\\/A-Za-z0-9]+)[\\s]+[A-Za-z]+[\\s]+([0-9]+)[\\s]+([0-9]+)[\\s]+([\\-0-9]+).*");
String s = "/dev/mapper/VolGroup00-LogVol01\tpartition\t524280\t0\t-1\n/dev/mapper/VolGroup00-LogVol02\tpartition\t324280\t0\t-1";
String[] columns = s.split("\t");
for (String line : columns) {
HashMap<String, Long> usageData2 = new HashMap<>();
usageData2.put("allSwap", Long.parseLong(columns[2]));
usageData2.put("utilizedSwap", Long.parseLong(columns[3]));
data.put(columns[0], usageData2);
}
return data;
}
Maybe it would be better to use StringTokenizer with delimiter tab("\t") and retrieve required columns.
I'm building a RMI game and the client would load a file that has some keys and values which are going to be used on several different objects. It is a save game file but I can't use java.util.Properties for this (it is under the specification). I have to read the entire file and ignore commented lines and the keys that are not relevant in some classes. These properties are unique but they may be sorted in any order. My file current file looks like this:
# Bio
playerOrigin=Newlands
playerClass=Warlock
# Armor
playerHelmet=empty
playerUpperArmor=armor900
playerBottomArmor=armor457
playerBoots=boot109
etc
These properties are going to be written and placed according to the player's progress and the filereader would have to reach the end of file and get only the matched keys. I've tried different approaches but so far nothing came close to the results that I would had using java.util.Properties. Any idea?
This will read your "properties" file line by line and parse each input line and place the values in a key/value map. Each key in the map is unique (duplicate keys are not allowed).
package samples;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.TreeMap;
public class ReadProperties {
public static void main(String[] args) {
try {
TreeMap<String, String> map = getProperties("./sample.properties");
System.out.println(map);
}
catch (IOException e) {
// error using the file
}
}
public static TreeMap<String, String> getProperties(String infile) throws IOException {
final int lhs = 0;
final int rhs = 1;
TreeMap<String, String> map = new TreeMap<String, String>();
BufferedReader bfr = new BufferedReader(new FileReader(new File(infile)));
String line;
while ((line = bfr.readLine()) != null) {
if (!line.startsWith("#") && !line.isEmpty()) {
String[] pair = line.trim().split("=");
map.put(pair[lhs].trim(), pair[rhs].trim());
}
}
bfr.close();
return(map);
}
}
The output looks like:
{playerBoots=boot109, playerBottomArmor=armor457, playerClass=Warlock, playerHelmet=empty, playerOrigin=Newlands, playerUpperArmor=armor900}
You access each element of the map with map.get("key string");.
EDIT: this code doesn't check for a malformed or missing "=" string. You could add that yourself on the return from split by checking the size of the pair array.
I 'm currently unable to come up with a framework that would just provide that (I'm sure there are plenty though), however, you should be able to do that yourself.
Basically you just read the file line by line and check whether the first non whitespace character is a hash (#) or whether the line is whitespace only. You'd ignore those lines and try to split the others on =. If for such a split you don't get an array of 2 strings you have a malformed entry and handle that accordingly. Otherwise the first array element is your key and the second is your value.
Alternately, you could use a regular expression to get the key/value pairs.
(?m)^[^#]([\w]+)=([\w]+)$
will return capture groups for each key and its value, and will ignore comment lines.
EDIT:
This can be made a bit simpler:
[^#]([\w]+)=([\w]+)
After some study i came up with this solution:
public static String[] getUserIdentification(File file) throws IOException {
String key[] = new String[3];
FileReader fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr);
String lines;
try {
while ((lines = br.readLine()) != null) {
String[] value = lines.split("=");
if (lines.startsWith("domain=") && key[0] == null) {
if (value.length <= 1) {
throw new IOException(
"Missing domain information");
} else {
key[0] = value[1];
}
}
if (lines.startsWith("user=") && key[1] == null) {
if (value.length <= 1) {
throw new IOException("Missing user information");
} else {
key[1] = value[1];
}
}
if (lines.startsWith("password=") && key[2] == null) {
if (value.length <= 1) {
throw new IOException("Missing password information");
} else {
key[2] = value[1];
}
} else
continue;
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
return key;
}
I'm using this piece of code to check the properties. Of course it would be wiser to use Properties library but unfortunately I can't.
The shorter way how to do that:
Properties properties = new Properties();
String confPath = "src/main/resources/.env";
try {
properties.load(new FileInputStream(confPath));
} catch (IOException e) {
e.printStackTrace();
}
String specificValueByKey = properties.getProperty("KEY");
Set<Object> allKeys = properties.keySet();
Collection<Object> values = properties.values();
I put a key-value pair in a Java HashMap and converted it to a String using the toString() method.
Is it possible to convert this String representation back to a HashMap object and retrieve the value with its corresponding key?
Thanks
It will work if toString() contains all data needed to restore the object. For example it will work for map of strings (where string is used as key and value):
// create map
Map<String, String> map = new HashMap<String, String>();
// populate the map
// create string representation
String str = map.toString();
// use properties to restore the map
Properties props = new Properties();
props.load(new StringReader(str.substring(1, str.length() - 1).replace(", ", "\n")));
Map<String, String> map2 = new HashMap<String, String>();
for (Map.Entry<Object, Object> e : props.entrySet()) {
map2.put((String)e.getKey(), (String)e.getValue());
}
This works although I really do not understand why do you need this.
toString() approach relies on implementation of toString() and it can be lossy in most of the cases.
There cannot be non lossy solution here. but a better one would be to use Object serialization
serialize Object to String
private static String serialize(Serializable o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();
return Base64.getEncoder().encodeToString(baos.toByteArray());
}
deserialize String back to Object
private static Object deserialize(String s) throws IOException,
ClassNotFoundException {
byte[] data = Base64.getDecoder().decode(s);
ObjectInputStream ois = new ObjectInputStream(
new ByteArrayInputStream(data));
Object o = ois.readObject();
ois.close();
return o;
}
Here if the user object has fields which are transient, they will be lost in the process.
old answer
Once you convert HashMap to String using toString(); It's not that you can convert back it to Hashmap from that String, Its just its String representation.
You can either pass the reference to HashMap to method or you can serialize it
Here is the description for toString() toString()
Here is the sample code with explanation for Serialization.
and to pass hashMap to method as arg.
public void sayHello(Map m){
}
//calling block
Map hm = new HashMap();
sayHello(hm);
you cannot do this directly but i did this in a crazy way as below...
The basic idea is that, 1st you need to convert HashMap String into Json then you can deserialize Json using Gson/Genson etc into HashMap again.
#SuppressWarnings("unchecked")
private HashMap<String, Object> toHashMap(String s) {
HashMap<String, Object> map = null;
try {
map = new Genson().deserialize(toJson(s), HashMap.class);
} catch (TransformationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return map;
}
private String toJson(String s) {
s = s.substring(0, s.length()).replace("{", "{\"");
s = s.substring(0, s.length()).replace("}", "\"}");
s = s.substring(0, s.length()).replace(", ", "\", \"");
s = s.substring(0, s.length()).replace("=", "\":\"");
s = s.substring(0, s.length()).replace("\"[", "[");
s = s.substring(0, s.length()).replace("]\"", "]");
s = s.substring(0, s.length()).replace("}\", \"{", "}, {");
return s;
}
implementation...
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("Name", "Suleman");
map.put("Country", "Pakistan");
String s = map.toString();
HashMap<String, Object> newMap = toHashMap(s);
System.out.println(newMap);
i converted HashMap into an String
using toString() method and pass to
the another method that take an String
and convert this String into HashMap
object
This is a very, very bad way to pass around a HashMap.
It can theoretically work, but there's just way too much that can go wrong (and it will perform very badly). Obviously, in your case something does go wrong. We can't say what without seeing your code.
But a much better solution would be to change that "another method" so that it just takes a HashMap as parameter rather than a String representation of one.
You can make use of Google's "GSON" open-source Java library for this,
Example input (Map.toString) : {name=Bane, id=20}
To Insert again in to HashMap you can use below code:
yourMap = new Gson().fromJson(yourString, HashMap.class);
That's it Enjoy.
(In Jackson Library mapper It will produce exception "expecting double-quote to start field name")
What did you try?
objectOutputStream.writeObject(hashMap);
should work just fine, providing that all the objects in the hashMap implement Serializable.
You cannot revert back from string to an Object. So you will need to do this:
HashMap<K, V> map = new HashMap<K, V>();
//Write:
OutputStream os = new FileOutputStream(fileName.ser);
ObjectOutput oo = new ObjectOutputStream(os);
oo.writeObject(map);
oo.close();
//Read:
InputStream is = new FileInputStream(fileName.ser);
ObjectInput oi = new ObjectInputStream(is);
HashMap<K, V> newMap = oi.readObject();
oi.close();
Are you restricted to use only HashMap ??
Why can't it be so much flexible JSONObject you can do a lot with it.
You can convert String jsonString to JSONObject jsonObj
JSONObject jsonObj = new JSONObject(jsonString);
Iterator it = jsonObj.keys();
while(it.hasNext())
{
String key = it.next().toString();
String value = jsonObj.get(key).toString();
}
Using ByteStream can convert the String but it can encounter OutOfMemory exception in case of large Strings. Baeldung provides some nice solutions in his pot here : https://www.baeldung.com/java-map-to-string-conversion
Using StringBuilder :
public String convertWithIteration(Map<Integer, ?> map) {
StringBuilder mapAsString = new StringBuilder("{");
for (Integer key : map.keySet()) {
mapAsString.append(key + "=" + map.get(key) + ", ");
}
mapAsString.delete(mapAsString.length()-2, mapAsString.length()).append("}");
return mapAsString.toString(); }
Please note that lambdas are only available at language level 8 and above
Using Stream :
public String convertWithStream(Map<Integer, ?> map) {
String mapAsString = map.keySet().stream()
.map(key -> key + "=" + map.get(key))
.collect(Collectors.joining(", ", "{", "}"));
return mapAsString; }
Converting String Back to Map using Stream :
public Map<String, String> convertWithStream(String mapAsString) {
Map<String, String> map = Arrays.stream(mapAsString.split(","))
.map(entry -> entry.split("="))
.collect(Collectors.toMap(entry -> entry[0], entry -> entry[1]));
return map; }
I hope you actually need to get the value from string by passing the hashmap key. If that is the case, then we don't have to convert it back to Hashmap. Use following method and you will be able to get the value as if it was retrieved from Hashmap itself.
String string = hash.toString();
String result = getValueFromStringOfHashMap(string, "my_key");
/**
* To get a value from string of hashmap by passing key that existed in Hashmap before converting to String.
* Sample string: {fld_category=Principal category, test=test 1, fld_categoryID=1}
*
* #param string
* #param key
* #return value
*/
public static String getValueFromStringOfHashMap(String string, String key) {
int start_index = string.indexOf(key) + key.length() + 1;
int end_index = string.indexOf(",", start_index);
if (end_index == -1) { // because last key value pair doesn't have trailing comma (,)
end_index = string.indexOf("}");
}
String value = string.substring(start_index, end_index);
return value;
}
Does the job for me.
It is possible to rebuild a collection out of its string presentation but it will not work if the elements of the collection don't override their own toString method.
Therefore it's much safer and easier to use third party library like XStream which streams objects in human readable XML.
This may be inefficient and indirect. But
String mapString = "someMap.toString()";
new HashMap<>(net.sf.json.JSONObject.fromObject(mapString));
should work !!!
i like to encode a java map of strings as a single base 64 encoded string. The encoded string will be transmitted to a remote endpoint and maybe manipulated by a not nice person. So the worst thing that should happen are invaild key,value-tuples, but should not bring any other security risks aside.
Example:
Map<String,String> map = ...
String encoded = Base64.encode(map);
// somewhere else
Map<String,String> map = Base64.decode(encoded);
Yes, must be Base64. Not like that or that or any other of these. Is there an existing lightweight solution (Single Utils-Class prefered) out there? Or do i have to create my own?
Anything better than this?
// marshalling
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(map);
oos.close();
String encoded = new String(Base64.encodeBase64(baos.toByteArray()));
// unmarshalling
byte[] decoded = Base64.decodeBase64(encoded.getBytes());
ByteArrayInputStream bais = new ByteArrayInputStream(decoded);
ObjectInputStream ois = new ObjectInputStream(bais);
map = (Map<String,String>) ois.readObject();
ois.close();
Thanks,
my primary requirements are: encoded string should be as short as possible and contain only latin characters or characters from the base64 alphabet (not my call). there are no other reqs.
Use Google Gson to convert Map to JSON. Use GZIPOutputStream to compress the JSON string. Use Apache Commons Codec Base64 or Base64OutputStream to encode the compressed bytes to a Base64 string.
Kickoff example:
public static void main(String[] args) throws IOException {
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");
String serialized = serialize(map);
Map<String, String> deserialized = deserialize(serialized, new TypeToken<Map<String, String>>() {}.getType());
System.out.println(deserialized);
}
public static String serialize(Object object) throws IOException {
ByteArrayOutputStream byteaOut = new ByteArrayOutputStream();
GZIPOutputStream gzipOut = null;
try {
gzipOut = new GZIPOutputStream(new Base64OutputStream(byteaOut));
gzipOut.write(new Gson().toJson(object).getBytes("UTF-8"));
} finally {
if (gzipOut != null) try { gzipOut.close(); } catch (IOException logOrIgnore) {}
}
return new String(byteaOut.toByteArray());
}
public static <T> T deserialize(String string, Type type) throws IOException {
ByteArrayOutputStream byteaOut = new ByteArrayOutputStream();
GZIPInputStream gzipIn = null;
try {
gzipIn = new GZIPInputStream(new Base64InputStream(new ByteArrayInputStream(string.getBytes("UTF-8"))));
for (int data; (data = gzipIn.read()) > -1;) {
byteaOut.write(data);
}
} finally {
if (gzipIn != null) try { gzipIn.close(); } catch (IOException logOrIgnore) {}
}
return new Gson().fromJson(new String(byteaOut.toByteArray()), type);
}
Another possible way would be using JSON which is a very ligthweight lib.
The the encoding then would look like this:
JSONObject jso = new JSONObject( map );
String encoded = new String(Base64.encodeBase64( jso.toString( 4 ).toByteArray()));
Your solution works. The only other approach would be to serialize the map yourself (iterate over the keys and values). That would mean you'd have to make sure you handle all the cases correctly (for example, if you transmit the values as key=value, you must find a way to allow = in the key/value and you must separate the pairs somehow which means you must also allow this separation character in the name, etc).
All in all, it's hard to get right, easy to get wrong and would take a whole lot more code and headache. Plus don't forget that you'd have to write a lot of error handling code in the parser (receiver side).