From 4a96d9cad446ea405b51dfeebb01a1b6d7f6fb2b Mon Sep 17 00:00:00 2001 From: Ben Culkin Date: Tue, 27 Sep 2022 19:21:16 -0400 Subject: Add some interesting new things Adds a number of things based off of some of the notes I've made over time, plus a few papers I've read. More details to come later, whenever I decide to actually get serious about documentation and examples and the like --- base/src/main/java/bjc/utils/graph/ASGraph.java | 179 +++++++++++ .../main/java/bjc/utils/graph/ASGraphGrammar.java | 30 ++ base/src/main/java/bjc/utils/graph/AlgGraph.java | 88 ++++++ .../main/java/bjc/utils/graph/SimpleAlgGraph.java | 83 +++++ .../java/bjc/utils/patterns/ComplexPattern.java | 347 ++++++++++++++------- .../src/main/java/bjc/utils/services/Bordello.java | 53 ++++ .../main/java/bjc/utils/services/Implementor.java | 24 ++ .../main/java/bjc/utils/services/package-info.java | 1 + 8 files changed, 698 insertions(+), 107 deletions(-) create mode 100644 base/src/main/java/bjc/utils/graph/ASGraph.java create mode 100644 base/src/main/java/bjc/utils/graph/ASGraphGrammar.java create mode 100644 base/src/main/java/bjc/utils/graph/AlgGraph.java create mode 100644 base/src/main/java/bjc/utils/graph/SimpleAlgGraph.java create mode 100644 base/src/main/java/bjc/utils/services/Bordello.java create mode 100644 base/src/main/java/bjc/utils/services/Implementor.java create mode 100644 base/src/main/java/bjc/utils/services/package-info.java (limited to 'base') diff --git a/base/src/main/java/bjc/utils/graph/ASGraph.java b/base/src/main/java/bjc/utils/graph/ASGraph.java new file mode 100644 index 0000000..777598f --- /dev/null +++ b/base/src/main/java/bjc/utils/graph/ASGraph.java @@ -0,0 +1,179 @@ +package bjc.utils.graph; + +import java.util.*; +import java.util.function.Function; + +import bjc.data.Pair; +import bjc.esodata.*; +import bjc.funcdata.Freezable; +import bjc.funcdata.ObjectFrozen; +import bjc.functypes.Unit; + +public class ASGraph implements Freezable> { + private Set nodes; + private Function nodeValuer; + private PairMap arcMap; + + // Used for implementation efficiency + private Multimap nodeToValue; + private Multimap valueToNode; + + private boolean frozen; + private boolean deepFrozen; + + public ASGraph(Function valuer) { + this.nodes = new HashSet<>(); + this.nodeValuer = valuer; + this.arcMap = new PairMap<>(); + + this.nodeToValue = new TSetMultimap<>(); + this.valueToNode = new TSetMultimap<>(); + } + + /** + * Retrieve a read-only view of the nodes + * + * @return A read-only set of the nodes. + */ + public Set getNodes() { + return Collections.unmodifiableSet(nodes); + } + + /** + * Get the value for a node in the graph. + * + * @param nd The node to check. + * + * @return The value for that node. + */ + public Value getValue(Node nd) { + return nodeValuer.apply(nd); + } + + /** + * Add a node to the graph. + * + * @param nd The node to add. + */ + public void addNode(Node nd) { + if (deepFrozen) + throw new ObjectFrozen(); + + this.nodes.add(nd); + + Value v = nodeValuer.apply(nd); + this.nodeToValue.add(nd, v); + this.valueToNode.add(v, nd); + } + + /** + * Remove a node from the graph. + * + * @param nd The node to remove + */ + public void removeNode(Node nd) { + if (deepFrozen) + throw new ObjectFrozen(); + this.nodes.remove(nd); + + Value v = nodeValuer.apply(nd); + this.nodeToValue.remove(nd); + this.valueToNode.remove(v, nd); + } + + /** + * Add an arc to the graph. + * + * Note that badness can happen if you add a arc that refers to nodes not in the + * graph. + * + * @param nd1 The source node for the arc. + * @param nd2 The destination node for the arc. + * @param lab The label for the arc. + */ + public void addArc(Node nd1, Node nd2, Label lab) { + if (deepFrozen) + throw new ObjectFrozen(); + arcMap.put(Pair.pair(nd1, nd2), lab); + } + + /** + * Remove an arc from the graph. + * + * @param nd1 The source node for the arc. + * @param nd2 The destination node for the arc. + * @param lab The label for the arc. + */ + public void removeArc(Node nd1, Node nd2, Label lab) { + if (deepFrozen) + throw new ObjectFrozen(); + arcMap.remove(Pair.pair(nd1, nd2), lab); + } + + /** + * Check if this graph is partitioned by the given graphs. + * + *
    + *
  1. Every node in nodes is contained in exactly one partition
  2. + *
  3. For every node in nodes, the value the partition assigns it is equal to + * the value from nodeValuer
  4. + *
  5. Every arc in a partition is also in arcMap
  6. + *
+ * + * @param partitions The graphs to check partitioning for. + * + * @return Whether this graph is partitioned by the given nodes. + */ + public boolean partitionedBy(@SuppressWarnings("unchecked") ASGraph... partitions) { + // TODO: Implement me + return false; + } + + /** + * Create a graph representing a given string + * + * Note that because of the way the value function is implemented, modifying the + * graph will not work well. + * + * @param strang The string to represent. + * + * @return The graph representing the string. + */ + public static ASGraph fromString(String strang) { + ASGraph ret = new ASGraph<>(strang::charAt); + + for (int i = 0; i < strang.length(); i++) { + ret.addNode(i); + if (i != 0) + ret.addArc(i - 1, i, Unit.UNIT); + } + + return ret; + } + + @Override + public boolean freeze() { + frozen = true; + return true; + } + + @Override + public boolean thaw() { + if (deepFrozen) + return false; + + return false; + } + + @Override + public boolean deepFreeze() { + deepFrozen = true; + frozen = true; + return true; + } + + @Override + public boolean isFrozen() { + return frozen; + } +} diff --git a/base/src/main/java/bjc/utils/graph/ASGraphGrammar.java b/base/src/main/java/bjc/utils/graph/ASGraphGrammar.java new file mode 100644 index 0000000..847107d --- /dev/null +++ b/base/src/main/java/bjc/utils/graph/ASGraphGrammar.java @@ -0,0 +1,30 @@ +package bjc.utils.graph; + +import java.util.Set; + +import bjc.data.Either; + +// See https://web.archive.org/web/20190414072011/https://core.ac.uk/download/pdf/82129679.pdf +public class ASGraphGrammar { + public class Rule { + private ASGraph starting; + + // Must contain at least one node + private ASGraph, Label> production; + + // Start node and end node must be in production + private Either startNode; + private Either endNode; + + public void derive(ASGraph, Label> graph) { + // The derivation of H from G according to the rule A -> K(I/O) consists simply of replacing + // a node N' in G whose value is A by the graph K. Arcs leading into N' are replaced by + // arcs leading to I, arcs exiting from B' are replaced by arcs exiting from O, and any + // loop arcs on N' are replaced by arcs from O to I. + + } + } + + // int is perhaps not the best node type, but it works + private Set> rules; +} diff --git a/base/src/main/java/bjc/utils/graph/AlgGraph.java b/base/src/main/java/bjc/utils/graph/AlgGraph.java new file mode 100644 index 0000000..7c9d38d --- /dev/null +++ b/base/src/main/java/bjc/utils/graph/AlgGraph.java @@ -0,0 +1,88 @@ +package bjc.utils.graph; + +import bjc.functypes.Container; + +/** + * A directed algebraic graph + * + * (ref. Algebraic Graphs with Class, Andrey Mokhov} + * + * @author bjcul + * + * @param The type of the vertexes + * @param Containing type parameter + */ +public interface AlgGraph> extends Container> { + /** + * Create a empty algebraic graph. + * + * @param The type of the vertices + * + * @return A new empty graph. + */ + AlgGraph empty(); + + /** + * Create a algebraic graph with a single vertex + * + * @param val The value for the vertex + * + * @return A new graph with the given vertex + */ + AlgGraph vertex(B val); + + /** + * Overlay a graph onto this one, adding its vertices and edges. + * + * @param other The graph to overlay + */ + void overlay(AlgGraph other); + + /** + * Connect a graph to this one, adding its vertices and edges; as well as + * establishing an edge from each node in this graph, to each node in the other + * graph. + * + * @param other The graph to connect + */ + void connect(AlgGraph other); + + /** + * Overlay two graphs into a new graph. + * + * @param The type of the vertices. + * + * @param left The graph to overlay onto + * @param right The graph being overlaid + * + * @return The overlaid graph. + */ + public static > G overlay(G left, G right) { + @SuppressWarnings("unchecked") + // We know this cast is good, java just can't tell + G result = (G) left.empty(); + result.overlay(left); + result.overlay(right); + + return result; + } + + /** + * Connect two graphs into a new graph. + * + * @param The type of the vertices. + * + * @param left The graph to connect to + * @param right The graph being connected + * + * @return The connected graph. + */ + public static > G connect(G left, G right) { + @SuppressWarnings("unchecked") + // We know this cast is good, java just can't tell + G result = (G) left.empty(); + result.overlay(left); + result.connect(right); + return result; + } +} \ No newline at end of file diff --git a/base/src/main/java/bjc/utils/graph/SimpleAlgGraph.java b/base/src/main/java/bjc/utils/graph/SimpleAlgGraph.java new file mode 100644 index 0000000..2c7ba08 --- /dev/null +++ b/base/src/main/java/bjc/utils/graph/SimpleAlgGraph.java @@ -0,0 +1,83 @@ +package bjc.utils.graph; + +import java.util.*; + +/** + * A basic implementation of a directed algebraic graph. + * + * @author bjcul + * + * @param The type of the vertices + */ +public class SimpleAlgGraph implements AlgGraph> { + // TODO: consider if some function for labeling edges would make sense + private Set vertices; + private Map edges; + + /** + * Create a new empty graph. + */ + public SimpleAlgGraph() { + vertices = new HashSet<>(); + edges = new HashMap<>(); + } + + /** + * Create a new graph with the given vertices, but no edges. + * + * @param vertexes The vertices for the graph. + */ + @SafeVarargs + public SimpleAlgGraph(Vertex... vertexes) { + this(); + + for (Vertex vertex : vertexes) + vertices.add(vertex); + } + + @Override + public SimpleAlgGraph empty() { + return new SimpleAlgGraph<>(); + } + + @Override + public SimpleAlgGraph vertex(B val) { + return new SimpleAlgGraph<>(); + } + + /** + * Overlay a graph onto this one, adding its vertices and edges. + * + * @param other The graph to overlay + */ + @Override + public void overlay(AlgGraph> other) { + SimpleAlgGraph graph = (SimpleAlgGraph) other; + + vertices.addAll(graph.vertices); + edges.putAll(graph.edges); + } + + /** + * Connect a graph to this one, adding its vertices and edges; as well as + * establishing an edge from each node in this graph, to each node in the other + * graph. + * + * @param other The graph to connect + */ + @Override + public void connect(AlgGraph> other) { + SimpleAlgGraph graph = (SimpleAlgGraph) other; + + // note: this could be inefficent for large graphs + for (Vertex left : vertices) { + for (Vertex right : graph.vertices) { + edges.put(left, right); + } + } + + overlay(other); + } + + +} \ No newline at end of file diff --git a/base/src/main/java/bjc/utils/patterns/ComplexPattern.java b/base/src/main/java/bjc/utils/patterns/ComplexPattern.java index 2d9fc71..69ee67c 100644 --- a/base/src/main/java/bjc/utils/patterns/ComplexPattern.java +++ b/base/src/main/java/bjc/utils/patterns/ComplexPattern.java @@ -1,18 +1,21 @@ package bjc.utils.patterns; +import java.util.Optional; import java.util.function.*; import java.util.regex.*; import bjc.data.*; +import bjc.functypes.ID; +import bjc.functypes.Unit; /** * A pattern that can be matched against. * * @author Ben Culkin * - * @param The type of object being matched against. + * @param The type of object being matched against. * @param The type returned by the pattern. - * @param The state type returned by the predicate. + * @param The state type returned by the predicate. */ public interface ComplexPattern { /** @@ -20,11 +23,11 @@ public interface ComplexPattern { * * @param input The object to check against this pattern. * - * @return Whether or not this pattern is matched, as well as a state value - * that will get passed to the pattern if it did match. + * @return Whether or not this pattern is matched, as well as a state value that + * will get passed to the pattern if it did match. */ Pair matches(InputType input); - + /** * Apply this pattern, once it has matched. * @@ -34,9 +37,9 @@ public interface ComplexPattern { * @return The result of applying this pattern. */ ReturnType apply(InputType input, PredType state); - + /* Pattern producing functions */ - + /** * Create a pattern composed from a predicate & a function. * @@ -44,153 +47,125 @@ public interface ComplexPattern { * @param The type used as intermediate state. * @param The type initially matched against. * - * @param matcher The predicate that says what this pattern matches. - * @param accepter The action that happens when this pattern matches. + * @param matcher The predicate that says what this pattern matches. + * @param accepter The action that happens when this pattern matches. * * @return A pattern composed from the passed in functions. */ static ComplexPattern from( - Function> matcher, - BiFunction accepter) - { + Function> matcher, BiFunction accepter) { return new FunctionalPattern<>(matcher, accepter); } - + /** - * Create a pattern which checks if an object is of a given type (or a subtype of it). + * Create a pattern which checks if an object is of a given type (or a subtype + * of it). * * @param The type to check if the object is an instance of. - * @param The type returned by the action. - * @param The type of the thing to match. + * @param The type returned by the action. + * @param The type of the thing to match. * - * @param clasz The Class instance for the type you want to check. - * @param action The action to execute if the pattern does match. + * @param clasz The Class instance for the type you want to check. + * @param action The action to execute if the pattern does match. * * @return A pattern which follows the specified condition. */ @SuppressWarnings("unchecked") - static ComplexPattern ofClass( - Class clasz, - Function action) - { - return from( - (input) -> Pair.pair(clasz.isInstance(input), null), - (input, ignored) -> action.apply((ClassType)input) - ); - } - + static ComplexPattern ofClass(Class clasz, + Function action) { + return from((input) -> Pair.pair(clasz.isInstance(input), null), + (input, ignored) -> action.apply((ClassType) input)); + } + /** * Creates a pattern which matches a given object. * * @param The type returned when the pattern matches. * @param The type of the thing to match. * - * @param obj The object being tested for equality. - * @param action The action to execute when the object matches. + * @param obj The object being tested for equality. + * @param action The action to execute when the object matches. * * @return A pattern which tests against the equality of an object. */ - static ComplexPattern matchesObject( - InpType obj, - Function action - ) - { - return from( - (input) -> Pair.pair(obj.equals(input), null), - (input, ignored) -> action.apply(input) - ); - } - + static ComplexPattern matchesObject(InpType obj, + Function action) { + return from((input) -> Pair.pair(obj.equals(input), null), (input, ignored) -> action.apply(input)); + } + /** * Tests if the toString rendition of an object matches a given condition. * * @param The type returned by the pattern. * @param The type of the thing to match. * - * @param pattern The string to check against. - * @param action The action to check when the toString of the object matches - * the provided string. This is passed both the object, and its - * string form (in the event that you don't want to call toString - * multiple times, for whatever reason) - * - * @return A pattern which tests against the toString representation of an object. + * @param pattern The string to check against. + * @param action The action to check when the toString of the object matches + * the provided string. This is passed both the object, and its + * string form (in the event that you don't want to call + * toString multiple times, for whatever reason) + * + * @return A pattern which tests against the toString representation of an + * object. */ - static ComplexPattern equalsString( - String pattern, - BiFunction action - ) - { + static ComplexPattern equalsString(String pattern, + BiFunction action) { Function> matcher = (input) -> { String objString = input.toString(); - + return Pair.pair(pattern.equals(objString), objString); }; - - return from( - matcher, - (input, objString) -> action.apply(input, objString) - ); + + return from(matcher, (input, objString) -> action.apply(input, objString)); } - + /** * Check if the toString of a given object matches a regex. * * @param The type returned by the pattern. - * @param The type of object to match against. + * @param The type of object to match against. * - * @param regex The regex to match against. - * @param cond The predicate to use to determine if the regex matched. - * @param action The action to call when the regex matched. + * @param regex The regex to match against. + * @param cond The predicate to use to determine if the regex matched. + * @param action The action to call when the regex matched. * * @return A pattern which does the regex matching. */ - static ComplexPattern matchesRegex( - String regex, - Predicate cond, - BiFunction action - ) - { + static ComplexPattern matchesRegex(String regex, + Predicate cond, BiFunction action) { java.util.regex.Pattern regexPat = java.util.regex.Pattern.compile(regex); Function> matcher = (input) -> { String inpString = input.toString(); - + Matcher mat = regexPat.matcher(inpString); - - if (cond.test(mat)) return Pair.pair(true, mat); - else return Pair.pair(false, null); + + if (cond.test(mat)) + return Pair.pair(true, mat); + return Pair.pair(false, null); }; - - return from( - matcher, - (input, res) -> action.apply(input, res) - ); + + return from(matcher, (input, res) -> action.apply(input, res)); } - + // @TODO Nov 21, 2020 Ben Culkin :MorePatterns // Try and write something to iterate over Iterator in a type-safe manner // Also, something for doing a sub-pattern match - + /** * Create a pattern which will always execute. * * @param The type returned. * @param The type being matched against. * - * @param action The action to execute. + * @param action The action to execute. * * @return A pattern which will be executed. */ - static ComplexPattern otherwise( - Function action - ) - { - return from( - (input) -> Pair.pair(true, null), - (input, ignored) -> action.apply(input) - ); - } - + static ComplexPattern otherwise(Function action) { + return from((input) -> Pair.pair(true, null), (input, ignored) -> action.apply(input)); + } + /** * Create a pattern which checks if the string form of a given object starts * with a specific string. @@ -198,26 +173,184 @@ public interface ComplexPattern { * @param The type returned by the matcher. * @param The type being matched against. * - * @param pattern The string to check against. - * @param action The action to execute. + * @param pattern The string to check against. + * @param action The action to execute. * * @return A pattern which functions as described. */ - static ComplexPattern startsWith( - String pattern, - Function action) - { + static ComplexPattern startsWith(String pattern, + Function action) { return from((input) -> { String objString = input.toString(); - - if (objString.startsWith(pattern)) { - return Pair.pair( - true, - objString.substring( - pattern.length())); - } else { + + if (objString.startsWith(pattern)) + return Pair.pair(true, objString.substring(pattern.length())); + return Pair.pair(false, null); + }, (input, state) -> action.apply(state)); + } + + // TODO: See about generalizing these to be able to take different return types + /** + * Create a pattern which matches if any of its two components match. + * + * @param The type each pattern returns + * @param The input for each pattern. + * @param The first state type. + * @param The second state type. + * + * @param left The first pattern. + * @param right The second pattern. + * + * @return A pattern which matches if either of its components do. + */ + static ComplexPattern, InpType> or( + ComplexPattern left, ComplexPattern right) { + // It would be convenient if we could just omit the two state types. + // However, java isn't smart enough to infer the right types without the help + Function>> matcher = (inp) -> { + Pair leftRes = left.matches(inp); + if (leftRes.getLeft()) { + return Pair.pair(true, Either.left(leftRes.getRight())); + } + + Pair rightRes = right.matches(inp); + if (rightRes.getLeft()) { + return Pair.pair(true, Either.right(rightRes.getRight())); + } + return Pair.pair(false, null); + }; + return from(matcher, (input, state) -> state.isLeft() ? left.apply(input, state.forceLeft()) + : right.apply(input, state.forceRight())); + } + + /** + * Create a pattern which matches if both component patterns do. + * + * @param The type returned by the patterns + * @param The input for the patterns + * @param The state for the right pattern + * @param The state for the left pattern + * + * @param left The left pattern + * @param right The right pattern + * + * @return A pattern which matches if both of the given patterns do. + */ + static ComplexPattern, Pair, InpType> and( + ComplexPattern left, + ComplexPattern right) { + Function>> matcher = (inp) -> { + Pair leftRes = left.matches(inp); + if (!leftRes.getLeft()) + return Pair.pair(false, null); + Pair rightRes = right.matches(inp); + if (!rightRes.getLeft()) + return Pair.pair(false, null); + return Pair.pair(true, Pair.pair(leftRes.getRight(), rightRes.getRight())); + }; + + return from(matcher, (input, state) -> { + return Pair.pair(left.apply(input, state.getLeft()), right.apply(input, state.getRight())); + }); + } + + static ComplexPattern, Either>, InpType> then( + ComplexPattern first, ComplexPattern second) { + Function>>> matcher = (inp) -> { + Pair firstRes = first.matches(inp); + if (!firstRes.getLeft()) + return Pair.pair(false, null); + Shared shared = first.apply(inp, firstRes.getRight()); + Pair secondRes = second.matches(shared); + if (!secondRes.getLeft()) + return Pair.pair(true, Either.left(shared)); + return Pair.pair(true, Either.right(Pair.pair(shared, secondRes.getRight()))); + }; + + return from(matcher, (input, state) -> { + if (state.isLeft()) + return state.newRight(); + Pair right = state.forceRight(); + return Either.right(second.apply(right.getLeft(), right.getRight())); + }); + } + + static ComplexPattern, Other>, + Either>, Other>, InpType> maybeThen( + ComplexPattern, State1, InpType> first, + ComplexPattern second) { + Function>, Other>>> matcher = (inp) -> { + Pair firstRes = first.matches(inp); + if (!firstRes.getLeft()) return Pair.pair(false, null); + Either maybeShared = first.apply(inp, firstRes.getRight()); + if (!maybeShared.isLeft()) + return Pair.pair(true, maybeShared.newLeft()); + Shared shared = maybeShared.forceLeft(); + Pair secondRes = second.matches(shared); + if (!secondRes.getLeft()) + return Pair.pair(true, Either.left(Either.left(shared))); + return Pair.pair(true, Either.left(Either.right(Pair.pair(shared, secondRes.getRight())))); + }; + + // Can't inline matcher, that breaks type-inference + return from(matcher, (input, state) -> { + if (!state.isLeft()) { + return state.newLeft(); + } + Either> left = state.forceLeft(); + if (left.isLeft()) { + return Either.left(left.newRight()); } - }, (ignored, input) -> action.apply(input)); + Pair right = left.forceRight(); + return Either.left(Either.right(second.apply(right.getLeft(), right.getRight()))); + }); + } + + static ComplexPattern collapse( + ComplexPattern, State, Input> patt, Function f, + Function g) { + return patt.mapOutput(eth -> eth.extract(f, g)); + } + + static ComplexPattern collapse( + ComplexPattern, State, Input> patt) { + return patt.mapOutput(Either::collapse); + } + + static ComplexPattern, ?, Input> maybe(ComplexPattern pat) { + return from((inp) -> { + Pair res = pat.matches(inp); + if (res.getLeft()) + return Pair.pair(true, Either.left(res.getRight())); + return Pair.pair(true, Either.right(null)); + }, (Input inp, Either state) -> { + // Need to specify the type; inference isn't smart enough to guess the right + // thing + if (state.isLeft()) + return Optional.of(pat.apply(inp, state.forceLeft())); + return Optional.empty(); + }); + } + + default ComplexPattern, NewInput> mapInput( + Function func) { + return from((inp) -> { + InputType procInput = func.apply(inp); + Pair res = matches(procInput); + return Pair.pair(res.getLeft(), Pair.pair(res.getRight(), procInput)); + }, (input, state) -> apply(state.getRight(), state.getLeft())); + } + + default ComplexPattern mapOutput(Function func) { + return from(ComplexPattern.this::matches, (inp, state) -> func.apply(apply(inp, state))); + } + + default ComplexPattern, PredType, InputType> withState() { + return from(ComplexPattern.this::matches, (inp, state) -> Pair.pair(apply(inp, state), state)); } -} +} \ No newline at end of file diff --git a/base/src/main/java/bjc/utils/services/Bordello.java b/base/src/main/java/bjc/utils/services/Bordello.java new file mode 100644 index 0000000..104c4ab --- /dev/null +++ b/base/src/main/java/bjc/utils/services/Bordello.java @@ -0,0 +1,53 @@ +package bjc.utils.services; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A place to retrieve services from + * + * @author bjcul + * + */ +public class Bordello { + private static final Map, Object> services = new ConcurrentHashMap<>(); + + /** + * Retrieve the implementation of a given service. + * + * @param The type of the service. + * + * @param interfaceClass The class of the service. + * + * @return The default implementation of the service. + */ + public static T get(Class interfaceClass) { + synchronized (interfaceClass) { + Object service = services.get(interfaceClass); + if (service == null) { + try { + Class implementor = interfaceClass.getAnnotation(Implementor.class).value(); + service = implementor.getDeclaredConstructor().newInstance(); + services.put(interfaceClass, implementor); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + return interfaceClass.cast(service); + } + } + + /** + * Set an implementation for a given service to be something other than the default. + * + * @param The type of the service + * + * @param interfaceClass The class of the service + * @param implementor The alternate implementation for the service. + */ + public static void set(Class interfaceClass, T implementor) { + synchronized (interfaceClass) { + services.put(interfaceClass, implementor); + } + } +} diff --git a/base/src/main/java/bjc/utils/services/Implementor.java b/base/src/main/java/bjc/utils/services/Implementor.java new file mode 100644 index 0000000..3dac860 --- /dev/null +++ b/base/src/main/java/bjc/utils/services/Implementor.java @@ -0,0 +1,24 @@ +package bjc.utils.services; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.*; + +/** + * Indicates the default implementation for a given service. + * + * @author bjcul + * + */ +@Documented +@Retention(RUNTIME) +@Target(TYPE) +public @interface Implementor { + /** + * The default implementation for the service this annotates. + * + * @return The default impl. for the service this annotates + */ + Class value(); +} diff --git a/base/src/main/java/bjc/utils/services/package-info.java b/base/src/main/java/bjc/utils/services/package-info.java new file mode 100644 index 0000000..24f6855 --- /dev/null +++ b/base/src/main/java/bjc/utils/services/package-info.java @@ -0,0 +1 @@ +package bjc.utils.services; \ No newline at end of file -- cgit v1.2.3