#![warn(missing_docs)]
use autosar_data_specification::{AttributeSpec, CharacterDataSpec, ContentMode, ElementType};
use fxhash::{FxBuildHasher, FxHashMap};
use indexmap::IndexMap;
pub use iterators::*;
use parking_lot::RwLock;
use parser::ArxmlParser;
use smallvec::SmallVec;
use std::collections::HashSet;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Weak};
use std::{fs::File, io::Read};
use thiserror::Error;
mod arxmlfile;
mod autosarmodel;
mod chardata;
mod element;
mod elementraw;
mod iterators;
mod lexer;
mod parser;
pub use lexer::ArxmlLexerError;
pub use parser::ArxmlParserError;
pub use autosar_data_specification::AttributeName;
pub use autosar_data_specification::AutosarVersion;
pub use autosar_data_specification::ElementName;
pub use autosar_data_specification::EnumItem;
type FxIndexMap<K, V> = IndexMap<K, V, FxBuildHasher>;
#[derive(Clone)]
pub struct AutosarModel(Arc<RwLock<AutosarModelRaw>>);
#[derive(Clone)]
pub(crate) struct WeakAutosarModel(Weak<RwLock<AutosarModelRaw>>);
pub(crate) struct AutosarModelRaw {
root_element: Element,
files: Vec<ArxmlFile>,
identifiables: FxIndexMap<String, WeakElement>,
reference_origins: FxHashMap<String, Vec<WeakElement>>,
}
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum AutosarDataError {
#[error("Failed to read {}: {ioerror}", .filename.to_string_lossy())]
IoErrorRead {
filename: PathBuf,
ioerror: std::io::Error,
},
#[error("Failed to open {}: {ioerror}", .filename.to_string_lossy())]
IoErrorOpen {
filename: PathBuf,
ioerror: std::io::Error,
},
#[error("Failed to write {}: {ioerror}", .filename.to_string_lossy())]
IoErrorWrite {
filename: PathBuf,
ioerror: std::io::Error,
},
#[error("Could not {verb} file {}: A file with this name is already loaded", .filename.to_string_lossy())]
DuplicateFilenameError {
verb: &'static str,
filename: PathBuf,
},
#[error("Failed to tokenize {} on line {line}: {source}", .filename.to_string_lossy())]
LexerError {
filename: PathBuf,
line: usize,
source: ArxmlLexerError,
},
#[error("Failed to parse {}:{line}: {source}", .filename.to_string_lossy())]
ParserError {
filename: PathBuf,
line: usize,
source: ArxmlParserError,
},
#[error("Loading failed: element path {path} of new data in {} overlaps with the existing loaded data", .filename.to_string_lossy())]
OverlappingDataError {
filename: PathBuf,
path: String,
},
#[error("Operation failed: the item has been deleted")]
ItemDeleted,
#[error("Invalid position for an element of this kind")]
InvalidPosition,
#[error("Version mismatch between existing {} and new {}", .version_cur, .version_new)]
VersionMismatch {
version_cur: AutosarVersion,
version_new: AutosarVersion,
},
#[error("Version {} is not compatible with the element data", .version)]
VersionIncompatibleData {
version: AutosarVersion,
},
#[error("The element at {} is not identifiable", .xmlpath)]
ElementNotIdentifiable {
xmlpath: String,
},
#[error("An item name is required for element {}", .element)]
ItemNameRequired {
element: ElementName,
},
#[error("Incorrect content type for element {}", .element)]
IncorrectContentType {
element: ElementName,
},
#[error("Element insertion conflict: {} could not be inserted in {}", .element, .parent)]
ElementInsertionConflict {
parent: ElementName,
element: ElementName,
},
#[error("Element {} is not a valid sub element of {}", .element, .parent)]
InvalidSubElement {
parent: ElementName,
element: ElementName,
},
#[error("element {} not found in parent {}", .target, .parent)]
ElementNotFound {
target: ElementName,
parent: ElementName,
},
#[error("the SHORT-NAME sub element may not be removed")]
ShortNameRemovalForbidden,
#[error("The current element is not a reference")]
NotReferenceElement,
#[error("The reference is not valid")]
InvalidReference,
#[error("Duplicate item name {} in {}", .item_name, .element)]
DuplicateItemName {
element: ElementName,
item_name: String,
},
#[error("Cannot move an element into its own sub element")]
ForbiddenMoveToSubElement,
#[error("Cannot create a copy that includes the destination")]
ForbiddenCopyOfParent,
#[error("A parent element is currently locked by a different operation")]
ParentElementLocked,
#[error("The attribute is not valid for this element")]
InvalidAttribute,
#[error("The given value is not valid for this attribute")]
InvalidAttributeValue,
#[error("The file is from a different model and may not be used in this operation")]
InvalidFile,
#[error("The file is empty and cannot be serialized")]
EmptyFile,
#[error("The new file could not be merged, because it diverges from the model on non-splittable element {}", .path)]
InvalidFileMerge {
path: String,
},
#[error("The operation cannot be completed because the model does not contain any files")]
NoFilesInModel,
#[error("Modifying the fileset of this element is not allowed, because the parent of the element is not marked as splittable")]
FilesetModificationForbidden,
}
#[derive(Clone)]
pub struct ArxmlFile(Arc<RwLock<ArxmlFileRaw>>);
#[derive(Clone)]
pub struct WeakArxmlFile(Weak<RwLock<ArxmlFileRaw>>);
pub(crate) struct ArxmlFileRaw {
pub(crate) version: AutosarVersion,
model: WeakAutosarModel,
pub(crate) filename: PathBuf,
pub(crate) xml_standalone: Option<bool>, }
#[derive(Clone)]
pub struct Element(Arc<RwLock<ElementRaw>>);
#[derive(Clone)]
pub struct WeakElement(Weak<RwLock<ElementRaw>>);
pub(crate) struct ElementRaw {
pub(crate) parent: ElementOrModel,
pub(crate) elemname: ElementName,
pub(crate) elemtype: ElementType,
pub(crate) content: SmallVec<[ElementContent; 4]>,
pub(crate) attributes: SmallVec<[Attribute; 1]>,
pub(crate) file_membership: HashSet<WeakArxmlFile>,
pub(crate) comment: Option<String>,
}
#[derive(Clone, PartialEq, Eq)]
pub struct Attribute {
pub attrname: AttributeName,
pub content: CharacterData,
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum ElementContent {
Element(Element),
CharacterData(CharacterData),
}
#[derive(Debug, PartialEq, Clone)]
pub enum CharacterData {
Enum(EnumItem),
String(String),
UnsignedInteger(u64),
Float(f64),
}
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum ContentType {
Elements,
CharacterData,
Mixed,
}
#[derive(Clone)]
pub(crate) enum ElementOrModel {
Element(WeakElement),
Model(WeakAutosarModel),
None, }
pub enum CompatibilityError {
IncompatibleElement {
element: Element,
version_mask: u32,
},
IncompatibleAttribute {
element: Element,
attribute: AttributeName,
version_mask: u32,
},
IncompatibleAttributeValue {
element: Element,
attribute: AttributeName,
attribute_value: String,
version_mask: u32,
},
}
pub struct ValidSubElementInfo {
pub element_name: ElementName,
pub is_named: bool,
pub is_allowed: bool,
}
const CHECK_FILE_SIZE: usize = 4096;
pub fn check_file<P: AsRef<Path>>(filename: P) -> bool {
let mut buffer: [u8; CHECK_FILE_SIZE] = [0; CHECK_FILE_SIZE];
if File::open(filename).and_then(|mut file| file.read(&mut buffer)).is_ok() {
check_buffer(&buffer)
} else {
false
}
}
#[must_use]
pub fn check_buffer(buffer: &[u8]) -> bool {
let mut parser = ArxmlParser::new(PathBuf::from("none"), buffer, false);
parser.check_arxml_header()
}
impl std::fmt::Debug for Attribute {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Attribute: {:?} = {:?}", self.attrname, self.content)
}
}
impl PartialOrd for Attribute {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Attribute {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.attrname
.to_str()
.cmp(other.attrname.to_str())
.then(self.content.cmp(&other.content))
}
}
#[cfg(test)]
mod test {
use std::{error::Error, io::Write, path::PathBuf};
use tempfile::tempdir;
use crate::*;
#[test]
fn error_traits() {
let err = AutosarDataError::ParserError {
filename: PathBuf::from("filename.arxml"),
line: 123,
source: crate::parser::ArxmlParserError::InvalidArxmlFileHeader,
};
assert!(err.source().is_some());
let errstr = format!("{err}");
let errdbg = format!("{err:#?}");
assert!(errstr != errdbg);
}
#[test]
fn test_check_file() {
let dir = tempdir().unwrap();
assert!(!check_file(dir.path()));
let nonexistent = dir.path().with_file_name("nonexistent.arxml");
assert!(!check_file(nonexistent));
let not_arxml_file = dir.path().with_file_name("not_arxml.bin");
File::create(¬_arxml_file)
.and_then(|mut file| write!(file, "text"))
.unwrap();
assert!(!check_file(not_arxml_file));
let header = r#"<?xml version="1.0" encoding="utf-8"?>
<!-- comment --><!-- comment 2 -->
<AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">"#;
let arxml_file = dir.path().with_file_name("file.arxml");
File::create(&arxml_file)
.and_then(|mut file| file.write(header.as_bytes()))
.unwrap();
assert!(check_file(arxml_file));
assert!(check_buffer(header.as_bytes()));
}
#[test]
fn attribute_order() {
let a1 = Attribute {
attrname: AttributeName::Uuid,
content: CharacterData::String("Value1".to_string()),
};
let a2 = Attribute {
attrname: AttributeName::Uuid,
content: CharacterData::String("Value2".to_string()),
};
assert!(a1 < a2);
let a3 = Attribute {
attrname: AttributeName::T,
content: CharacterData::String("xyz".to_string()),
};
assert!(a3 < a1);
assert!(a1.partial_cmp(&a2) == Some(std::cmp::Ordering::Less));
}
}