How should I compare URIs in Java? - java

I have a service class which gates certain URIs and allows them to bypass certain checks.
To do this, I'm adding a list of allowed URIs in a map with the action allowed, as so:
private static final Map<String, Set<String>> ALLOWED_URIS =
ImmutableMap.<String, Set<String>>builder()
.put("https://myurl.com/foo", ImmutableSet.of("GET"))
.put("https://anotherurl/bar", ImmutableSet.of("GET"))
.put("https://example.com/foo2", ImmutableSet.of("GET"))
.build();
It's not clear to me how I should compare a string URI to this, because URIs can potentially end in / or have query parameters or be case insensitive on domain name etc.
Say the input I receive is https://myurl.com/foo?key=value, the comparison would fail here because there's no key in the map that's equal to it.
private static boolean isAllowed(final String uri, final String action) {
return ALLOWED_URIS.getOrDefault(uri, emptySet()).contains(action);
}
What's the best way to do my comparison? Is there a utility class available for this?

Related

Check 2 strings without case sensitivity or use equalsIgnoreCase method

I have some inputted String String usrInput; that user could import some string once into App without any case-sensitivity policy like: "start","Start","START","end" ,"END" and etc.
And I have a Map that i inserted my strings for example "start" into that and put it into HashMap<String, String> myMap:
Map<String, String> listOfActions = new HashMap<>();
listOfActions.put(myStr, myStr);
Now I want to check listOfActions members to get for example "start" filed in every case model ("start","Start","START") , currently I do like below:
if (listOfActions.containsKey(usrInput.toUpperCase())
|| listOfActions.containsKey(usrInput.toLowerCase())) {
/// some do
}
So I want to know:
1. Is there any way to get String value without case-sensitivity?
I will also add this here I couldn't use equalsIgnoreCase() method for get items from Map because its return Boolean.
2. I have similar problem in switch-case statements to check 2 string equality without case-sensitivity.
You can use
Map<String, String> listOfActions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
Other solutions can be Apache's CaseInsensitiveMap or Spring's LinkedCaseInsensitiveMap.
Please see https://www.baeldung.com/java-map-with-case-insensitive-keys for more details about these solutions.
If you only use inputs as map keys (i.e. you don't need to later reproduce the strings in original casing), I suggest just lowercasing all inputs before inserting them into the map:
Map<String, String> listOfActions = new HashMap<>();
listOfActions.put(myStr.toLowerCase(), myStr);
This will simplify locating the values later on, since you know that all keys are already lowercased, so the lookup becomes easy:
if (listOfActions.containsKey(myStr.toLowerCase())) {
// do something
}
When you create a new instance of HashMap, you can override some of its methods, such as put and containsKey like this:
Map<String, String> map = new HashMap<>() {
#Override
public String put(String key, String value) {
return super.put(key.toLowerCase(), value);
}
#Override
public boolean containsKey(Object key) {
return super.containsKey(key.toString().toLowerCase());
}
};
map.put("START", "doStart");
System.out.println(map); // {start=doStart}
System.out.println(map.containsKey("START")); // true
System.out.println(map.containsKey("Start")); // true
System.out.println(map.containsKey("start")); // true
One thing you can do is make everything upper-case or lower-case, then compare them.
string.toLowerCase().equals("other string");
string.toUpperCase().equals("OTHERSTRING");
This way, whether it is lower-case or upper-case, it will only be compared as one or the other, and acts as though it were case insensitive.

JAVA: Partitioning list of user objects on a condition using Stream API and convert to Map<String,String>

I have a class Agent, which has following members:
class Agent{
String name;
long funds;
//... getters and setters, parameterized constructor
}
Now, I have a list of Agent class objects.
ArrayList<Agent> listAgents=new ArrayList<Agent>();
I want to give stars to the top performers, like 5 star for someone who has funds of more than 100000, 4 stars to someone with fund of 80000, and so on.
I want to store this record in Map<String,String> like
<ABC,*****> <PQR,****>
I tried the following code:
Map<String,String> star=listAgents
.stream()
.collect(Collectors.groupingBy(agn->giveStars(agn.getGeneratedFund())));
Here giveStars is function defined as below:
public static String giveStars(long generatedFund) {
if(generatedFund>=100000)
return "*****";
else if(generatedFund<100000&& generatedFund>=80000)
return "****";
else if(generatedFund<80000 && generatedFund>=60000)
return "***";
return "";
}
This didn't work. Also, I don't think this is the right way to do this.
How to do this using Stream API's functions?
Any help would be appreciated. Thanks in advance.
Assuming <ABC,*****> <PQR,****> means you have a map of agent name to its star rating, you don't want grouping, you want to map your existing items to different ones.
Here's one way how:
Map<String,String> star = listAgents.stream()
.collect(Collectors.toMap(Agent::getName, agn->giveStars(agn.getGeneratedFund())));
If you group items, you don't change them but assign to different groups (doh). Mapping is a process where you change an object to be something different.
First, there is an issue in the method giveStars which should be rewritten:
public static String giveStars(long generatedFund) {
if (generatedFund >= 1_000_000)
return "*****";
else if (generatedFund >=800_000)
return "****";
else if (generatedFund >= 600_000)
return "***";
return "";
}
Next, the code Collectors.groupingBy(agn->giveStars(agn.getGeneratedFund())) contradicts the intention to have a map Map<String, String> - it will create Map<String, List<Agent>> where the key is the star rating. So maybe your real intention is to change the type of the map.
Map<String, List<Agent>> groupByStars = listAgents.stream()
.collect(Collectors.groupingBy(agent -> giveStars(agent.getGeneratedFund())));
Such map will let to get top agents immediately.
If Map<String, String> (agent name to stars rating) is required, toMap collector may be used as suggested earlier, but this would work only if agent names are unique. If some duplicate values may occur, a merge function is required (e.g. to keep the agent with a better rating):
Map<String, String> groupByAgentName = listAgents.stream()
.collect(Collectors.toMap(
Agent::getName,
agent -> giveStars(agent.getGeneratedFund()),
(rating1, rating2) -> rating1.length() > rating2.length() ? rating1 : rating2,
LinkedHashMap::new // keep insertion order
));

How can I split a String using Splitter.MapSplitter from Guava until a specific key?

I have a String like "key1:value1|prop:id|key3:value3|id:ABC.CDE|key4:value4", how can I split using Splitter.MapSplitter from Guava until id key?
Splitter.MapSplitter.on('|').withKeyValuePairs(':").split() returns an unmodifiable Map, so I need to walk through Map until id key and put entries into a new Map in this case. It does not looks an effective solution.
String.substring(0, String.indexOf('|', String.indexOf("id"))) is not an option because "id" String can be substring of any value before id key.
Or I can use two splitter, entrySplitter to split string into entries and keyValueSplitter to split entries into key-value pairs and then put them into a map while specific key hasn't been reached.
What is the best solution?
Other than copying the output of MapSplitter to another Map and manipulate that (assuming the keys order is preserved), I see no other solution than partially parsing yourself.
Your remark about "id" possibly appearing elsewhere is correct, so you need to search something more specific, like |id:...| or id:...| if id is the first key.
private static final Pattern ID_REGEX = Pattern.compile("(^|\\|)id:.+?\\|");
...
Matcher matcher = ID_REGEX.matcher(line);
if (matcher.find()) {
lineForMapSplitter = line.substring(0, matcher.end()-1);
}
First of all, don't use Splitter.MapSplitter directly, but rather Splitter#withKeyValueSeparator (here: Splitter.on('|').withKeyValueSeparator(':'). Secondly, in your case the most efficient way would be manually split pairs and then split pairs if your predicate (about key == id) is not met, and not create map until the very end.
TIMTOWDI, but I'm using jOOL, which has useful methods for your use case. Seq.seq(Iterable) is a simple helper for creating stream and, what's more important, Seq#limitUntilClosed(Predicate) will pick up all values until it finds id key:
private static final Splitter PAIRS_SPLITTER = Splitter.on('|');
private static final Splitter KEY_VALUE_SPLITTER = Splitter.on(':');
private static final String INVALID_ENTRY_MESSAGE = "Chunk [%s] is not a valid entry";
private Map<String, String> homebrewMapSplitter(final String string)
{
return Seq.seq(PAIRS_SPLITTER.split(string))
.map(this::createEntry)
.limitUntilClosed(e -> e.getKey().equals("id"))
.collect(ImmutableMap.toImmutableMap(
Map.Entry::getKey,
Map.Entry::getValue)
);
}
// ~copied from MapSplitter#split(CharSequence)
private Map.Entry<String, String> createEntry(final String entry)
{
Iterator<String> entryFields = KEY_VALUE_SPLITTER.split(entry).iterator();
checkArgument(entryFields.hasNext(), INVALID_ENTRY_MESSAGE, entry);
String key = entryFields.next();
checkArgument(entryFields.hasNext(), INVALID_ENTRY_MESSAGE, entry);
String value = entryFields.next();
checkArgument(!entryFields.hasNext(), INVALID_ENTRY_MESSAGE, entry);
return Maps.immutableEntry(key, value);
}

BiMap single function to convert values to concatenated string value?

Is there something equivalent to get all keys (or inverses) from a bit map and concat each with a special character as a completely new string (without iterating through the map and building it manually?
private static final BiMap<String, String> stuff = HashBiMap.create();
static {
stuff.put("S1", "STUFF_TYPE_1");
stuff.put("S2", "STUFF_TYPE_2");
stuff.put("S3", "STUFF_TYPE_3");
}
// The non-terminal <> is what I'm asking if something like exists either with bimap or some other container?
private static final String concateKeys = <stuff.getAllKeys().assignDelimiter("|").toString();>
Then the Value for concateKeys = "S1|S2|S3"
Assuming this is a Guava BiMap, this is just
Joiner.on('|').join(stuff.keySet());
Maybe you want to take a look at the Joiner class of the Google Guava library.

convert fetched data into string , string array map

i am fetching a data from sqlite in android which is as follows
URL PHONE
---------------------------------
/test/img1.png 98989898
/test/img1.png 61216121
/test/img2.png 75757575
/test/img2.png 40404040
/test/img3.png 36363636
now i want to create such a map which stores the data as follows
/test/img1.png [98989898 , 61216121 ]
/test/img2.png [75757575 , 40404040 ]
/test/img3.png [36363636]
so that i can pass the whole map to the function which function eventually in background pick up the image url and send the data to the arrays listed to the phone number. so how can i transform the data that i have fetched into the key to string array style ?
I'd create a Map<String, List<String>> (aka "multi-map"). You don't have to know how many phone numbers for a given URL before you start if you use List<String>. That's not so if you choose the array route.
Map<String, List<String>> results = new HashMap<String, List<String>>();
while (rs.next()) {
String url = rs.getString(1);
String phone = rs.getString(2);
List<String> phones = (results.contains(url) ? results.get(url) : new ArrayList<String>());
phones.add(phone);
results.put(url, phones);
}
Google Collections has a multi-map that you can use out of the box, but I think you'll agree that this is sufficient.
If you want to store more items (e.g. name) you should start thinking about an object that encapsulates all of them together into one coherent thing. Java's an object-oriented language. You sound like you're guilty of thinking at too low a level. Strings, primitives, and data structures are building blocks for objects. Perhaps you need a Person here:
package model;
public class Person {
private String name;
private Map<String, List<String>> contacts;
// need constructors and other methods. This one is key
public void addPhone(String url, String phone) {
List<String> phones = (this.contacts.contains(url) ? this.contacts.get(url) : new ArrayList<String>());
phones.add(phone);
this.contacts.put(url, phones);
}
}
I'll leave the rest for you.
If you go this way, you'll need to map a result set into a Person. But you should see the idea from the code I've posted.

Categories