/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// demo_exception.cpp

// (C) Copyright 2002-4 Robert Ramey - http://www.rrsd.com . 
// Use, modification and distribution is subject to the Boost Software
// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// Example of safe exception handling for pointer de-serialization
// 
// This example was prepared by Robert Ramey to demonstrate and test 
// safe exception handling during the de-serialization of pointers in
// a non-trivial example.
//
// Hopefully, this addresses exception issues raised by
// Vahan Margaryan who spent considerable time and effort
// in the analysis and testing of issues of exception safety 
// of the serialization library.

#include <algorithm>
#include <iostream>
#include <fstream>
#include <string>

#include <cstdio> // remove
#include <boost/config.hpp>
#if defined(BOOST_NO_STDC_NAMESPACE)
namespace std{ 
    using ::remove;
}
#endif

#include <boost/archive/tmpdir.hpp>

#ifndef BOOST_NO_EXCEPTIONS
#include <exception>
#endif

#include <boost/serialization/list.hpp>
#include <boost/serialization/split_member.hpp>

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>

template<class TPTR>
struct deleter
{
    void operator()(TPTR t){
        delete t;
    }
};

class Course;
class Student;

class Student
{
public:
    static int count;
    Student(){
        count++;
    }
    ~Student(){
        some_courses.clear();
        count--;
    }
    std::list<Course *> some_courses;
private:
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int /* file_version */){
        ar & some_courses;
    }
};

int Student::count = 0;

class Course
{
public:
    static int count;
    Course(){
        count++;
    }
    ~Course(){
        // doesnt delete pointers in list
        // since it doesn't "own" them
        some_students.clear();
        count--;
    }
    std::list<Student *> some_students;
private:
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int /* file_version */){
        ar & some_students;
    }
};

int Course::count = 0;

class School
{
public:
    ~School(){
        // must delete all the students because
        // it "owns" them
        std::for_each(all_students.begin(), all_students.end(), deleter<Student *>());
        all_students.clear();
        // as well as courses
        std::for_each(all_courses.begin(), all_courses.end(), deleter<Course *>());
        all_courses.clear();
    }
    std::list<Student *> all_students;
    std::list<Course *> all_courses;
private:
    friend class boost::serialization::access;
    BOOST_SERIALIZATION_SPLIT_MEMBER()
    template<class Archive>
    void save(Archive & ar, const unsigned int file_version) const;
    template<class Archive>
    void load(Archive & ar, const unsigned int file_version);
};

#if 0
// case 1:
template<class Archive>
void School::serialize(Archive & ar, const unsigned int /* file_version */){
    // if an exeception occurs while loading courses
    // the structure courses may have some courses each
    // with students
    ar & all_courses;
    // while all_students will have no members.
    ar & all_students; // create students that have no courses
    // so ~School() will delete all members of courses
    // but this will NOT delete any students - see above
    // a memory leak will be the result.
}

// switching the order of serialization doesn't help in this case
// case 2:
template<class Archive>
void School::serialize(Archive & ar, const unsigned int /* file_version */){
    ar & all_students;
    ar >> all_courses;  // create any courses that have no students
}
#endif

template<class Archive>
void School::save(Archive & ar, const unsigned int /* file_version */) const {
    ar << all_students;
    ar << all_courses;
}

template<class Archive>
void School::load(Archive & ar, const unsigned int /* file_version */){
    // if an exeception occurs while loading courses
    // the structure courses may have some courses each
    // with students
    try{
        // deserialization of a Course * will in general provoke the
        // deserialization of Student * which are added to the list of
        // students for a class.  That is, this process will result
        // in the copying of a pointer.
        ar >> all_courses;
        ar >> all_students; // create students that have no courses
    }
    catch(std::exception){
        // elminate any dangling references
        all_courses.clear();
        all_students.clear();
        throw;
    }
}

void init(School *school){
    Student *bob = new Student();
    Student *ted = new Student();
    Student *carol = new Student();
    Student *alice = new Student();

    school->all_students.push_back(bob);
    school->all_students.push_back(ted);
    school->all_students.push_back(carol);
    school->all_students.push_back(alice);

    Course *math = new Course();
    Course *history = new Course();
    Course *literature = new Course();
    Course *gym = new Course();

    school->all_courses.push_back(math);
    school->all_courses.push_back(history);
    school->all_courses.push_back(literature);
    school->all_courses.push_back(gym);

    bob->some_courses.push_back(math);
    math->some_students.push_back(bob);
    bob->some_courses.push_back(literature);
    literature->some_students.push_back(bob);

    ted->some_courses.push_back(math);
    math->some_students.push_back(ted);
    ted->some_courses.push_back(history);
    history->some_students.push_back(ted);

    alice->some_courses.push_back(literature);
    literature->some_students.push_back(alice);
    alice->some_courses.push_back(history);
    history->some_students.push_back(alice);

    // no students signed up for gym
    // carol has no courses
}

void save(School *school, const char *filename){
    std::ofstream ofile(filename);
    boost::archive::text_oarchive ar(ofile);
    ar << school;
}

void load(School * & school, const char *filename){
    std::ifstream ifile(filename);
    boost::archive::text_iarchive ar(ifile);
    try{
        ar >> school;
    }
    catch(std::exception){
        // eliminate dangling reference
        school = NULL;
    }
}

int main(int argc, char *argv[]){
    std::string filename(boost::archive::tmpdir());
    filename += "/demofile.txt";

    School *school = new School();
    std::cout << "1. student count = " << Student::count << std::endl;
    std::cout << "2. class count = " << Course::count << std::endl;
    init(school);
    std::cout << "3. student count = " << Student::count << std::endl;
    std::cout << "4. class count = " << Course::count << std::endl;
    save(school, filename.c_str());
    delete school;
    school = NULL;
    std::cout << "5. student count = " << Student::count << std::endl;
    std::cout << "6. class count = " << Course::count << std::endl;
    load(school, filename.c_str());
    std::cout << "7. student count = " << Student::count << std::endl;
    std::cout << "8. class count = " << Course::count << std::endl;
    delete school;
    std::cout << "9. student count = " << Student::count << std::endl;
    std::cout << "10. class count = " << Course::count << std::endl;
    std::remove(filename.c_str());
    return Student::count + Course::count;
}
