use super::metadata::{ElementMeta, ImportMetadata, SourceInfo};
use super::model::{Element, ElementId, ElementKind, Model, Visibility};
use std::collections::HashMap;
use std::fmt::Write;
use std::sync::Arc;
pub struct DecompileResult {
pub text: String,
pub metadata: ImportMetadata,
}
pub fn decompile(model: &Model) -> DecompileResult {
let mut ctx = DecompileContext::new(model);
ctx.decompile_model();
DecompileResult {
text: ctx.output,
metadata: ctx.metadata,
}
}
pub fn decompile_with_source(model: &Model, source: SourceInfo) -> DecompileResult {
let mut ctx = DecompileContext::new(model);
ctx.metadata.source = source;
ctx.decompile_model();
DecompileResult {
text: ctx.output,
metadata: ctx.metadata,
}
}
struct DecompileContext<'a> {
model: &'a Model,
output: String,
metadata: ImportMetadata,
indent_level: usize,
qualified_names: HashMap<ElementId, String>,
sibling_order: HashMap<ElementId, u32>,
}
impl<'a> DecompileContext<'a> {
fn new(model: &'a Model) -> Self {
Self {
model,
output: String::new(),
metadata: ImportMetadata::new(),
indent_level: 0,
qualified_names: HashMap::new(),
sibling_order: HashMap::new(),
}
}
fn decompile_model(&mut self) {
self.compute_qualified_names();
for root_id in &self.model.roots {
if let Some(element) = self.model.elements.get(root_id) {
self.decompile_element(element);
}
}
}
fn compute_qualified_names(&mut self) {
for (id, _element) in &self.model.elements {
let qn = self.compute_qualified_name(id.clone());
self.qualified_names.insert(id.clone(), qn);
}
}
fn compute_qualified_name(&self, id: ElementId) -> String {
let Some(element) = self.model.elements.get(&id) else {
return id.as_str().to_string();
};
if element.kind.is_relationship() {
if let Some(owner_id) = &element.owner {
return self.compute_qualified_name(owner_id.clone());
}
return id.as_str().to_string();
}
let name = element
.name
.as_deref()
.unwrap_or_else(|| element.id.as_str());
if let Some(owner_id) = &element.owner {
if self.model.elements.contains_key(owner_id) {
let owner_qn = self.compute_qualified_name(owner_id.clone());
return format!("{}::{}", owner_qn, name);
}
}
name.to_string()
}
fn decompile_element(&mut self, element: &Element) {
if element.kind.is_relationship() {
self.decompile_transparent_container(element);
return;
}
self.record_metadata(element);
match element.kind {
ElementKind::Package => self.decompile_package(element),
ElementKind::LibraryPackage => self.decompile_library_package(element),
ElementKind::PartDefinition => self.decompile_definition(element, "part def"),
ElementKind::ItemDefinition => self.decompile_definition(element, "item def"),
ElementKind::PortDefinition => self.decompile_definition(element, "port def"),
ElementKind::AttributeDefinition => self.decompile_definition(element, "attribute def"),
ElementKind::ActionDefinition => self.decompile_definition(element, "action def"),
ElementKind::ConnectionDefinition => {
self.decompile_definition(element, "connection def")
}
ElementKind::InterfaceDefinition => self.decompile_definition(element, "interface def"),
ElementKind::AllocationDefinition => {
self.decompile_definition(element, "allocation def")
}
ElementKind::RequirementDefinition => {
self.decompile_definition(element, "requirement def")
}
ElementKind::ConstraintDefinition => {
self.decompile_definition(element, "constraint def")
}
ElementKind::StateDefinition => self.decompile_definition(element, "state def"),
ElementKind::CalculationDefinition => self.decompile_definition(element, "calc def"),
ElementKind::UseCaseDefinition => self.decompile_definition(element, "use case def"),
ElementKind::AnalysisCaseDefinition => {
self.decompile_definition(element, "analysis def")
}
ElementKind::ViewDefinition => self.decompile_definition(element, "view def"),
ElementKind::ViewpointDefinition => self.decompile_definition(element, "viewpoint def"),
ElementKind::RenderingDefinition => self.decompile_definition(element, "rendering def"),
ElementKind::EnumerationDefinition => self.decompile_definition(element, "enum def"),
ElementKind::MetadataDefinition => self.decompile_definition(element, "metadata def"),
ElementKind::ConcernDefinition => self.decompile_definition(element, "concern def"),
ElementKind::PartUsage => self.decompile_usage(element, "part"),
ElementKind::ItemUsage => self.decompile_usage(element, "item"),
ElementKind::PortUsage => self.decompile_usage(element, "port"),
ElementKind::AttributeUsage => self.decompile_usage(element, "attribute"),
ElementKind::ActionUsage => self.decompile_usage(element, "action"),
ElementKind::ConnectionUsage => self.decompile_usage(element, "connection"),
ElementKind::InterfaceUsage => self.decompile_usage(element, "interface"),
ElementKind::AllocationUsage => self.decompile_usage(element, "allocation"),
ElementKind::RequirementUsage => self.decompile_usage(element, "requirement"),
ElementKind::ConstraintUsage => self.decompile_usage(element, "constraint"),
ElementKind::StateUsage => self.decompile_usage(element, "state"),
ElementKind::CalculationUsage => self.decompile_usage(element, "calc"),
ElementKind::ReferenceUsage => self.decompile_usage(element, "ref"),
ElementKind::OccurrenceUsage => self.decompile_usage(element, "occurrence"),
ElementKind::FlowConnectionUsage => self.decompile_usage(element, "flow"),
ElementKind::Class => self.decompile_definition(element, "class"),
ElementKind::DataType => self.decompile_definition(element, "datatype"),
ElementKind::Structure => self.decompile_definition(element, "struct"),
ElementKind::Classifier => self.decompile_definition(element, "classifier"),
ElementKind::Feature => self.decompile_feature(element),
ElementKind::MultiplicityRange => self.decompile_multiplicity(element),
ElementKind::Comment => self.decompile_comment(element),
ElementKind::Documentation => self.decompile_documentation(element),
ElementKind::Alias => self.decompile_alias(element),
ElementKind::Other => self.decompile_transparent_container(element),
_ => {}
}
}
fn record_metadata(&mut self, element: &Element) {
let qn = self
.qualified_names
.get(&element.id)
.cloned()
.unwrap_or_else(|| element.id.as_str().to_string());
let mut meta = ElementMeta::with_id(element.id.as_str());
if let Some(owner_id) = &element.owner {
let order = self.sibling_order.entry(owner_id.clone()).or_insert(0);
meta = meta.with_order(*order);
*order += 1;
}
for (key, value) in &element.properties {
if key.starts_with('_') {
continue;
}
let json_value = property_to_json(value);
meta = meta.with_unmapped(key.as_ref(), json_value);
}
self.metadata.add_element(qn, meta);
}
fn indent(&self) -> String {
" ".repeat(self.indent_level)
}
fn write_line(&mut self, text: &str) {
let indent = self.indent();
let _ = writeln!(self.output, "{}{}", indent, text);
}
fn write_blank_line(&mut self) {
let _ = writeln!(self.output);
}
fn decompile_package(&mut self, element: &Element) {
self.write_visibility(element);
if let Some(_name) = &element.name {
let short = self.format_short_name(element);
let name_str = self.format_element_name(element);
self.write_line(&format!("package {}{} {{", short, name_str));
} else {
self.write_line("package {");
}
self.decompile_body(element);
self.write_line("}");
self.write_blank_line();
}
fn decompile_library_package(&mut self, element: &Element) {
self.write_visibility(element);
let is_standard_key: Arc<str> = Arc::from("isStandard");
let standard_kw = match element.properties.get(&is_standard_key) {
Some(super::model::PropertyValue::Boolean(true)) => "standard ",
Some(super::model::PropertyValue::String(s)) if s.as_ref() == "true" => "standard ",
_ => "",
};
if let Some(_name) = &element.name {
let short = self.format_short_name(element);
let name_str = self.format_element_name(element);
self.write_line(&format!(
"{}library package {}{} {{",
standard_kw, short, name_str
));
} else {
self.write_line(&format!("{}library package {{", standard_kw));
}
self.decompile_body(element);
self.write_line("}");
self.write_blank_line();
}
fn decompile_definition(&mut self, element: &Element, keyword: &str) {
self.write_visibility(element);
let abstract_kw = if element.is_abstract { "abstract " } else { "" };
let variation_kw = if element.is_variation {
"variation "
} else {
""
};
let individual_kw = if element.is_individual {
"individual "
} else {
""
};
let short = self.format_short_name(element);
let name_str = self.format_element_name(element);
let specializations = self.format_specializations(&element.id);
if let Some(_name) = &element.name {
if element.owned_elements.is_empty() && element.documentation.is_none() {
self.write_line(&format!(
"{}{}{}{} {}{}{};",
variation_kw,
abstract_kw,
individual_kw,
keyword,
short,
name_str,
specializations
));
} else {
self.write_line(&format!(
"{}{}{}{} {}{}{} {{",
variation_kw,
abstract_kw,
individual_kw,
keyword,
short,
name_str,
specializations
));
self.decompile_body(element);
self.write_line("}");
}
} else {
self.write_line(&format!(
"{}{}{}{} {{{}",
variation_kw, abstract_kw, individual_kw, keyword, specializations
));
self.decompile_body(element);
self.write_line("}");
}
self.write_blank_line();
}
fn decompile_usage(&mut self, element: &Element, keyword: &str) {
self.write_visibility(element);
let direction_prefix = self.format_direction(element);
let end_kw = if element.is_end { "end " } else { "" };
let readonly_kw = if element.is_readonly { "readonly " } else { "" };
let derived_kw = if element.is_derived { "derived " } else { "" };
let abstract_kw = if element.is_abstract { "abstract " } else { "" };
let variation_kw = if element.is_variation {
"variation "
} else {
""
};
let portion_kw = if element.is_portion { "portion " } else { "" };
let typing = self.format_typing(&element.id);
let subsetting = self.format_subsetting(&element.id);
let redefinition = self.format_redefinition(&element.id);
let value = self.format_feature_value(&element.id);
let short = self.format_short_name(element);
let multiplicity = self.format_usage_multiplicity(element);
let is_anonymous = element
.name
.as_ref()
.is_none_or(|n| n.contains('#') && n.contains('@'));
let relations = format!("{}{}{}", typing, subsetting, redefinition);
let has_body_children = element.owned_elements.iter().any(|child_id| {
self.model
.get(child_id)
.map(|c| !c.kind.is_inline_rendered())
.unwrap_or(false)
});
let prefix = format!(
"{}{}{}{}{}{}{}",
direction_prefix,
end_kw,
readonly_kw,
derived_kw,
abstract_kw,
variation_kw,
portion_kw
);
if is_anonymous {
if !relations.is_empty() || !value.is_empty() {
self.write_line(&format!(
"{}{}{}{}{};",
prefix, keyword, relations, multiplicity, value
));
}
} else if let Some(_name) = &element.name {
let name_str = self.format_element_name(element);
if !has_body_children && element.documentation.is_none() {
self.write_line(&format!(
"{}{} {}{}{}{}{};",
prefix, keyword, short, name_str, relations, multiplicity, value
));
} else {
self.write_line(&format!(
"{}{} {}{}{}{}{} {{",
prefix, keyword, short, name_str, relations, multiplicity, value
));
self.decompile_body(element);
self.write_line("}");
}
} else if !relations.is_empty() || !value.is_empty() {
self.write_line(&format!(
"{}{}{}{}{};",
prefix, keyword, relations, multiplicity, value
));
}
}
fn decompile_feature(&mut self, element: &Element) {
self.write_visibility(element);
let abstract_kw = if element.is_abstract { "abstract " } else { "" };
let typing = self.format_typing(&element.id);
let subsetting = self.format_subsetting(&element.id);
let redefinition = self.format_redefinition(&element.id);
let chaining = self.format_chaining(&element.id);
let multiplicity = self.format_inline_multiplicity(&element.id);
let short = self.format_short_name(element);
let mut modifiers = Vec::new();
let is_unique_key: Arc<str> = Arc::from("isUnique");
if let Some(pv) = element.properties.get(&is_unique_key) {
match pv {
super::model::PropertyValue::Boolean(false) => modifiers.push("nonunique"),
super::model::PropertyValue::String(s) if s.as_ref() == "false" => {
modifiers.push("nonunique")
}
_ => {}
}
}
let mod_str = if modifiers.is_empty() {
String::new()
} else {
format!(" {}", modifiers.join(" "))
};
let value = self.format_feature_value(&element.id);
let has_body_children = element.owned_elements.iter().any(|child_id| {
self.model
.get(child_id)
.map(|c| !c.kind.is_inline_rendered())
.unwrap_or(false)
});
if let Some(name) = &element.name {
let decl = format!(
"{}feature {}{}{}{}{}{}{}{}{}",
abstract_kw,
short,
name,
typing,
multiplicity,
mod_str,
subsetting,
chaining,
redefinition,
value,
);
if !has_body_children && element.documentation.is_none() {
self.write_line(&format!("{};", decl));
} else {
self.write_line(&format!("{} {{", decl));
self.decompile_body(element);
self.write_line("}");
}
} else {
let decl = format!(
"{}feature{}{}{}{}{}{}",
abstract_kw, typing, multiplicity, mod_str, subsetting, chaining, redefinition
);
self.write_line(&format!("{};", decl));
}
self.write_blank_line();
}
fn decompile_multiplicity(&mut self, element: &Element) {
if let Some(name) = &element.name {
let bounds = self.format_multiplicity_bounds(&element.id);
let has_doc = element.owned_elements.iter().any(|child_id| {
self.model
.elements
.get(child_id)
.map(|c| {
c.kind == ElementKind::Documentation
|| c.owned_elements.iter().any(|gc_id| {
self.model
.elements
.get(gc_id)
.map(|gc| gc.kind == ElementKind::Documentation)
.unwrap_or(false)
})
})
.unwrap_or(false)
});
if has_doc {
self.write_line(&format!("multiplicity {} {} {{", name, bounds));
self.indent_level += 1;
for child_id in &element.owned_elements {
if let Some(child) = self.model.elements.get(child_id) {
if child.kind == ElementKind::Documentation {
self.decompile_documentation(child);
}
for grandchild_id in &child.owned_elements {
if let Some(grandchild) = self.model.elements.get(grandchild_id) {
if grandchild.kind == ElementKind::Documentation {
self.decompile_documentation(grandchild);
}
}
}
}
}
self.indent_level -= 1;
self.write_line("}");
} else {
self.write_line(&format!("multiplicity {} {};", name, bounds));
}
self.write_blank_line();
}
}
fn format_inline_multiplicity(&self, element_id: &ElementId) -> String {
if let Some(element) = self.model.elements.get(element_id) {
for child_id in &element.owned_elements {
if let Some(child) = self.model.elements.get(child_id) {
if child.kind == ElementKind::MultiplicityRange {
return self.format_multiplicity_bounds(&child.id);
}
if child.kind.is_relationship() {
for grandchild_id in &child.owned_elements {
if let Some(grandchild) = self.model.elements.get(grandchild_id) {
if grandchild.kind == ElementKind::MultiplicityRange {
return self.format_multiplicity_bounds(&grandchild.id);
}
}
}
}
}
}
}
String::new()
}
fn format_direction(&self, element: &Element) -> String {
let dir_key: Arc<str> = Arc::from("direction");
match element.properties.get(&dir_key) {
Some(super::model::PropertyValue::String(s)) => match s.as_ref() {
"in" => "in ".to_string(),
"out" => "out ".to_string(),
"inout" => "inout ".to_string(),
_ => String::new(),
},
_ => String::new(),
}
}
fn format_usage_multiplicity(&self, element: &Element) -> String {
let lower_key: Arc<str> = Arc::from("multiplicityLower");
let upper_key: Arc<str> = Arc::from("multiplicityUpper");
let lower = element.properties.get(&lower_key).and_then(|v| match v {
super::model::PropertyValue::String(s) => Some(s.to_string()),
super::model::PropertyValue::Integer(n) => Some(n.to_string()),
_ => None,
});
let upper = element.properties.get(&upper_key).and_then(|v| match v {
super::model::PropertyValue::String(s) => Some(s.to_string()),
super::model::PropertyValue::Integer(n) => Some(n.to_string()),
_ => None,
});
let inline_mult = self.format_inline_multiplicity(&element.id);
if !inline_mult.is_empty() {
return format!(" {}", inline_mult);
}
match (lower, upper) {
(Some(l), Some(u)) => {
if l == u {
format!(" [{}]", l)
} else {
format!(" [{}..{}]", l, u)
}
}
(None, Some(u)) => format!(" [{}]", u),
(Some(l), None) => format!(" [{}]", l),
(None, None) => String::new(),
}
}
fn format_multiplicity_bounds(&self, mult_id: &ElementId) -> String {
if let Some(mult_elem) = self.model.elements.get(mult_id) {
let mut all_literals = Vec::new();
for child_id in &mult_elem.owned_elements {
if let Some(child) = self.model.elements.get(child_id) {
all_literals.extend(self.collect_literals(child));
}
}
let lower = all_literals.first().cloned();
let upper = all_literals.get(1).cloned();
match (lower, upper) {
(Some(l), Some(u)) => format!("[{}..{}]", l, u),
(Some(l), None) => format!("[{}]", l),
(None, Some(u)) => format!("[0..{}]", u),
(None, None) => String::new(),
}
} else {
String::new()
}
}
fn collect_literals(&self, element: &Element) -> Vec<String> {
let mut result = Vec::new();
let value_key: Arc<str> = Arc::from("value");
match element.kind {
ElementKind::LiteralInteger => {
if let Some(pv) = element.properties.get(&value_key) {
match pv {
super::model::PropertyValue::Integer(v) => result.push(v.to_string()),
super::model::PropertyValue::String(s) => result.push(s.to_string()),
_ => result.push("0".to_string()),
}
} else {
result.push("0".to_string());
}
}
ElementKind::LiteralReal => {
if let Some(pv) = element.properties.get(&value_key) {
match pv {
super::model::PropertyValue::Real(v) => result.push(v.to_string()),
super::model::PropertyValue::String(s) => result.push(s.to_string()),
_ => result.push("0.0".to_string()),
}
} else {
result.push("0.0".to_string());
}
}
ElementKind::LiteralInfinity => {
result.push("*".to_string());
}
_ => {
for child_id in &element.owned_elements {
if let Some(child) = self.model.elements.get(child_id) {
result.extend(self.collect_literals(child));
}
}
}
}
result
}
fn decompile_comment(&mut self, element: &Element) {
if let Some(doc) = &element.documentation {
if doc.contains('\n') {
self.write_line(&format!("/* {} */", doc));
} else {
self.write_line(&format!("// {}", doc));
}
}
}
fn decompile_documentation(&mut self, element: &Element) {
if let Some(doc) = &element.documentation {
self.write_line(&format!("doc /* {} */", doc));
}
}
fn decompile_alias(&mut self, element: &Element) {
if let Some(name) = &element.name {
let target_key: Arc<str> = Arc::from("aliasTarget");
let target = element.properties.get(&target_key).and_then(|v| match v {
super::model::PropertyValue::String(s) => Some(s.to_string()),
_ => None,
});
if let Some(target_name) = target {
self.write_line(&format!("alias {} for {};", name, target_name));
} else {
self.write_line(&format!("alias {};", name));
}
}
}
fn decompile_transparent_container(&mut self, element: &Element) {
for child_id in &element.owned_elements {
if let Some(child) = self.model.elements.get(child_id) {
self.decompile_element(child);
}
}
}
fn decompile_body(&mut self, element: &Element) {
self.indent_level += 1;
self.decompile_imports(&element.id);
if let Some(doc) = &element.documentation {
self.write_line(&format!("doc /* {} */", doc));
}
self.decompile_children_inner(element);
self.indent_level -= 1;
}
fn decompile_children_inner(&mut self, element: &Element) {
for child_id in &element.owned_elements {
if let Some(child) = self.model.elements.get(child_id) {
self.decompile_element(child);
}
}
}
fn write_visibility(&mut self, element: &Element) {
match element.visibility {
Visibility::Private => {
let _ = write!(self.output, "{}private ", self.indent());
}
Visibility::Protected => {
let _ = write!(self.output, "{}protected ", self.indent());
}
Visibility::Public => {}
}
}
fn format_short_name(&self, element: &Element) -> String {
if let Some(short) = &element.short_name {
format!("<{}> ", short)
} else {
String::new()
}
}
fn format_element_name(&self, element: &Element) -> String {
match &element.name {
Some(name) => {
if name.contains(' ') || name.contains('/') || name.contains('\\') {
format!("'{}'", name)
} else {
name.to_string()
}
}
None => String::new(),
}
}
fn format_specializations(&self, element_id: &ElementId) -> String {
let specializations: Vec<&str> = self
.model
.rel_elements_of_kind(element_id, ElementKind::Specialization)
.filter_map(|re| {
re.target()
.and_then(|tid| self.model.elements.get(tid))
.and_then(|e| e.name.as_deref())
})
.collect();
if specializations.is_empty() {
String::new()
} else {
format!(" :> {}", specializations.join(", "))
}
}
fn format_typing(&self, element_id: &ElementId) -> String {
let mut types: Vec<String> = self
.model
.rel_elements_of_kind(element_id, ElementKind::FeatureTyping)
.filter_map(|re| re.target().and_then(|tid| self.get_element_ref_name(tid)))
.collect();
if let Some(element) = self.model.elements.get(element_id) {
for child_id in &element.owned_elements {
if let Some(child) = self.model.elements.get(child_id) {
if child.kind == ElementKind::FeatureTyping {
let href_key: Arc<str> = Arc::from("href_target_name");
if let Some(super::model::PropertyValue::String(name)) =
child.properties.get(&href_key)
{
types.push(name.to_string());
}
}
for grandchild_id in &child.owned_elements {
if let Some(grandchild) = self.model.elements.get(grandchild_id) {
if grandchild.kind == ElementKind::FeatureTyping {
let href_key: Arc<str> = Arc::from("href_target_name");
if let Some(super::model::PropertyValue::String(name)) =
grandchild.properties.get(&href_key)
{
types.push(name.to_string());
}
}
}
}
}
}
}
if types.is_empty() {
String::new()
} else {
format!(" : {}", types.join(", "))
}
}
fn decompile_imports(&mut self, element_id: &ElementId) {
for re in self
.model
.rel_elements_owned_by(element_id, ElementKind::NamespaceImport)
.collect::<Vec<_>>()
{
let visibility_prefix = match re.visibility {
Visibility::Private => "private ",
Visibility::Protected => "protected ",
Visibility::Public => "",
};
let from_target = re
.target()
.and_then(|tid| self.model.elements.get(tid))
.and_then(|e| e.qualified_name.as_deref().or(e.name.as_deref()))
.map(|n| n.to_string());
let resolved = from_target.or_else(|| {
let key: Arc<str> = Arc::from("importedNamespace");
re.properties.get(&key).and_then(|v| match v {
super::model::PropertyValue::String(ns) => Some(ns.to_string()),
_ => None,
})
});
if let Some(ns) = resolved {
self.write_line(&format!("{}import {}::*;", visibility_prefix, ns));
}
}
for re in self
.model
.rel_elements_owned_by(element_id, ElementKind::MembershipImport)
.collect::<Vec<_>>()
{
let visibility_prefix = match re.visibility {
Visibility::Private => "private ",
Visibility::Protected => "protected ",
Visibility::Public => "",
};
let from_target = re
.target()
.and_then(|tid| self.model.elements.get(tid))
.and_then(|e| e.qualified_name.as_deref().or(e.name.as_deref()))
.map(|n| n.to_string());
let resolved = from_target.or_else(|| {
let key: Arc<str> = Arc::from("importedMembership");
re.properties.get(&key).and_then(|v| match v {
super::model::PropertyValue::String(ns) => Some(ns.to_string()),
_ => None,
})
});
if let Some(path) = resolved {
self.write_line(&format!("{}import {};", visibility_prefix, path));
}
}
}
fn format_subsetting(&self, element_id: &ElementId) -> String {
let subsets: Vec<String> = self
.model
.rel_elements_of_kind(element_id, ElementKind::Subsetting)
.filter_map(|re| re.target().and_then(|tid| self.get_element_ref_name(tid)))
.collect();
if subsets.is_empty() {
String::new()
} else {
format!(" subsets {}", subsets.join(", "))
}
}
fn format_redefinition(&self, element_id: &ElementId) -> String {
let redefines: Vec<String> = self
.model
.rel_elements_of_kind(element_id, ElementKind::Redefinition)
.filter_map(|re| {
re.target()
.and_then(|tid| self.get_qualified_element_ref(tid))
})
.collect();
if redefines.is_empty() {
String::new()
} else {
format!(" redefines {}", redefines.join(", "))
}
}
fn format_chaining(&self, element_id: &ElementId) -> String {
let chains: Vec<String> = self
.model
.rel_elements_of_kind(element_id, ElementKind::FeatureChaining)
.filter_map(|re| re.target().and_then(|tid| self.get_chaining_ref(tid)))
.collect();
if chains.is_empty() {
String::new()
} else {
format!(" chains {}", chains.join("."))
}
}
fn format_feature_value(&self, element_id: &ElementId) -> String {
if let Some(element) = self.model.get(element_id) {
for child_id in &element.owned_elements {
if let Some(child) = self.model.get(child_id) {
if child.kind == ElementKind::FeatureValue {
for lit_id in &child.owned_elements {
if let Some(lit) = self.model.get(lit_id) {
let value_key: Arc<str> = Arc::from("value");
if let Some(pv) = lit.properties.get(&value_key) {
return match (lit.kind, pv) {
(
ElementKind::LiteralString,
super::model::PropertyValue::String(s),
) => {
format!(" = \"{}\"", s)
}
(
ElementKind::LiteralInteger,
super::model::PropertyValue::Integer(v),
) => {
format!(" = {}", v)
}
(
ElementKind::LiteralBoolean,
super::model::PropertyValue::Boolean(b),
) => {
format!(" = {}", b)
}
(
ElementKind::LiteralReal,
super::model::PropertyValue::Real(v),
) => {
format!(" = {}", v)
}
(ElementKind::NullExpression, _) => " = null".to_string(),
(
ElementKind::FeatureReferenceExpression,
super::model::PropertyValue::String(s),
) => {
format!(" = {}", s)
}
_ => String::new(),
};
}
}
}
}
}
}
}
String::new()
}
fn get_chaining_ref(&self, target_id: &ElementId) -> Option<String> {
self.model
.elements
.get(target_id)
.and_then(|e| e.name.as_ref())
.map(|n| n.to_string())
}
fn get_element_ref_name(&self, target_id: &ElementId) -> Option<String> {
if let Some(element) = self.model.elements.get(target_id) {
let href_key: Arc<str> = Arc::from("href");
if let Some(super::model::PropertyValue::String(href)) =
element.properties.get(&href_key)
{
return Some(self.extract_name_from_href(href));
}
if let Some(name) = &element.name {
return Some(name.to_string());
}
if let Some(qn) = &element.qualified_name {
return Some(qn.to_string());
}
return None;
}
None
}
fn get_qualified_element_ref(&self, target_id: &ElementId) -> Option<String> {
if let Some(element) = self.model.elements.get(target_id) {
let href_key: Arc<str> = Arc::from("href");
if let Some(super::model::PropertyValue::String(href)) =
element.properties.get(&href_key)
{
return Some(self.extract_name_from_href(href));
}
let name = element.name.as_deref()?;
let owner_name = self.find_named_owner(target_id);
if let Some(owner) = owner_name {
return Some(format!("{}::{}", owner, name));
}
return Some(name.to_string());
}
None
}
fn find_named_owner(&self, element_id: &ElementId) -> Option<String> {
let element = self.model.elements.get(element_id)?;
let mut current_owner_id = element.owner.clone();
while let Some(owner_id) = current_owner_id {
if let Some(owner) = self.model.elements.get(&owner_id) {
if let Some(name) = &owner.name {
if !owner.kind.is_relationship() {
return Some(name.to_string());
}
}
current_owner_id = owner.owner.clone();
} else {
break;
}
}
None
}
fn extract_name_from_href(&self, href: &str) -> String {
if let Some(hash_pos) = href.rfind('#') {
let id = &href[hash_pos + 1..];
if let Some(element) = self.model.elements.get(&ElementId::new(id)) {
if let Some(qn) = &element.qualified_name {
return qn.to_string();
}
if let Some(name) = &element.name {
return name.to_string();
}
}
}
if let Some(hash_pos) = href.rfind('#') {
let path = &href[..hash_pos];
if let Some(file_start) = path.rfind('/') {
let file = &path[file_start + 1..];
if let Some(ext_pos) = file.rfind('.') {
return file[..ext_pos].to_string();
}
} else if let Some(ext_pos) = path.rfind('.') {
return path[..ext_pos].to_string();
}
}
href.to_string()
}
}
fn property_to_json(value: &super::model::PropertyValue) -> serde_json::Value {
use super::model::PropertyValue;
match value {
PropertyValue::String(s) => serde_json::Value::String(s.to_string()),
PropertyValue::Integer(i) => serde_json::Value::Number((*i).into()),
PropertyValue::Real(f) => serde_json::json!(*f),
PropertyValue::Boolean(b) => serde_json::Value::Bool(*b),
PropertyValue::Reference(id) => serde_json::Value::String(format!("ref:{}", id.as_str())),
PropertyValue::List(items) => {
serde_json::Value::Array(items.iter().map(property_to_json).collect())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decompile_empty_model() {
let model = Model::new();
let result = decompile(&model);
assert!(result.text.is_empty());
assert_eq!(result.metadata.version, ImportMetadata::CURRENT_VERSION);
}
#[test]
fn test_decompile_simple_package() {
let mut model = Model::new();
let pkg = Element::new("pkg-1", ElementKind::Package).with_name("MyPackage");
model.elements.insert(pkg.id.clone(), pkg);
model.roots.push(ElementId::from("pkg-1"));
let result = decompile(&model);
assert!(result.text.contains("package MyPackage {"));
assert!(result.text.contains("}"));
let meta = result.metadata.get_element("MyPackage").unwrap();
assert_eq!(meta.original_id.as_deref(), Some("pkg-1"));
}
#[test]
fn test_decompile_part_definition() {
let mut model = Model::new();
let def = Element::new("def-1", ElementKind::PartDefinition).with_name("Vehicle");
model.elements.insert(def.id.clone(), def);
model.roots.push(ElementId::from("def-1"));
let result = decompile(&model);
assert!(result.text.contains("part def Vehicle;"));
}
#[test]
fn test_decompile_abstract_definition() {
let mut model = Model::new();
let mut def = Element::new("def-1", ElementKind::PartDefinition).with_name("AbstractPart");
def.is_abstract = true;
model.elements.insert(def.id.clone(), def);
model.roots.push(ElementId::from("def-1"));
let result = decompile(&model);
assert!(result.text.contains("abstract part def AbstractPart;"));
}
#[test]
fn test_decompile_definition_with_short_name() {
let mut model = Model::new();
let def = Element::new("def-1", ElementKind::PartDefinition)
.with_name("Vehicle")
.with_short_name("V");
model.elements.insert(def.id.clone(), def);
model.roots.push(ElementId::from("def-1"));
let result = decompile(&model);
assert!(result.text.contains("part def <V> Vehicle;"));
}
#[test]
fn test_decompile_nested_elements() {
let mut model = Model::new();
let pkg_id = ElementId::from("pkg-1");
let def_id = ElementId::from("def-1");
let mut pkg = Element::new(pkg_id.clone(), ElementKind::Package).with_name("MyPackage");
pkg.owned_elements.push(def_id.clone());
let def = Element::new(def_id.clone(), ElementKind::PartDefinition)
.with_name("Part1")
.with_owner(pkg_id.clone());
model.elements.insert(pkg_id.clone(), pkg);
model.elements.insert(def_id.clone(), def);
model.roots.push(pkg_id);
let result = decompile(&model);
assert!(result.text.contains("package MyPackage {"));
assert!(result.text.contains("part def Part1;"));
assert!(result.metadata.get_element("MyPackage::Part1").is_some());
}
#[test]
fn test_decompile_with_specialization() {
let mut model = Model::new();
let base = Element::new("base-1", ElementKind::PartDefinition).with_name("Base");
model.elements.insert(base.id.clone(), base);
model.roots.push(ElementId::from("base-1"));
let derived = Element::new("derived-1", ElementKind::PartDefinition).with_name("Derived");
model.elements.insert(derived.id.clone(), derived);
model.roots.push(ElementId::from("derived-1"));
model.add_rel(
"rel-1",
ElementKind::Specialization,
"derived-1",
"base-1",
None,
);
let result = decompile(&model);
assert!(result.text.contains("part def Derived :> Base;"));
}
#[test]
fn test_decompile_part_usage_with_typing() {
let mut model = Model::new();
let pkg_id = ElementId::from("pkg-1");
let def_id = ElementId::from("def-1");
let usage_id = ElementId::from("usage-1");
let def = Element::new(def_id.clone(), ElementKind::PartDefinition).with_name("Engine");
let usage = Element::new(usage_id.clone(), ElementKind::PartUsage)
.with_name("engine")
.with_owner(pkg_id.clone());
let mut pkg = Element::new(pkg_id.clone(), ElementKind::Package).with_name("Car");
pkg.owned_elements.push(def_id.clone());
pkg.owned_elements.push(usage_id.clone());
model.elements.insert(pkg_id.clone(), pkg);
model.elements.insert(def_id.clone(), def);
model.elements.insert(usage_id.clone(), usage);
model.roots.push(pkg_id);
model.add_rel(
"rel-1",
ElementKind::FeatureTyping,
"usage-1",
"def-1",
None,
);
let result = decompile(&model);
assert!(result.text.contains("part engine : Engine;"));
}
#[test]
fn test_decompile_with_documentation() {
let mut model = Model::new();
let mut def = Element::new("def-1", ElementKind::PartDefinition).with_name("Documented");
def.documentation = Some("This is a documented element.".into());
def.owned_elements.push(ElementId::from("dummy"));
let child =
Element::new("dummy", ElementKind::Comment).with_owner(ElementId::from("def-1"));
model.elements.insert(child.id.clone(), child);
model.elements.insert(def.id.clone(), def);
model.roots.push(ElementId::from("def-1"));
let result = decompile(&model);
assert!(
result
.text
.contains("doc /* This is a documented element. */")
);
}
#[test]
fn test_decompile_library_package() {
let mut model = Model::new();
let pkg = Element::new("pkg-1", ElementKind::LibraryPackage).with_name("MyLibrary");
model.elements.insert(pkg.id.clone(), pkg);
model.roots.push(ElementId::from("pkg-1"));
let result = decompile(&model);
assert!(result.text.contains("library package MyLibrary {"));
}
#[test]
fn test_metadata_preserves_source_info() {
let model = Model::new();
let source = SourceInfo::from_path("/path/to/model.xmi").with_format("xmi");
let result = decompile_with_source(&model, source);
assert_eq!(
result.metadata.source.path.as_deref(),
Some("/path/to/model.xmi")
);
assert_eq!(result.metadata.source.format.as_deref(), Some("xmi"));
}
#[test]
fn test_decompile_private_visibility() {
let mut model = Model::new();
let mut def = Element::new("def-1", ElementKind::PartDefinition).with_name("PrivatePart");
def.visibility = Visibility::Private;
model.elements.insert(def.id.clone(), def);
model.roots.push(ElementId::from("def-1"));
let result = decompile(&model);
assert!(result.text.contains("private part def PrivatePart;"));
}
#[test]
fn test_decompile_namespace_import() {
let mut model = Model::new();
let target_pkg = Element::new("target-1", ElementKind::Package).with_name("TargetPackage");
model.elements.insert(target_pkg.id.clone(), target_pkg);
model.roots.push(ElementId::from("target-1"));
let mut pkg = Element::new("pkg-1", ElementKind::Package).with_name("MyPackage");
pkg.owned_elements.push(ElementId::from("dummy"));
let child = Element::new("dummy", ElementKind::PartDefinition)
.with_name("Dummy")
.with_owner(ElementId::from("pkg-1"));
model.elements.insert(child.id.clone(), child);
model.elements.insert(pkg.id.clone(), pkg);
model.roots.push(ElementId::from("pkg-1"));
model.add_rel(
"import-1",
ElementKind::NamespaceImport,
"pkg-1",
"target-1",
Some(ElementId::from("pkg-1")),
);
let result = decompile(&model);
assert!(result.text.contains("import TargetPackage::*;"));
}
#[test]
fn test_decompile_with_subsetting() {
let mut model = Model::new();
let base = Element::new("base-1", ElementKind::PartUsage).with_name("basePart");
model.elements.insert(base.id.clone(), base);
model.roots.push(ElementId::from("base-1"));
let derived = Element::new("derived-1", ElementKind::PartUsage).with_name("derivedPart");
model.elements.insert(derived.id.clone(), derived);
model.roots.push(ElementId::from("derived-1"));
model.add_rel(
"rel-1",
ElementKind::Subsetting,
"derived-1",
"base-1",
None,
);
let result = decompile(&model);
assert!(result.text.contains("part derivedPart subsets basePart;"));
}
#[test]
fn test_decompile_with_redefinition() {
let mut model = Model::new();
let original = Element::new("orig-1", ElementKind::PartUsage).with_name("originalPart");
model.elements.insert(original.id.clone(), original);
model.roots.push(ElementId::from("orig-1"));
let redefining = Element::new("redef-1", ElementKind::PartUsage).with_name("redefPart");
model.elements.insert(redefining.id.clone(), redefining);
model.roots.push(ElementId::from("redef-1"));
model.add_rel(
"rel-1",
ElementKind::Redefinition,
"redef-1",
"orig-1",
None,
);
let result = decompile(&model);
assert!(
result
.text
.contains("part redefPart redefines originalPart;")
);
}
#[test]
fn test_decompile_usage_with_typing_and_subsetting() {
let mut model = Model::new();
let type_def = Element::new("type-1", ElementKind::PartDefinition).with_name("Engine");
model.elements.insert(type_def.id.clone(), type_def);
model.roots.push(ElementId::from("type-1"));
let base = Element::new("base-1", ElementKind::PartUsage).with_name("basePart");
model.elements.insert(base.id.clone(), base);
model.roots.push(ElementId::from("base-1"));
let feature = Element::new("feat-1", ElementKind::PartUsage).with_name("myEngine");
model.elements.insert(feature.id.clone(), feature);
model.roots.push(ElementId::from("feat-1"));
model.add_rel(
"rel-1",
ElementKind::FeatureTyping,
"feat-1",
"type-1",
None,
);
model.add_rel("rel-2", ElementKind::Subsetting, "feat-1", "base-1", None);
let result = decompile(&model);
assert!(
result
.text
.contains("part myEngine : Engine subsets basePart;")
);
}
#[test]
fn test_decompile_xmi_roundtrip() {
use crate::interchange::{ModelFormat, Xmi};
use crate::syntax::parser::parse_content;
use std::path::Path;
let xmi_content = br#"<?xml version="1.0" encoding="UTF-8"?>
<xmi:XMI xmlns:xmi="http://www.omg.org/spec/XMI/20131001"
xmlns:sysml="http://www.omg.org/spec/SysML/20230201">
<sysml:Package xmi:id="pkg1" name="VehicleModel">
<ownedMember>
<sysml:PartDefinition xmi:id="pd1" name="Vehicle"/>
</ownedMember>
<ownedMember>
<sysml:PartDefinition xmi:id="pd2" name="Car"/>
</ownedMember>
</sysml:Package>
</xmi:XMI>"#;
let model = Xmi.read(xmi_content).expect("Failed to read XMI");
assert_eq!(model.element_count(), 3);
let result = decompile(&model);
assert!(
result.text.contains("package VehicleModel"),
"Missing package: {}",
result.text
);
assert!(
result.text.contains("part def Vehicle"),
"Missing Vehicle: {}",
result.text
);
assert!(
result.text.contains("part def Car"),
"Missing Car: {}",
result.text
);
let parse_result = parse_content(&result.text, Path::new("generated.sysml"));
assert!(
parse_result.is_ok(),
"Parse failed: {:?}",
parse_result.err()
);
assert!(result.metadata.get_element("VehicleModel").is_some());
assert!(
result
.metadata
.get_element("VehicleModel::Vehicle")
.is_some()
);
assert!(result.metadata.get_element("VehicleModel::Car").is_some());
let pkg_meta = result.metadata.get_element("VehicleModel").unwrap();
assert_eq!(pkg_meta.original_id.as_deref(), Some("pkg1"));
}
#[test]
fn test_metadata_keyed_by_qualified_name() {
let mut model = Model::new();
let mut pkg = Element::new("pkg-1", ElementKind::Package).with_name("TestPackage");
pkg.owned_elements.push(ElementId::from("def-1"));
model.add_element(pkg);
let mut def = Element::new("def-1", ElementKind::PartDefinition)
.with_name("OuterDef")
.with_owner("pkg-1");
def.owned_elements.push(ElementId::from("usage-1"));
model.add_element(def);
model.add_element(
Element::new("usage-1", ElementKind::PartUsage)
.with_name("innerPart")
.with_owner("def-1"),
);
let result = decompile(&model);
assert!(
result.metadata.get_element("TestPackage").is_some(),
"Should have metadata for TestPackage"
);
assert!(
result
.metadata
.get_element("TestPackage::OuterDef")
.is_some(),
"Should have metadata for TestPackage::OuterDef, got keys: {:?}",
result.metadata.elements.keys().collect::<Vec<_>>()
);
assert!(
result
.metadata
.get_element("TestPackage::OuterDef::innerPart")
.is_some(),
"Should have metadata for TestPackage::OuterDef::innerPart"
);
assert_eq!(
result
.metadata
.get_element("TestPackage")
.unwrap()
.original_id
.as_deref(),
Some("pkg-1")
);
assert_eq!(
result
.metadata
.get_element("TestPackage::OuterDef")
.unwrap()
.original_id
.as_deref(),
Some("def-1")
);
assert_eq!(
result
.metadata
.get_element("TestPackage::OuterDef::innerPart")
.unwrap()
.original_id
.as_deref(),
Some("usage-1")
);
}
}