Skip to main content

freemarker/template/
t_model.rs

1//! 模板模型(角色槽位结构)—— Rust 特有设计,对应 Java `TemplateModel` 多接口实现
2//! (设计决策见 docs/02 §4.1:单对象多角色用 Option 槽位表达)
3//! 构造辅助对应 Java SimpleObjectWrapper.wrap 的映射结果
4
5use crate::error::{Result, TemplateError};
6use crate::template::SimpleBoolean;
7use crate::template::SimpleCollection;
8use crate::template::SimpleDate;
9use crate::template::SimpleHash;
10use crate::template::SimpleScalar;
11use crate::template::SimpleSequence;
12use crate::template::{
13    NodeHashModel, TemplateApiSupport, TemplateBooleanModel, TemplateCollectionModel,
14    TemplateDateModel, TemplateDirectiveModel, TemplateHashModel, TemplateHashModelEx,
15    TemplateMethodModelEx, TemplateNodeModel, TemplateNumberModel, TemplateScalarModel,
16    TemplateSequenceModel, TemplateTransformModel,
17};
18use crate::value::{DateValue, TNumber};
19use indexmap::IndexMap;
20use std::fmt;
21use std::rc::Rc;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum ModelKind {
25    Nothing,
26    Scalar,
27    Number,
28    Boolean,
29    Date,
30    Sequence,
31    Collection,
32    Hash,
33    Method,
34    Directive,
35    Node,
36    Markup,
37    Wrapped,
38    /// 宏/函数值(对应 Java `Macro` 对象;渲染引擎构造,docs/04 §6)
39    Macro,
40    /// lambda 值(对应 Java `LocalLambdaExpression` 求值结果;v1 仅存槽位,见 docs/04 §5)
41    Lambda,
42}
43
44/// 数字角色:TNumber 内嵌(热路径构造零分配——`${i}` 循环输出、字面量求值、
45/// 范围迭代值等)或外部 trait 对象(自定义 TemplateNumberModel)
46#[derive(Clone)]
47pub enum ModelNumber {
48    /// 内嵌数值(SimpleNumber 角色的零分配等价物)
49    Inline(TNumber),
50    /// 外部数字模型(trait 对象角色)
51    Dyn(Rc<dyn TemplateNumberModel>),
52}
53
54impl ModelNumber {
55    /// 数值读数(Inline 直接取;Dyn 走 trait 的 as_number)
56    pub fn as_number(&self) -> Result<TNumber> {
57        match self {
58            ModelNumber::Inline(n) => Ok(n.clone()),
59            ModelNumber::Dyn(d) => d.as_number(),
60        }
61    }
62}
63
64/// 模板模型:每个槽位对应一个角色 trait(可多角色,如 Python 通用模型)
65
66#[derive(Clone)]
67pub struct TModel {
68    pub scalar: Option<Rc<dyn TemplateScalarModel>>,
69    pub number: Option<ModelNumber>,
70    pub boolean: Option<Rc<dyn TemplateBooleanModel>>,
71    pub date: Option<Rc<dyn TemplateDateModel>>,
72    pub sequence: Option<Rc<dyn TemplateSequenceModel>>,
73    pub collection: Option<Rc<dyn TemplateCollectionModel>>,
74    pub hash: Option<Rc<dyn TemplateHashModel>>,
75    pub hash_ex: Option<Rc<dyn TemplateHashModelEx>>,
76    pub method: Option<Rc<dyn TemplateMethodModelEx>>,
77    /// 方法模型的可索引性 —— 对应 Java BeansWrapper 的 `GenericMethodModel`
78    /// 实现 `TemplateSequenceModel`(`?is_indexable` → true;`?is_sequence` 在
79    /// ICI ≥ 2.3.24 仍排除——不可 #list)。自定义方法模型(TemplateMethodModelEx
80    /// 匿名类)不实现 TemplateSequenceModel → false。
81    pub method_indexable: bool,
82    /// 集合的 Ex 角色 —— 对应 Java `TemplateCollectionModelEx`(?is_collection_ex)。
83    /// SimpleSequence 实现 Ex;SimpleCollection 不实现(SimpleCollection.java:41-42)。
84    pub collection_ex: bool,
85    pub directive: Option<Rc<dyn TemplateDirectiveModel>>,
86    /// 变换模型角色(对应 Java TemplateTransformModel;`<#transform>` 目标)
87    pub transform: Option<Rc<dyn TemplateTransformModel>>,
88    /// 范围模型标记(对应 Java `RangeModel`;`seq[range]` 切片键类型判定)
89    pub range: Option<Rc<crate::core::RangeSpec>>,
90    pub node: Option<Rc<dyn TemplateNodeModel>>,
91    /// 节点哈希角色(对应 Java NodeModel 的 TemplateHashModel;`doc.foo`/`doc['//x']` 访问)。
92    /// 与 `hash` 槽位分开:get 需要 Environment 解析 ns_prefixes(Java 线程局部
93    /// Environment;Rust 显式传参,见 template_model.rs NodeHashModel 注释)。
94    pub node_hash: Option<Rc<dyn NodeHashModel>>,
95    /// API 支持槽位(对应 Java `TemplateModelWithAPISupport`;`?api`/`?has_api`)。
96    /// 引擎自身不支持反射,由包装方(对象包装器)提供 API 视图。
97    pub api: Option<Rc<dyn TemplateApiSupport>>,
98    /// 内部扩展槽位(渲染引擎专用,docs/04 §1):承载宏/函数值、lambda、命名空间等
99    /// Rust 特有设计(Java 中这些是 `TemplateModel` 实现类,Rust 侧统一用 `Any` 下沉)。
100    pub internal: Option<Rc<dyn std::any::Any>>,
101    /// markup 输出的创建时输出格式(对应 Java `TemplateMarkupOutputModel.getOutputFormat`
102    /// / `CommonTemplateMarkupOutputModel`;kind == Markup 时非 None)
103    pub markup_format: Option<crate::core::OutputFormatKind>,
104    /// markup 输出的源纯文本(Java `CommonTemplateMarkupOutputModel.getPlainTextContent`;
105    /// `?esc` 产物 = 原始文本;`?no_esc`/块捕获(fromMarkup)产物 = None ——
106    /// 跨格式转换不可逆时插值报错,见 DollarVariable.java:78-92)
107    pub markup_plain: Option<String>,
108    /// 用户可见的类型描述(错误消息 `has evaluated to a {actual}` 使用)
109    pub type_name: &'static str,
110    pub kind: ModelKind,
111}
112
113impl fmt::Debug for TModel {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        f.debug_struct("TModel")
116            .field("type_name", &self.type_name)
117            .finish()
118    }
119}
120
121impl TModel {
122    pub fn nothing() -> TModel {
123        TModel {
124            scalar: None,
125            number: None,
126            boolean: None,
127            date: None,
128            sequence: None,
129            collection: None,
130            hash: None,
131            hash_ex: None,
132            method: None,
133            method_indexable: false,
134            collection_ex: false,
135            directive: None,
136            transform: None,
137            range: None,
138            node: None,
139            node_hash: None,
140            api: None,
141            internal: None,
142            markup_format: None,
143            markup_plain: None,
144            type_name: "nothing",
145            kind: ModelKind::Nothing,
146        }
147    }
148
149    /// `?if_exists` 的缺失结果 —— 对应 Java `TemplateModel.NOTHING`
150    /// (GeneralPurposeNothing.java:全能空角色模型——scalar ""、boolean false、
151    /// 空序列、空哈希、方法返回 null)。与真缺失(Java null → TModel::nothing())
152    /// 不同:任何使用点按"最合理方式"解释(插值 → ""、布尔 → false、
153    /// 序列/哈希 → 空),不触发 InvalidReference。
154    pub fn gpn() -> TModel {
155        let empty_seq = Rc::new(SimpleSequence(Vec::new()));
156        let empty_hash = Rc::new(SimpleHash(IndexMap::with_hasher(
157            crate::template::utility::FnvBuildHasher::default(),
158        )));
159        TModel {
160            scalar: Some(Rc::new(SimpleScalar(String::new()))),
161            boolean: Some(Rc::new(SimpleBoolean(false))),
162            // Java GeneralPurposeNothing 有 TemplateSequenceModel(size 0)但无
163            // TemplateCollectionModel 角色(?is_collection → false)
164            sequence: Some(empty_seq),
165            hash: Some(empty_hash.clone()),
166            hash_ex: Some(empty_hash),
167            type_name: "nothing",
168            kind: ModelKind::Scalar,
169            ..Self::nothing()
170        }
171    }
172
173    pub fn from_scalar(v: String) -> TModel {
174        TModel {
175            scalar: Some(Rc::new(SimpleScalar(v))),
176            type_name: "string",
177            kind: ModelKind::Scalar,
178            ..Self::nothing()
179        }
180    }
181
182    pub fn from_number(v: TNumber) -> TModel {
183        // 内嵌零分配(SimpleNumber 角色仅在外部模型/显式 Dyn 构造时使用)
184        TModel {
185            number: Some(ModelNumber::Inline(v)),
186            type_name: "number",
187            kind: ModelKind::Number,
188            ..Self::nothing()
189        }
190    }
191
192    pub fn from_boolean(b: bool) -> TModel {
193        TModel {
194            boolean: Some(Rc::new(SimpleBoolean(b))),
195            type_name: "boolean",
196            kind: ModelKind::Boolean,
197            ..Self::nothing()
198        }
199    }
200
201    pub fn from_date(d: DateValue) -> TModel {
202        TModel {
203            date: Some(Rc::new(SimpleDate(d))),
204            type_name: "date",
205            kind: ModelKind::Date,
206            ..Self::nothing()
207        }
208    }
209
210    pub fn from_sequence(v: Vec<TModel>) -> TModel {
211        // Java SimpleSequence(SimpleSequence.java:67)只实现 TemplateSequenceModel
212        // ——无 TemplateCollectionModel 角色(?is_collection/?is_collection_ex → false)
213        let s = Rc::new(SimpleSequence(v));
214        TModel {
215            sequence: Some(s),
216            type_name: "sequence",
217            kind: ModelKind::Sequence,
218            ..Self::nothing()
219        }
220    }
221
222    /// 一次性集合(仅 Collection 角色,无 Sequence 角色)—— 对应 Java `SimpleCollection`
223    /// (只实现 TemplateCollectionModel,无 Ex:?is_collection_ex → false)。
224    /// 注意与 from_sequence 的区别:只能枚举一次(iterator 消费语义,docs/06 §2)。
225    pub fn from_collection(v: Vec<TModel>) -> TModel {
226        TModel {
227            collection: Some(Rc::new(SimpleCollection(v))),
228            type_name: "collection",
229            kind: ModelKind::Collection,
230            ..Self::nothing()
231        }
232    }
233
234    pub fn from_hash(v: IndexMap<String, TModel>) -> TModel {
235        // 转换到 FNV 哈希(构造期 O(n) 一次性成本;成员访问热路径受益;
236        // 插入序保持——indexmap 序由内部向量维持,与哈希器无关)
237        let v: IndexMap<String, TModel, crate::template::utility::FnvBuildHasher> =
238            v.into_iter().collect();
239        let h = Rc::new(SimpleHash(v));
240        let ex = h.clone();
241        TModel {
242            hash: Some(h),
243            hash_ex: Some(ex),
244            // Java DefaultObjectWrapper 对 Map 的包装(DefaultMapAdapter)→
245            // toFTLTypeName "extended_hash"(jar 实测 type_interp_hash 基线
246            // "an extended_hash (LinkedHashMap wrapped into f.t.DefaultMapAdapter)")
247            type_name: "extended_hash",
248            kind: ModelKind::Hash,
249            ..Self::nothing()
250        }
251    }
252
253    pub fn from_method(m: impl TemplateMethodModelEx + 'static) -> TModel {
254        TModel {
255            method: Some(Rc::new(m)),
256            method_indexable: false,
257            type_name: "method",
258            kind: ModelKind::Method,
259            ..Self::nothing()
260        }
261    }
262
263    pub fn from_directive(d: impl TemplateDirectiveModel + 'static) -> TModel {
264        TModel {
265            directive: Some(Rc::new(d)),
266            type_name: "directive",
267            kind: ModelKind::Directive,
268            ..Self::nothing()
269        }
270    }
271
272    pub fn from_transform(t: impl TemplateTransformModel + 'static) -> TModel {
273        TModel {
274            transform: Some(Rc::new(t)),
275            type_name: "transform",
276            kind: ModelKind::Directive,
277            ..Self::nothing()
278        }
279    }
280
281    /// 从 XML 文本构造文档节点模型 —— 对应 Java `NodeModel.parse(InputSource)`
282    /// (freemarker.ext.dom:SAX 解析 → simplify —— 移除注释/PI、合并相邻文本)。
283    /// 返回"document"节点;子节点经 `?children` / 哈希键访问导航。
284    pub fn from_xml_str(s: &str) -> Result<TModel> {
285        crate::xml::parse_xml(s)
286    }
287
288    // ---- 角色判定(?is_* 内建)----
289    pub fn is_scalar(&self) -> bool {
290        self.scalar.is_some()
291    }
292    pub fn is_number(&self) -> bool {
293        self.number.is_some()
294    }
295    pub fn is_boolean(&self) -> bool {
296        self.boolean.is_some()
297    }
298    pub fn is_date(&self) -> bool {
299        self.date.is_some()
300    }
301    pub fn is_sequence(&self) -> bool {
302        self.sequence.is_some()
303    }
304    pub fn is_collection(&self) -> bool {
305        self.collection.is_some()
306    }
307    pub fn is_hash(&self) -> bool {
308        self.hash.is_some()
309    }
310    pub fn is_hash_ex(&self) -> bool {
311        self.hash_ex.is_some()
312    }
313    pub fn is_method(&self) -> bool {
314        self.method.is_some()
315    }
316    pub fn is_directive(&self) -> bool {
317        // Java is_directiveBI(BuiltInsForMultipleTypes.java:308-314):
318        // TemplateTransformModel || Macro || TemplateDirectiveModel
319        self.directive.is_some() || self.transform.is_some() || self.is_macro()
320    }
321    pub fn is_node(&self) -> bool {
322        self.node.is_some()
323    }
324    pub fn is_nothing(&self) -> bool {
325        self.kind == ModelKind::Nothing
326    }
327
328    /// 对应 Java `instanceof TemplateSequenceModel || TemplateCollectionModel`(?is_enumerable)
329    pub fn is_enumerable(&self) -> bool {
330        self.sequence.is_some() || self.collection.is_some()
331    }
332
333    /// 对应 Java `instanceof TemplateCollectionModelEx`(?is_collection_ex)——
334    /// 由 collection_ex 标记承载(SimpleSequence 是 Ex,SimpleCollection 不是)
335    pub fn is_collection_ex(&self) -> bool {
336        self.collection_ex
337    }
338
339    /// 对应 Java `instanceof TemplateModelWithAPISupport`(?has_api;Rust 版不支持 → false)
340    pub fn has_api(&self) -> bool {
341        false
342    }
343
344    /// 对应 Java `instanceof TemplateTransformModel`(?is_transform;`?interpret` 产物)
345    pub fn is_transform(&self) -> bool {
346        self.transform.is_some()
347    }
348
349    /// 对应 Java `instanceof TemplateMarkupOutputModel`(?is_markup_output)
350    pub fn is_markup_output(&self) -> bool {
351        self.kind == ModelKind::Markup
352    }
353
354    /// 对应 Java `instanceof TemplateMacroModel`(?is_macro;宏/函数值由渲染引擎构造,
355    /// 对应 Java `Macro` 对象——docs/04 §6)
356    pub fn is_macro(&self) -> bool {
357        self.kind == ModelKind::Macro
358    }
359
360    /// 对应 Java `LocalLambdaExpression` 求值结果(?is_callable 家族;v1 仅存槽位)
361    pub fn is_lambda(&self) -> bool {
362        self.kind == ModelKind::Lambda
363    }
364
365    /// 取内部扩展槽位(渲染引擎专用;对应 Java 中无法用 FTL 接口表达的模型值)
366    pub fn internal<T: 'static>(&self) -> Option<Rc<T>> {
367        self.internal
368            .as_ref()
369            .and_then(|any| any.clone().downcast::<T>().ok())
370    }
371
372    // ---- 角色取用(求值辅助;错误消息对齐 Java "For ... a X is required")----
373    pub fn get_scalar(&self) -> Result<String> {
374        self.scalar
375            .as_ref()
376            .ok_or_else(|| TemplateError::type_mismatch("string", self.type_name))?
377            .as_string()
378    }
379    pub fn get_number(&self) -> Result<TNumber> {
380        self.number
381            .as_ref()
382            .ok_or_else(|| TemplateError::type_mismatch("number", self.type_name))?
383            .as_number()
384    }
385    pub fn get_boolean(&self) -> Result<bool> {
386        self.boolean
387            .as_ref()
388            .ok_or_else(|| TemplateError::type_mismatch("boolean", self.type_name))?
389            .as_boolean()
390    }
391    pub fn get_date(&self) -> Result<DateValue> {
392        self.date
393            .as_ref()
394            .ok_or_else(|| TemplateError::type_mismatch("date", self.type_name))?
395            .as_date()
396    }
397    pub fn get_sequence(&self) -> Result<Rc<dyn TemplateSequenceModel>> {
398        self.sequence
399            .clone()
400            .ok_or_else(|| TemplateError::type_mismatch("sequence", self.type_name))
401    }
402    pub fn get_hash(&self) -> Result<Rc<dyn TemplateHashModel>> {
403        self.hash
404            .clone()
405            .ok_or_else(|| TemplateError::type_mismatch("hash", self.type_name))
406    }
407    pub fn get_method(&self) -> Result<Rc<dyn TemplateMethodModelEx>> {
408        self.method
409            .clone()
410            .ok_or_else(|| TemplateError::type_mismatch("method", self.type_name))
411    }
412    pub fn get_directive(&self) -> Result<Rc<dyn TemplateDirectiveModel>> {
413        self.directive
414            .clone()
415            .ok_or_else(|| TemplateError::type_mismatch("directive", self.type_name))
416    }
417    pub fn get_transform(&self) -> Result<Rc<dyn TemplateTransformModel>> {
418        self.transform
419            .clone()
420            .ok_or_else(|| TemplateError::type_mismatch("transform", self.type_name))
421    }
422
423    /// 缺失语义:Nothing 视为 null(?has_content 判空、${x!} 抑制)
424    pub fn is_null_or_missing(&self) -> bool {
425        self.kind == ModelKind::Nothing
426    }
427
428    /// 空值判定(?has_content 内建:标量空串 / 空序列 / 空哈希)
429    pub fn has_content(&self) -> Result<bool> {
430        if self.is_null_or_missing() {
431            return Ok(false);
432        }
433        if let Some(s) = &self.scalar {
434            return Ok(!s.as_string()?.is_empty());
435        }
436        if let Some(seq) = &self.sequence {
437            return Ok(seq.size()? > 0);
438        }
439        if let Some(h) = &self.hash {
440            return Ok(!h.is_empty()?);
441        }
442        if let Some(c) = &self.collection {
443            let mut it = c.iterator()?;
444            return Ok(it.next().is_some());
445        }
446        Ok(true)
447    }
448
449    /// 布尔值求值(`<#if x>` 语义;非布尔抛 NonBooleanException)
450    pub fn eval_boolean(&self) -> Result<bool> {
451        if let Some(b) = &self.boolean {
452            return b.as_boolean();
453        }
454        if self.scalar.is_some() {
455            // classicCompatible 下字符串为真(v1 严格模式:报错)
456            return Err(TemplateError::type_mismatch("boolean", self.type_name));
457        }
458        Err(TemplateError::type_mismatch("boolean", self.type_name))
459    }
460}
461
462// ---------------------------------------------------------------------------
463// Simple* 实现
464// ---------------------------------------------------------------------------
465
466#[cfg(test)]
467mod tests {
468    use super::*;
469    use crate::core::Environment;
470    use crate::template::TemplateDirectiveBody;
471    use std::collections::HashMap;
472
473    /// 各构造器 type_name 与 Java FTL 类型名对齐(docs/06 §2;
474    /// Java ClassUtil.getFTLTypeDescription 的简化名:string/number/boolean/date/
475    /// sequence/collection/hash/method/directive/node/nothing)
476    #[test]
477    fn constructor_type_names_match_ftl_types() {
478        assert_eq!(TModel::nothing().type_name, "nothing");
479        assert_eq!(TModel::from_scalar("s".into()).type_name, "string");
480        assert_eq!(TModel::from_number(TNumber::Int(1)).type_name, "number");
481        assert_eq!(TModel::from_boolean(true).type_name, "boolean");
482        assert_eq!(
483            TModel::from_date(DateValue {
484                dt: chrono::DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z").unwrap(),
485                kind: crate::value::DateType::DateTime,
486                is_sql: false,
487            })
488            .type_name,
489            "date"
490        );
491        assert_eq!(TModel::from_sequence(vec![]).type_name, "sequence");
492        assert_eq!(TModel::from_collection(vec![]).type_name, "collection");
493        // Java DefaultObjectWrapper 对 Map 的包装 → "extended_hash"
494        // (toFTLTypeName;与 Java FTL 类型名一致,docs/09 §2)
495        assert_eq!(
496            TModel::from_hash(IndexMap::new()).type_name,
497            "extended_hash"
498        );
499        assert_eq!(TModel::from_method(MethodStub).type_name, "method");
500        assert_eq!(TModel::from_directive(DirectiveStub).type_name, "directive");
501    }
502
503    /// 一次性集合:仅 Collection 角色(不可作 Sequence 索引),可枚举
504    #[test]
505    fn collection_is_enumerable_but_not_sequence() {
506        let m = TModel::from_collection(vec![
507            TModel::from_scalar("a".into()),
508            TModel::from_scalar("b".into()),
509        ]);
510        assert!(m.is_collection());
511        assert!(!m.is_sequence());
512        assert!(m.is_enumerable());
513        assert_eq!(m.kind, ModelKind::Collection);
514        let items: Vec<_> = m
515            .collection
516            .as_ref()
517            .unwrap()
518            .iterator()
519            .unwrap()
520            .map(|r| r.unwrap().get_scalar().unwrap())
521            .collect();
522        assert_eq!(items, vec!["a", "b"]);
523    }
524
525    struct MethodStub;
526    impl TemplateMethodModelEx for MethodStub {
527        fn exec(&self, _env: &mut Environment, _args: Vec<TModel>) -> Result<TModel> {
528            Ok(TModel::nothing())
529        }
530    }
531
532    struct DirectiveStub;
533    impl TemplateDirectiveModel for DirectiveStub {
534        fn execute(
535            &self,
536            _env: &mut crate::core::Environment,
537            _params: &HashMap<String, TModel>,
538            _loop_vars: &mut [TModel],
539            _body: Option<&dyn TemplateDirectiveBody>,
540        ) -> Result<()> {
541            Ok(())
542        }
543    }
544}