// Chemfiles, a modern library for chemistry file reading and writing
// Copyright (C) 2015-2018 Guillaume Fraux -- BSD licensed
use std::ops::{Drop, Deref};
use std::marker::PhantomData;
use std::ptr;
use chemfiles_sys::*;
use errors::{check_not_null, check_success};
use property::{Property, RawProperty, PropertiesIter};
use strings;
/// A `Residue` is a group of atoms belonging to the same logical unit. They
/// can be small molecules, amino-acids in a protein, monomers in polymers,
/// *etc.*
pub struct Residue {
handle: *mut CHFL_RESIDUE,
}
/// An analog to a reference to a residue (`&Residue`)
pub struct ResidueRef<'a> {
inner: Residue,
marker: PhantomData<&'a Residue>
}
impl<'a> Deref for ResidueRef<'a> {
type Target = Residue;
fn deref(&self) -> &Residue {
&self.inner
}
}
impl Clone for Residue {
fn clone(&self) -> Residue {
unsafe {
let new_handle = chfl_residue_copy(self.as_ptr());
Residue::from_ptr(new_handle)
}
}
}
impl Residue {
/// Create a `Residue` from a C pointer.
///
/// This function is unsafe because no validity check is made on the pointer.
#[inline]
pub(crate) unsafe fn from_ptr(ptr: *mut CHFL_RESIDUE) -> Residue {
check_not_null(ptr);
Residue {
handle: ptr
}
}
/// Create a borrowed `Residue` from a C pointer.
///
/// This function is unsafe because no validity check is made on the
/// pointer, except for it being non-null, and the caller is responsible
/// for setting the right lifetime
#[inline]
pub(crate) unsafe fn ref_from_ptr<'a>(ptr: *const CHFL_RESIDUE) -> ResidueRef<'a> {
ResidueRef {
inner: Residue::from_ptr(ptr as *mut CHFL_RESIDUE),
marker: PhantomData,
}
}
/// Get the underlying C pointer as a const pointer.
#[inline]
pub(crate) fn as_ptr(&self) -> *const CHFL_RESIDUE {
self.handle
}
/// Get the underlying C pointer as a mutable pointer.
#[inline]
pub(crate) fn as_mut_ptr(&mut self) -> *mut CHFL_RESIDUE {
self.handle
}
/// Create a new residue with the given `name`
///
/// # Example
/// ```
/// # use chemfiles::Residue;
/// let residue = Residue::new("ALA");
/// assert_eq!(residue.name(), "ALA");
/// assert_eq!(residue.id(), None);
/// ```
pub fn new<'a>(name: impl Into<&'a str>) -> Residue {
let buffer = strings::to_c(name.into());
unsafe {
let handle = chfl_residue(buffer.as_ptr());
Residue::from_ptr(handle)
}
}
/// Create a new residue with the given `name` and `id` as identifier.
///
/// # Example
/// ```
/// # use chemfiles::Residue;
/// let residue = Residue::with_id("ALA", 67);
/// assert_eq!(residue.name(), "ALA");
/// assert_eq!(residue.id(), Some(67));
/// ```
pub fn with_id<'a>(name: impl Into<&'a str>, id: i64) -> Residue {
let buffer = strings::to_c(name.into());
unsafe {
let handle = chfl_residue_with_id(buffer.as_ptr(), id);
Residue::from_ptr(handle)
}
}
/// Get the number of atoms in this residue.
///
/// # Example
/// ```
/// # use chemfiles::Residue;
/// let mut residue = Residue::new("water");
/// assert_eq!(residue.size(), 0);
///
/// residue.add_atom(0);
/// residue.add_atom(1);
/// residue.add_atom(2);
/// assert_eq!(residue.size(), 3);
/// ```
pub fn size(&self) -> usize {
let mut size = 0;
unsafe {
check_success(chfl_residue_atoms_count(self.as_ptr(), &mut size));
}
#[allow(clippy::cast_possible_truncation)]
return size as usize;
}
/// Get the identifier of this residue in the initial topology file.
///
/// # Example
/// ```
/// # use chemfiles::Residue;
/// let residue = Residue::with_id("", 42);
/// assert_eq!(residue.id(), Some(42));
/// ```
pub fn id(&self) -> Option<i64> {
let mut resid = 0;
let status = unsafe {
chfl_residue_id(self.as_ptr(), &mut resid)
};
if status == chfl_status::CHFL_SUCCESS {
return Some(resid);
} else if status == chfl_status::CHFL_GENERIC_ERROR {
return None;
}
// call check_success to panic in case of error
check_success(status);
unreachable!();
}
/// Get the name of this residue.
///
/// # Example
/// ```
/// # use chemfiles::Residue;
/// let residue = Residue::new("water");
/// assert_eq!(residue.name(), "water");
/// ```
pub fn name(&self) -> String {
let get_name = |ptr, len| unsafe { chfl_residue_name(self.as_ptr(), ptr, len) };
let name = strings::call_autogrow_buffer(64, get_name).expect("getting residue name failed");
return strings::from_c(name.as_ptr());
}
/// Add the atom at index `atom` in this residue.
///
/// This will fail if the atom is already in the residue.
///
/// # Example
/// ```
/// # use chemfiles::Residue;
/// let mut residue = Residue::new("water");
/// assert_eq!(residue.size(), 0);
/// assert_eq!(residue.contains(56), false);
///
/// residue.add_atom(56);
/// assert_eq!(residue.size(), 1);
/// assert_eq!(residue.contains(56), true);
///
/// // Adding the same atom twice is fine
/// residue.add_atom(56);
/// assert_eq!(residue.size(), 1);
/// ```
pub fn add_atom(&mut self, atom: usize) {
unsafe {
check_success(chfl_residue_add_atom(self.as_mut_ptr(), atom as u64));
}
}
/// Check if the atom at index `i` is in this residue
///
/// # Example
/// ```
/// # use chemfiles::Residue;
/// let mut residue = Residue::new("water");
/// assert_eq!(residue.contains(56), false);
///
/// residue.add_atom(56);
/// assert_eq!(residue.contains(56), true);
/// ```
pub fn contains(&self, atom: usize) -> bool {
let mut inside = 0;
unsafe {
check_success(chfl_residue_contains(self.as_ptr(), atom as u64, &mut inside));
}
return inside != 0;
}
/// Get the list of atoms of this residue.
///
/// # Example
/// ```
/// # use chemfiles::Residue;
/// let mut residue = Residue::new("water");
/// assert_eq!(residue.atoms(), vec![]);
///
/// residue.add_atom(56);
/// assert_eq!(residue.atoms(), vec![56]);
/// ```
pub fn atoms(&self) -> Vec<usize> {
let size = self.size();
let count = size as u64;
let mut indices = vec![u64::max_value(); size];
unsafe {
check_success(chfl_residue_atoms(
self.as_ptr(),
indices.as_mut_ptr(),
count,
));
}
#[allow(clippy::cast_possible_truncation)]
return indices.into_iter().map(|idx| idx as usize).collect();
}
/// Add a new `property` with the given `name` to this residue.
///
/// If a property with the same name already exists, this function override
/// the existing property with the new one.
///
/// # Examples
/// ```
/// # use chemfiles::{Residue, Property};
/// let mut residue = Residue::new("ALA");
/// residue.set("a string", "hello");
/// residue.set("a double", 3.2);
///
/// assert_eq!(residue.get("a string"), Some(Property::String("hello".into())));
/// assert_eq!(residue.get("a double"), Some(Property::Double(3.2)));
/// ```
pub fn set(&mut self, name: &str, property: impl Into<Property>) {
let buffer = strings::to_c(name);
let property = property.into().as_raw();
unsafe {
check_success(chfl_residue_set_property(
self.as_mut_ptr(), buffer.as_ptr(), property.as_ptr()
));
}
}
/// Get a property with the given `name` in this frame, if it exist.
///
/// # Examples
/// ```
/// # use chemfiles::{Residue, Property};
/// let mut residue = Residue::new("ALA");
/// residue.set("foo", Property::Double(22.2));
///
/// assert_eq!(residue.get("foo"), Some(Property::Double(22.2)));
/// assert_eq!(residue.get("Bar"), None);
/// ```
pub fn get(&self, name: &str) -> Option<Property> {
let buffer = strings::to_c(name);
unsafe {
let handle = chfl_residue_get_property(self.as_ptr(), buffer.as_ptr());
if handle.is_null() {
None
} else {
let raw = RawProperty::from_ptr(handle);
Some(Property::from_raw(raw))
}
}
}
/// Get an iterator over all (name, property) pairs for this frame
///
/// # Examples
/// ```
/// # use chemfiles::{Residue, Property};
/// let mut residue = Residue::new("ALA");
/// residue.set("foo", Property::Double(22.2));
/// residue.set("bar", Property::Bool(false));
///
/// for (name, property) in residue.properties() {
/// if name == "foo" {
/// assert_eq!(property, Property::Double(22.2));
/// } else if name == "bar" {
/// assert_eq!(property, Property::Bool(false));
/// }
/// }
/// ```
pub fn properties(&self) -> PropertiesIter {
let mut count = 0;
unsafe {
check_success(chfl_residue_properties_count(self.as_ptr(), &mut count));
}
#[allow(clippy::cast_possible_truncation)]
let size = count as usize;
let mut c_names = vec![ptr::null_mut(); size];
unsafe {
check_success(chfl_residue_list_properties(self.as_ptr(), c_names.as_mut_ptr(), count));
}
let mut names = Vec::new();
for ptr in c_names {
names.push(strings::from_c(ptr));
}
PropertiesIter {
names: names.into_iter(),
getter: Box::new(move |name| self.get(name).expect("failed to get property"))
}
}
}
impl Drop for Residue {
fn drop(&mut self) {
unsafe {
let _ = chfl_free(self.as_ptr().cast());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clone() {
let mut residue = Residue::new("A");
assert_eq!(residue.size(), 0);
let copy = residue.clone();
assert_eq!(copy.size(), 0);
residue.add_atom(3);
residue.add_atom(7);
assert_eq!(residue.size(), 2);
assert_eq!(copy.size(), 0);
}
#[test]
fn name() {
let residue = Residue::new("A");
assert_eq!(residue.name(), "A");
}
#[test]
fn id() {
let residue = Residue::new("A");
assert_eq!(residue.id(), None);
let residue = Residue::with_id("A", 42);
assert_eq!(residue.id(), Some(42));
let residue = Residue::with_id("A", -3);
assert_eq!(residue.id(), Some(-3));
}
#[test]
fn atoms() {
let mut residue = Residue::new("A");
assert_eq!(residue.size(), 0);
residue.add_atom(0);
residue.add_atom(3);
residue.add_atom(45);
assert_eq!(residue.size(), 3);
assert!(residue.contains(3));
assert!(!residue.contains(5));
assert_eq!(residue.atoms(), vec![0, 3, 45]);
}
#[test]
fn property() {
let mut residue = Residue::new("ALA");
residue.set("foo", -22.0);
assert_eq!(residue.get("foo"), Some(Property::Double(-22.0)));
assert_eq!(residue.get("bar"), None);
residue.set("bar", Property::String("here".into()));
for (name, property) in residue.properties() {
if name == "foo" {
assert_eq!(property, Property::Double(-22.0));
} else if name == "bar" {
assert_eq!(property, Property::String("here".into()));
}
}
}
}