sorting a List<Map<String, String>> - java

I have a list of a map of strings:
List<Map<String, String>> list = new ArrayList<Map<String, String>>();
This gets populated with the following:
Map<String, String> action1 = new LinkedHashMap<>();
map.put("name", "CreateFirstName");
map.put("nextAction", "CreateLastName");
Map<String, String> action2 = new LinkedHashMap<>();
map.put("name", "CreateAddress");
map.put("nextAction", "CreateEmail");
Map<String, String> action3 = new LinkedHashMap<>();
map.put("name", "CreateLastName");
map.put("nextAction", "CreateAddress");
Map<String, String> action4 = new LinkedHashMap<>();
map.put("name", "CreateEmail");
list.add(action1);
list.add(action2);
list.add(action3);
list.add(action4);
action4 doesn't have a nextAction because it is the last action, but might be easier to just give it a nextAction that is a placeholder for no next action?
Question: How can I sort my list, so that the actions are in order?
ie: the nextAction of an action, is the same as the name of the next action in the list.

Although this seems to be a case of the XY-Problem, and this list of maps is certainly not a "nicely designed data model", and there is likely a representation that is "better" in many ways (although nobody can give recommendations about what the "best" model could be, as long as the overall goal is not known), this is the task that you have at hand, and here is how it could be solved:
First of all, you have to determine the first element of the sorted list. This is exactly the map that has a "name" entry that does not appear as the "nextAction" entry of any other map.
After you have this first map, you can add it to the (sorted) list. Then, determining the next element boils down to finding the map whose "name" is the same as the "nextAction" of the previous map. To quickly find these successors, you can build a map that maps each "name" entry to the map itself.
Here is a basic implementation of this sorting approach:
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class SortListWithMaps
{
public static void main(String[] args)
{
List<Map<String, String>> list = new ArrayList<Map<String, String>>();
Map<String, String> action1 = new LinkedHashMap<>();
action1.put("name", "CreateFirstName");
action1.put("nextAction", "CreateLastName");
Map<String, String> action2 = new LinkedHashMap<>();
action2.put("name", "CreateAddress");
action2.put("nextAction", "CreateEmail");
Map<String, String> action3 = new LinkedHashMap<>();
action3.put("name", "CreateLastName");
action3.put("nextAction", "CreateAddress");
Map<String, String> action4 = new LinkedHashMap<>();
action4.put("name", "CreateEmail");
list.add(action1);
list.add(action2);
list.add(action3);
list.add(action4);
// Make it a bit more interesting...
Collections.shuffle(list);
System.out.println("Before sorting");
for (Map<String, String> map : list)
{
System.out.println(map);
}
List<Map<String, String>> sortedList = sort(list);
System.out.println("After sorting");
for (Map<String, String> map : sortedList)
{
System.out.println(map);
}
}
private static List<Map<String, String>> sort(
List<Map<String, String>> list)
{
// Compute a map from "name" to the actual map
Map<String, Map<String, String>> nameToMap =
new LinkedHashMap<String, Map<String,String>>();
for (Map<String, String> map : list)
{
String name = map.get("name");
nameToMap.put(name, map);
}
// Determine the first element for the sorted list. For that,
// create the set of all names, and remove all of them that
// appear as the "nextAction" of another entry
Set<String> names =
new LinkedHashSet<String>(nameToMap.keySet());
for (Map<String, String> map : list)
{
String nextAction = map.get("nextAction");
names.remove(nextAction);
}
if (names.size() != 1)
{
System.out.println("Multiple possible first elements: " + names);
return null;
}
// Insert the elements, in sorted order, into the result list
List<Map<String, String>> result =
new ArrayList<Map<String, String>>();
String currentName = names.iterator().next();
while (currentName != null)
{
Map<String, String> element = nameToMap.get(currentName);
result.add(element);
currentName = element.get("nextAction");
}
return result;
}
}

Instead of using a Map to store the properties of an action (the name and the nextAction), create your own type that's composed of those properties:
class Action {
private String name;
//nextAction
public void perform() {
//do current action
//use nextAction to perform the next action
}
}
The nextAction can now be a reference to the next action:
abstract class Action implements Action {
private String name;
private Action nextAction;
public Action(String name) {
this.name = name;
}
public final void perform() {
perform(name);
nextAction.perform();
}
protected abstract void perform(String name);
}
You can now create your actions by subtyping the Action class:
class CreateFirstName extends Action {
public CreateFirstName(Action nextAction) {
super("CreateFirstName", nextAction);
}
protected final void perform(String name) {
System.out.println("Performing " + name);
}
}
And chain them together:
Action action = new CreateFirstName(new CreateLastName(new CreateEmail(...)));
The nested expressions can get pretty messy, but we'll get to that later. There's a bigger problem here.
action4 doesn't have a nextAction because it is the last action, but might be easier to just give it a nextAction that is a placeholder for no next action
The same problem applies to the code above.
Right now, every action must have a next action, due to the constructor Action(String, Action). We could take the easy route and pass in a placeholder for no next action (null being the easiest route):
class End extends Action {
public End() {
super("", null);
}
}
And do a null check:
//class Action
public void perform() {
perform(name);
if(nextAction != null) {
nextAction.perform(); //performs next action
}
}
But this would be a code smell. You can stop reading here and use the simple fix, or continue below for the more involved (and educational) route.
There's a good chance that when you do use null, you're falling victim to a code smell. Although it doesn't apply to all cases (due to Java's poor null safety), you should try to avoid null if possible. Instead, rethink your design as in this example. If all else fails, use Optional.
The last action is not the same as the other actions. It can still perform like the other, but it has different property requirements.
This means they could both share the same behavior abstraction, but must differ when it comes to defining properties:
interface Action {
void perform();
}
abstract class ContinuousAction implements Action {
private String name;
private Action nextAction;
public ContinuousAction(String name) {
this.name = name;
}
public final void perform() {
perform(name);
nextAction.perform();
}
protected abstract void perform(String name);
}
abstract class PlainAction implements Action {
private String name;
public PlainAction(String name) {
this.name = name;
}
public final void perform() {
perform(name);
}
protected abstract void perform(String name);
}
The last action would extend PlainAction, while the others would extend ContinuousAction.
Lastly, to prevent long chains:
new First(new Second(new Third(new Fourth(new Fifth(new Sixth(new Seventh(new Eighth(new Ninth(new Tenth())))))))))
You could specify the next action within each concrete action:
class CreateFirstName extends ContinuousAction {
public CreateFirstName() {
super("CreateFirstName", new CreateLastName());
}
//...
}
class CreateLastName extends ContinuousAction {
public CreateLastName() {
super("CreateLastName", new CreateEmail());
}
//...
}
class CreateEmail extends PlainAction {
public CreateEmail() {
super("CreateEmail");
}
//...
}
The ContinuousAction and PlainAction can be abstracted further. They are both named actions (they have names), and that property affects their contract in the samw way (passing it to the template method process(String)):
abstract class NamedAction implements Action {
private String name;
public NamedAction(String name) {
this.name = name;
}
public final void perform() {
perform(name);
}
protected abstract void perform(String name);
}
//class ContinuousAction extends NamedAction
//class PlainAction extends NamedAction

Related

Populating a tableview with a class maps

I basically am trying to populate a table with a class that contains a string field and a map with arraylist as value.
EDIT: So what I want in the table is is something like this:
Student name
Science
Bro Man
Passs
---
Excellent
The class
public class ItemList {
private String studentNameList;
private HashMap<String, ArrayList<SimpleStringProperty>> remarksList;
public String getStudentNameList() {
return studentNameList;
}
public void setStudentNameList(String studentNameList) {
this.studentNameList = studentNameList;
}
public HashMap<String, ArrayList<SimpleStringProperty>> getRemarksList() {
return remarksList;
}
public void setRemarksList(HashMap<String, ArrayList<SimpleStringProperty>> remarksList) {
this.remarksList = remarksList;
}
}
The controller class has a tableview that will hold the class entries
I have a loadTable method that loads the class into the tableview
The controller class
public class HelloController implements Initializable {
#FXML
private TableView tvTable;
// Observable object to hold the classes
private ObservableList<Item> tablelist = FXCollections.observableArrayList();
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// Item with arraylist as value
ItemList itemlist = new ItemList();
itemlist.setStudentNameList("Bro Man");
HashMap <String, ArrayList<SimpleStringProperty>> remarks = new HashMap<>();
ArrayList<SimpleStringProperty> remarksString = new ArrayList<>();
remarksString.add(new SimpleStringProperty("Pass"));
remarksString.add(new SimpleStringProperty("Excellent"));
remarks.put("Science",remarksString);
itemlist.setRemarksList(remarks);
tablelist.add(itemlist);
Method to load table call
loadTableList(tablelist);
}
The method
private void loadTableList(ObservableList<ItemList> remarks) {
tvTable.setItems(remarks);
tvTable.setVisible(true);
TableColumn<ItemList, String> column = new TableColumn<ItemList, String>("student name");
column.setCellValueFactory(cd -> new SimpleStringProperty(cd.getValue().getStudentNameList()));
tvTable.getColumns().add(column);
// The looping through the hashmap containing arraylist value
for (Map.Entry<String, ArrayList<SimpleStringProperty>> entry : remarks.get(0).getRemarksList().entrySet()) {
// Using the key of the map as column name
TableColumn<ItemList, Number> col = new TableColumn<ItemList, Number>(entry.getKey());
// What should the code here contains to loop through the arraylist of the values and use as the rows
}
}
How do I loop through the hashmap arraylist
Maybe the beginning of the loop itself NOT right
I might need a better solution

How to check String equality while iterating over two lists?

I have two java classes:
public class MyClass1 {
private String userId;
private String userName;
private List<CustomList1> customList1;
// getters and setters
// inner CustomList1 class
}
public class MyClass2 {
private String userId;
private List<CustomList2> customList2;
// getters and setters
// inner CustomList2 class
}
Now, I have have lists of these classes:
List<MyClass1> classOneList;
List<MyClass2> classTwoList;
In both classOneList and classTwoList lists, object should be sorted with userId ascending. userId in both lists should have same values. What I want to check is that:
Has both lists same size? If not, thow error exception about.
Has every next element from both list the same userId? If not, throw another exception.
Step 1. I have done with simply if statement.
By prototype, step 2. should look like this:
for (el1, el2 : classOneList, classTwoList) {
el1.getUserId().isEqualTo(el2.getUserId());
}
Try the below code for your problem.
public class Testing {
public static void main(String[] args) {
Map<String, List<String>> map1 = new LinkedHashMap<String, List<String>>();
List<String> m1l1 = new LinkedList<String>();
m1l1.add("One");
m1l1.add("Two");
m1l1.add("Three");
m1l1.add("Four");
map1.put("1", m1l1);
List<String> m1l2 = new LinkedList<String>();
m1l2.add("One");
m1l2.add("Two");
m1l2.add("Three");
m1l2.add("Four");
map1.put("2", m1l2);
// Add more element into the map1 by creating more list.
Map<String, List<String>> map2 = new LinkedHashMap<String, List<String>>();
List<String> m2l1 = new LinkedList<String>();
m2l1.add("One");
m2l1.add("Two");
m2l1.add("Three");
m2l1.add("Four");
map2.put("1", m2l1);
// Add more element into the map2 by creating more list.
for (Entry<String, List<String>> entry : map1.entrySet()) {
if (map2.containsKey(entry.getKey())) {
if (entry.getValue().size() == map2.get(entry.getKey()).size()) {
} else {
System.out.println("UserId are same but list are different for userid: " + entry.getKey());
}
}
else {
System.out.println("Userid '"+entry.getKey()+"' exists in map1 but is not found in map2");
}
}
}
}
Hope this may help you.
if(classOneList.size() != classTwoList.size()){
throw new ErrorException();
}else{
classOneList = classOneList.stream().sorted(Comparator.comparing(MyClass1::getUserId)).collect(Collectors.toList());
classTwoList = classTwoList.stream().sorted(Comparator.comparing(MyClass2::getUserId)).collect(Collectors.toList());
for (int i = 0; i < classOneList.size(); i++){
if(!classOneList.get(i).getUserId().equals(classTwoList.get(i).getUserId())){
throw new AnotherErrorException();
}
}
}

Best way to avoid multiple parallel if else loop in java 8

What is the best way to avoid multiple parallel if-else loop. I tried with switch statement as well, but again that doesn't look readable. I have hundreds of such statements:
public static Map getKqvSecureNodeResponse(Sample secureNodeData, Map<String, Object> map) {
if(map.containsKey(Constants.NAME_KQV)) {
map.put(Constants.NAME_KQV, secureNodeData.getNodename());
}
if(map.containsKey(Constants.SPOV)) {
map.put(Constants.SPOV, secureNodeData.getOverride());
}
if(map.containsKey(Constants.SPEP)) {
map.put(Constants.SPEP, secureNodeData.getEnabledProtocol());
}
if(map.containsKey(Constants.SPTO)) {
map.put(Constants.SPTO, secureNodeData.getAuthTimeout());
}
if(map.containsKey(Constants.TLCN)) {
map.put(Constants.TLCN, secureNodeData.getCommonName());
}
if(map.containsKey(Constants.SEDT)) {
map.put(Constants.SEDT, secureNodeData.getEncryptData());
}
if(map.containsKey(Constants.TLCF)) {
map.put(Constants.TLCF, secureNodeData.getKeyCertLabel());
}
if(map.containsKey(Constants.TLCL)) {
map.put(Constants.TLCL, secureNodeData.getCipherSuites());
}
return map;
}
Please note that I have to invoke different getter of secureNodeData for every check.
For each Constants value (e.g. Constants.NAME_KQV), you can provide a Function<Sample, Object> (e.g. sample -> sample.getNodename()).
If you organised it in a structure like Map or enum (here, I used a enum), you could end up with a simple loop:
public static Map<String, Object> getKqvSecureNodeResponse(Sample secureNodeData, Map<String, Object> map) {
for (Constant constant : Constant.values()) {
final String name = constant.getName();
if (map.containsKey(name)) {
map.put(name, constant.getFunction().apply(secureNodeData));
}
}
return map;
}
The enum was defined as:
enum Constant {
NAME_KQV(Constants.NAME_KQV, Sample::getNodename);
// other definitions
final String name;
final Function<Sample, Object> function;
Constant(String name, Function<Sample, Object> function) {
this.name = name;
this.function = function;
}
public String getName() {
return name;
}
public Function<Sample, Object> getFunction() {
return function;
}
}
It seems like this method does a lot. (1) It's unclear why it overrides existing values. (2) The method name is obscure. (3) You are using a raw Map, replace it with Map<String, Object> at least, and figure out how to substitute the Object part. (4)
I feel rethinking the design would help much more than the above approach and these small corrections.
You can try to take advantage of method references:
public static Map getKqvSecureNodeResponse(Sample node, Map<String, Object> map) {
applyParam(Constants.NAME_KQV, map, node::getNodename);
applyParam(Constants.SPOV, map, node::getOverride);
// ...
}
public static void applyParam(String key, Map<String, Object> data, Supplier<Object> getter) {
if (data.containsKey(key)) {
data.put(key, getter.get());
}
}
Alternatively you can use Function references that are instance independent:
private static final Map<String, Function<Sample, Object>> MAPPING;
static {
MAPPING = new LinkedHashMap<>();
MAPPING.put(Constants.NAME_KQV, Sample::getNodename);
MAPPING.put(Constants.SPOV, Sample::getOverride);
}
public static Map getKqvSecureNodeResponse(Sample node, Map<String, Object> map) {
for (String key : MAPPING.keySet()) {
if (map.containsKey(key)) {
map.put(key, MAPPING.get(key).apply(node));
}
}
}
There are many ways how you can approach your specific use case, but method references in general makes developer's life much much easier.

Using ConfigurationProperties to fill Map in generic way

I'm wondering, if there is a generic way to fill a map with properties you just know the prefix.
Assuming there are a bunch of properties like
namespace.prop1=value1
namespace.prop2=value2
namespace.iDontKnowThisNameAtCompileTime=anothervalue
I'd like to have a generic way to fill this property inside a map, something like
#Component
#ConfigurationProperties("namespace")
public class MyGenericProps {
private Map<String, String> propmap = new HashMap<String, String>();
// setter and getter for propmap omitted
public Set<String> returnAllKeys() {
return propmap.keySet();
}
}
Or is there another convenient way to collect all properties with a certain prefix, instead of iterating over all PropertySources in the environment?
Thanks
Hansjoerg
As long as you're happy having every property added into the map, rather than just those that you don't know in advance, you can do this with #ConfigurationProperties. If you want to grab everything that's beneath namespace then you need to use an empty prefix and provide a getter for a map named namespace:
#ConfigurationProperties("")
public class CustomProperties {
private final Map<String, String> namespace = new HashMap<>();
public Map<String, String> getNamespace() {
return namespace;
}
}
Spring Boot uses the getNamespace method to retrieve the map so that it can add the properties to it. With these properties:
namespace.a=alpha
namespace.b=bravo
namespace.c=charlie
The namespace map will contain three entries:
{a=alpha, b=bravo, c=charlie}
If the properties were nested more deeply, for example:
namespace.foo.bar.a=alpha
namespace.foo.bar.b=bravo
namespace.foo.bar.c=charlie
Then you'd use namespace.foo as the prefix and rename namespace and getNamespace on CustomProperties to bar and getBar respectively.
Note that you should apply #EnableConfigurationProperties to your configuration to enable support for #ConfigurationProperties. You can then reference any beans that you want to be processed using that annotation, rather than providing an #Bean method for them, or using #Component to have them discovered by component scanning:
#SpringBootApplication
#EnableConfigurationProperties(CustomProperties.class)
public class YourApplication {
// …
}
In addition to this, my problem was that I didn't had multiple simple key/value properties but whole objects:
zuul:
routes:
query1:
path: /api/apps/test1/query/**
stripPrefix: false
url: "https://test.url.com/query1"
query2:
path: /api/apps/test2/query/**
stripPrefix: false
url: "https://test.url.com/query2"
index1:
path: /api/apps/*/index/**
stripPrefix: false
url: "https://test.url.com/index"
Following Jake's advice I tried to use a Map with a Pojo like this:
#ConfigurationProperties("zuul")
public class RouteConfig {
private Map<String, Route> routes = new HashMap<>();
public Map<String, Route> getRoutes() {
return routes;
}
public static class Route {
private String path;
private boolean stripPrefix;
String url;
// [getters + setters]
}
}
Works like a charm,
Thanks!
I was going nuts trying to understand why #Andy's answer wasn't working for me (as in, the Map was remaining empty) just to realize that I had Lombok's #Builder annotation getting in the way, which added a non-empty constructor. I'm adding this answer to emphasize that in order for #ConfigurationProperties to work on Map, the value type must have a No-Arguments constructor. This is also mentioned in Spring's documentation:
Such arrangement relies on a default empty constructor and getters and setters are usually mandatory ...
I hope this will save someone else some time.
I wrote myself a MapFilter class to handle this efficiently. Essentially, you create a Map and then filter it by specifying a prefix for the key. There is also a constructor that takes a Properties for convenience.
Be aware that this just filters the main map. Any changes applied to the filtered map are also applied to the base map, including deletions etc but obviously changes to the main map will not be reflected in the filtered map until something causes a rebuild.
It is also very easy (and efficient) to filter already filtered maps.
public class MapFilter<T> implements Map<String, T> {
// The enclosed map -- could also be a MapFilter.
final private Map<String, T> map;
// Use a TreeMap for predictable iteration order.
// Store Map.Entry to reflect changes down into the underlying map.
// The Key is the shortened string. The entry.key is the full string.
final private Map<String, Map.Entry<String, T>> entries = new TreeMap<>();
// The prefix they are looking for in this map.
final private String prefix;
public MapFilter(Map<String, T> map, String prefix) {
// Store my backing map.
this.map = map;
// Record my prefix.
this.prefix = prefix;
// Build my entries.
rebuildEntries();
}
public MapFilter(Map<String, T> map) {
this(map, "");
}
private synchronized void rebuildEntries() {
// Start empty.
entries.clear();
// Build my entry set.
for (Map.Entry<String, T> e : map.entrySet()) {
String key = e.getKey();
// Retain each one that starts with the specified prefix.
if (key.startsWith(prefix)) {
// Key it on the remainder.
String k = key.substring(prefix.length());
// Entries k always contains the LAST occurrence if there are multiples.
entries.put(k, e);
}
}
}
#Override
public String toString() {
return "MapFilter (" + prefix + ") of " + map + " containing " + entrySet();
}
// Constructor from a properties file.
public MapFilter(Properties p, String prefix) {
// Properties extends HashTable<Object,Object> so it implements Map.
// I need Map<String,T> so I wrap it in a HashMap for simplicity.
// Java-8 breaks if we use diamond inference.
this(new HashMap<String, T>((Map) p), prefix);
}
// Helper to fast filter the map.
public MapFilter<T> filter(String prefix) {
// Wrap me in a new filter.
return new MapFilter<>(this, prefix);
}
// Count my entries.
#Override
public int size() {
return entries.size();
}
// Are we empty.
#Override
public boolean isEmpty() {
return entries.isEmpty();
}
// Is this key in me?
#Override
public boolean containsKey(Object key) {
return entries.containsKey(key);
}
// Is this value in me.
#Override
public boolean containsValue(Object value) {
// Walk the values.
for (Map.Entry<String, T> e : entries.values()) {
if (value.equals(e.getValue())) {
// Its there!
return true;
}
}
return false;
}
// Get the referenced value - if present.
#Override
public T get(Object key) {
return get(key, null);
}
// Get the referenced value - if present.
public T get(Object key, T dflt) {
Map.Entry<String, T> e = entries.get((String) key);
return e != null ? e.getValue() : dflt;
}
// Add to the underlying map.
#Override
public T put(String key, T value) {
T old = null;
// Do I have an entry for it already?
Map.Entry<String, T> entry = entries.get(key);
// Was it already there?
if (entry != null) {
// Yes. Just update it.
old = entry.setValue(value);
} else {
// Add it to the map.
map.put(prefix + key, value);
// Rebuild.
rebuildEntries();
}
return old;
}
// Get rid of that one.
#Override
public T remove(Object key) {
// Do I have an entry for it?
Map.Entry<String, T> entry = entries.get((String) key);
if (entry != null) {
entries.remove(key);
// Change the underlying map.
return map.remove(prefix + key);
}
return null;
}
// Add all of them.
#Override
public void putAll(Map<? extends String, ? extends T> m) {
for (Map.Entry<? extends String, ? extends T> e : m.entrySet()) {
put(e.getKey(), e.getValue());
}
}
// Clear everything out.
#Override
public void clear() {
// Just remove mine.
// This does not clear the underlying map - perhaps it should remove the filtered entries.
for (String key : entries.keySet()) {
map.remove(prefix + key);
}
entries.clear();
}
#Override
public Set<String> keySet() {
return entries.keySet();
}
#Override
public Collection<T> values() {
// Roll them all out into a new ArrayList.
List<T> values = new ArrayList<>();
for (Map.Entry<String, T> v : entries.values()) {
values.add(v.getValue());
}
return values;
}
#Override
public Set<Map.Entry<String, T>> entrySet() {
// Roll them all out into a new TreeSet.
Set<Map.Entry<String, T>> entrySet = new TreeSet<>();
for (Map.Entry<String, Map.Entry<String, T>> v : entries.entrySet()) {
entrySet.add(new Entry<>(v));
}
return entrySet;
}
/**
* An entry.
*
* #param <T>
*
* The type of the value.
*/
private static class Entry<T> implements Map.Entry<String, T>, Comparable<Entry<T>> {
// Note that entry in the entry is an entry in the underlying map.
private final Map.Entry<String, Map.Entry<String, T>> entry;
Entry(Map.Entry<String, Map.Entry<String, T>> entry) {
this.entry = entry;
}
#Override
public String getKey() {
return entry.getKey();
}
#Override
public T getValue() {
// Remember that the value is the entry in the underlying map.
return entry.getValue().getValue();
}
#Override
public T setValue(T newValue) {
// Remember that the value is the entry in the underlying map.
return entry.getValue().setValue(newValue);
}
#Override
public boolean equals(Object o) {
if (!(o instanceof Entry)) {
return false;
}
Entry e = (Entry) o;
return getKey().equals(e.getKey()) && getValue().equals(e.getValue());
}
#Override
public int hashCode() {
return getKey().hashCode() ^ getValue().hashCode();
}
#Override
public String toString() {
return getKey() + "=" + getValue();
}
#Override
public int compareTo(Entry<T> o) {
return getKey().compareTo(o.getKey());
}
}
// Simple tests.
public static void main(String[] args) {
String[] samples = {
"Some.For.Me",
"Some.For.You",
"Some.More",
"Yet.More"};
Map map = new HashMap();
for (String s : samples) {
map.put(s, s);
}
Map all = new MapFilter(map);
Map some = new MapFilter(map, "Some.");
Map someFor = new MapFilter(some, "For.");
System.out.println("All: " + all);
System.out.println("Some: " + some);
System.out.println("Some.For: " + someFor);
}
}
Most of the places I pass through are not fully descriptive or are a bit outdated, so here are the full steps that I have taken to do that. Note: The Spring boot version I used is 2.4.0:
Add in pom.xml - spring-boot-configuration-processor
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
Create a properties file and set data in it - shared-messages.properties Note: "LIBRARY_10001" is the map key, "Unable to find the book" is the map value
shared-messages.messages.LIBRARY_10001=Unable to find the book
shared-messages.messages.LIBRARY_10002=Book already exists
Create a Properties class that uses the properties file
#ConfigurationProperties("shared-messages")
#Getter
public class LibraryProperties {
private final Map<String, String> messages = new HashMap<>();
}
On the Application level, define the Property source and Enable Configuration Property
#EnableConfigurationProperties(LibraryProperties.class)
#PropertySource("shared-messages.properties")
public class LibraryApplication {
....
}
On the Service level, inject "LibraryProperties" class and access the property you need from
#Autowired
private LibraryProperties libraryProperties;
libraryProperties.getMessages().get("LIBRARY_10001")
It might not be the perfect solution but I shared the way I managed to do that because I tried different combinations that didn't work for me

My method is too specific. How can I make it more generic?

I have a class, the outline of which is basically listed below.
import org.apache.commons.math.stat.Frequency;
public class WebUsageLog {
private Collection<LogLine> logLines;
private Collection<Date> dates;
WebUsageLog() {
this.logLines = new ArrayList<LogLine>();
this.dates = new ArrayList<Date>();
}
SortedMap<Double, String> getFrequencyOfVisitedSites() {
SortedMap<Double, String> frequencyMap = new TreeMap<Double, String>(Collections.reverseOrder()); //we reverse order to sort from the highest percentage to the lowest.
Collection<String> domains = new HashSet<String>();
Frequency freq = new Frequency();
for (LogLine line : this.logLines) {
freq.addValue(line.getVisitedDomain());
domains.add(line.getVisitedDomain());
}
for (String domain : domains) {
frequencyMap.put(freq.getPct(domain), domain);
}
return frequencyMap;
}
}
The intention of this application is to allow our Human Resources folks to be able to view Web Usage Logs we send to them. However, I'm sure that over time, I'd like to be able to offer the option to view not only the frequency of visited sites, but also other members of LogLine (things like the frequency of assigned categories, accessed types [text/html, img/jpeg, etc...] filter verdicts, and so on). Ideally, I'd like to avoid writing individual methods for compilation of data for each of those types, and they could each end up looking nearly identical to the getFrequencyOfVisitedSites() method.
So, my question is twofold: first, can you see anywhere where this method should be improved, from a mechanical standpoint? And secondly, how would you make this method more generic, so that it might be able to handle an arbitrary set of data?
This is basically the same thing as Eugene's solution, I just left all the frequency calculation stuff in the original method and use the strategy only for getting the field to work on.
If you don't like enums you could certainly do this with an interface instead.
public class WebUsageLog {
private Collection<LogLine> logLines;
private Collection<Date> dates;
WebUsageLog() {
this.logLines = new ArrayList<LogLine>();
this.dates = new ArrayList<Date>();
}
SortedMap<Double, String> getFrequency(LineProperty property) {
SortedMap<Double, String> frequencyMap = new TreeMap<Double, String>(Collections.reverseOrder()); //we reverse order to sort from the highest percentage to the lowest.
Collection<String> values = new HashSet<String>();
Frequency freq = new Frequency();
for (LogLine line : this.logLines) {
freq.addValue(property.getValue(line));
values.add(property.getValue(line));
}
for (String value : values) {
frequencyMap.put(freq.getPct(value), value);
}
return frequencyMap;
}
public enum LineProperty {
VISITED_DOMAIN {
#Override
public String getValue(LogLine line) {
return line.getVisitedDomain();
}
},
CATEGORY {
#Override
public String getValue(LogLine line) {
return line.getCategory();
}
},
VERDICT {
#Override
public String getValue(LogLine line) {
return line.getVerdict();
}
};
public abstract String getValue(LogLine line);
}
}
Then given an instance of WebUsageLog you could call it like this:
WebUsageLog usageLog = ...
SortedMap<Double, String> visitedSiteFrequency = usageLog.getFrequency(VISITED_DOMAIN);
SortedMap<Double, String> categoryFrequency = usageLog.getFrequency(CATEGORY);
I'd introduce an abstraction like "data processor" for each computation type, so you can just call individual processors for each line:
...
void process(Collection<Processor> processors) {
for (LogLine line : this.logLines) {
for (Processor processor : processors) {
processor.process();
}
}
for (Processor processor : processors) {
processor.complete();
}
}
...
public interface Processor {
public void process(LogLine line);
public void complete();
}
public class FrequencyProcessor implements Processor {
SortedMap<Double, String> frequencyMap = new TreeMap<Double, String>(Collections.reverseOrder()); //we reverse order to sort from the highest percentage to the lowest.
Collection<String> domains = new HashSet<String>();
Frequency freq = new Frequency();
public void process(LogLine line)
String property = getProperty(line);
freq.addValue(property);
domains.add(property);
}
protected String getProperty(LogLine line) {
return line.getVisitedDomain();
}
public void complete()
for (String domain : domains) {
frequencyMap.put(freq.getPct(domain), domain);
}
}
}
You could also change a LogLine API to be more like a Map, i.e. instead of strongly typed line.getVisitedDomain() could use line.get("VisitedDomain"), then you can write a generic FrequencyProcessor for all properties and just pass a property name in its constructor.

Categories