assemble_core/flow/
attributes.rs

1//! Control attribute based flow for attribute selection
2
3use crate::named::Named;
4use std::cmp::Ordering;
5
6use std::marker::PhantomData;
7
8/// Some attribute
9pub trait Attribute: PartialEq {
10    fn attribute_id(&self) -> String {
11        std::any::type_name::<Self>().to_string()
12    }
13}
14
15pub struct AttributeSchema<T: Attribute> {
16    compatibility: AttributeCompatibilityChain<T>,
17    disambiguation: MultipleCandidatesChain<T>,
18}
19
20impl<T: Attribute> AttributeSchema<T> {
21    pub const fn new() -> Self {
22        Self {
23            compatibility: AttributeCompatibilityChain::new(),
24            disambiguation: MultipleCandidatesChain::new(),
25        }
26    }
27
28    pub fn compatibility(&self) -> &AttributeCompatibilityChain<T> {
29        &self.compatibility
30    }
31    pub fn disambiguation(&self) -> &MultipleCandidatesChain<T> {
32        &self.disambiguation
33    }
34
35    pub fn compatibility_mut(&mut self) -> &mut AttributeCompatibilityChain<T> {
36        &mut self.compatibility
37    }
38    pub fn disambiguation_mut(&mut self) -> &mut MultipleCandidatesChain<T> {
39        &mut self.disambiguation
40    }
41
42    /// Attempt to find the matching producer for a given consumer
43    pub fn find_match<'a, I: IntoIterator<Item = &'a Named<T>>>(
44        &self,
45        consumer: &'a Named<T>,
46        producers: I,
47    ) -> Option<&'a Named<T>> {
48        let mut compat_producers: Vec<&Named<T>> = producers
49            .into_iter()
50            .filter(|producer: &&'a Named<T>| {
51                self.compatibility().is_compatible(producer, consumer)
52            })
53            .collect();
54        println!("compat: {:?}", compat_producers);
55        match compat_producers.len() {
56            0 => None,
57            1 => Some(compat_producers.remove(0)),
58            _ => self
59                .disambiguation()
60                .try_disambiguate(consumer, compat_producers),
61        }
62    }
63}
64
65pub struct AttributeCompatibilityChain<T: Attribute> {
66    rules: Vec<Box<dyn AttributeCompatibilityRule<T>>>,
67}
68
69impl<T: Attribute> AttributeCompatibilityChain<T> {
70    pub const fn new() -> Self {
71        Self { rules: Vec::new() }
72    }
73
74    /// Add a compatibility rule
75    pub fn add<R: AttributeCompatibilityRule<T> + 'static>(&mut self, rule: R) {
76        self.rules.push(Box::new(rule));
77    }
78
79    /// Add the [PartialOrd](PartialOrd) as a compatibility rule, where Some(Equals) and Some(Less) is compatible
80    pub fn ordered(&mut self)
81    where
82        T: PartialOrd,
83    {
84        self.add(|check: &mut CompatibilityCheck<T>| {
85            let consumer = check.consumer();
86            let producer = check.producer();
87            match consumer.partial_cmp(producer) {
88                Some(Ordering::Equal) | Some(Ordering::Less) => check.compatible(),
89                Some(Ordering::Greater) => check.incompatible(),
90                None => {}
91            };
92        })
93    }
94
95    /// Add the [PartialOrd](PartialOrd) as a compatibility rule, where Some(Equals) and Some(Greater) is compatible
96    pub fn ordered_rev(&mut self)
97    where
98        T: PartialOrd,
99    {
100        self.add(|check: &mut CompatibilityCheck<T>| {
101            let consumer = check.consumer();
102            let producer = check.producer();
103            match consumer.partial_cmp(producer) {
104                Some(Ordering::Equal) | Some(Ordering::Greater) => check.compatible(),
105                Some(Ordering::Less) => check.incompatible(),
106                None => {}
107            };
108        })
109    }
110
111    /// Check if two attributes are compatible.
112    ///
113    /// Will check all registered rules, and if none specify [`compatible()`](CompatibilityCheck::compatible) or
114    /// [`incompatible()`](CompatibilityCheck::incompatible).
115    pub fn is_compatible(&self, producer: &Named<T>, consumer: &Named<T>) -> bool {
116        let mut check = CompatibilityCheck::new(consumer, producer);
117        for rule in &self.rules {
118            rule.check_capability(&mut check);
119            if let Some(compatability) = check.is_compatible {
120                return compatability;
121            }
122        }
123        producer == consumer
124    }
125}
126
127pub trait AttributeCompatibilityRule<T: Attribute> {
128    /// Check the capability of an attribute
129    fn check_capability(&self, check: &mut CompatibilityCheck<T>);
130}
131
132impl<F, T> AttributeCompatibilityRule<T> for F
133where
134    for<'a> F: Fn(&'a mut CompatibilityCheck<T>),
135    T: Attribute,
136{
137    fn check_capability(&self, check: &mut CompatibilityCheck<T>) {
138        (self)(check)
139    }
140}
141
142/// Shorthand for adding compatiblity
143pub struct IsCompatible<T: Attribute> {
144    _ty: PhantomData<T>,
145    consumer: String,
146    producer: Vec<String>,
147}
148
149impl<T: Attribute> AttributeCompatibilityRule<T> for IsCompatible<T> {
150    fn check_capability(&self, check: &mut CompatibilityCheck<T>) {
151        if self.consumer == check.consumer().name()
152            && self.producer.contains(&check.producer().name().to_string())
153        {
154            check.compatible()
155        }
156    }
157}
158
159impl<T: Attribute> IsCompatible<T> {
160    pub fn new<'a>(consumer: &str, producer: impl IntoIterator<Item = &'a str>) -> Self {
161        Self {
162            _ty: PhantomData,
163            consumer: consumer.to_string(),
164            producer: producer.into_iter().map(str::to_string).collect(),
165        }
166    }
167}
168
169/// data to pass compatibility check information
170pub struct CompatibilityCheck<'a, T: Attribute> {
171    consumer: &'a Named<T>,
172    producer: &'a Named<T>,
173    is_compatible: Option<bool>,
174}
175
176impl<'a, T: Attribute> CompatibilityCheck<'a, T> {
177    fn new(consumer: &'a Named<T>, producer: &'a Named<T>) -> Self {
178        Self {
179            consumer,
180            producer,
181            is_compatible: None,
182        }
183    }
184}
185
186impl<'a, T: Attribute> CompatibilityCheck<'a, T> {
187    /// Get the consumer attribute
188    pub fn consumer(&self) -> &Named<T> {
189        self.consumer
190    }
191
192    /// Get the producer attribute
193    pub fn producer(&self) -> &Named<T> {
194        self.producer
195    }
196
197    /// Calling this method will indicate that the attributes are compatible
198    pub fn compatible(&mut self) {
199        self.is_compatible = Some(true);
200    }
201
202    /// Calling this method will indicate that the attributes are incompatible
203    pub fn incompatible(&mut self) {
204        self.is_compatible = Some(false);
205    }
206}
207
208pub trait MultipleCandidatesRule<T: Attribute> {
209    /// Try to disambiguate
210    fn disambiguate(&self, details: &mut MultipleCandidates<T>);
211}
212
213impl<F, T> MultipleCandidatesRule<T> for F
214where
215    for<'a> F: Fn(&'a mut MultipleCandidates<T>),
216    T: Attribute,
217{
218    fn disambiguate(&self, details: &mut MultipleCandidates<T>) {
219        (self)(details)
220    }
221}
222
223pub struct MultipleCandidatesChain<T: Attribute> {
224    chain: Vec<Box<dyn MultipleCandidatesRule<T>>>,
225}
226
227impl<T: Attribute> MultipleCandidatesChain<T> {
228    pub const fn new() -> Self {
229        Self { chain: Vec::new() }
230    }
231
232    pub fn add<R: MultipleCandidatesRule<T> + 'static>(&mut self, rule: R) {
233        self.chain.push(Box::new(rule));
234    }
235
236    pub fn try_disambiguate<'a, I>(
237        &self,
238        consumer: &'a Named<T>,
239        candidates: I,
240    ) -> Option<&'a Named<T>>
241    where
242        I: IntoIterator<Item = &'a Named<T>>,
243        T: 'a,
244    {
245        let mut details = MultipleCandidates::new(consumer, candidates.into_iter().collect());
246        for rule in &self.chain {
247            rule.disambiguate(&mut details);
248            if let Some(closest) = details.closest_match {
249                return Some(closest);
250            }
251        }
252        None
253    }
254}
255
256pub struct MultipleCandidates<'a, T: Attribute> {
257    consumer_value: &'a Named<T>,
258    candidate_values: Vec<&'a Named<T>>,
259    closest_match: Option<&'a Named<T>>,
260}
261
262impl<'a, T: Attribute> MultipleCandidates<'a, T> {
263    fn new(consumer_value: &'a Named<T>, candidate_values: Vec<&'a Named<T>>) -> Self {
264        Self {
265            consumer_value,
266            candidate_values,
267            closest_match: None,
268        }
269    }
270
271    pub fn consumer_value(&self) -> &Named<T> {
272        self.consumer_value
273    }
274
275    pub fn candidate_values(&self) -> &[&'a Named<T>] {
276        &self.candidate_values[..]
277    }
278
279    pub fn closest_match(&mut self, value: &'a Named<T>) {
280        self.closest_match = Some(value);
281    }
282}
283
284pub struct Equality;
285
286impl<T: Attribute> MultipleCandidatesRule<T> for Equality {
287    fn disambiguate(&self, multiple: &mut MultipleCandidates<T>) {
288        let mut closest = None;
289        for prod in multiple.candidate_values() {
290            if *prod == multiple.consumer_value() {
291                closest = Some(*prod);
292                break;
293            }
294        }
295        if let Some(closest) = closest {
296            multiple.closest_match(closest);
297        }
298    }
299}
300
301#[macro_export]
302macro_rules! named_attribute {
303    ($attribute_type:ident, $make:expr, $name:ident) => {
304        static $name: once_cell::sync::Lazy<Named<$attribute_type>> =
305            once_cell::sync::Lazy::new(|| Named::new(stringify!($name), ($make)));
306    };
307    ($attribute_type:ident, $name:ident) => {
308        $crate::named_attribute!($attribute_type, $attribute_type, $name);
309    };
310    ($name:ident) => {
311        $crate::named_attribute!(Self, $name);
312    };
313}
314
315/// Container of [`Attribute`s](Attribute)
316#[derive(Debug, Clone)]
317pub struct AttributeContainer;
318
319/// Something that carries attributes
320pub trait HasAttributes {
321    fn get_attributes(&self) -> &AttributeContainer;
322}
323
324/// Something that carries attributes and is configurable
325pub trait ConfigurableAttributes: HasAttributes {
326    fn attributes<F: FnOnce(&mut AttributeContainer)>(&mut self, func: F);
327}
328
329#[cfg(test)]
330mod tests {
331    use crate::flow::attributes::{Attribute, AttributeSchema, Equality, IsCompatible};
332    use crate::named::Named;
333
334    #[derive(PartialEq, Eq, Clone, Debug)]
335    pub struct Usage;
336
337    impl Attribute for Usage {}
338
339    named_attribute!(Usage, CLASSES);
340    named_attribute!(Usage, JAR);
341
342    #[test]
343    fn java_style_resolution() {
344        let mut compatibility: AttributeSchema<Usage> = AttributeSchema::new();
345        compatibility
346            .compatibility_mut()
347            .add(IsCompatible::new("CLASSES", ["CLASSES", "JAR"]));
348
349        compatibility.disambiguation_mut().add(Equality);
350
351        let classes = &*CLASSES;
352        let jar = &*JAR;
353        let compat = compatibility.find_match(classes, [jar]);
354        assert!(compat.is_some());
355        let compat = compat.unwrap();
356        assert_eq!(compat, &*JAR);
357
358        let compat = compatibility.find_match(classes, [jar, classes]);
359        assert!(compat.is_some());
360        let compat = compat.unwrap();
361        assert_eq!(compat, &*CLASSES);
362
363        let compat = compatibility.find_match(jar, [jar, classes]);
364        assert!(compat.is_some());
365        let compat = compat.unwrap();
366        assert_eq!(compat, &*JAR);
367
368        let compat = compatibility.find_match(jar, [classes]);
369        assert!(compat.is_none());
370    }
371}