1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//! A trait for bidirectional mappings between terms and *indexes* of a smaller type.

use crate::term::factory::{FTerm, TermFactory};
use crate::term::RefTerm;

/// A bidirectionnal mapping between [`Term`]s and *indexes* of a smaller type.
///
/// The TermIndexMap maintains a reference count for each index,
/// to automatically free them whenever they are not used.
///
/// One special index (called the *null index*) is never mapped to any [`Term`],
/// and is used to represent `None` (the absence of graph name in a [`Quad`]).
///
/// [`Term`]: ../../term/enum.Term.html
/// [`Quad`]: ../../quad/trait.Quad.html
///
pub trait TermIndexMap: Default {
    /// The type used to represent terms
    type Index: Copy + Eq;
    /// The factory used to instantiate terms.
    type Factory: TermFactory;

    // A reserved index representing no term
    const NULL_INDEX: Self::Index;

    /// Return the index associated to the given term, if it exists.
    fn get_index(&self, t: &RefTerm) -> Option<Self::Index>;
    /// Return the index associated to the given term, creating it if required, and increasing its ref count.
    fn make_index(&mut self, t: &RefTerm) -> Self::Index;
    /// Return the term associated to the given index, if it exists.
    fn get_term(&self, i: Self::Index) -> Option<&FTerm<Self::Factory>>;
    /// Increase the reference count of a given index (or do nothing if i is the null index).
    fn inc_ref(&mut self, i: Self::Index);
    /// Decrease the reference count of a given index (or do nothing if i is the null index).
    fn dec_ref(&mut self, i: Self::Index);
    /// Shrinks the capacity of the TermIndexMap as much as possible.
    fn shrink_to_fit(&mut self);

    // The following methods have a default impl, and would generally not be overriden

    /// Return the index associated to the given graph name, if it exists.
    fn get_index_for_graph_name(&self, g: Option<&RefTerm>) -> Option<Self::Index> {
        match g {
            None => Some(Self::NULL_INDEX),
            Some(t) => self.get_index(t),
        }
    }
    /// Return the index associated to the given graph name, creating it if required, and increasing its ref count.
    fn make_index_for_graph_name(&mut self, g: Option<&RefTerm>) -> Self::Index {
        match g {
            None => Self::NULL_INDEX,
            Some(t) => self.make_index(t),
        }
    }
    /// Return the graph name associated to the given index, if it exists.
    ///
    /// NB: a graph name is already an `Option`, `None` meaning the (unnamed) default graph.
    /// As a consequence, this methods returns *an option of option* :
    /// * `None` means that given index is *not* associated to any graph name,
    /// * `Some(None)` means that the given index is associated to the default graph,
    /// * `Some(Some(term))` means that given index is associetd to a proper graph name.
    #[allow(clippy::option_option)]
    fn get_graph_name(&self, i: Self::Index) -> Option<Option<&FTerm<Self::Factory>>> {
        if i == Self::NULL_INDEX {
            Some(None)
        } else {
            self.get_term(i).map(Some)
        }
    }
}

#[cfg(test)]
/// Takes an empty TermIndexMap, and checks that it behaves as expected.
pub fn assert_term_index_map_works<T: TermIndexMap>(ti: &mut T) {
    let t = RefTerm::new_iri("http://example.org/").unwrap();
    assert!(ti.get_index(&t).is_none());

    // insert term, then remove it

    let it = ti.make_index(&t);
    assert!(ti.get_index(&t).is_some());
    assert!(ti.get_index(&t).unwrap() == it);
    assert!(ti.get_term(it).is_some());
    assert!(ti.get_term(it).unwrap() == &t);

    ti.dec_ref(it);
    assert!(ti.get_index(&t).is_none());
    assert!(ti.get_term(it).is_none());

    // insert term twice, then remove it

    let it = ti.make_index(&t);
    assert!(ti.get_index(&t).is_some());
    assert!(ti.get_index(&t).unwrap() == it);
    assert!(ti.get_term(it).is_some());
    assert!(ti.get_term(it).unwrap() == &t);

    let it2 = ti.make_index(&t);
    assert!(it == it2);

    ti.dec_ref(it);
    assert!(ti.get_index(&t).is_some());
    assert!(ti.get_index(&t).unwrap() == it);
    assert!(ti.get_term(it).is_some());
    assert!(ti.get_term(it).unwrap() == &t);

    ti.dec_ref(it);
    assert!(ti.get_index(&t).is_none());
    assert!(ti.get_term(it).is_none());

    // insert term, incref it, then remove it

    let it = ti.make_index(&t);
    assert!(ti.get_index(&t).is_some());
    assert!(ti.get_index(&t).unwrap() == it);
    assert!(ti.get_term(it).is_some());
    assert!(ti.get_term(it).unwrap() == &t);

    ti.inc_ref(it);

    ti.dec_ref(it);
    assert!(ti.get_index(&t).is_some());
    assert!(ti.get_index(&t).unwrap() == it);
    assert!(ti.get_term(it).is_some());
    assert!(ti.get_term(it).unwrap() == &t);

    ti.dec_ref(it);
    assert!(ti.get_index(&t).is_none());
    assert!(ti.get_term(it).is_none());

    // insert two terms, then remove them

    let t1 = t;
    let t2 = RefTerm::new_iri("http://example.org/2").unwrap();
    let it1 = ti.make_index(&t1);
    let it2 = ti.make_index(&t2);
    assert!(it1 != it2);

    ti.dec_ref(it2);
    assert!(ti.get_index(&t1).is_some());
    assert!(ti.get_index(&t2).is_none());

    ti.dec_ref(it1);
    assert!(ti.get_index(&t1).is_none());
    assert!(ti.get_index(&t2).is_none());
}

#[cfg(test)]
mod test {
    // Nothing really worth testing here
}