<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Copyright (C) 2017 ScyllaDB
 *
 * Modified by ScyllaDB
 */

/*
 * This file is part of Scylla.
 *
 * Scylla is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Scylla is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Scylla.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <boost/range/algorithm/transform.hpp>
#include <boost/range/adaptors.hpp>

#include "clustering_bounds_comparator.hh"
#include "cql3/statements/select_statement.hh"
#include "cql3/util.hh"
#include "db/view/view.hh"

namespace db {

namespace view {

cql3::statements::select_statement& view::select_statement() const {
    if (!_select_statement) {
        std::vector<sstring_view> included;
        if (!_schema->view_info()->include_all_columns()) {
            included.reserve(_schema->all_columns_in_select_order().size());
            boost::transform(_schema->all_columns_in_select_order(), std::back_inserter(included), std::mem_fn(&column_definition::name_as_text));
        }
        auto raw = cql3::util::build_select_statement(_schema->view_info()->base_name(), _schema->view_info()->where_clause(), std::move(included));
        raw->prepare_keyspace(_schema->ks_name());
        raw->set_bound_variables({});
        cql3::cql_stats ignored;
        auto prepared = raw->prepare(service::get_local_storage_proxy().get_db().local(), ignored, true);
        _select_statement = static_pointer_cast<cql3::statements::select_statement>(prepared->statement);
    }
    return *_select_statement;
}

const query::partition_slice& view::partition_slice() const {
    if (!_partition_slice) {
        _partition_slice = select_statement().make_partition_slice(cql3::query_options({ }));
    }
    return *_partition_slice;
}

const dht::partition_range_vector& view::partition_ranges() const {
    if (!_partition_ranges) {
        _partition_ranges = select_statement().get_restrictions()->get_partition_key_ranges(cql3::query_options({ }));
    }
    return *_partition_ranges;
}

bool view::partition_key_matches(const ::schema& base, const dht::decorated_key& key) const {
    dht::ring_position rp(key);
    auto& ranges = partition_ranges();
    return std::any_of(ranges.begin(), ranges.end(), [&] (auto&& range) {
        return range.contains(rp, dht::ring_position_comparator(base));
    });
}

bool view::clustering_prefix_matches(const ::schema& base, const partition_key& key, const clustering_key_prefix& ck) const {
    bound_view::compare less(base);
    auto& ranges = partition_slice().row_ranges(base, key);
    return std::any_of(ranges.begin(), ranges.end(), [&] (auto&& range) {
        auto bounds = bound_view::from_range(range);
        return !less(ck, bounds.first) && !less(bounds.second, ck);
    });
}

bool view::may_be_affected_by(const ::schema& base, const dht::decorated_key& key, const rows_entry& update) const {
    // We can guarantee that the view won't be affected if:
    //  - the primary key is excluded by the view filter (note that this isn't true of the filter on regular columns:
    //    even if an update don't match a view condition on a regular column, that update can still invalidate a
    //    pre-existing entry);
    //  - the update doesn't modify any of the columns impacting the view (where "impacting" the view means that column
    //    is neither included in the view, nor used by the view filter).
    if (!partition_key_matches(base, key) && !clustering_prefix_matches(base, key.key(), update.key())) {
        return false;
    }

    // We want to check if the update modifies any of the columns that are part of the view (in which case the view is
    // affected). But iff the view includes all the base table columns, or the update has either a row deletion or a
    // row marker, we know the view is affected right away.
    if (_schema->view_info()->include_all_columns() || update.row().deleted_at() || update.row().marker().is_live()) {
        return true;
    }

    bool affected = false;
    update.row().cells().for_each_cell_until([&] (column_id id, const atomic_cell_or_collection& cell) {
        affected = _schema->get_column_definition(base.column_at(column_kind::regular_column, id).name());
        return stop_iteration(affected);
    });
    return affected;
}

bool view::matches_view_filter(const ::schema& base, const partition_key& key, const clustering_row& update, gc_clock::time_point now) const {
    return clustering_prefix_matches(base, key, update.key()) &&
                boost::algorithm::all_of(
                    select_statement().get_restrictions()->get_non_pk_restriction() | boost::adaptors::map_values,
                    [&] (auto&& r) {
                        return r->is_satisfied_by(base, key, update.key(), update.cells(), cql3::query_options({ }), now);
                    });
}

void view::set_base_non_pk_column_in_view_pk(const ::schema& base) {
    for (auto&& base_col : base.regular_columns()) {
        auto view_col = _schema->get_column_definition(base_col.name());
        if (view_col && view_col->is_primary_key()) {
            _base_non_pk_column_in_view_pk = view_col;
            return;
        }
    }
    _base_non_pk_column_in_view_pk = nullptr;
}

class view_updates final {
    lw_shared_ptr<const db::view::view> _view;
    schema_ptr _base;
    std::unordered_map<partition_key, mutation_partition, partition_key::hashing, partition_key::equality> _updates;
public:
    explicit view_updates(lw_shared_ptr<const db::view::view> view, schema_ptr base)
            : _view(std::move(view))
            , _base(std::move(base))
            , _updates(8, partition_key::hashing(*_base), partition_key::equality(*_base)) {
    }

    void move_to(std::vector<mutation>& mutations) && {
        auto& partitioner = dht::global_partitioner();
        std::transform(_updates.begin(), _updates.end(), std::back_inserter(mutations), [&, this] (auto&& m) {
            return mutation(_view->schema(), partitioner.decorate_key(*_base, std::move(m.first)), std::move(m.second));
        });
    }

private:
    mutation_partition& partition_for(partition_key&& key) {
        auto it = _updates.find(key);
        if (it != _updates.end()) {
            return it->second;
        }
        return _updates.emplace(std::move(key), mutation_partition(_view->schema())).first->second;
    }
    row_marker compute_row_marker(const clustering_row& base_row) const;
    deletable_row& get_view_row(const partition_key& base_key, const clustering_row& update);
    void create_entry(const partition_key& base_key, const clustering_row& update, gc_clock::time_point now);
};

row_marker view_updates::compute_row_marker(const clustering_row& base_row) const {
    /*
     * We need to compute both the timestamp and expiration.
     *
     * For the timestamp, it makes sense to use the bigger timestamp for all view PK columns.
     *
     * This is more complex for the expiration. We want to maintain consistency between the base and the view, so the
     * entry should only exist as long as the base row exists _and_ has non-null values for all the columns that are part
     * of the view PK.
     * Which means we really have 2 cases:
     *   1) There is a column that is not in the base PK but is in the view PK. In that case, as long as that column
     *      lives, the view entry does too, but as soon as it expires (or is deleted for that matter) the entry also
     *      should expire. So the expiration for the view is the one of that column, regardless of any other expiration.
     *      To take an example of that case, if you have:
     *        CREATE TABLE t (a int, b int, c int, PRIMARY KEY (a, b))
     *        CREATE MATERIALIZED VIEW mv AS SELECT * FROM t WHERE c IS NOT NULL AND a IS NOT NULL AND b IS NOT NULL PRIMARY KEY (c, a, b)
     *        INSERT INTO t(a, b) VALUES (0, 0) USING TTL 3;
     *        UPDATE t SET c = 0 WHERE a = 0 AND b = 0;
     *      then even after 3 seconds elapsed, the row will still exist (it just won't have a "row marker" anymore) and so
     *      the MV should still have a corresponding entry.
     *   2) The columns for the base and view PKs are exactly the same. In that case, the view entry should live
     *      as long as the base row lives. This means the view entry should only expire once *everything* in the
     *      base row has expired. So, the row TTL should be the max of any other TTL. This is particularly important
     *      in the case where the base row has a TTL, but a column *absent* from the view holds a greater TTL.
     */

    auto marker = base_row.marker();
    auto* col = _view->base_non_pk_column_in_view_pk();
    if (col) {
        // Note: multi-cell columns can't be part of the primary key.
        auto cell = base_row.cells().cell_at(col->id).as_atomic_cell();
        auto timestamp = std::max(marker.timestamp(), cell.timestamp());
        return cell.is_live_and_has_ttl() ? row_marker(timestamp, cell.ttl(), cell.expiry()) : row_marker(timestamp);
    }

    if (!marker.is_expiring()) {
        return marker;
    }

    auto ttl = marker.ttl();
    auto expiry = marker.expiry();
    auto maybe_update_expiry_and_ttl = [&] (atomic_cell_view&& cell) {
        // Note: Cassandra compares cell.ttl() here, but that seems very wrong.
        // See CASSANDRA-13127.
        if (cell.is_live_and_has_ttl() && cell.expiry() > expiry) {
            expiry = cell.expiry();
            ttl = cell.ttl();
        }
    };

    base_row.cells().for_each_cell([&] (column_id id, const atomic_cell_or_collection& c) {
        auto& def = _base->regular_column_at(id);
        if (def.is_atomic()) {
            maybe_update_expiry_and_ttl(c.as_atomic_cell());
        } else {
            static_pointer_cast<const collection_type_impl>(def.type)->for_each_cell(c.as_collection_mutation(), maybe_update_expiry_and_ttl);
        }
    });

    return row_marker(marker.timestamp(), ttl, expiry);
}

deletable_row& view_updates::get_view_row(const partition_key& base_key, const clustering_row& update) {
    auto get_value = boost::adaptors::transformed([&, this] (const column_definition& cdef) {
        auto* base_col = _base->get_column_definition(cdef.name());
        assert(base_col);
        switch (base_col->kind) {
        case column_kind::partition_key:
            return base_key.get_component(*_base, base_col->position());
        case column_kind::clustering_key:
            return update.key().get_component(*_base, base_col->position());
        default:
            auto& c = update.cells().cell_at(base_col->id);
            if (base_col->is_atomic()) {
                return c.as_atomic_cell().value();
            }
            return c.as_collection_mutation().data;
        }
    });
    auto& view_schema = *_view->schema();
    auto& partition = partition_for(partition_key::from_range(view_schema.partition_key_columns() | get_value));
    auto ckey = clustering_key::from_range(view_schema.clustering_key_columns() | get_value);
    return partition.clustered_row(view_schema, std::move(ckey));
}

static const column_definition* view_column(const schema& base, const schema& view, column_id base_id) {
    // FIXME: Map base column_ids to view_column_ids, which can be something like
    // a boost::small_vector where the position is the base column_id, and the
    // value is either empty or the view's column_id.
    return view.get_column_definition(base.regular_column_at(base_id).name());
}

static void add_cells_to_view(const schema& base, const schema& view, const row& base_cells, row& view_cells) {
    base_cells.for_each_cell([&] (column_id id, const atomic_cell_or_collection& c) {
        auto* view_col = view_column(base, view, id);
        if (view_col && !view_col->is_primary_key()) {
            view_cells.append_cell(view_col->id, c);
        }
    });
}

/**
 * Creates a view entry corresponding to the provided base row.
 * This method checks that the base row does match the view filter before applying anything.
 */
void view_updates::create_entry(const partition_key& base_key, const clustering_row& update, gc_clock::time_point now) {
    if (!_view->matches_view_filter(*_base, base_key, update, now)) {
        return;
    }
    deletable_row& r = get_view_row(base_key, update);
    r.apply(compute_row_marker(update));
    r.apply(update.tomb());
    add_cells_to_view(*_base, *_view->schema(), update.cells(), r.cells());
}

} // namespace view
} // namespace db

<commit_msg>view_updates: Delete old view entry<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Copyright (C) 2017 ScyllaDB
 *
 * Modified by ScyllaDB
 */

/*
 * This file is part of Scylla.
 *
 * Scylla is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Scylla is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Scylla.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <boost/range/algorithm/transform.hpp>
#include <boost/range/adaptors.hpp>

#include "clustering_bounds_comparator.hh"
#include "cql3/statements/select_statement.hh"
#include "cql3/util.hh"
#include "db/view/view.hh"

namespace db {

namespace view {

cql3::statements::select_statement& view::select_statement() const {
    if (!_select_statement) {
        std::vector<sstring_view> included;
        if (!_schema->view_info()->include_all_columns()) {
            included.reserve(_schema->all_columns_in_select_order().size());
            boost::transform(_schema->all_columns_in_select_order(), std::back_inserter(included), std::mem_fn(&column_definition::name_as_text));
        }
        auto raw = cql3::util::build_select_statement(_schema->view_info()->base_name(), _schema->view_info()->where_clause(), std::move(included));
        raw->prepare_keyspace(_schema->ks_name());
        raw->set_bound_variables({});
        cql3::cql_stats ignored;
        auto prepared = raw->prepare(service::get_local_storage_proxy().get_db().local(), ignored, true);
        _select_statement = static_pointer_cast<cql3::statements::select_statement>(prepared->statement);
    }
    return *_select_statement;
}

const query::partition_slice& view::partition_slice() const {
    if (!_partition_slice) {
        _partition_slice = select_statement().make_partition_slice(cql3::query_options({ }));
    }
    return *_partition_slice;
}

const dht::partition_range_vector& view::partition_ranges() const {
    if (!_partition_ranges) {
        _partition_ranges = select_statement().get_restrictions()->get_partition_key_ranges(cql3::query_options({ }));
    }
    return *_partition_ranges;
}

bool view::partition_key_matches(const ::schema& base, const dht::decorated_key& key) const {
    dht::ring_position rp(key);
    auto& ranges = partition_ranges();
    return std::any_of(ranges.begin(), ranges.end(), [&] (auto&& range) {
        return range.contains(rp, dht::ring_position_comparator(base));
    });
}

bool view::clustering_prefix_matches(const ::schema& base, const partition_key& key, const clustering_key_prefix& ck) const {
    bound_view::compare less(base);
    auto& ranges = partition_slice().row_ranges(base, key);
    return std::any_of(ranges.begin(), ranges.end(), [&] (auto&& range) {
        auto bounds = bound_view::from_range(range);
        return !less(ck, bounds.first) && !less(bounds.second, ck);
    });
}

bool view::may_be_affected_by(const ::schema& base, const dht::decorated_key& key, const rows_entry& update) const {
    // We can guarantee that the view won't be affected if:
    //  - the primary key is excluded by the view filter (note that this isn't true of the filter on regular columns:
    //    even if an update don't match a view condition on a regular column, that update can still invalidate a
    //    pre-existing entry);
    //  - the update doesn't modify any of the columns impacting the view (where "impacting" the view means that column
    //    is neither included in the view, nor used by the view filter).
    if (!partition_key_matches(base, key) && !clustering_prefix_matches(base, key.key(), update.key())) {
        return false;
    }

    // We want to check if the update modifies any of the columns that are part of the view (in which case the view is
    // affected). But iff the view includes all the base table columns, or the update has either a row deletion or a
    // row marker, we know the view is affected right away.
    if (_schema->view_info()->include_all_columns() || update.row().deleted_at() || update.row().marker().is_live()) {
        return true;
    }

    bool affected = false;
    update.row().cells().for_each_cell_until([&] (column_id id, const atomic_cell_or_collection& cell) {
        affected = _schema->get_column_definition(base.column_at(column_kind::regular_column, id).name());
        return stop_iteration(affected);
    });
    return affected;
}

bool view::matches_view_filter(const ::schema& base, const partition_key& key, const clustering_row& update, gc_clock::time_point now) const {
    return clustering_prefix_matches(base, key, update.key()) &&
                boost::algorithm::all_of(
                    select_statement().get_restrictions()->get_non_pk_restriction() | boost::adaptors::map_values,
                    [&] (auto&& r) {
                        return r->is_satisfied_by(base, key, update.key(), update.cells(), cql3::query_options({ }), now);
                    });
}

void view::set_base_non_pk_column_in_view_pk(const ::schema& base) {
    for (auto&& base_col : base.regular_columns()) {
        auto view_col = _schema->get_column_definition(base_col.name());
        if (view_col && view_col->is_primary_key()) {
            _base_non_pk_column_in_view_pk = view_col;
            return;
        }
    }
    _base_non_pk_column_in_view_pk = nullptr;
}

class view_updates final {
    lw_shared_ptr<const db::view::view> _view;
    schema_ptr _base;
    std::unordered_map<partition_key, mutation_partition, partition_key::hashing, partition_key::equality> _updates;
public:
    explicit view_updates(lw_shared_ptr<const db::view::view> view, schema_ptr base)
            : _view(std::move(view))
            , _base(std::move(base))
            , _updates(8, partition_key::hashing(*_base), partition_key::equality(*_base)) {
    }

    void move_to(std::vector<mutation>& mutations) && {
        auto& partitioner = dht::global_partitioner();
        std::transform(_updates.begin(), _updates.end(), std::back_inserter(mutations), [&, this] (auto&& m) {
            return mutation(_view->schema(), partitioner.decorate_key(*_base, std::move(m.first)), std::move(m.second));
        });
    }

private:
    mutation_partition& partition_for(partition_key&& key) {
        auto it = _updates.find(key);
        if (it != _updates.end()) {
            return it->second;
        }
        return _updates.emplace(std::move(key), mutation_partition(_view->schema())).first->second;
    }
    row_marker compute_row_marker(const clustering_row& base_row) const;
    deletable_row& get_view_row(const partition_key& base_key, const clustering_row& update);
    void create_entry(const partition_key& base_key, const clustering_row& update, gc_clock::time_point now);
    void delete_old_entry(const partition_key& base_key, const clustering_row& existing, gc_clock::time_point now);
    void do_delete_old_entry(const partition_key& base_key, const clustering_row& existing, gc_clock::time_point now);
};

row_marker view_updates::compute_row_marker(const clustering_row& base_row) const {
    /*
     * We need to compute both the timestamp and expiration.
     *
     * For the timestamp, it makes sense to use the bigger timestamp for all view PK columns.
     *
     * This is more complex for the expiration. We want to maintain consistency between the base and the view, so the
     * entry should only exist as long as the base row exists _and_ has non-null values for all the columns that are part
     * of the view PK.
     * Which means we really have 2 cases:
     *   1) There is a column that is not in the base PK but is in the view PK. In that case, as long as that column
     *      lives, the view entry does too, but as soon as it expires (or is deleted for that matter) the entry also
     *      should expire. So the expiration for the view is the one of that column, regardless of any other expiration.
     *      To take an example of that case, if you have:
     *        CREATE TABLE t (a int, b int, c int, PRIMARY KEY (a, b))
     *        CREATE MATERIALIZED VIEW mv AS SELECT * FROM t WHERE c IS NOT NULL AND a IS NOT NULL AND b IS NOT NULL PRIMARY KEY (c, a, b)
     *        INSERT INTO t(a, b) VALUES (0, 0) USING TTL 3;
     *        UPDATE t SET c = 0 WHERE a = 0 AND b = 0;
     *      then even after 3 seconds elapsed, the row will still exist (it just won't have a "row marker" anymore) and so
     *      the MV should still have a corresponding entry.
     *   2) The columns for the base and view PKs are exactly the same. In that case, the view entry should live
     *      as long as the base row lives. This means the view entry should only expire once *everything* in the
     *      base row has expired. So, the row TTL should be the max of any other TTL. This is particularly important
     *      in the case where the base row has a TTL, but a column *absent* from the view holds a greater TTL.
     */

    auto marker = base_row.marker();
    auto* col = _view->base_non_pk_column_in_view_pk();
    if (col) {
        // Note: multi-cell columns can't be part of the primary key.
        auto cell = base_row.cells().cell_at(col->id).as_atomic_cell();
        auto timestamp = std::max(marker.timestamp(), cell.timestamp());
        return cell.is_live_and_has_ttl() ? row_marker(timestamp, cell.ttl(), cell.expiry()) : row_marker(timestamp);
    }

    if (!marker.is_expiring()) {
        return marker;
    }

    auto ttl = marker.ttl();
    auto expiry = marker.expiry();
    auto maybe_update_expiry_and_ttl = [&] (atomic_cell_view&& cell) {
        // Note: Cassandra compares cell.ttl() here, but that seems very wrong.
        // See CASSANDRA-13127.
        if (cell.is_live_and_has_ttl() && cell.expiry() > expiry) {
            expiry = cell.expiry();
            ttl = cell.ttl();
        }
    };

    base_row.cells().for_each_cell([&] (column_id id, const atomic_cell_or_collection& c) {
        auto& def = _base->regular_column_at(id);
        if (def.is_atomic()) {
            maybe_update_expiry_and_ttl(c.as_atomic_cell());
        } else {
            static_pointer_cast<const collection_type_impl>(def.type)->for_each_cell(c.as_collection_mutation(), maybe_update_expiry_and_ttl);
        }
    });

    return row_marker(marker.timestamp(), ttl, expiry);
}

deletable_row& view_updates::get_view_row(const partition_key& base_key, const clustering_row& update) {
    auto get_value = boost::adaptors::transformed([&, this] (const column_definition& cdef) {
        auto* base_col = _base->get_column_definition(cdef.name());
        assert(base_col);
        switch (base_col->kind) {
        case column_kind::partition_key:
            return base_key.get_component(*_base, base_col->position());
        case column_kind::clustering_key:
            return update.key().get_component(*_base, base_col->position());
        default:
            auto& c = update.cells().cell_at(base_col->id);
            if (base_col->is_atomic()) {
                return c.as_atomic_cell().value();
            }
            return c.as_collection_mutation().data;
        }
    });
    auto& view_schema = *_view->schema();
    auto& partition = partition_for(partition_key::from_range(view_schema.partition_key_columns() | get_value));
    auto ckey = clustering_key::from_range(view_schema.clustering_key_columns() | get_value);
    return partition.clustered_row(view_schema, std::move(ckey));
}

static const column_definition* view_column(const schema& base, const schema& view, column_id base_id) {
    // FIXME: Map base column_ids to view_column_ids, which can be something like
    // a boost::small_vector where the position is the base column_id, and the
    // value is either empty or the view's column_id.
    return view.get_column_definition(base.regular_column_at(base_id).name());
}

static void add_cells_to_view(const schema& base, const schema& view, const row& base_cells, row& view_cells) {
    base_cells.for_each_cell([&] (column_id id, const atomic_cell_or_collection& c) {
        auto* view_col = view_column(base, view, id);
        if (view_col && !view_col->is_primary_key()) {
            view_cells.append_cell(view_col->id, c);
        }
    });
}

/**
 * Creates a view entry corresponding to the provided base row.
 * This method checks that the base row does match the view filter before applying anything.
 */
void view_updates::create_entry(const partition_key& base_key, const clustering_row& update, gc_clock::time_point now) {
    if (!_view->matches_view_filter(*_base, base_key, update, now)) {
        return;
    }
    deletable_row& r = get_view_row(base_key, update);
    r.apply(compute_row_marker(update));
    r.apply(update.tomb());
    add_cells_to_view(*_base, *_view->schema(), update.cells(), r.cells());
}

/**
 * Deletes the view entry corresponding to the provided base row.
 * This method checks that the base row does match the view filter before bothering.
 */
void view_updates::delete_old_entry(const partition_key& base_key, const clustering_row& existing, gc_clock::time_point now) {
    // Before deleting an old entry, make sure it was matching the view filter
    // (otherwise there is nothing to delete)
    if (_view->matches_view_filter(*_base, base_key, existing, now)) {
        do_delete_old_entry(base_key, existing, now);
    }
}

void view_updates::do_delete_old_entry(const partition_key& base_key, const clustering_row& existing, gc_clock::time_point now) {
    // We delete the old row using a shadowable row tombstone, making sure that
    // the tombstone deletes everything in the row (or it might still show up).
    // FIXME: If the entry is "resurrected" by a later update, we would need to
    // ensure that the timestamp for the entry then is bigger than the tombstone
    // we're just inserting, which is not currently guaranteed. See CASSANDRA-11500
    // for details.
    auto& view_schema = *_view->schema();
    auto ts = existing.marker().timestamp();
    auto set_max_ts = [&ts] (atomic_cell_view&& cell) {
        ts = std::max(ts, cell.timestamp());
    };
    existing.cells().for_each_cell([&, this] (column_id id, const atomic_cell_or_collection& cell) {
        auto* def = view_column(*_base, view_schema, id);
        if (!def) {
            return;
        }
        if (def->is_atomic()) {
            set_max_ts(cell.as_atomic_cell());
        } else {
            static_pointer_cast<const collection_type_impl>(def->type)->for_each_cell(cell.as_collection_mutation(), set_max_ts);
        }
    });
    get_view_row(base_key, existing).apply(tombstone(ts, now));
}

} // namespace view
} // namespace db

<|endoftext|>


<commit_before>#include "robot_diagrams_0.0.hpp"

#include <iostream>
#include <fstream>
#include <iterator>
#include <string>
#include <vector>
#include <cstdlib>

std::vector<std::string> split(std::string s)
{
  std::stringstream ss(s);
  std::istream_iterator<std::string> begin(ss);
  std::istream_iterator<std::string> end;
  std::vector<std::string> vstrings(begin, end);
  return vstrings;
}

// TODO: delete copy constructor of robot!
bool add_element(rob_diag::Robot& robot, std::string line)
{
  // TODO: do float parsing better...std::stof is only C++11...std::strtod sucks for error conditions.
  try
  {
    std::vector<std::string> words = split(line);
    if (words.size() == 0)
    {
      std::cerr << "No arguments on line!" << std::endl;
      return false;
    }
    if (words[0] == "base" || words[0] == "invisible_base")
    {
      if (words.size() == 1)
      {
        rob_diag::RobotElement* base = (rob_diag::RobotElement*)new rob_diag::Base();
        if (words[0] == "invisible_base")
          ((rob_diag::Base*)base)->visible_ = false;
        robot.elements_.push_back(base);
        return true;
      }
      else if (words.size() == 2)
      {
        double width = std::strtod (words[1].c_str(), NULL);
        rob_diag::RobotElement* base = (rob_diag::RobotElement*)new rob_diag::Base(width);
        if (words[0] == "invisible_base")
          ((rob_diag::Base*)base)->visible_ = false;
        robot.elements_.push_back(base);
        return true;
      }
      std::cerr << "Invalid arguments for " << words[0] << std::endl;
      return false;
    }
    else if (words[0] == "link" || words[0] == "vector") // TODO: remove me! hack to workaround gmail blocking an attachment!
    {
      if (words.size() == 2)
      {
        double length = std::strtod (words[1].c_str(), NULL);
        rob_diag::RobotElement* link = (rob_diag::RobotElement*)new rob_diag::Link(length);
        robot.elements_.push_back(link);
        return true;
      }
      else if (words.size() == 3)
      {
        double length = std::strtod (words[1].c_str(), NULL);
        rob_diag::RobotElement* link = (rob_diag::RobotElement*)new rob_diag::Link(length, words[2]);
        robot.elements_.push_back(link);
        return true;
      }
      std::cerr << "Invalid arguments for " << words[0] << std::endl;
      return false;
    }
    else if (words[0] == "invisible_link")
    {
      if (words.size() == 2)
      {
        double length = std::strtod (words[1].c_str(), NULL);
        rob_diag::Link* tmp = new rob_diag::Link(length);
        tmp->visible_ = false;
        rob_diag::RobotElement* link = (rob_diag::RobotElement*)tmp;
        robot.elements_.push_back(link);
        return true;
      }
      std::cerr << "Invalid arguments for " << words[0] << std::endl;
      return false;
    }
    else if (words[0] == "frames")
    {
      if (words.size() == 1)
      {
        rob_diag::RobotElement* link = (rob_diag::RobotElement*)new rob_diag::Frames();
        robot.elements_.push_back(link);
        return true;
      }
      std::cerr << "Invalid (or not yet supported) arguments for " << words[0] << std::endl;
      return false;
    }
    else if (words[0] == "point") // NOTE: this is a hack to work around missed gmail attachment!
    {
      if (words.size() == 1 || words.size() == 2)
      {
        rob_diag::RobotElement* rjoint = (rob_diag::RobotElement*)new rob_diag::RJoint(0);
        robot.elements_.push_back(rjoint);
        return true;
      }
      std::cerr << "Invalid arguments for " << words[0] << std::endl;
      return false;
    }
    else if (words[0] == "rjoint" || words[0] == "invisible_rjoint")
    {
      if (words.size() == 2)
      {
        double theta = std::strtod (words[1].c_str(), NULL);
        rob_diag::RobotElement* rjoint = (rob_diag::RobotElement*)new rob_diag::RJoint(theta);
        if (words[0] == "invisible_rjoint")
          ((rob_diag::RJoint*)rjoint)->visible_ = false;
        robot.elements_.push_back(rjoint);
        return true;
      }
      std::cerr << "Invalid arguments for " << words[0] << std::endl;
      return false;
    }
    else if (words[0] == "pjoint")
    {
      if (words.size() == 1)
      {
        rob_diag::RobotElement* pjoint = (rob_diag::RobotElement*)new rob_diag::PJoint();
        robot.elements_.push_back(pjoint);
        return true;
      }
      std::cerr << "Invalid arguments for " << words[0] << std::endl;
      return false;
    }
    else if (words[0] == "effector")
    {
      if (words.size() == 1)
      {
        rob_diag::RobotElement* ee = (rob_diag::RobotElement*)new rob_diag::EndEffector();
        robot.elements_.push_back(ee);
        return true;
      }
      std::cerr << "Invalid (or not yet supported) arguments for " << words[0] << std::endl;
      return false;
    }
    else if (words[0] == "vector")
    {
      if (words.size() == 2)
      {
        double length = std::strtod (words[1].c_str(), NULL);
        rob_diag::RobotElement* vec = (rob_diag::RobotElement*)new rob_diag::Vector(length);
        robot.elements_.push_back(vec);
        return true;
      }
      else if (words.size() == 3)
      {
        double length = std::strtod (words[1].c_str(), NULL);
        rob_diag::RobotElement* vec = (rob_diag::RobotElement*)new rob_diag::Vector(length, words[2]);
        robot.elements_.push_back(vec);
        return true;
      }
      std::cerr << "Invalid arguments for " << words[0] << std::endl;
      return false;
    }
    else if (words[0] == "point")
    {
      if (words.size() == 1)
      {
        rob_diag::RobotElement* pt = (rob_diag::RobotElement*)new rob_diag::RobPoint();
        robot.elements_.push_back(pt);
        return true;
      }
      else if (words.size() == 2)
      {
        rob_diag::RobotElement* pt = (rob_diag::RobotElement*)new rob_diag::RobPoint(2, words[1]);
        robot.elements_.push_back(pt);
        return true;
      }
      std::cerr << "Invalid arguments for " << words[0] << std::endl;
      return false;
    }
    else
    {
      std::cerr << words[0] << " not recognized!" << std::endl;
      return false;
    }
  }
  catch (...)
  {
    return false;
  }

  return false;
}

void draw_robot(rob_diag::Robot& robot, std::string filename)
{
  // Compute dimensions
  rob_diag::Rect bounds = robot.compute_dimensions();
  double width = bounds.right_ - bounds.left_;
  double height = bounds.top_ - bounds.bottom_;
  double margin = 10;
  svg::Dimensions dimensions(width + margin * 2.0, height + margin * 2.0);

  // Draw to file:
  Document doc(filename, svg::Layout(dimensions, svg::Layout::BottomLeft));
  rob_diag::Pose origin(-bounds.left_ + margin, -bounds.bottom_ + margin, 0);
  robot.draw_at(doc, origin);

  // Save and quit
  doc.save();
}

void delete_robot(rob_diag::Robot& robot)
{
  // TODO: move to a destructor? Don't store pointers?
  for (int i = 0; i < robot.elements_.size(); ++i)
    delete robot.elements_[i];
  robot.elements_.clear();
  // TODO! NOTE: ensure this is called on any failure, even after a bad 'add element'
}

bool draw_robot(const char* filename)
{
  std::string file_string(filename);
  if (file_string.size() < 6 || file_string.substr(file_string.size() - 6, 6) != ".robot")
  {
    std::cerr << filename << " is not a valid .robot filename." << std::endl;
    return false;
  }
  std::string file_base = file_string.substr(0, file_string.size() - 6);
  std::string file_ext = file_string.substr(0, file_string.size() - 6);

  std::string line;
  std::ifstream robot_config (filename);
  if (robot_config.is_open())
  {
    rob_diag::Robot robot;
    while ( getline (robot_config,line) )
    {
      if (!add_element(robot, line))
      {
        std::cerr << "Bad configuration line for " << filename << ":" << std::endl << line << std::endl;
        robot_config.close();
        delete_robot(robot);
        return false;
      }
    }
    robot_config.close();
    draw_robot(robot, file_base + ".svg");
    delete_robot(robot);
    return true;
  }
  else
  {
    std::cout << "Unable to open " << filename << std::endl;
    return false;
  }

  std::cout << "Unknown error with " << filename << std::endl;
  return false;
}

int main(int argc, char** argv)
{
  if (argc < 2)
  {
    std::cout << "Usage: ./generate_robots <list of .robot files>" << std::endl;
    return -1;
  }

  int num_good = 0;
  std::cout << "Converting files..." << std::endl;
  for (int i = 1; i < argc; i++)
  {
    if (draw_robot(argv[i]))
    {
      ++num_good;
      std::cout << argv[i] << ": success" << std::endl;
    }
    // NOTE: error from failure will be displayed in the 'draw_robot' function.
  }

  std::cout << "Converted " << num_good << "/" << argc - 1 << " files." << std::endl;
  return 0;
}
<commit_msg>Removed unnecessary changes to generate robots.<commit_after>#include "robot_diagrams_0.0.hpp"

#include <iostream>
#include <fstream>
#include <iterator>
#include <string>
#include <vector>
#include <cstdlib>

std::vector<std::string> split(std::string s)
{
  std::stringstream ss(s);
  std::istream_iterator<std::string> begin(ss);
  std::istream_iterator<std::string> end;
  std::vector<std::string> vstrings(begin, end);
  return vstrings;
}

// TODO: delete copy constructor of robot!
bool add_element(rob_diag::Robot& robot, std::string line)
{
  // TODO: do float parsing better...std::stof is only C++11...std::strtod sucks for error conditions.
  try
  {
    std::vector<std::string> words = split(line);
    if (words.size() == 0)
    {
      std::cerr << "No arguments on line!" << std::endl;
      return false;
    }
    if (words[0] == "base" || words[0] == "invisible_base")
    {
      if (words.size() == 1)
      {
        rob_diag::RobotElement* base = (rob_diag::RobotElement*)new rob_diag::Base();
        if (words[0] == "invisible_base")
          ((rob_diag::Base*)base)->visible_ = false;
        robot.elements_.push_back(base);
        return true;
      }
      else if (words.size() == 2)
      {
        double width = std::strtod (words[1].c_str(), NULL);
        rob_diag::RobotElement* base = (rob_diag::RobotElement*)new rob_diag::Base(width);
        if (words[0] == "invisible_base")
          ((rob_diag::Base*)base)->visible_ = false;
        robot.elements_.push_back(base);
        return true;
      }
      std::cerr << "Invalid arguments for " << words[0] << std::endl;
      return false;
    }
    else if (words[0] == "link")
    {
      if (words.size() == 2)
      {
        double length = std::strtod (words[1].c_str(), NULL);
        rob_diag::RobotElement* link = (rob_diag::RobotElement*)new rob_diag::Link(length);
        robot.elements_.push_back(link);
        return true;
      }
      else if (words.size() == 3)
      {
        double length = std::strtod (words[1].c_str(), NULL);
        rob_diag::RobotElement* link = (rob_diag::RobotElement*)new rob_diag::Link(length, words[2]);
        robot.elements_.push_back(link);
        return true;
      }
      std::cerr << "Invalid arguments for " << words[0] << std::endl;
      return false;
    }
    else if (words[0] == "invisible_link")
    {
      if (words.size() == 2)
      {
        double length = std::strtod (words[1].c_str(), NULL);
        rob_diag::Link* tmp = new rob_diag::Link(length);
        tmp->visible_ = false;
        rob_diag::RobotElement* link = (rob_diag::RobotElement*)tmp;
        robot.elements_.push_back(link);
        return true;
      }
      std::cerr << "Invalid arguments for " << words[0] << std::endl;
      return false;
    }
    else if (words[0] == "frames")
    {
      if (words.size() == 1)
      {
        rob_diag::RobotElement* link = (rob_diag::RobotElement*)new rob_diag::Frames();
        robot.elements_.push_back(link);
        return true;
      }
      std::cerr << "Invalid (or not yet supported) arguments for " << words[0] << std::endl;
      return false;
    }
    else if (words[0] == "rjoint" || words[0] == "invisible_rjoint")
    {
      if (words.size() == 2)
      {
        double theta = std::strtod (words[1].c_str(), NULL);
        rob_diag::RobotElement* rjoint = (rob_diag::RobotElement*)new rob_diag::RJoint(theta);
        if (words[0] == "invisible_rjoint")
          ((rob_diag::RJoint*)rjoint)->visible_ = false;
        robot.elements_.push_back(rjoint);
        return true;
      }
      std::cerr << "Invalid arguments for " << words[0] << std::endl;
      return false;
    }
    else if (words[0] == "pjoint")
    {
      if (words.size() == 1)
      {
        rob_diag::RobotElement* pjoint = (rob_diag::RobotElement*)new rob_diag::PJoint();
        robot.elements_.push_back(pjoint);
        return true;
      }
      std::cerr << "Invalid arguments for " << words[0] << std::endl;
      return false;
    }
    else if (words[0] == "effector")
    {
      if (words.size() == 1)
      {
        rob_diag::RobotElement* ee = (rob_diag::RobotElement*)new rob_diag::EndEffector();
        robot.elements_.push_back(ee);
        return true;
      }
      std::cerr << "Invalid (or not yet supported) arguments for " << words[0] << std::endl;
      return false;
    }
    else if (words[0] == "vector")
    {
      if (words.size() == 2)
      {
        double length = std::strtod (words[1].c_str(), NULL);
        rob_diag::RobotElement* vec = (rob_diag::RobotElement*)new rob_diag::Vector(length);
        robot.elements_.push_back(vec);
        return true;
      }
      else if (words.size() == 3)
      {
        double length = std::strtod (words[1].c_str(), NULL);
        rob_diag::RobotElement* vec = (rob_diag::RobotElement*)new rob_diag::Vector(length, words[2]);
        robot.elements_.push_back(vec);
        return true;
      }
      std::cerr << "Invalid arguments for " << words[0] << std::endl;
      return false;
    }
    else if (words[0] == "point")
    {
      if (words.size() == 1)
      {
        rob_diag::RobotElement* pt = (rob_diag::RobotElement*)new rob_diag::RobPoint();
        robot.elements_.push_back(pt);
        return true;
      }
      else if (words.size() == 2)
      {
        rob_diag::RobotElement* pt = (rob_diag::RobotElement*)new rob_diag::RobPoint(2, words[1]);
        robot.elements_.push_back(pt);
        return true;
      }
      std::cerr << "Invalid arguments for " << words[0] << std::endl;
      return false;
    }
    else
    {
      std::cerr << words[0] << " not recognized!" << std::endl;
      return false;
    }
  }
  catch (...)
  {
    return false;
  }

  return false;
}

void draw_robot(rob_diag::Robot& robot, std::string filename)
{
  // Compute dimensions
  rob_diag::Rect bounds = robot.compute_dimensions();
  double width = bounds.right_ - bounds.left_;
  double height = bounds.top_ - bounds.bottom_;
  double margin = 10;
  svg::Dimensions dimensions(width + margin * 2.0, height + margin * 2.0);

  // Draw to file:
  Document doc(filename, svg::Layout(dimensions, svg::Layout::BottomLeft));
  rob_diag::Pose origin(-bounds.left_ + margin, -bounds.bottom_ + margin, 0);
  robot.draw_at(doc, origin);

  // Save and quit
  doc.save();
}

void delete_robot(rob_diag::Robot& robot)
{
  // TODO: move to a destructor? Don't store pointers?
  for (int i = 0; i < robot.elements_.size(); ++i)
    delete robot.elements_[i];
  robot.elements_.clear();
  // TODO! NOTE: ensure this is called on any failure, even after a bad 'add element'
}

bool draw_robot(const char* filename)
{
  std::string file_string(filename);
  if (file_string.size() < 6 || file_string.substr(file_string.size() - 6, 6) != ".robot")
  {
    std::cerr << filename << " is not a valid .robot filename." << std::endl;
    return false;
  }
  std::string file_base = file_string.substr(0, file_string.size() - 6);
  std::string file_ext = file_string.substr(0, file_string.size() - 6);

  std::string line;
  std::ifstream robot_config (filename);
  if (robot_config.is_open())
  {
    rob_diag::Robot robot;
    while ( getline (robot_config,line) )
    {
      if (!add_element(robot, line))
      {
        std::cerr << "Bad configuration line for " << filename << ":" << std::endl << line << std::endl;
        robot_config.close();
        delete_robot(robot);
        return false;
      }
    }
    robot_config.close();
    draw_robot(robot, file_base + ".svg");
    delete_robot(robot);
    return true;
  }
  else
  {
    std::cout << "Unable to open " << filename << std::endl;
    return false;
  }

  std::cout << "Unknown error with " << filename << std::endl;
  return false;
}

int main(int argc, char** argv)
{
  if (argc < 2)
  {
    std::cout << "Usage: ./generate_robots <list of .robot files>" << std::endl;
    return -1;
  }

  int num_good = 0;
  std::cout << "Converting files..." << std::endl;
  for (int i = 1; i < argc; i++)
  {
    if (draw_robot(argv[i]))
    {
      ++num_good;
      std::cout << argv[i] << ": success" << std::endl;
    }
    // NOTE: error from failure will be displayed in the 'draw_robot' function.
  }

  std::cout << "Converted " << num_good << "/" << argc - 1 << " files." << std::endl;
  return 0;
}
<|endoftext|>


<commit_before>#include "mocca/base/CommandLineParser.h"

#include "mocca/base/Containers.h"
#include "mocca/base/Error.h"
#include "mocca/base/StringTools.h"

#include <iostream>
#include <sstream>

using namespace mocca;

CommandLineParser::CommandLineParser() {
    Option usage;
    usage.key = "--usage";
    usage.help = "prints usage";
    usage.callback = std::bind(&CommandLineParser::printUsage, this, std::placeholders::_1);
    options_.push_back(usage);
}

void CommandLineParser::addOption(const Option& option) {
    options_.push_back(option);
}

void mocca::CommandLineParser::printUsage(const std::string&) const {
    std::stringstream stream;
    for (const auto& opt : options_) {
        stream << opt.key << ": " << opt.help << std::endl;
        if (!opt.allowedValues.empty()) {
            for (const auto& val : opt.allowedValues) {
                stream << "\t" << val.value << ": " << val.help << std::endl;
            }
        }
    }
    std::cout << stream.str();
    std::exit(0);
}

void CommandLineParser::parse(int argc, const char** argv) {
    for (int i = 1; i < argc; ++i) { // skip first arg because it contains the program name
        auto arg = std::string(argv[i]);
        auto separatorIndex = arg.find_first_of("=");
        auto argKey = arg.substr(0, separatorIndex);
        if (findMemberEqual(options_, &Option::key, argKey) == end(options_)) {
            throw Error(formatString("Unknown option '%%'", argKey), __FILE__, __LINE__);
        }
        std::string argValue;
        if (separatorIndex != std::string::npos) {
            argValue = arg.substr(separatorIndex + 1, std::string::npos);
        }
        for (const auto& opt : options_) {
            if (opt.key == argKey) {
                if (!opt.allowedValues.empty()) {
                    if (findMemberEqual(opt.allowedValues, &OptionValue::value, argValue) == end(opt.allowedValues)) {
                        auto allowedValues = collectMembers(begin(opt.allowedValues), end(opt.allowedValues), &OptionValue::value);
                        auto allowedValuesStr = makeString(begin(allowedValues), end(allowedValues));
                        throw Error(
                            formatString("Invalid value '%%' for option '%%'. Allowed values are: %%", argValue, opt.key, allowedValuesStr),
                            __FILE__, __LINE__);
                    }
                }
                opt.callback(argValue);
                break;
            }
        }
    }
}<commit_msg>nicer format for usage text in CommandLineParser<commit_after>#include "mocca/base/CommandLineParser.h"

#include "mocca/base/Containers.h"
#include "mocca/base/Error.h"
#include "mocca/base/StringTools.h"

#include <iostream>
#include <sstream>

using namespace mocca;

CommandLineParser::CommandLineParser() {
    Option usage;
    usage.key = "--usage";
    usage.help = "prints usage";
    usage.callback = std::bind(&CommandLineParser::printUsage, this, std::placeholders::_1);
    options_.push_back(usage);
}

void CommandLineParser::addOption(const Option& option) {
    options_.push_back(option);
}

void mocca::CommandLineParser::printUsage(const std::string&) const {
    std::stringstream stream;
    for (const auto& opt : options_) {
        stream << opt.key << ": " << opt.help << std::endl;
        if (!opt.allowedValues.empty()) {
            for (const auto& val : opt.allowedValues) {
                stream << "\t" << val.value << ": " << val.help << std::endl;
            }
        }
        stream << std::endl;
    }
    std::cout << stream.str();
    std::exit(0);
}

void CommandLineParser::parse(int argc, const char** argv) {
    for (int i = 1; i < argc; ++i) { // skip first arg because it contains the program name
        auto arg = std::string(argv[i]);
        auto separatorIndex = arg.find_first_of("=");
        auto argKey = arg.substr(0, separatorIndex);
        if (findMemberEqual(options_, &Option::key, argKey) == end(options_)) {
            throw Error(formatString("Unknown option '%%'", argKey), __FILE__, __LINE__);
        }
        std::string argValue;
        if (separatorIndex != std::string::npos) {
            argValue = arg.substr(separatorIndex + 1, std::string::npos);
        }
        for (const auto& opt : options_) {
            if (opt.key == argKey) {
                if (!opt.allowedValues.empty()) {
                    if (findMemberEqual(opt.allowedValues, &OptionValue::value, argValue) == end(opt.allowedValues)) {
                        auto allowedValues = collectMembers(begin(opt.allowedValues), end(opt.allowedValues), &OptionValue::value);
                        auto allowedValuesStr = makeString(begin(allowedValues), end(allowedValues));
                        throw Error(
                            formatString("Invalid value '%%' for option '%%'. Allowed values are: %%", argValue, opt.key, allowedValuesStr),
                            __FILE__, __LINE__);
                    }
                }
                opt.callback(argValue);
                break;
            }
        }
    }
}<|endoftext|>


<commit_before>// This file is a part of the OpenSurgSim project.
// Copyright 2012-2013, SimQuest Solutions Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/// \file
/// Render Tests for the OsgCapsuleRepresentation class.

#include <SurgSim/Graphics/OsgManager.h>
#include <SurgSim/Graphics/OsgCapsuleRepresentation.h>
#include <SurgSim/Graphics/OsgViewElement.h>
#include <SurgSim/Framework/Scene.h>
#include <SurgSim/Framework/SceneElement.h>
#include <SurgSim/Framework/Runtime.h>
#include <SurgSim/Math/Vector.h>
#include <SurgSim/Testing/MathUtilities.h>

#include <gtest/gtest.h>

using SurgSim::Framework::Runtime;
using SurgSim::Framework::Scene;
using SurgSim::Framework::SceneElement;
using SurgSim::Math::RigidTransform3d;
using SurgSim::Math::Vector2d;
using SurgSim::Math::Vector3d;
using SurgSim::Math::makeRigidTransform;
using SurgSim::Math::makeRotationQuaternion;
using SurgSim::Testing::interpolate;

namespace SurgSim
{

namespace Graphics
{

TEST(OsgCapsuleRepresentationRenderTests, MovingCapsuleTest)
{
    /// Initial positoin of capsule 1
    Vector3d startPosition1(-0.1, 0.0, -0.2);
    /// Final position of capsule 1
    Vector3d endPosition1(0.1, 0.0, -0.2);
	/// Initial size (radius, hegiht) of capsule 1
	Vector2d startSize1(0.001, 0.011);
	/// Final size (radius, hegiht) of capsule 1
	Vector2d endSize1(0.01, 0.02);
	/// Initial angles (X, Y, Z) of the capsule 1
	Vector3d startAngles1(0.0, 0.0, 0.0);
	/// Final angles (X, Y, Z) of the capsule 1
	Vector3d endAngles1(-M_PI / 4.0, -M_PI / 4.0, -M_PI / 4.0);

    /// Initial position capsule 2
    Vector3d startPosition2(0.0, -0.1, -0.2);
    /// Final position capsule 2
    Vector3d endPosition2(0.0, 0.1, -0.2);
	/// Initial size (radius, hegiht) of capsule 2
	Vector2d startSize2(0.001, 0.01);
	/// Final size (radius, hegiht) of capsule 2
	Vector2d endSize2(0.011, 0.02);
	/// Initial angles (X, Y, Z) of the capsule 2
	Vector3d startAngles2(-M_PI / 2.0, -M_PI / 2.0, -M_PI / 2.0);
	/// Final angles (X, Y, Z) of the capsule 2
	Vector3d endAngles2(M_PI, M_PI, M_PI);

    /// Number of times to step the capsule position and radius from start to end.
    /// This number of steps will be done in 1 second.
    int numSteps = 100;

    std::shared_ptr<Runtime> runtime = std::make_shared<Runtime>();

	std::shared_ptr<OsgManager> manager = std::make_shared<OsgManager>();
    runtime->addManager(manager);

    std::shared_ptr<Scene> scene = std::make_shared<Scene>();
    runtime->setScene(scene);

    /// Add a graphics view element to the scene
    std::shared_ptr<OsgViewElement> viewElement = std::make_shared<OsgViewElement>("view element");
    scene->addSceneElement(viewElement);

    /// Add the two capsule representation to the view element so we don't need to make another concrete scene element
    std::shared_ptr<CapsuleRepresentation> capsuleRepresentation1 =
        std::make_shared<OsgCapsuleRepresentation>("capsule representation 1");
    viewElement->addComponent(capsuleRepresentation1);
    std::shared_ptr<CapsuleRepresentation> capsuleRepresentation2 =
        std::make_shared<OsgCapsuleRepresentation>("capsule representation 2");
    viewElement->addComponent(capsuleRepresentation2);

    /// Run the thread
    runtime->start();
    EXPECT_TRUE(manager->isInitialized());
    boost::this_thread::sleep(boost::posix_time::milliseconds(1000));

    enum SetterType {SetterTypeIndividual,
                        SetterTypeTogether,
                        SetterTypeVector2d,
                        // Add more setter types above this line.
                        BoxSetterTypeCount};
    int setterType = 0;
    Vector2d capsule1Size, capsule2Size;

    for (int i = 0; i < numSteps; ++i)
    {
        /// Calculate t in [0.0, 1.0]
        double t = static_cast<double>(i) / numSteps;
        /// Interpolate position and angle
        capsuleRepresentation1->setPose(makeRigidTransform(
            makeRotationQuaternion(interpolate(startAngles1.x(), endAngles1.x(), t), Vector3d(1.0, 0.0, 0.0)) *
			makeRotationQuaternion(interpolate(startAngles1.y(), endAngles1.y(), t), Vector3d(0.0, 1.0, 0.0)) *
			makeRotationQuaternion(interpolate(startAngles1.z(), endAngles1.z(), t), Vector3d(0.0, 0.0, 1.0)),
			interpolate(startPosition1, endPosition1, t)));
        capsuleRepresentation2->setPose(makeRigidTransform(
            makeRotationQuaternion(interpolate(startAngles2.x(), endAngles2.x(), t), Vector3d(1.0, 0.0, 0.0)) *
			makeRotationQuaternion(interpolate(startAngles2.y(), endAngles2.y(), t), Vector3d(0.0, 1.0, 0.0)) *
			makeRotationQuaternion(interpolate(startAngles2.z(), endAngles2.z(), t), Vector3d(0.0, 0.0, 1.0)),
			interpolate(startPosition2, endPosition2, t)));

		/// Test different ways to set the size of capsule
        if(setterType == static_cast<int>(SetterTypeIndividual))
        {
            capsuleRepresentation1->setRadius(interpolate(startSize1.x(), endSize1.x(), t));
            capsuleRepresentation1->setHeight(interpolate(startSize1.y(), endSize1.y(), t));

            capsuleRepresentation2->setRadius(interpolate(startSize2.x(), endSize2.x(), t));
            capsuleRepresentation2->setHeight(interpolate(startSize2.y(), endSize2.y(), t));
        }
        else if(setterType == static_cast<int>(SetterTypeTogether))
        {
            capsuleRepresentation1->setSize(interpolate(startSize1.x(), endSize1.x(), t),
											interpolate(startSize1.y(), endSize1.y(), t));

            capsuleRepresentation2->setSize(interpolate(startSize2.x(), endSize2.x(), t),
											interpolate(startSize2.y(), endSize2.y(), t));
        }
        else if(setterType == static_cast<int>(SetterTypeVector2d))
        {
            capsule1Size.x() = interpolate(startSize1.x(), endSize1.x(), t);
            capsule1Size.y() = interpolate(startSize1.y(), endSize1.y(), t);
            capsuleRepresentation1->setSize(capsule1Size);

            capsule2Size.x() = interpolate(startSize2.x(), endSize2.x(), t);
            capsule2Size.y() = interpolate(startSize2.y(), endSize2.y(), t);
            capsuleRepresentation2->setSize(capsule2Size);
        }
        setterType = (setterType + 1) % BoxSetterTypeCount;
        /// The total number of steps should complete in 1 second
        boost::this_thread::sleep(boost::posix_time::milliseconds(1000 / numSteps));
    }

    runtime->stop();
}

};  // namespace Graphics

};  // namespace SurgSim<commit_msg>Added a test fixture to OsgCapsuleRepresentationRenderTests.<commit_after>// This file is a part of the OpenSurgSim project.
// Copyright 2012-2013, SimQuest Solutions Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/// \file
/// Render Tests for the OsgCapsuleRepresentation class.

#include <SurgSim/Graphics/OsgManager.h>
#include <SurgSim/Graphics/OsgCapsuleRepresentation.h>
#include <SurgSim/Graphics/OsgViewElement.h>
#include <SurgSim/Framework/Scene.h>
#include <SurgSim/Framework/SceneElement.h>
#include <SurgSim/Framework/Runtime.h>
#include <SurgSim/Math/Vector.h>
#include <SurgSim/Testing/MathUtilities.h>

#include <gtest/gtest.h>

using SurgSim::Framework::Runtime;
using SurgSim::Framework::Scene;
using SurgSim::Framework::SceneElement;
using SurgSim::Math::RigidTransform3d;
using SurgSim::Math::Vector2d;
using SurgSim::Math::Vector3d;
using SurgSim::Math::makeRigidTransform;
using SurgSim::Math::makeRotationQuaternion;
using SurgSim::Testing::interpolate;

namespace SurgSim
{

namespace Graphics
{

struct OsgCapsuleRepresentationRenderTests : public ::testing::Test
{
	virtual void SetUp()
	{
		runtime = std::make_shared<SurgSim::Framework::Runtime>();
		manager = std::make_shared<SurgSim::Graphics::OsgManager>();

		runtime->addManager(manager);

		scene = std::make_shared<SurgSim::Framework::Scene>();
		runtime->setScene(scene);

		viewElement = std::make_shared<OsgViewElement>("view element");
		scene->addSceneElement(viewElement);

	}

	virtual void TearDown()
	{
		runtime->stop();
	}

	std::shared_ptr<SurgSim::Framework::Runtime> runtime;
	std::shared_ptr<SurgSim::Graphics::OsgManager> manager;
	std::shared_ptr<SurgSim::Framework::Scene> scene;
	std::shared_ptr<OsgViewElement> viewElement;

protected:

};

TEST_F(OsgCapsuleRepresentationRenderTests, MovingCapsuleTest)
{
	/// Add the two capsule representation to the view element
    std::shared_ptr<CapsuleRepresentation> capsuleRepresentation1 =
        std::make_shared<OsgCapsuleRepresentation>("capsule representation 1");
    viewElement->addComponent(capsuleRepresentation1);
    std::shared_ptr<CapsuleRepresentation> capsuleRepresentation2 =
        std::make_shared<OsgCapsuleRepresentation>("capsule representation 2");
    viewElement->addComponent(capsuleRepresentation2);

    /// Run the thread
    runtime->start();
    EXPECT_TRUE(manager->isInitialized());
    boost::this_thread::sleep(boost::posix_time::milliseconds(1000));

    enum SetterType {SetterTypeIndividual,
                        SetterTypeTogether,
                        SetterTypeVector2d,
                        // Add more setter types above this line.
                        BoxSetterTypeCount};
    int setterType = 0;

    Vector2d capsule1Size, capsule2Size;

	/// Initial positoin of capsule 1
    Vector3d startPosition1(-0.1, 0.0, -0.2);
    /// Final position of capsule 1
    Vector3d endPosition1(0.1, 0.0, -0.2);
	/// Initial size (radius, hegiht) of capsule 1
	Vector2d startSize1(0.001, 0.011);
	/// Final size (radius, hegiht) of capsule 1
	Vector2d endSize1(0.01, 0.02);
	/// Initial angles (X, Y, Z) of the capsule 1
	Vector3d startAngles1(0.0, 0.0, 0.0);
	/// Final angles (X, Y, Z) of the capsule 1
	Vector3d endAngles1(-M_PI_4, -M_PI_4, -M_PI_4);

    /// Initial position capsule 2
    Vector3d startPosition2(0.0, -0.1, -0.2);
    /// Final position capsule 2
    Vector3d endPosition2(0.0, 0.1, -0.2);
	/// Initial size (radius, hegiht) of capsule 2
	Vector2d startSize2(0.001, 0.01);
	/// Final size (radius, hegiht) of capsule 2
	Vector2d endSize2(0.011, 0.02);
	/// Initial angles (X, Y, Z) of the capsule 2
	Vector3d startAngles2(-M_PI_2, -M_PI_2, -M_PI_2);
	/// Final angles (X, Y, Z) of the capsule 2
	Vector3d endAngles2(M_PI, M_PI, M_PI);

	/// Number of times to step the capsule position and radius from start to end.
    /// This number of steps will be done in 1 second.
    int numSteps = 100;
    for (int i = 0; i < numSteps; ++i)
    {
        /// Calculate t in [0.0, 1.0]
        double t = static_cast<double>(i) / numSteps;
        /// Interpolate position and angle
        capsuleRepresentation1->setPose(makeRigidTransform(
            makeRotationQuaternion(interpolate(startAngles1.x(), endAngles1.x(), t), Vector3d(1.0, 0.0, 0.0)) *
			makeRotationQuaternion(interpolate(startAngles1.y(), endAngles1.y(), t), Vector3d(0.0, 1.0, 0.0)) *
			makeRotationQuaternion(interpolate(startAngles1.z(), endAngles1.z(), t), Vector3d(0.0, 0.0, 1.0)),
			interpolate(startPosition1, endPosition1, t)));
        capsuleRepresentation2->setPose(makeRigidTransform(
            makeRotationQuaternion(interpolate(startAngles2.x(), endAngles2.x(), t), Vector3d(1.0, 0.0, 0.0)) *
			makeRotationQuaternion(interpolate(startAngles2.y(), endAngles2.y(), t), Vector3d(0.0, 1.0, 0.0)) *
			makeRotationQuaternion(interpolate(startAngles2.z(), endAngles2.z(), t), Vector3d(0.0, 0.0, 1.0)),
			interpolate(startPosition2, endPosition2, t)));

		/// Test different ways to set the size of capsule
        if(setterType == static_cast<int>(SetterTypeIndividual))
        {
            capsuleRepresentation1->setRadius(interpolate(startSize1.x(), endSize1.x(), t));
            capsuleRepresentation1->setHeight(interpolate(startSize1.y(), endSize1.y(), t));

            capsuleRepresentation2->setRadius(interpolate(startSize2.x(), endSize2.x(), t));
            capsuleRepresentation2->setHeight(interpolate(startSize2.y(), endSize2.y(), t));
        }
        else if(setterType == static_cast<int>(SetterTypeTogether))
        {
            capsuleRepresentation1->setSize(interpolate(startSize1.x(), endSize1.x(), t),
											interpolate(startSize1.y(), endSize1.y(), t));

            capsuleRepresentation2->setSize(interpolate(startSize2.x(), endSize2.x(), t),
											interpolate(startSize2.y(), endSize2.y(), t));
        }
        else if(setterType == static_cast<int>(SetterTypeVector2d))
        {
            capsule1Size.x() = interpolate(startSize1.x(), endSize1.x(), t);
            capsule1Size.y() = interpolate(startSize1.y(), endSize1.y(), t);
            capsuleRepresentation1->setSize(capsule1Size);

            capsule2Size.x() = interpolate(startSize2.x(), endSize2.x(), t);
            capsule2Size.y() = interpolate(startSize2.y(), endSize2.y(), t);
            capsuleRepresentation2->setSize(capsule2Size);
        }
        setterType = (setterType + 1) % BoxSetterTypeCount;
        /// The total number of steps should complete in 1 second
        boost::this_thread::sleep(boost::posix_time::milliseconds(1000 / numSteps));
    }

}

};  // namespace Graphics

};  // namespace SurgSim<|endoftext|>


<commit_before>/******************************************************************************\
* File:          header.cpp
* Purpose:       Implementation of wxExHeader class
* Author:        Anton van Wezenbeek
* RCS-ID:        $Id$
*
* Copyright (c) 2009 Anton van Wezenbeek
* All rights are reserved. Reproduction in whole or part is prohibited
* without the written consent of the copyright owner.
\******************************************************************************/

#include <wx/extension/header.h>
#include <wx/extension/configdialog.h>
#include <wx/extension/file.h>
#include <wx/extension/util.h>

wxExHeader::wxExHeader(wxExConfig* config)
  : m_Config(config)
{
}

const wxString wxExHeader::Get(const wxExFileName* filename) const
{
  const wxString h_name      = "Name:      ";
  const wxString h_purpose   = "Purpose:   ";
  const wxString h_author    = "Author:    ";
  const wxString h_created   = "Created:   ";
  const wxString h_rcs       = "RCS-ID:    $";
  const wxString h_copyright = "Copyright: ";
  const wxString h_license   = "License:   ";

  const wxString author = m_Config->Get(_("Author"));
  const wxString company = m_Config->Get(_("Company"));
  const wxString license = m_Config->Get(_("License"));
  const wxString email = m_Config->Get(_("Email"));
  const wxString purpose = m_Config->Get(_("Purpose"));
  const wxString email_field = (!email.empty() ? " < " + email + ">": email);

  wxString header;
  const wxExLexer l = filename->GetLexer();

  if (!l.GetCommentEnd().empty())
  {
    header << l.GetCommentBegin() << "\n";
    header << h_name << filename->GetFullName() << "\n";
    header << wxExAlignText(purpose, h_purpose) << "\n";
    header << h_author << author << "\n";
    header << h_created << wxDateTime::Now().FormatISODate() << "\n";
    if (m_Config->GetBool("SVN"))
    // Prevent the Id to be expanded by SVN itself here.
    header << h_rcs << wxString("Id$") << "\n";
    header << h_copyright << "(c) " << wxDateTime::Now().Format("%Y") << " " <<
      (!company.empty() ? company: author) << email_field << "\n";
    if (!license.empty())
    header << h_license << license << "\n";
    header << l.GetCommentEnd() << "\n";
  }
  else
  {
    header << l.MakeComment(wxEmptyString, false) << "\n";
    header << l.MakeComment(h_name, filename->GetFullName()) << "\n";
    header << l.MakeComment(h_purpose, purpose) << "\n";
    header << l.MakeComment(h_author, author) << "\n";
    header << l.MakeComment(h_created, wxDateTime::Now().FormatISODate()) << "\n";
    if (m_Config->GetBool("SVN"))
    // Prevent the Id to be expanded by SVN itself here.
    header << l.MakeComment(h_rcs, wxString("Id$")) << "\n";
    header << l.MakeComment(h_copyright, "(c) " + wxDateTime::Now().Format("%Y") + " " +
      (!company.empty() ? company: author) + email_field) << "\n";
    if (!license.empty())
    header << l.MakeComment(h_license, license) << "\n";
    header << l.MakeComment(wxEmptyString, false) << "\n";
  }

  header << "\n";

  if (filename->GetExt() == "h" &&
      filename->GetStat().st_size == 0)
  {
    wxString argument = "__" + filename->GetName() + "_h";

    header << "\n";
    header << "#if !defined (" << argument << ")" << "\n";
    header << "#define " << argument << "\n";
    header << "#endif" << "\n";
  }

  return header;
}

int wxExHeader::ShowDialog(wxWindow* parent, const wxString& title) const
{
  std::vector<wxExConfigItem> v;

  // Purpose is required.
  v.push_back(wxExConfigItem(_("Purpose"), wxEmptyString, wxTE_MULTILINE, true));

  // Author is required, but only presented if empty.
  // Email and License also are only presented if Author empty.
  if (m_Config->Get(_("Author")).empty())
  {
    v.push_back(wxExConfigItem(_("Author"), wxEmptyString, 0, true));

    if (m_Config->Get(_("Email")).empty())
    {
      v.push_back(wxExConfigItem(_("Email")));
    }

    if (m_Config->Get(_("License")).empty())
    {
      v.push_back(wxExConfigItem(_("License")));
    }
  }

  return wxExConfigDialog(
    parent,
    m_Config, v, title).ShowModal();
}
<commit_msg>removed company field<commit_after>/******************************************************************************\
* File:          header.cpp
* Purpose:       Implementation of wxExHeader class
* Author:        Anton van Wezenbeek
* RCS-ID:        $Id$
*
* Copyright (c) 2009 Anton van Wezenbeek
* All rights are reserved. Reproduction in whole or part is prohibited
* without the written consent of the copyright owner.
\******************************************************************************/

#include <wx/extension/header.h>
#include <wx/extension/configdialog.h>
#include <wx/extension/file.h>
#include <wx/extension/util.h>

wxExHeader::wxExHeader(wxExConfig* config)
  : m_Config(config)
{
}

const wxString wxExHeader::Get(const wxExFileName* filename) const
{
  const wxString h_name      = "Name:      ";
  const wxString h_purpose   = "Purpose:   ";
  const wxString h_author    = "Author:    ";
  const wxString h_created   = "Created:   ";
  const wxString h_rcs       = "RCS-ID:    $";
  const wxString h_copyright = "Copyright: ";
  const wxString h_license   = "License:   ";

  const wxString author = m_Config->Get(_("Author"));
  const wxString license = m_Config->Get(_("License"));
  const wxString email = m_Config->Get(_("Email"));
  const wxString purpose = m_Config->Get(_("Purpose"));
  const wxString email_field = (!email.empty() ? " < " + email + ">": email);

  wxString header;
  const wxExLexer l = filename->GetLexer();

  if (!l.GetCommentEnd().empty())
  {
    header << l.GetCommentBegin() << "\n";
    header << h_name << filename->GetFullName() << "\n";
    header << wxExAlignText(purpose, h_purpose) << "\n";
    header << h_author << author << "\n";
    header << h_created << wxDateTime::Now().FormatISODate() << "\n";
    if (m_Config->GetBool("SVN"))
    // Prevent the Id to be expanded by SVN itself here.
    header << h_rcs << wxString("Id$") << "\n";
    header << h_copyright << "(c) " << wxDateTime::Now().Format("%Y") << " " <<
      author << email_field << "\n";
    if (!license.empty())
    header << h_license << license << "\n";
    header << l.GetCommentEnd() << "\n";
  }
  else
  {
    header << l.MakeComment(wxEmptyString, false) << "\n";
    header << l.MakeComment(h_name, filename->GetFullName()) << "\n";
    header << l.MakeComment(h_purpose, purpose) << "\n";
    header << l.MakeComment(h_author, author) << "\n";
    header << l.MakeComment(h_created, wxDateTime::Now().FormatISODate()) << "\n";
    if (m_Config->GetBool("SVN"))
    // Prevent the Id to be expanded by SVN itself here.
    header << l.MakeComment(h_rcs, wxString("Id$")) << "\n";
    header << l.MakeComment(h_copyright, "(c) " + wxDateTime::Now().Format("%Y") + " " +
      author + email_field) << "\n";
    if (!license.empty())
    header << l.MakeComment(h_license, license) << "\n";
    header << l.MakeComment(wxEmptyString, false) << "\n";
  }

  header << "\n";

  if (filename->GetExt() == "h" &&
      filename->GetStat().st_size == 0)
  {
    wxString argument = "__" + filename->GetName() + "_h";

    header << "\n";
    header << "#if !defined (" << argument << ")" << "\n";
    header << "#define " << argument << "\n";
    header << "#endif" << "\n";
  }

  return header;
}

int wxExHeader::ShowDialog(wxWindow* parent, const wxString& title) const
{
  std::vector<wxExConfigItem> v;

  // Purpose is required.
  v.push_back(wxExConfigItem(_("Purpose"), wxEmptyString, wxTE_MULTILINE, true));

  // Author is required, but only presented if empty.
  // Email and License also are only presented if Author empty.
  if (m_Config->Get(_("Author")).empty())
  {
    v.push_back(wxExConfigItem(_("Author"), wxEmptyString, 0, true));

    if (m_Config->Get(_("Email")).empty())
    {
      v.push_back(wxExConfigItem(_("Email")));
    }

    if (m_Config->Get(_("License")).empty())
    {
      v.push_back(wxExConfigItem(_("License")));
    }
  }

  return wxExConfigDialog(
    parent,
    m_Config, v, title).ShowModal();
}
<|endoftext|>