1use super::{PlanningAnnotation, ShadowAnnotation};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
5pub struct DomainClass {
6 pub name: String,
7 #[serde(default)]
8 pub annotations: Vec<PlanningAnnotation>,
9 #[serde(default)]
10 pub fields: Vec<FieldDescriptor>,
11}
12
13impl DomainClass {
14 pub fn new(name: impl Into<String>) -> Self {
15 Self {
16 name: name.into(),
17 annotations: Vec::new(),
18 fields: Vec::new(),
19 }
20 }
21
22 pub fn with_annotation(mut self, annotation: PlanningAnnotation) -> Self {
23 self.annotations.push(annotation);
24 self
25 }
26
27 pub fn with_field(mut self, field: FieldDescriptor) -> Self {
28 self.fields.push(field);
29 self
30 }
31
32 pub fn is_planning_entity(&self) -> bool {
33 self.annotations
34 .iter()
35 .any(|a| matches!(a, PlanningAnnotation::PlanningEntity))
36 }
37
38 pub fn is_planning_solution(&self) -> bool {
39 self.annotations
40 .iter()
41 .any(|a| matches!(a, PlanningAnnotation::PlanningSolution))
42 }
43
44 pub fn get_planning_variables(&self) -> impl Iterator<Item = &FieldDescriptor> {
45 self.fields.iter().filter(|f| f.is_planning_variable())
46 }
47
48 pub fn get_planning_id_field(&self) -> Option<&FieldDescriptor> {
49 self.fields
50 .iter()
51 .find(|f| f.has_annotation(|a| matches!(a, PlanningAnnotation::PlanningId)))
52 }
53
54 pub fn get_score_field(&self) -> Option<&FieldDescriptor> {
55 self.fields
56 .iter()
57 .find(|f| f.has_annotation(|a| matches!(a, PlanningAnnotation::PlanningScore { .. })))
58 }
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
62pub struct FieldDescriptor {
63 pub name: String,
64 pub field_type: FieldType,
65 #[serde(default)]
66 pub planning_annotations: Vec<PlanningAnnotation>,
67 #[serde(default)]
68 pub shadow_annotations: Vec<ShadowAnnotation>,
69 #[serde(default)]
70 pub accessor: Option<DomainAccessor>,
71}
72
73impl FieldDescriptor {
74 pub fn new(name: impl Into<String>, field_type: FieldType) -> Self {
75 Self {
76 name: name.into(),
77 field_type,
78 planning_annotations: Vec::new(),
79 shadow_annotations: Vec::new(),
80 accessor: None,
81 }
82 }
83
84 pub fn with_planning_annotation(mut self, annotation: PlanningAnnotation) -> Self {
85 self.planning_annotations.push(annotation);
86 self
87 }
88
89 pub fn with_shadow_annotation(mut self, annotation: ShadowAnnotation) -> Self {
90 self.shadow_annotations.push(annotation);
91 self
92 }
93
94 pub fn with_accessor(mut self, accessor: DomainAccessor) -> Self {
95 self.accessor = Some(accessor);
96 self
97 }
98
99 pub fn is_planning_variable(&self) -> bool {
100 self.planning_annotations
101 .iter()
102 .any(|a| a.is_any_variable())
103 }
104
105 pub fn is_shadow_variable(&self) -> bool {
106 !self.shadow_annotations.is_empty()
107 }
108
109 pub fn has_annotation<F>(&self, predicate: F) -> bool
110 where
111 F: Fn(&PlanningAnnotation) -> bool,
112 {
113 self.planning_annotations.iter().any(predicate)
114 }
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
118pub struct DomainAccessor {
119 pub getter: String,
120 pub setter: String,
121}
122
123impl DomainAccessor {
124 pub fn new(getter: impl Into<String>, setter: impl Into<String>) -> Self {
125 Self {
126 getter: getter.into(),
127 setter: setter.into(),
128 }
129 }
130
131 pub fn from_field_name(field_name: &str) -> Self {
132 let capitalized = capitalize_first(field_name);
133 Self {
134 getter: format!("get{}", capitalized),
135 setter: format!("set{}", capitalized),
136 }
137 }
138}
139
140fn capitalize_first(s: &str) -> String {
141 let mut chars = s.chars();
142 match chars.next() {
143 None => String::new(),
144 Some(first) => first.to_uppercase().chain(chars).collect(),
145 }
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
149#[serde(tag = "kind")]
150pub enum FieldType {
151 Primitive(PrimitiveType),
152 Object {
153 class_name: String,
154 },
155 Array {
156 element_type: Box<FieldType>,
157 },
158 List {
159 element_type: Box<FieldType>,
160 },
161 Set {
162 element_type: Box<FieldType>,
163 },
164 Map {
165 key_type: Box<FieldType>,
166 value_type: Box<FieldType>,
167 },
168 Score(ScoreType),
169}
170
171impl FieldType {
172 pub fn object(class_name: impl Into<String>) -> Self {
173 FieldType::Object {
174 class_name: class_name.into(),
175 }
176 }
177
178 pub fn array(element_type: FieldType) -> Self {
179 FieldType::Array {
180 element_type: Box::new(element_type),
181 }
182 }
183
184 pub fn list(element_type: FieldType) -> Self {
185 FieldType::List {
186 element_type: Box::new(element_type),
187 }
188 }
189
190 pub fn set(element_type: FieldType) -> Self {
191 FieldType::Set {
192 element_type: Box::new(element_type),
193 }
194 }
195
196 pub fn map(key_type: FieldType, value_type: FieldType) -> Self {
197 FieldType::Map {
198 key_type: Box::new(key_type),
199 value_type: Box::new(value_type),
200 }
201 }
202
203 pub fn is_collection(&self) -> bool {
204 matches!(
205 self,
206 FieldType::Array { .. } | FieldType::List { .. } | FieldType::Set { .. }
207 )
208 }
209
210 pub fn to_type_string(&self) -> String {
212 match self {
213 FieldType::Primitive(p) => match p {
214 PrimitiveType::Bool => "boolean".to_string(),
215 PrimitiveType::Int => "int".to_string(),
216 PrimitiveType::Long => "long".to_string(),
217 PrimitiveType::Float => "float".to_string(),
218 PrimitiveType::Double => "double".to_string(),
219 PrimitiveType::String => "String".to_string(),
220 PrimitiveType::Date => "LocalDate".to_string(),
221 PrimitiveType::DateTime => "LocalDateTime".to_string(),
222 },
223 FieldType::Object { class_name } => class_name.clone(),
224 FieldType::Array { element_type } => format!("{}[]", element_type.to_type_string()),
225 FieldType::List { element_type } => format!("{}[]", element_type.to_type_string()),
226 FieldType::Set { element_type } => format!("{}[]", element_type.to_type_string()),
227 FieldType::Map {
228 key_type,
229 value_type,
230 } => {
231 format!(
232 "Map<{}, {}>",
233 key_type.to_type_string(),
234 value_type.to_type_string()
235 )
236 }
237 FieldType::Score(s) => match s {
238 ScoreType::Simple => "SimpleScore".to_string(),
239 ScoreType::HardSoft => "HardSoftScore".to_string(),
240 ScoreType::HardMediumSoft => "HardMediumSoftScore".to_string(),
241 ScoreType::SimpleDecimal => "SimpleBigDecimalScore".to_string(),
242 ScoreType::HardSoftDecimal => "HardSoftBigDecimalScore".to_string(),
243 ScoreType::HardMediumSoftDecimal => "HardMediumSoftBigDecimalScore".to_string(),
244 ScoreType::Bendable { .. } => "BendableScore".to_string(),
245 ScoreType::BendableDecimal { .. } => "BendableBigDecimalScore".to_string(),
246 },
247 }
248 }
249}
250
251#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
252pub enum PrimitiveType {
253 Bool,
254 Int,
255 Long,
256 Float,
257 Double,
258 String,
259 Date, DateTime, }
262
263#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
264pub enum ScoreType {
265 Simple,
266 HardSoft,
267 HardMediumSoft,
268 SimpleDecimal,
269 HardSoftDecimal,
270 HardMediumSoftDecimal,
271 Bendable {
272 hard_levels: usize,
273 soft_levels: usize,
274 },
275 BendableDecimal {
276 hard_levels: usize,
277 soft_levels: usize,
278 },
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[test]
286 fn test_domain_class_new() {
287 let class = DomainClass::new("Lesson");
288 assert_eq!(class.name, "Lesson");
289 assert!(class.annotations.is_empty());
290 assert!(class.fields.is_empty());
291 }
292
293 #[test]
294 fn test_domain_class_builder() {
295 let class = DomainClass::new("Lesson")
296 .with_annotation(PlanningAnnotation::PlanningEntity)
297 .with_field(
298 FieldDescriptor::new("id", FieldType::Primitive(PrimitiveType::String))
299 .with_planning_annotation(PlanningAnnotation::PlanningId),
300 )
301 .with_field(
302 FieldDescriptor::new("room", FieldType::object("Room")).with_planning_annotation(
303 PlanningAnnotation::planning_variable(vec!["rooms".to_string()]),
304 ),
305 );
306
307 assert!(class.is_planning_entity());
308 assert!(!class.is_planning_solution());
309 assert_eq!(class.get_planning_variables().count(), 1);
310 assert!(class.get_planning_id_field().is_some());
311 }
312
313 #[test]
314 fn test_field_descriptor() {
315 let field = FieldDescriptor::new("room", FieldType::object("Room"))
316 .with_planning_annotation(PlanningAnnotation::planning_variable(vec![
317 "rooms".to_string()
318 ]))
319 .with_accessor(DomainAccessor::from_field_name("room"));
320
321 assert!(field.is_planning_variable());
322 assert!(!field.is_shadow_variable());
323 assert!(field.accessor.is_some());
324
325 let accessor = field.accessor.unwrap();
326 assert_eq!(accessor.getter, "getRoom");
327 assert_eq!(accessor.setter, "setRoom");
328 }
329
330 #[test]
331 fn test_shadow_field() {
332 let field = FieldDescriptor::new("vehicle", FieldType::object("Vehicle"))
333 .with_shadow_annotation(ShadowAnnotation::inverse_relation("visits"));
334
335 assert!(!field.is_planning_variable());
336 assert!(field.is_shadow_variable());
337 }
338
339 #[test]
340 fn test_field_type_object() {
341 let ft = FieldType::object("Room");
342 match ft {
343 FieldType::Object { class_name } => assert_eq!(class_name, "Room"),
344 _ => panic!("Expected Object"),
345 }
346 }
347
348 #[test]
349 fn test_field_type_collection() {
350 let list = FieldType::list(FieldType::object("Lesson"));
351 assert!(list.is_collection());
352
353 let obj = FieldType::object("Room");
354 assert!(!obj.is_collection());
355 }
356
357 #[test]
358 fn test_field_type_nested() {
359 let map = FieldType::map(
360 FieldType::Primitive(PrimitiveType::String),
361 FieldType::list(FieldType::object("Lesson")),
362 );
363
364 match map {
365 FieldType::Map {
366 key_type,
367 value_type,
368 } => {
369 assert!(matches!(
370 *key_type,
371 FieldType::Primitive(PrimitiveType::String)
372 ));
373 assert!(matches!(*value_type, FieldType::List { .. }));
374 }
375 _ => panic!("Expected Map"),
376 }
377 }
378
379 #[test]
380 fn test_score_type() {
381 let bendable = ScoreType::Bendable {
382 hard_levels: 2,
383 soft_levels: 3,
384 };
385 match bendable {
386 ScoreType::Bendable {
387 hard_levels,
388 soft_levels,
389 } => {
390 assert_eq!(hard_levels, 2);
391 assert_eq!(soft_levels, 3);
392 }
393 _ => panic!("Expected Bendable"),
394 }
395 }
396
397 #[test]
398 fn test_domain_accessor_from_field() {
399 let accessor = DomainAccessor::from_field_name("timeslot");
400 assert_eq!(accessor.getter, "getTimeslot");
401 assert_eq!(accessor.setter, "setTimeslot");
402 }
403
404 #[test]
405 fn test_solution_class() {
406 let solution = DomainClass::new("Timetable")
407 .with_annotation(PlanningAnnotation::PlanningSolution)
408 .with_field(
409 FieldDescriptor::new("score", FieldType::Score(ScoreType::HardSoft))
410 .with_planning_annotation(PlanningAnnotation::planning_score()),
411 );
412
413 assert!(solution.is_planning_solution());
414 assert!(solution.get_score_field().is_some());
415 }
416
417 #[test]
418 fn test_json_serialization() {
419 let class = DomainClass::new("Lesson")
420 .with_annotation(PlanningAnnotation::PlanningEntity)
421 .with_field(
422 FieldDescriptor::new("id", FieldType::Primitive(PrimitiveType::String))
423 .with_planning_annotation(PlanningAnnotation::PlanningId),
424 );
425
426 let json = serde_json::to_string(&class).unwrap();
427 let parsed: DomainClass = serde_json::from_str(&json).unwrap();
428 assert_eq!(parsed.name, class.name);
429 assert_eq!(parsed.fields.len(), class.fields.len());
430 }
431}