use crate::data::core::plugins::IPropertyAccessor;
use crate::data::core::{BindingExpression, ValueType, ValueTypes, WeakValue};
use crate::data::{BindingError, BindingErrorType, BindingNotification};
use crate::{BoxedValue, FerroObject, FerroProperty, Ref};
use std::any::Any;
use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
#[derive(Clone)]
pub enum NodeSource {
Unset,
Value(Option<BoxedValue>),
}
pub struct NodeState {
index: Cell<usize>,
owner: RefCell<Option<Weak<BindingExpression>>>,
source: RefCell<Option<WeakValue>>,
value: RefCell<HeldValue>,
}
enum HeldValue {
Value(Option<BoxedValue>),
Element(Option<WeakValue>),
}
impl Default for NodeState {
fn default() -> Self {
Self::new()
}
}
impl NodeState {
pub fn new() -> Self {
Self {
index: Cell::new(0),
owner: RefCell::new(None),
source: RefCell::new(None),
value: RefCell::new(HeldValue::Value(Some(FerroProperty::unset_value()))),
}
}
pub fn locating_an_element() -> Self {
Self { value: RefCell::new(HeldValue::Element(Some(WeakValue::new(&FerroProperty::unset_value())))), ..Self::new() }
}
pub fn index(&self) -> usize {
self.index.get()
}
pub fn owner(&self) -> Option<Rc<BindingExpression>> {
self.owner.borrow().as_ref().and_then(Weak::upgrade)
}
pub fn source(&self) -> Option<BoxedValue> {
let source = self.source.borrow().clone();
source.and_then(|s| s.upgrade())
}
pub fn source_object(&self) -> Option<Ref<FerroObject>> {
self.source().and_then(|s| ValueTypes::as_object(&*s))
}
pub fn value(&self) -> Option<BoxedValue> {
match &*self.value.borrow() {
HeldValue::Value(value) => value.clone(),
HeldValue::Element(value) => value.as_ref().and_then(WeakValue::upgrade),
}
}
pub(crate) fn set_raw_value(&self, value: Option<BoxedValue>) {
let mut held = self.value.borrow_mut();
let old = match &*held {
HeldValue::Value(_) => std::mem::replace(&mut *held, HeldValue::Value(value)),
HeldValue::Element(_) => {
std::mem::replace(&mut *held, HeldValue::Element(value.as_ref().map(WeakValue::new)))
}
};
drop(held);
drop(old);
}
pub fn clear_value(&self) {
self.set_value(Some(FerroProperty::unset_value()), None);
}
pub fn short_circuit_null(&self) {
self.set_raw_value(None);
if let Some(owner) = self.owner() {
owner.on_node_null_short_circuit(self.index());
}
}
pub fn set_data_validation_error(&self, error: BindingError) {
if let Some(owner) = self.owner() {
owner.on_data_validation_error(error);
}
}
pub fn set_error(&self, message: &str) {
self.set_raw_value(Some(FerroProperty::unset_value()));
if let Some(owner) = self.owner() {
owner.on_node_error(self.index() as isize, message);
}
}
pub fn set_value_or_notification(&self, value_or_notification: Option<BoxedValue>) {
let Some(notification) = value_or_notification.as_ref().and_then(|v| v.downcast_ref::<BindingNotification>())
else {
self.set_value(value_or_notification, None);
return;
};
match notification.error_type() {
BindingErrorType::Error => {
let message = notification.error().map(|e| e.to_string()).unwrap_or_default();
self.set_error(&message);
}
BindingErrorType::DataValidationError => {
if notification.has_value() {
let inner = notification.value();
if inner.as_ref().is_some_and(|v| v.is::<BindingNotification>()) {
self.set_value_or_notification(inner);
} else {
self.set_value(inner, notification.error().as_ref());
}
} else if let Some(error) = notification.error() {
self.set_data_validation_error(error);
}
}
BindingErrorType::None => self.set_value_or_notification_inner(notification.value()),
}
}
fn set_value_or_notification_inner(&self, value: Option<BoxedValue>) {
if value.as_ref().is_some_and(|v| v.is::<BindingNotification>()) {
self.set_value_or_notification(value);
} else {
self.set_value(value, None);
}
}
pub fn set_value(&self, value: Option<BoxedValue>, data_validation_error: Option<&BindingError>) {
debug_assert!(!value.as_ref().is_some_and(|v| v.is::<BindingNotification>()));
self.set_raw_value(value.clone());
if let Some(owner) = self.owner() {
owner.on_node_value_changed(self.index(), value, data_validation_error);
}
}
pub fn validate_non_null_source(&self, source: Option<&BoxedValue>) -> bool {
if source.is_none() {
if let Some(owner) = self.owner() {
owner.on_node_error(self.index() as isize - 1, "Value is null.");
}
self.set_raw_value(None);
return false;
}
true
}
}
pub trait ISettableNode {
fn value_type(&self) -> Option<ValueType>;
fn write_value_to_source(
&self,
value: Option<&BoxedValue>,
nodes: &[Rc<dyn ExpressionNode>],
) -> Result<bool, BindingError>;
}
pub trait IPropertyAccessorNode {
fn property_name(&self) -> &str;
fn accessor(&self) -> Option<Rc<dyn IPropertyAccessor>>;
fn enable_data_validation(&self);
}
pub trait SourceNode {
fn select_source(
&self,
source: Option<&Option<BoxedValue>>,
target: &Ref<FerroObject>,
_anchor: Option<&Ref<FerroObject>>,
) -> Option<BoxedValue> {
match source {
Some(source) => source.clone(),
None => Some(Rc::new(target.clone())),
}
}
fn should_log_errors(&self, state: &NodeState, _target: &FerroObject) -> bool {
state.value().is_some()
}
}
pub trait ExpressionNode: Any {
fn state(&self) -> &NodeState;
fn build_string(&self, _builder: &mut String) {}
fn build_string_with_nodes(&self, builder: &mut String, nodes: &[Rc<dyn ExpressionNode>]) {
let index = self.state().index();
if index > 0 {
nodes[index - 1].build_string_with_nodes(builder, nodes);
}
self.build_string(builder);
}
fn on_source_changed(&self, source: Option<&BoxedValue>, data_validation_error: Option<&BindingError>);
fn unsubscribe(&self, _old_source: &BoxedValue) {}
fn as_settable(&self) -> Option<&dyn ISettableNode> {
None
}
fn as_property_accessor_node(&self) -> Option<&dyn IPropertyAccessorNode> {
None
}
fn as_source_node(&self) -> Option<&dyn SourceNode> {
None
}
fn is_data_context_node(&self) -> bool {
false
}
}
impl dyn ExpressionNode {
pub fn set_owner(&self, owner: Weak<BindingExpression>, index: usize) {
let state = self.state();
let mut slot = state.owner.borrow_mut();
if slot.is_some() {
panic!("The expression node already has an owner.");
}
*slot = Some(owner);
state.index.set(index);
}
pub fn set_source(&self, source: NodeSource, data_validation_error: Option<&BindingError>) {
let state = self.state();
let old_source = state.source();
let unchanged = match (&source, &old_source) {
(NodeSource::Unset, None) => true,
(NodeSource::Value(Some(new)), Some(old)) => Rc::ptr_eq(new, old) || (**new).value_eq(&**old),
_ => false,
};
if unchanged {
return;
}
if let Some(old) = &old_source {
self.unsubscribe(old);
}
match source {
NodeSource::Unset => {
state.source.replace(None);
state.set_raw_value(Some(FerroProperty::unset_value()));
}
NodeSource::Value(value) => {
state.source.replace(value.as_ref().map(WeakValue::new));
self.on_source_changed(value.as_ref(), data_validation_error);
}
}
}
pub(crate) fn propagate_null_short_circuit_value(&self) {
self.set_source(NodeSource::Unset, None);
self.state().set_raw_value(None);
}
}
pub(crate) fn append_member(builder: &mut String, name: &str) {
if !builder.is_empty() && !builder.ends_with('!') {
builder.push('.');
}
builder.push_str(name);
}