1use crate::named::Named;
4use std::cmp::Ordering;
5
6use std::marker::PhantomData;
7
8pub 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 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 pub fn add<R: AttributeCompatibilityRule<T> + 'static>(&mut self, rule: R) {
76 self.rules.push(Box::new(rule));
77 }
78
79 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 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 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 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
142pub 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
169pub 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 pub fn consumer(&self) -> &Named<T> {
189 self.consumer
190 }
191
192 pub fn producer(&self) -> &Named<T> {
194 self.producer
195 }
196
197 pub fn compatible(&mut self) {
199 self.is_compatible = Some(true);
200 }
201
202 pub fn incompatible(&mut self) {
204 self.is_compatible = Some(false);
205 }
206}
207
208pub trait MultipleCandidatesRule<T: Attribute> {
209 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#[derive(Debug, Clone)]
317pub struct AttributeContainer;
318
319pub trait HasAttributes {
321 fn get_attributes(&self) -> &AttributeContainer;
322}
323
324pub 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}