Last active
April 19, 2021 20:56
-
-
Save Garciat/f747fcb5d384ccdabb3ae49107db7fe2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package example.playground; | |
import lombok.RequiredArgsConstructor; | |
import lombok.Value; | |
import lombok.With; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.Parameter; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.Collections; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Objects; | |
import java.util.Set; | |
import java.util.Stack; | |
import java.util.concurrent.atomic.AtomicReference; | |
import java.util.stream.Collectors; | |
public final class MicroWire { | |
public static final String NO_NAME = ""; | |
interface IThingA { | |
} | |
@Value | |
public static class ThingA implements IThingA { | |
} | |
interface IThingB { | |
} | |
@Value | |
public static class ThingB implements IThingB { | |
IThingA a; | |
} | |
@Value | |
public static class ThingC { | |
IThingB b; | |
} | |
public static void main(String[] args) { | |
Container container = builder() | |
.with(singleton(new ThingA())) | |
.with(constructing(ThingB.class).lowering(IThingB.class)) | |
.with(constructing(ThingC.class).named("hello")) | |
.build(); | |
System.out.println(container.get(ThingC.class)); | |
System.out.println(container.get(ThingC.class) == container.get(ThingC.class)); | |
} | |
public static Builder builder() { | |
return new TheBuilder(); | |
} | |
public static <T> Binder<T> constructing(Class<T> type) { | |
return new Binder<>( | |
new Descriptor<>(NO_NAME, type), | |
new CachingProvider<>(new ConstructorProvider<>(type))); | |
} | |
public static <T> Binder<T> singleton(T object) { | |
@SuppressWarnings("unchecked") | |
Class<T> cls = (Class<T>) object.getClass(); | |
return new Binder<>( | |
new Descriptor<>(NO_NAME, cls), | |
new InstanceProvider<>(object)); | |
} | |
public interface Builder { | |
Builder with(Binder<?> binder); | |
Container build(); | |
} | |
@Value | |
public static class Descriptor<T> { | |
@With String name; | |
@With Class<T> type; | |
public Provider<? extends T> cast(Provider<?> provider) { | |
if (!type.isAssignableFrom(provider.principalType())) { | |
throw new ClassCastException("bad bind"); | |
} | |
@SuppressWarnings("unchecked") | |
Provider<? extends T> cast = (Provider<? extends T>) provider; | |
return cast; | |
} | |
} | |
public interface Provider<T> { | |
Class<? extends T> principalType(); | |
T resolve(Container container); | |
} | |
@Value | |
public static class Binder<T> { | |
Descriptor<T> descriptor; | |
Provider<? extends T> provider; | |
public Binder<T> named(String name) { | |
return new Binder<>(descriptor.withName(name), provider); | |
} | |
public Binder<? super T> lowering(Class<? super T> cls) { | |
return lower(this, cls); | |
} | |
public static <T extends U, U> Binder<U> lower(Binder<T> binder, Class<U> cls) { | |
return new Binder<>( | |
new Descriptor<>(binder.getDescriptor().getName(), cls), | |
binder.getProvider()); | |
} | |
} | |
public interface Container { | |
<T> T get(Descriptor<T> descriptor); | |
default <T> T get(Class<T> type) { | |
return get(type, NO_NAME); | |
} | |
default <T> T get(Class<T> type, String name) { | |
return get(new Descriptor<>(name, type)); | |
} | |
default Object[] bindAll(Parameter[] parameters) { | |
return Arrays.stream(parameters) | |
.map(p -> get(p.getType(), p.getName())) | |
.toArray(Object[]::new); | |
} | |
} | |
public interface Generalizer { | |
<T> Set<Descriptor<? super T>> generalize(Descriptor<T> descriptor); | |
default Generalizer andThen(Generalizer next) { | |
return new Generalizer() { | |
@Override | |
public <T> Set<Descriptor<? super T>> generalize(Descriptor<T> d) { | |
return Generalizer.this.generalize(d) | |
.stream() | |
.map(next::generalize) | |
.flatMap(Set::stream) | |
.collect(Collectors.toSet()); | |
} | |
}; | |
} | |
static Generalizer direct() { | |
return new Generalizer() { | |
@Override | |
public <T> Set<Descriptor<? super T>> generalize(Descriptor<T> d) { | |
return new HashSet<>(Collections.singletonList(d)); | |
} | |
}; | |
} | |
static Generalizer anonymizing() { | |
return new Generalizer() { | |
@Override | |
public <T> Set<Descriptor<? super T>> generalize(Descriptor<T> d) { | |
return new HashSet<>(Arrays.asList(d, d.withName(NO_NAME))); | |
} | |
}; | |
} | |
} | |
@RequiredArgsConstructor | |
private static class TypeHierarchyGeneralizer implements Generalizer { | |
@Override | |
public <T> Set<Descriptor<? super T>> generalize(Descriptor<T> descriptor) { | |
Stack<Class<? super T>> next = new Stack<>(); | |
next.push(descriptor.getType()); | |
HashSet<Class<? super T>> visited = new HashSet<>(); | |
while (!next.isEmpty()) { | |
Class<? super T> type = next.pop(); | |
if (!visited.contains(type)) { | |
visited.add(type); | |
next.addAll(immediateSupertypes(type)); | |
} | |
} | |
HashSet<Descriptor<? super T>> set = new HashSet<>(); | |
for (Class<? super T> type : visited) { | |
set.add(new Descriptor<>(descriptor.getName(), type)); | |
} | |
return set; | |
} | |
private <T> List<Class<? super T>> immediateSupertypes(Class<T> type) { | |
List<Class<? super T>> supertypes = new ArrayList<>(); | |
if (type.getSuperclass() != null) { | |
supertypes.add(type.getSuperclass()); | |
} | |
for (Class<?> iface : type.getInterfaces()) { | |
@SuppressWarnings("unchecked") | |
Class<? super T> fixed = (Class<? super T>) iface; | |
supertypes.add(fixed); | |
} | |
return supertypes; | |
} | |
} | |
public interface ProviderSource { | |
<T> List<Provider<? extends T>> get(Descriptor<T> descriptor); | |
} | |
@RequiredArgsConstructor | |
public static class ProviderMap implements ProviderSource { | |
private final Map<Descriptor<?>, List<Provider<?>>> providers; | |
@Override | |
public <T> List<Provider<? extends T>> get(Descriptor<T> descriptor) { | |
return providers | |
.getOrDefault(descriptor, Collections.emptyList()) | |
.stream() | |
.map(descriptor::cast) | |
.collect(Collectors.toList()); | |
} | |
} | |
public interface ProviderSourceFactory { | |
ProviderSource create(List<Binder<?>> binders); | |
} | |
@RequiredArgsConstructor | |
public static class GeneralizingProviderMapFactory implements ProviderSourceFactory { | |
private final Generalizer generalizer; | |
@Override | |
public ProviderSource create(List<Binder<?>> binders) { | |
Map<Descriptor<?>, List<Provider<?>>> map = new HashMap<>(); | |
for (Binder<?> binder : binders) { | |
for (Descriptor<?> descriptor : generalizer.generalize(binder.getDescriptor())) { | |
map.computeIfAbsent(descriptor, $ -> new ArrayList<>()).add(binder.getProvider()); | |
} | |
} | |
return new ProviderMap(map); | |
} | |
} | |
public interface Picker { | |
<T> List<Provider<? extends T>> pick(ProviderSource source, Descriptor<T> descriptor); | |
} | |
@RequiredArgsConstructor | |
public static class LenientPicker implements Picker { | |
@Override | |
public <T> List<Provider<? extends T>> pick(ProviderSource source, Descriptor<T> descriptor) { | |
List<Provider<? extends T>> named = source.get(descriptor); | |
if (named.isEmpty()) { | |
return source.get(descriptor.withName(NO_NAME)); | |
} else { | |
return named; | |
} | |
} | |
} | |
@RequiredArgsConstructor | |
public static class ConstructorProvider<T> implements Provider<T> { | |
private final Class<T> type; | |
@Override | |
public Class<? extends T> principalType() { | |
return type; | |
} | |
@Override | |
public T resolve(Container container) { | |
@SuppressWarnings("unchecked") | |
Constructor<T>[] constructors = (Constructor<T>[]) type.getConstructors(); | |
switch (constructors.length) { | |
case 0: | |
throw new RuntimeException("no constructor"); | |
case 1: | |
break; | |
default: | |
throw new RuntimeException("no canonical constructor"); | |
} | |
Constructor<T> constructor = constructors[0]; | |
Parameter[] parameters = constructor.getParameters(); | |
Object[] arguments = container.bindAll(parameters); | |
try { | |
return constructor.newInstance(arguments); | |
} catch (Exception ex) { | |
throw new RuntimeException(ex); | |
} | |
} | |
} | |
@RequiredArgsConstructor | |
public static class CachingProvider<T> implements Provider<T> { | |
private final AtomicReference<T> store = new AtomicReference<>(); | |
private final Provider<T> subject; | |
@Override | |
public Class<? extends T> principalType() { | |
return subject.principalType(); | |
} | |
@Override | |
public T resolve(Container container) { | |
return store.updateAndGet(current -> { | |
if (current == null) { | |
return Objects.requireNonNull(subject.resolve(container)); | |
} else { | |
return current; | |
} | |
}); | |
} | |
} | |
@RequiredArgsConstructor | |
public static class InstanceProvider<T> implements Provider<T> { | |
private final T instance; | |
@SuppressWarnings("unchecked") | |
@Override | |
public Class<? extends T> principalType() { | |
return (Class<? extends T>) instance.getClass(); | |
} | |
@Override | |
public T resolve(Container container) { | |
return instance; | |
} | |
} | |
@RequiredArgsConstructor | |
private static class TheContainer implements Container { | |
private final ProviderSource providerSource; | |
private final Picker picker; | |
@Override | |
public <T> T get(Descriptor<T> descriptor) { | |
List<Provider<? extends T>> candidates = picker.pick(providerSource, descriptor); | |
switch (candidates.size()) { | |
case 0: | |
throw new RuntimeException("no providers"); | |
case 1: | |
Provider<? extends T> provider = candidates.get(0); | |
return provider.resolve(this); | |
default: | |
throw new RuntimeException("many providers: " + candidates.size()); | |
} | |
} | |
} | |
@RequiredArgsConstructor | |
private static class TheBuilder implements Builder { | |
private final List<Binder<?>> binders = new ArrayList<>(); | |
@Override | |
public Builder with(Binder<?> binder) { | |
binders.add(binder); | |
return this; | |
} | |
@Override | |
public Container build() { | |
Generalizer generalizer = | |
Generalizer.direct() | |
.andThen(Generalizer.anonymizing()) | |
.andThen(new TypeHierarchyGeneralizer()); | |
return new TheContainer( | |
new GeneralizingProviderMapFactory(generalizer).create(binders), | |
new LenientPicker()); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment