Well I guess I am trying to achieve more than I can but I wanted to create an aggregator class for all my dictionary values in my application. Therefore I created an enum which would have two arguments - first being dictionary name and second being the Supplier. I went for Supplier because supplied value will be calculated for each dictionary differently. So far I came up with this:
public enum DictionaryAggregator {
STATUS_X("statusX", StatusX::getDictionary),
STATUS_Y("statusY", new Supplier<Map<String, ?>>() {
#Override
public Map<String, ?> get() {
return <would like to reference to statusYCache which is inside DictionaryAggregatorServiceInjector>;
}
});
private final String dictionaryName;
private final Supplier<Map<String, ?>> dictionary;
public String getDictionaryName() {
return dictionaryName;
}
public Supplier<Map<String, ?>> getDictionary() {
return dictionary;
}
DictionaryAggregator(String dictionaryName, Supplier<Map<String, ?>> dictionary) {
this.dictionaryName = dictionaryName;
this.dictionary = dictionary;
}
#Component
static class DictionaryAggregatorServiceInjector {
#Autowired
private StatusYService statusYService;
}
}
but whatever I tried I could not come up with an idea how could I supply values from StatusYCache service. Ideally what I would like to achieve is that the Supplier for STATUS_Y could easily invoke methods from StatusYService.
Guess I should use my brain more frequently. I came up with this:
public enum DictionaryAggregator {
STATUS_X("statusX", StatusX::getDictionary),
STATUS_Y("statusY", new Supplier<Map<String, ?>>() {
#Override
public Map<String, ?> get() {
return new StatusYDictionaryClass(DictionaryAggregatorServiceInjector.shipmentStatusVos).getDictionary();
}
});
private final String dictionaryName;
private final Supplier<Map<String, ?>> dictionary;
public String getDictionaryName() {
return dictionaryName;
}
public Supplier<Map<String, ?>> getDictionary() {
return dictionary;
}
DictionaryAggregator(String dictionaryName, Supplier<Map<String, ?>> dictionary) {
this.dictionaryName = dictionaryName;
this.dictionary = dictionary;
}
#Component
static class DictionaryAggregatorServiceInjector {
static List<StatusYObj> statusYList;
#Autowired
public StatusYService statusYService;
#PostConstruct
public void init() {
statusYList = statusYService.getStatusYList();
}
}
}
It seems to be working.
Related
For example, there is a task, without violating the Open/Closed principle, to safely add new implementations for sending messages in different ways. The input comes with a parameter that contains the type of "transport" for sending messages or the device where the messages will arrive.
As an input parameter, you need to use Enum, but design the system so that you do not use switch when the parameter comes as a string, but immediately call the desired service in accordance with the stated conditions.
Here is my implementation.
Test
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class NotificationMasterTest extends MultiImplementationInterfacesAndEnumApplicationTests {
#Autowired
private NotificationMaster notification;
#Test
void send() {
String phone = "phone";
TypeCommunication typeCommunication =
TypeCommunication.valueOf("phone");//Runtime Exception
notification.send(TypeCommunication.PHONE);
}
}
enum
public enum TypeCommunication {
PHONE("phone"),
EMAIL("email"),
KAFKA("kafka");
private String value;
TypeCommunication(String value) {
this.value = value;
}
public String getType() {
return this.value;
}
}
interface
public interface Notification {
void sendNotice();
TypeCommunication getType();
}
PhoneImplementation
#Service
public class NotificationPhoneImpl implements Notification {
private final TypeCommunication typeCommunication = TypeCommunication.PHONE;
public NotificationPhoneImpl() {
}
#Override
public void sendNotice() {
System.out.println("Send through --- phone---");
}
#Override
public TypeCommunication getType() {
return this.typeCommunication;
}
}
EmailEmplementation
#Service
public class NotificationEmailImpl implements Notification {
private final TypeCommunication typeCommunication = TypeCommunication.EMAIL;
#Override
public void sendNotice() {
System.out.println("Send through --- email ---");
}
#Override
public TypeCommunication getType() {
return this.typeCommunication;
}
}
master
package com.example.multi.implementation.interfaces.services;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
#Component
public class NotificationMaster {
private Map<TypeCommunication, Notification> notificationService = new HashMap<>();
private Set<Notification> notification;
public NotificationMaster(Set<Notification> notification) {
this.notification = notification;
}
#PostConstruct
private void init(){
notification.stream()
.forEach(service -> {
TypeCommunication type = service.getType();
notificationService.put(type, service);
});
}
public void send(TypeCommunication typeCommunication) {
Notification notification = notificationService.get(typeCommunication);
notification.sendNotice();
}
}
I can't figure out how to pass a string and convert it to Enum on the fly (without using switch) and immediately get the desired implementation.
Maybe there is a more flexible solution in which Spring would have already prepared the components without me, so that I don't use Postconstruct and manually create a map with different service implementations?
I would say that it cannot be done "on the fly". So, I would create a static Map and static method getByType in TypeCommunication to retrieve the enum by its value:
public enum TypeCommunication {
PHONE("phone"),
EMAIL("email"),
KAFKA("kafka");
private static final Map<String, TypeCommunication> TYPE_COMMUNICATION_MAP;
private final String value;
static {
TYPE_COMMUNICATION_MAP = Arrays.stream(TypeCommunication.values())
.collect(Collectors.toMap(TypeCommunication::getType, Function.identity(),
(existing, replacement) -> existing));
}
TypeCommunication(String value) {
this.value = value;
}
public String getType() {
return this.value;
}
public static TypeCommunication getByType(String type) {
return TYPE_COMMUNICATION_MAP.get(type);
}
}
If you received this value in the controller in the request body, then you could use #JsonValue annotation on the getType() method to immediately deserialize it in the enum by the value.
About your NotificationMaster, spring cannot collect such a map automatically, but you can collect your map right in constructor without using #PostConstruct, and using Collectors.toMap() method, also take the EnumMap, since your key is Enum:
#Component
public class NotificationMaster {
private final Map<TypeCommunication, Notification> map;
public NotificationMaster(List<Notification> notifications) { // or Set
map = notifications.stream()
.collect(Collectors.toMap(Notification::getType, Function.identity(),
(existing, replacement) -> existing,
() -> new EnumMap<>(TypeCommunication.class)));
}
public void send(TypeCommunication typeCommunication) {
map.getOrDefault(typeCommunication, new DefaultNotificationImpl()).sendNotice();
}
private static class DefaultNotificationImpl implements Notification {
#Override
public void sendNotice() {
// some logic, e.g. throw new UnsupportedOperationException
}
#Override
public TypeCommunication getType() {
return null;
}
}
}
It also good to define the DefaultNotificationImpl to avoid NullPointerException in your send method and using now Map#getOrDefault method instead of Map#get.
You can also take out the logic for creating a map in a separate configuration class and then simple autowire this map in NotificationMaster:
#Configuration
public class Config {
#Bean
public Map<TypeCommunication, Notification> createMap(List<Notification> notifications) {
return notifications.stream()
.collect(Collectors.toMap(Notification::getType, Function.identity(),
(existing, replacement) -> existing,
() -> new EnumMap<>(TypeCommunication.class)));
}
}
#Component
public class NotificationMaster {
#Autowired
private Map<TypeCommunication, Notification> map;
...
}
I have a couple of classes having identical methods, except with respect to certain parameter types:
interface ICls<T> {
void doSomething(String key, T value);
Map<String, T> getSomething();
}
class ClsA implements ICls<Boolean> {
#Override public void doSomething(String key, Boolean value) { }
#Override public Map<String, Boolean> getSomething() { return Map.of(); }
}
class ClsB implements ICls<String> {
#Override public void doSomething(String key, String value) {}
#Override public Map<String, String> getSomething() { return Map.of(); }
}
Now I'm trying to have a main class that stores a mixed list of these class objects and for each of these instances, passes info between its two methods:
class Main {
List<ICls<?>> list = List.of(
new ClsA(),
new ClsB()
);
void run() {
list.forEach(cls -> {
Map<String, ?> data = cls.getSomething();
data.keySet().forEach(key -> cls.doSomething(key, data.get(key)));
});
}
The List<ICls<?>> and Map<String, ?> statements are OK. However, the map.get(key) throws an IDE error:
'doSomething(<String, capture<?>>) in '...ICls' cannot be applied to 'String, capture<?>'
Hovering the mouse cursor over the offending statement shows:
Required type: capture of ?
Provided: capture of ?
Assuming that I can't/don't want to change the generic type T to Object, and don't want to change the architecture either, what can I do to make the code here compile?
I've tried changing the signature of doSomething so that it accepts the entire Map<String, T> and call it like so, with no luck either:
cls.doSomething(cls.getSomething());
This compiles for me:
import java.util.List;
import java.util.Map;
public class Comparison {
interface ICls<T> {
void doSomething(String key, T value);
Map<String, T> getSomething();
}
static class ClsA implements ICls<Boolean> {
public void doSomething(String key, Boolean value) {}
public Map<String, Boolean> getSomething() { return null; }
}
static class ClsB implements ICls<String> {
public void doSomething(String key, String value) {}
public Map<String, String> getSomething() { return null; }
}
static class Main {
List<ICls<?>> list = List.of(
new ClsA(),
new ClsB()
);
void run() {
list.forEach(cls -> {
doIt(cls);
});
}
<T> void doIt(ICls<T> cls) {
Map<String, T> data = cls.getSomething();
data.keySet().forEach(key -> cls.doSomething(key, data.get(key)));
}
}
}
It makes clear the relationship between the map and the cls.
In the original context, because the type of the List is ICls<?> we can't get that relationship, but once we get a single ICls we can introduce a type variable T which allows us to express the relationship between getSomething and doSomething.
I make static variable like this in spring.
public class A {
private static final Map<String, Session> listMap = new HashMap<>();
public static Map<String> getMap() { return this.listMap.values() }
public static void addMap(String name, Session s) { return this.listMap.put(name, s) }
}
I save in service layer.
#Slf4j
public class BService {
public void addSession(String name, Session s) {
A.addMap("a", s);
log.info("added!");
}
}
After saving it, I used it in custom appender.(https://www.baeldung.com/log4j2-custom-appender)
#Plugin(
name = "MapAppender",
category = Core.CATEGORY_NAME,
elementType = Appender.ELEMENT_TYPE)
public class MapAppender extends AbstractAppender {
private ConcurrentMap<String, LogEvent> eventMap = new ConcurrentHashMap<>();
protected MapAppender(String name, Filter filter) {
super(name, filter, null);
}
#PluginFactory
public static MapAppender createAppender(
#PluginAttribute("name") String name,
#PluginElement("Filter") Filter filter) {
return new MapAppender(name, filter);
}
#Override
public void append(LogEvent event) {
Map<> resultMap = A.getMap();
send()
}
}
However, when the appender's append() method is executed, A.getMap() return nothing(size 0). (A.getMap() return correctly in service layer.)
Why is the static value different?..
listMap is loaded when you call addSessionso it is empty When append() method is called
I have an interface to represent a data structure with competing implementations. I need to use this in a class while decoupling the class from having to know the underlying data structure. And within this class, I will need to create several instances of this implementation. How does one do it using interface injection?
class Foo {
Map<String, IDataStructure> map = new HashMap<String, IDataStructure>();
public void addValue(String key, String value) {
if(!map.containsKey(key)) {
map.put(key, new SomeDataStructure(value));
}
}
}
EDIT
I found out an approach to use interface injection. Create a factory interface
class ADataStructureFactory implements DataStructureFactory {
IDataStructure create() {
return new SomeDataStructure();
}
}
And inject this in the constructor
Foo(DataStuctureFactory factory)
Change the add method
public void addValue(String key, String value) {
if(!map.containsKey(key)) {
map.put(key, factory.create());
}
}
This is what you can do :
Define an add method in IDataStructure :
public interface IDataStructure {
public void add(String value);
}
Create an implementation of IDataStrucutre called ListDataStructure as follows :
public class ListDataStructure implements IDataStructure {
private List<String> dataStructure = new ArrayList<String>();
#Override
public void add(String value) {
dataStructure.add(value);
}
}
Create an implementation of IDataStructure called SetDataStructure
public class SetDataStructure implements IDataStructure {
private Set<String> dataStructure = new HashSet<String>();
#Override
public void add(String value) {
dataStructure.add(value);
}
}
Modify your Foo class as follows :
class Foo {
private Map<String, IDataStructure> map;
public Foo(Map<String,IDataStructure> map) {
this.map = map;
}
public void addValue(String key, String value) {
if(map.containsKey(key)) {
map.get(key).add(value);
} else {
/*handle what happens when data structure does not exist. Maybe thow an exception
}
}
}
Example of how to inject the supported data structures. Note that you cannot define data structures on the fly. You need to prepopulate your map in Foo with the supported implementations of data structures.
public class DataStructureExample {
public static void main(String []args) {
Map<String,IDataStructure> dataStrucures = new HashMap<String,IDataStructure>();
//injecting different data structures into Foo
dataStrucures.put("List", new ListDataStructure());
dataStrucures.put("Set", new SetDataStructure());
Foo foo = new Foo(dataStrucures);
//add some value to a list data structure
foo.addValue("List", "Value1");
//add some valu to a set data structure
foo.addValue("Set", "Value1");
}
}
I found out an approach to use interface injection. Create an abstract factory.
class ADataStructureFactory implements DataStructureFactory {
IDataStructure create() {
return new SomeDataStructure();
}
}
And inject this in the constructor
Foo(DataStuctureFactory factory)
Change the add method
public void addValue(String key, String value) {
if(!map.containsKey(key)) {
map.put(key, factory.create());
}
}
I would like to use the generic type safe container pattern, described in Joshua Bloch's Effective Java, but would like to restrict the classes which can be used as keys by using an enum. Below is the code from Joshua's book.
public class Favorites {
private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();
public <T> void putFavorite(Class<T> type, T instance) {
if (type == null)
throw new NullPointerException("Type is null");
favorites.put(type, instance);
}
public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}
}
I would like to write a similar class, but limit the keys to say "Dog.class", and "Cat.class". Ideally, the acceptable keys would be described by an enum, and the "RestrictedFavorites" class would take members of the enum as keys. I'm not sure if I can get the compiler to do all these things for me (type safety, restriction by enum, generality), but if anybody has a suggestion, I'm all ears. Below is attempt V1, which uses runtime checks rather than compile time checks, and is not entirely satisfactory.
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Attempt V1 At a "RestrictedFavorites" class
*/
public class RestrictedFavorites {
public static enum RestrictedKey {
STRING(String.class),
INTEGER(Integer.class);
private static Set<Class<?>> classes;
static {
classes = new HashSet<>();
for (RestrictedKey key: values()) {
classes.add(key.getKlass());
}
}
private final Class<?> klass;
RestrictedKey(Class<?> klass) {
this.klass = klass;
}
public Class<?> getKlass() {
return klass;
}
public static boolean isValidClassKey(Class<?> klass) {
return classes.contains(klass);
}
}
private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();
//Ideally would use compile time checking
public <T> void putFavorite(RestrictedKey key, T instance) {
if (key == null) throw new NullPointerException("Type is null");
if (!key.getKlass().equals(instance.getClass())) {
throw new IllegalArgumentException(
"The type of the key must match the type of the instance");
}
favorites.put(key.getKlass(), instance);
}
//Ideally would take a RestrictedKey as an argument
public <T> T getFavorite(Class<T> key) {
if (!RestrictedKey.isValidClassKey(key)) {
throw new IllegalArgumentException(
"The key must be a member of RestrictedKeys");
}
return key.cast(favorites.get(key));
}
}
Below are some unit tests to verify that my class is doing roughly what I want it to:
public class RestrictedFavoritesTest extends TestCase {
public void testPutFavorite() {
RestrictedFavorites myFavorites = new RestrictedFavorites();
myFavorites.putFavorite(RestrictedKey.INTEGER, 1);
myFavorites.putFavorite(RestrictedKey.STRING, "hey");
int expectedInt = myFavorites.getFavorite(Integer.class);
assertEquals(1, expectedInt);
String expectedString = myFavorites.getFavorite(String.class);
assertEquals("hey", expectedString);
}
public void testPutFavorite_wrongType() {
RestrictedFavorites myFavorites = new RestrictedFavorites();
try {
myFavorites.putFavorite(RestrictedKey.INTEGER, "hey");
fail();
} catch (IllegalArgumentException expected) {}
}
public void testPutFavorite_wrongClass() {
RestrictedFavorites myFavorites = new RestrictedFavorites();
try {
myFavorites.getFavorite(Boolean.class);
} catch (IllegalArgumentException expected) {}
}
}
Answer (to my own question). Don't use Enums. Because enums can't be generic. Instead, create a class to represent the restricted keys, and restrict access to the constructor. Enumerate the valid keys as fields.
import java.util.HashMap;
import java.util.Map;
public class RestrictedFavorites {
private static final class RestrictedKey<T> {
private final Class<T> type;
private RestrictedKey(Class<T> type) {
this.type = type;
}
private Class<T> getMyType() {
return this.type;
}
}
public static final RestrictedKey<String> STRING_KEY =
new RestrictedKey<>(String.class);
public static final RestrictedKey<Integer> INTEGER_KEY =
new RestrictedKey<>(Integer.class);
private final Map<RestrictedKey<?>, Object> favorites =
new HashMap<RestrictedKey<?>, Object>();
public <T> void putFavorite(RestrictedKey<T> key, T instance) {
favorites.put(key, instance);
}
public <T> T getFavorite(RestrictedKey<T> key) {
return key.getMyType().cast(favorites.get(key));
}
}
And the unit tests:
public class RestrictedFavoritesTest extends TestCase {
public void testPutFavorite() {
RestrictedFavorites myFavorites = new RestrictedFavorites();
myFavorites.putFavorite(RestrictedFavorites.STRING_KEY, "hey");
myFavorites.putFavorite(RestrictedFavorites.INTEGER_KEY, 1);
assertEquals(new Integer(1), myFavorites.getFavorite(RestrictedFavorites.INTEGER_KEY));
assertEquals("hey", myFavorites.getFavorite(RestrictedFavorites.STRING_KEY));
}
}