<commit_before>/** \brief A tool to find and eliminate unused C++ includes.
 *  \author Dr. Johannes Ruscheinski (johannes.ruscheinski@uni-tuebingen.de)
 *
 *  \copyright 2017 Universitätsbibliothek Tübingen.  All rights reserved.
 *
 *  This program 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.
 *
 *  This program 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 Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <iostream>
#include <cstdlib>
#include <cstring>
#include "Compiler.h"
#include "FileUtil.h"
#include "StringUtil.h"
#include "util.h"

static void Usage() __attribute__((noreturn));

void Usage() {
    std::cerr << "Usage: " << ::progname << " [--report-only] file1 [file2 .. fileN]\n";
    std::exit(EXIT_FAILURE);
}

enum TokenType {
    STRING_CONSTANT, IDENT, OTHER_CHAR, CHARACTER_CONSTANT, END_OF_INPUT
};

class Scanner {
    File &input_;
    int pushed_back_chars_[2];
    unsigned pushed_back_char_count_;
    std::string last_ident_;
    char last_other_char_;
    std::string last_string_constant_;
    unsigned line_no_;
public:
    explicit Scanner(File * const input): input_(*input), pushed_back_char_count_(0), line_no_(1) { }
    TokenType getToken();
    inline const std::string &getLastIdent() const { return last_ident_; }
    inline char getLastOtherChar() const { return last_other_char_; }
    inline const std::string &getLastStringConstant() const { return last_string_constant_; }
    inline unsigned getCurrentLineNo() const { return line_no_; }
    inline void rewind() { input_.rewind(); line_no_ = 1; pushed_back_char_count_ = 0; }
private:
    void error(const std::string &msg) const;
    int get();
    void unget(int ch);
    void skipWhiteSpaceAndComments();
    void skipCharacterConstant();
    void extractStringConstant();
    void extractIdent();
};

TokenType Scanner::getToken() {
    skipWhiteSpaceAndComments();

    int ch(get());
    if (unlikely(ch == EOF))
        return END_OF_INPUT;

    if (ch == '\'') {
        skipCharacterConstant();
        return CHARACTER_CONSTANT;
    } else if (ch == '"') {
        extractStringConstant();
        return STRING_CONSTANT;
    } else if (ch == '_' or StringUtil::IsAsciiLetter(ch)) {
        unget(ch);
        extractIdent();
        return IDENT;
    }

    last_other_char_ = static_cast<char>(ch);
    return OTHER_CHAR;
}

void Scanner::error(const std::string &msg) const {
    errno = 0;
    logger->error("on line #" + std::to_string(line_no_) + ": " + msg);
}

int Scanner::get() {
    if (pushed_back_char_count_ > 0) {
        --pushed_back_char_count_;
        if (unlikely(pushed_back_chars_[pushed_back_char_count_] == '\n'))
            ++line_no_;
        return pushed_back_chars_[pushed_back_char_count_];
    }

    const int ch(input_.get());
    if (unlikely(ch == '\n'))
        ++line_no_;
    return ch;
}

void Scanner::unget(int ch) {
    if (unlikely(pushed_back_char_count_ == 2))
        logger->error("in Scanner::unget: can't unget more than 2 characters in a row!");
    pushed_back_chars_[pushed_back_char_count_++] = ch;
    if (unlikely(ch == '\n'))
        --line_no_;
}

void Scanner::skipWhiteSpaceAndComments() {
    int ch;
skip_white_space:
    for (;;) {
        ch = get();
        if (ch != ' ' and ch != '\t' and ch != '\n')
            break;
    }

    if (ch == '/') {
        ch = get();
        if (ch == '/') { // Single-line comment.
            // Skip to end-of-line:
            do
                ch = get();
            while (ch != '\n' and unlikely(ch != EOF));
            if (unlikely(ch == EOF))
                return;
        } else if (ch == '*') { // C-style comment.
            bool star_seen(false);
            for (;;) {
                ch = get();
                if (unlikely(ch == EOF))
                    logger->error("in Scanner::skipWhiteSpaceAndComments: unexpected EOF while looking for the end "
                                  "of a C-style comment!");
                if (star_seen) {
                    if (ch == '/')
                        break; // End of comment.
                }
                star_seen = ch == '*';
            }
        } else {
            unget(ch);
            unget('/');
            return;
        }
        goto skip_white_space;
    }

    if (likely(ch != EOF))
        unget(ch);
}

inline bool IsHexDigit(const char ch) {
    return ('0' <= ch and ch <= '9') or ('a' <= ch and ch <= 'z') or ('A' <= ch and ch <= 'Z');
}

inline bool IsOctalDigit(const char ch) {
    return '0' <= ch and ch <= '7';
}

void Scanner::skipCharacterConstant() {
    int ch(get());
    if (unlikely(ch == EOF))
        error("unexpected EOF while parsing a character constant!");
    if (unlikely(ch == '\\')) {
        ch = get();
        if (unlikely(ch == EOF))
            error("unexpected EOF while parsing a character constant! (2)");
        if (ch == 'x') { // hex escape
            do {
                ch = get();
                if (unlikely(ch == EOF))
                    error("unexpected EOF while parsing a character constant! (3)");
            } while (IsHexDigit(static_cast<char>(ch)));
            unget(ch);
        } else if (IsOctalDigit(static_cast<char>(ch))) { // octal escape
            do {
                ch = get();
                if (unlikely(ch == EOF))
                    error("unexpected EOF while parsing a character constant! (4)");
            } while (IsOctalDigit(static_cast<char>(ch)));
            unget(ch);
        }
    }
    ch = get();
    if (unlikely(ch == EOF))
        error("unexpected EOF while parsing a character constant! (5)");
    if (unlikely(ch != '\''))
        error("expected closing quote at end of a character constant, found '"
              + std::string(1, static_cast<char>(ch)) + "' instead!");
}

void Scanner::extractIdent() {
    last_ident_.clear();
    for (;;) {
        const int ch(get());
        if (unlikely(ch == EOF))
            error("unexpected EOF while parsing an identifier or a keyword!");
        if (ch == '_' or StringUtil::IsAsciiLetter(ch) or StringUtil::IsDigit(ch))
            last_ident_ += static_cast<char>(ch);
        else {
            unget(ch);
            return;
        }
    }
}

void Scanner::extractStringConstant() {
    last_string_constant_.clear();
    bool escaped(false);
    for (;;) {
        int ch(get());
        if (unlikely(ch == EOF))
            error("unexpected EOF while parsing a string constant!");
        else if (unlikely(escaped))
            escaped = false;
        else if (unlikely(ch == '"'))
            return;
        else if (unlikely(ch == '\\'))
            escaped = true;
        last_string_constant_ += static_cast<char>(ch);
    }
}

void ExtractIncludes(Scanner * const scanner, std::vector<std::string> * const includes) {
    includes->clear();

    for (;;) {
        TokenType token(scanner->getToken());
        if (unlikely(token == END_OF_INPUT))
            return;
        if (token == OTHER_CHAR and scanner->getLastOtherChar() == '#') {
            token = scanner->getToken();
            if (token == IDENT and scanner->getLastIdent() == "include") {
                token = scanner->getToken();
                if (token == STRING_CONSTANT)
                    includes->emplace_back(scanner->getLastStringConstant());
            }
        }
    }
}

void RemoveUsedNamespacesAndClassNames(Scanner * const scanner,
                                       std::set<std::string> * const namespaces_and_class_names)
{
    bool last_token_was_less_than(false);
    for (;;) {
        TokenType token(scanner->getToken());
        if (unlikely(token == END_OF_INPUT))
            return;
        if (token == OTHER_CHAR)
            last_token_was_less_than = scanner->getLastOtherChar() == '<';
        if (token == IDENT) {
            const std::string ident(scanner->getLastIdent());
            if (namespaces_and_class_names->find(ident) == namespaces_and_class_names->cend())
                continue;
            token = scanner->getToken();
            if (unlikely(token == END_OF_INPUT))
                return;
            if (token == IDENT)
                namespaces_and_class_names->erase(ident);
            else if (token == OTHER_CHAR) {
                const char ch(scanner->getLastOtherChar());
                if (ch == ':' or ch == '&' or ch == '*' or (ch == '>' and last_token_was_less_than))
                    namespaces_and_class_names->erase(ident);
            }
        }
    }
}

bool ShouldRemove(const std::string &line, std::set<std::string> * const namespaces_and_class_names) {
    if (line.find("#include") == std::string::npos)
        return false;

    for (const auto &namespace_or_class_name : *namespaces_and_class_names) {
        if (line.find(namespace_or_class_name + ".h") != std::string::npos) {
            namespaces_and_class_names->erase(namespace_or_class_name);
            return true;
        }
    }

    return false;
}

// \return True if at least one include was removed, otherwise false.
bool RemoveIncludes(File * const input, File * const output,
                    std::set<std::string> * const namespaces_and_class_names)
{
    bool removed_at_least_one(false);
    while (not input->eof()) {
        std::string line;
        input->getline(&line);
        if (ShouldRemove(line, namespaces_and_class_names))
            removed_at_least_one = true;
        else
            output->write(line + "\n");
    }

    if (not namespaces_and_class_names->empty()) {
        std::cerr << "Failed to remove all the following unnecessary includes from \"" << input->getPath()
                  << "\":\n";
        for (const auto &namespace_or_class_name : *namespaces_and_class_names)
            std::cerr << '\t' << namespace_or_class_name << ".h\n";
        std::exit(EXIT_FAILURE);
    }

    return removed_at_least_one;
}

// \return True if at least one include was removed, otherwise false.
bool ProcessFile(const bool report_only, File * const input) {
    Scanner scanner(input);

    std::vector<std::string> includes;
    ExtractIncludes(&scanner, &includes);
    if (report_only) {
        std::cout << "Found the following local includes:\n";
        for (const auto &include : includes)
            std::cout << '\t' << include << '\n';
    }

    const std::string input_filename(FileUtil::GetFilenameWithoutExtensionOrDie(FileUtil::GetBasename(input->getPath())));
    std::set<std::string> namespaces_and_class_names;
    for (const auto &include : includes) {
        if (StringUtil::EndsWith(include, ".h") and include != "util.h" and include != "Compiler.h" and include != input_filename + ".h")
            namespaces_and_class_names.emplace(include.substr(0, include.length() - 2));
    }

    scanner.rewind();

    RemoveUsedNamespacesAndClassNames(&scanner, &namespaces_and_class_names);
    if (not namespaces_and_class_names.empty()) {
        if (report_only) {
            std::cout << "Unused namespaces and class names:\n";
            for (const auto &namespace_or_class_name : namespaces_and_class_names)
                std::cout << '\t' << namespace_or_class_name << '\n';
        } else {
            input->rewind();
            std::unique_ptr<File> output(FileUtil::OpenOutputFileOrDie(input->getPath() + ".tmp"));
            return RemoveIncludes(input, output.get(), &namespaces_and_class_names);
        }
    }

    return false;
}

int main(int argc, char *argv[]) {
    ::progname = argv[0];

    if (argc < 2)
        Usage();

    bool report_only(false);
    if (std::strcmp(argv[1], "--report-only") == 0) {
        report_only = true;
        --argc, ++argv;
    }

    if (argc < 2)
        Usage();

    try {
        for (int arg_no(1); arg_no < argc; ++arg_no) {
            const std::string source_filename(argv[arg_no]);
            std::unique_ptr<File> input(FileUtil::OpenInputFileOrDie(source_filename));
            std::cout << "Processing " << source_filename << "...\n";
            if (not ProcessFile(report_only, input.get()))
                continue;

            input->close();
            if (not report_only) {
                if (not FileUtil::RenameFile(source_filename, source_filename + ".bak"))
                    logger->error("failed to rename \"" + source_filename + "\" to \"" + source_filename
                                  + ".bak\"!");
                if (not FileUtil::RenameFile(source_filename + ".tmp", source_filename))
                    logger->error("failed to rename \"" + source_filename + ".tmp\" to \"" + source_filename
                                  + "\"!");
            }
        }
    } catch (const std::exception &e) {
        logger->error("Caught exception: " + std::string(e.what()));
    }
}
<commit_msg>renamed variable<commit_after>/** \brief A tool to find and eliminate unused C++ includes.
 *  \author Dr. Johannes Ruscheinski (johannes.ruscheinski@uni-tuebingen.de)
 *
 *  \copyright 2017 Universitätsbibliothek Tübingen.  All rights reserved.
 *
 *  This program 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.
 *
 *  This program 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 Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <iostream>
#include <cstdlib>
#include <cstring>
#include "Compiler.h"
#include "FileUtil.h"
#include "StringUtil.h"
#include "util.h"

static void Usage() __attribute__((noreturn));

void Usage() {
    std::cerr << "Usage: " << ::progname << " [--report-only] file1 [file2 .. fileN]\n";
    std::exit(EXIT_FAILURE);
}

enum TokenType {
    STRING_CONSTANT, IDENT, OTHER_CHAR, CHARACTER_CONSTANT, END_OF_INPUT
};

class Scanner {
    File &input_;
    int pushed_back_chars_[2];
    unsigned pushed_back_char_count_;
    std::string last_ident_;
    char last_other_char_;
    std::string last_string_constant_;
    unsigned line_no_;
public:
    explicit Scanner(File * const input): input_(*input), pushed_back_char_count_(0), line_no_(1) { }
    TokenType getToken();
    inline const std::string &getLastIdent() const { return last_ident_; }
    inline char getLastOtherChar() const { return last_other_char_; }
    inline const std::string &getLastStringConstant() const { return last_string_constant_; }
    inline unsigned getCurrentLineNo() const { return line_no_; }
    inline void rewind() { input_.rewind(); line_no_ = 1; pushed_back_char_count_ = 0; }
private:
    void error(const std::string &msg) const;
    int get();
    void unget(int ch);
    void skipWhiteSpaceAndComments();
    void skipCharacterConstant();
    void extractStringConstant();
    void extractIdent();
};

TokenType Scanner::getToken() {
    skipWhiteSpaceAndComments();

    int ch(get());
    if (unlikely(ch == EOF))
        return END_OF_INPUT;

    if (ch == '\'') {
        skipCharacterConstant();
        return CHARACTER_CONSTANT;
    } else if (ch == '"') {
        extractStringConstant();
        return STRING_CONSTANT;
    } else if (ch == '_' or StringUtil::IsAsciiLetter(ch)) {
        unget(ch);
        extractIdent();
        return IDENT;
    }

    last_other_char_ = static_cast<char>(ch);
    return OTHER_CHAR;
}

void Scanner::error(const std::string &msg) const {
    errno = 0;
    logger->error("on line #" + std::to_string(line_no_) + ": " + msg);
}

int Scanner::get() {
    if (pushed_back_char_count_ > 0) {
        --pushed_back_char_count_;
        if (unlikely(pushed_back_chars_[pushed_back_char_count_] == '\n'))
            ++line_no_;
        return pushed_back_chars_[pushed_back_char_count_];
    }

    const int ch(input_.get());
    if (unlikely(ch == '\n'))
        ++line_no_;
    return ch;
}

void Scanner::unget(int ch) {
    if (unlikely(pushed_back_char_count_ == 2))
        logger->error("in Scanner::unget: can't unget more than 2 characters in a row!");
    pushed_back_chars_[pushed_back_char_count_++] = ch;
    if (unlikely(ch == '\n'))
        --line_no_;
}

void Scanner::skipWhiteSpaceAndComments() {
    int ch;
skip_white_space:
    for (;;) {
        ch = get();
        if (ch != ' ' and ch != '\t' and ch != '\n')
            break;
    }

    if (ch == '/') {
        ch = get();
        if (ch == '/') { // Single-line comment.
            // Skip to end-of-line:
            do
                ch = get();
            while (ch != '\n' and unlikely(ch != EOF));
            if (unlikely(ch == EOF))
                return;
        } else if (ch == '*') { // C-style comment.
            bool star_seen(false);
            for (;;) {
                ch = get();
                if (unlikely(ch == EOF))
                    logger->error("in Scanner::skipWhiteSpaceAndComments: unexpected EOF while looking for the end "
                                  "of a C-style comment!");
                if (star_seen) {
                    if (ch == '/')
                        break; // End of comment.
                }
                star_seen = ch == '*';
            }
        } else {
            unget(ch);
            unget('/');
            return;
        }
        goto skip_white_space;
    }

    if (likely(ch != EOF))
        unget(ch);
}

inline bool IsHexDigit(const char ch) {
    return ('0' <= ch and ch <= '9') or ('a' <= ch and ch <= 'z') or ('A' <= ch and ch <= 'Z');
}

inline bool IsOctalDigit(const char ch) {
    return '0' <= ch and ch <= '7';
}

void Scanner::skipCharacterConstant() {
    int ch(get());
    if (unlikely(ch == EOF))
        error("unexpected EOF while parsing a character constant!");
    if (unlikely(ch == '\\')) {
        ch = get();
        if (unlikely(ch == EOF))
            error("unexpected EOF while parsing a character constant! (2)");
        if (ch == 'x') { // hex escape
            do {
                ch = get();
                if (unlikely(ch == EOF))
                    error("unexpected EOF while parsing a character constant! (3)");
            } while (IsHexDigit(static_cast<char>(ch)));
            unget(ch);
        } else if (IsOctalDigit(static_cast<char>(ch))) { // octal escape
            do {
                ch = get();
                if (unlikely(ch == EOF))
                    error("unexpected EOF while parsing a character constant! (4)");
            } while (IsOctalDigit(static_cast<char>(ch)));
            unget(ch);
        }
    }
    ch = get();
    if (unlikely(ch == EOF))
        error("unexpected EOF while parsing a character constant! (5)");
    if (unlikely(ch != '\''))
        error("expected closing quote at end of a character constant, found '"
              + std::string(1, static_cast<char>(ch)) + "' instead!");
}

void Scanner::extractIdent() {
    last_ident_.clear();
    for (;;) {
        const int ch(get());
        if (unlikely(ch == EOF))
            error("unexpected EOF while parsing an identifier or a keyword!");
        if (ch == '_' or StringUtil::IsAsciiLetter(ch) or StringUtil::IsDigit(ch))
            last_ident_ += static_cast<char>(ch);
        else {
            unget(ch);
            return;
        }
    }
}

void Scanner::extractStringConstant() {
    last_string_constant_.clear();
    bool escaped(false);
    for (;;) {
        int ch(get());
        if (unlikely(ch == EOF))
            error("unexpected EOF while parsing a string constant!");
        else if (unlikely(escaped))
            escaped = false;
        else if (unlikely(ch == '"'))
            return;
        else if (unlikely(ch == '\\'))
            escaped = true;
        last_string_constant_ += static_cast<char>(ch);
    }
}

void ExtractIncludes(Scanner * const scanner, std::vector<std::string> * const includes) {
    includes->clear();

    for (;;) {
        TokenType token(scanner->getToken());
        if (unlikely(token == END_OF_INPUT))
            return;
        if (token == OTHER_CHAR and scanner->getLastOtherChar() == '#') {
            token = scanner->getToken();
            if (token == IDENT and scanner->getLastIdent() == "include") {
                token = scanner->getToken();
                if (token == STRING_CONSTANT)
                    includes->emplace_back(scanner->getLastStringConstant());
            }
        }
    }
}

void RemoveUsedNamespacesAndClassNames(Scanner * const scanner,
                                       std::set<std::string> * const namespaces_and_class_names)
{
    bool last_token_was_less_than(false);
    for (;;) {
        TokenType token(scanner->getToken());
        if (unlikely(token == END_OF_INPUT))
            return;
        if (token == OTHER_CHAR)
            last_token_was_less_than = scanner->getLastOtherChar() == '<';
        if (token == IDENT) {
            const std::string ident(scanner->getLastIdent());
            if (namespaces_and_class_names->find(ident) == namespaces_and_class_names->cend())
                continue;
            token = scanner->getToken();
            if (unlikely(token == END_OF_INPUT))
                return;
            if (token == IDENT)
                namespaces_and_class_names->erase(ident);
            else if (token == OTHER_CHAR) {
                const char ch(scanner->getLastOtherChar());
                if (ch == ':' or ch == '&' or ch == '*' or (ch == '>' and last_token_was_less_than))
                    namespaces_and_class_names->erase(ident);
            }
        }
    }
}

bool ShouldRemove(const std::string &line, std::set<std::string> * const namespaces_and_class_names) {
    if (line.find("#include") == std::string::npos)
        return false;

    for (const auto &namespace_or_class_name : *namespaces_and_class_names) {
        if (line.find(namespace_or_class_name + ".h") != std::string::npos) {
            namespaces_and_class_names->erase(namespace_or_class_name);
            return true;
        }
    }

    return false;
}

// \return True if at least one include was removed, otherwise false.
bool RemoveIncludes(File * const input, File * const output,
                    std::set<std::string> * const namespaces_and_class_names)
{
    bool removed_at_least_one(false);
    while (not input->eof()) {
        std::string line;
        input->getline(&line);
        if (ShouldRemove(line, namespaces_and_class_names))
            removed_at_least_one = true;
        else
            output->write(line + "\n");
    }

    if (not namespaces_and_class_names->empty()) {
        std::cerr << "Failed to remove all the following unnecessary includes from \"" << input->getPath()
                  << "\":\n";
        for (const auto &namespace_or_class_name : *namespaces_and_class_names)
            std::cerr << '\t' << namespace_or_class_name << ".h\n";
        std::exit(EXIT_FAILURE);
    }

    return removed_at_least_one;
}

// \return True if at least one include was removed, otherwise false.
bool ProcessFile(const bool report_only, File * const input) {
    Scanner scanner(input);

    std::vector<std::string> includes;
    ExtractIncludes(&scanner, &includes);
    if (report_only) {
        std::cout << "Found the following local includes:\n";
        for (const auto &include : includes)
            std::cout << '\t' << include << '\n';
    }

    const std::string input_filename_without_extension(FileUtil::GetFilenameWithoutExtensionOrDie(FileUtil::GetBasename(input->getPath())));
    std::set<std::string> namespaces_and_class_names;
    for (const auto &include : includes) {
        if (StringUtil::EndsWith(include, ".h") and include != "util.h" and include != "Compiler.h" and include != input_filename_without_extension + ".h")
            namespaces_and_class_names.emplace(include.substr(0, include.length() - 2));
    }

    scanner.rewind();

    RemoveUsedNamespacesAndClassNames(&scanner, &namespaces_and_class_names);
    if (not namespaces_and_class_names.empty()) {
        if (report_only) {
            std::cout << "Unused namespaces and class names:\n";
            for (const auto &namespace_or_class_name : namespaces_and_class_names)
                std::cout << '\t' << namespace_or_class_name << '\n';
        } else {
            input->rewind();
            std::unique_ptr<File> output(FileUtil::OpenOutputFileOrDie(input->getPath() + ".tmp"));
            return RemoveIncludes(input, output.get(), &namespaces_and_class_names);
        }
    }

    return false;
}

int main(int argc, char *argv[]) {
    ::progname = argv[0];

    if (argc < 2)
        Usage();

    bool report_only(false);
    if (std::strcmp(argv[1], "--report-only") == 0) {
        report_only = true;
        --argc, ++argv;
    }

    if (argc < 2)
        Usage();

    try {
        for (int arg_no(1); arg_no < argc; ++arg_no) {
            const std::string source_filename(argv[arg_no]);
            std::unique_ptr<File> input(FileUtil::OpenInputFileOrDie(source_filename));
            std::cout << "Processing " << source_filename << "...\n";
            if (not ProcessFile(report_only, input.get()))
                continue;

            input->close();
            if (not report_only) {
                if (not FileUtil::RenameFile(source_filename, source_filename + ".bak"))
                    logger->error("failed to rename \"" + source_filename + "\" to \"" + source_filename
                                  + ".bak\"!");
                if (not FileUtil::RenameFile(source_filename + ".tmp", source_filename))
                    logger->error("failed to rename \"" + source_filename + ".tmp\" to \"" + source_filename
                                  + "\"!");
            }
        }
    } catch (const std::exception &e) {
        logger->error("Caught exception: " + std::string(e.what()));
    }
}
<|endoftext|>


<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 2014 Cloudius Systems
 *
 * Modified by Cloudius Systems
 */

#pragma once

namespace cql3 {
namespace functions {

/**
 * Factory methods for aggregate functions.
 */
namespace aggregate_fcts {

class impl_count_function : public aggregate_function::aggregate {
    int64_t _count;
public:
    virtual void reset() override {
        _count = 0;
    }
    virtual opt_bytes compute(int protocol_version) override {
        return long_type->decompose(_count);
    }
    virtual void add_input(int protocol_version, const std::vector<opt_bytes>& values) override {
        ++_count;
    }
};

    /**
     * The function used to count the number of rows of a result set. This function is called when COUNT(*) or COUNT(1)
     * is specified.
     */
shared_ptr<aggregate_function>
make_count_rows_function() {
    return make_native_aggregate_function_using<impl_count_function>("countRows", long_type);
}

template <typename Type>
class impl_sum_function_for final : public aggregate_function::aggregate {
   Type _sum{};
public:
    virtual void reset() override {
        _sum = {};
    }
    virtual opt_bytes compute() override {
        return data_type_for<Type>()->decompose(_sum);
    }
    virtual void add_input(int protocol_version, const std::vector<opt_bytes>& values) override {
        if (!values[0]) {
            return;
        }
        _sum += boost::any_cast<Type>(data_type_for<Type>()->compose(*values[0]));
    }
};

template <typename Type>
class sum_function_for final : public native_aggregate_function {
public:
    sum_function_for() : native_aggregate_function("sum", data_type_for<Type>(), { data_type_for<Type>() }) {}
    virtual std::unique_ptr<aggregate> new_aggregate() override {
        return std::make_unique<impl_sum_function_for<Type>>();
    }
};

template <typename Type>
shared_ptr<aggregate_function>
make_sum_function() {
    return make_shared<sum_function_for<Type>>();
}

template <typename Type>
class impl_avg_function_for final : public aggregate_function::aggregate {
   Type _sum{};
   int64_t _count = 0;
public:
    virtual void reset() override {
        _sum = {};
        _count = 0;
    }
    virtual opt_bytes compute() override {
        Type ret = 0;
        if (_count) {
            ret = _sum / _count;
        }
        return data_type_for<Type>().decompose(ret);
    }
    virtual void add_input(int protocol_version, const std::vector<opt_bytes>& values) override {
        if (!values[0]) {
            return;
        }
        ++_count;
        _sum += boost::any_cast<Type>(data_type_for<Type>().compose(*values[0]));
    }
};

template <typename Type>
class avg_function_for final : public native_aggregate_function {
public:
    avg_function_for() : native_aggregate_function("avg", data_type_for<Type>(), { data_type_for<Type>() }) {}
    virtual std::unique_ptr<aggregate> new_aggregate() override {
        return std::make_unique<impl_avg_function_for<Type>>();
    }
};

template <typename Type>
shared_ptr<aggregate_function>
make_avg_function() {
    return make_shared<avg_function_for<Type>>();
}

template <typename Type>
class impl_max_function_for final : public aggregate_function::aggregate {
   std::experimental::optional<Type> _max{};
public:
    virtual void reset() override {
        _max = {};
    }
    virtual opt_bytes compute() override {
        if (!_max) {
            return {};
        }
        return data_type_for<Type>().decompose(*_max);
    }
    virtual void add_input(int protocol_version, const std::vector<opt_bytes>& values) override {
        if (!values[0]) {
            return;
        }
        if (!_max) {
            _max = values[0];
        } else {
            auto val = boost::any_cast<Type>(data_type_for<Type>().compose(*values[0]));
            _max = std::max(*_max, val);
        }
    }
};

template <typename Type>
class max_function_for final : public native_aggregate_function {
public:
    max_function_for() : native_aggregate_function("max", data_type_for<Type>(), { data_type_for<Type>() }) {}
    virtual std::unique_ptr<aggregate> new_aggregate() override {
        return std::make_unique<impl_max_function_for<Type>>();
    }
};

    /**
     * Creates a MAX function for the specified type.
     *
     * @param inputType the function input and output type
     * @return a MAX function for the specified type.
     */
template <typename Type>
shared_ptr<aggregate_function>
make_max_function() {
    return shared_ptr<max_function_for<Type>>();
}

template <typename Type>
class impl_min_function_for final : public aggregate_function::aggregate {
   std::experimental::optional<Type> _min{};
public:
    virtual void reset() override {
        _min = {};
    }
    virtual opt_bytes compute() override {
        if (!_min) {
            return {};
        }
        return data_type_for<Type>().decompose(*_min);
    }
    virtual void add_input(int protocol_version, const std::vector<opt_bytes>& values) override {
        if (!values[0]) {
            return;
        }
        if (!_min) {
            _min = values[0];
        } else {
            auto val = boost::any_cast<Type>(data_type_for<Type>().compose(*values[0]));
            _min = std::min(*_min, val);
        }
    }
};

template <typename Type>
class min_function_for final : public native_aggregate_function {
public:
    min_function_for() : native_aggregate_function("min", data_type_for<Type>(), { data_type_for<Type>() }) {}
    virtual std::unique_ptr<aggregate> new_aggregate() override {
        return std::make_unique<impl_min_function_for<Type>>();
    }
};

    /**
     * Creates a MIN function for the specified type.
     *
     * @param inputType the function input and output type
     * @return a MIN function for the specified type.
     */
template <typename Type>
shared_ptr<aggregate_function>
make_min_function() {
    return make_shared<min_function_for<Type>>();
}

template <typename Type>
class impl_count_function_for final : public aggregate_function::aggregate {
   int64_t _count = 0;
public:
    virtual void reset() override {
        _count = 0;
    }
    virtual opt_bytes compute() override {
        return long_type->decompose(_count);
    }
    virtual void add_input(int protocol_version, const std::vector<opt_bytes>& values) override {
        if (!values[0]) {
            return;
        }
        ++_count;
    }
};

template <typename Type>
class count_function_for final : public native_aggregate_function {
public:
    count_function_for() : native_aggregate_function("count", long_type, { data_type_for<Type>() }) {}
    virtual std::unique_ptr<aggregate> new_aggregate() override {
        return std::make_unique<impl_count_function_for<Type>>();
    }
};

    /**
     * Creates a COUNT function for the specified type.
     *
     * @param inputType the function input type
     * @return a COUNT function for the specified type.
     */
template <typename Type>
shared_ptr<aggregate_function>
make_count_function() {
    return make_shared<count_function_for<Type>>();
}

}
}
}

<commit_msg>cql: fix aggregate_fcts build errors<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 2014 Cloudius Systems
 *
 * Modified by Cloudius Systems
 */

#pragma once

#include "aggregate_function.hh"
#include "native_aggregate_function.hh"

namespace cql3 {
namespace functions {

/**
 * Factory methods for aggregate functions.
 */
namespace aggregate_fcts {

class impl_count_function : public aggregate_function::aggregate {
    int64_t _count;
public:
    virtual void reset() override {
        _count = 0;
    }
    virtual opt_bytes compute(int protocol_version) override {
        return long_type->decompose(_count);
    }
    virtual void add_input(int protocol_version, const std::vector<opt_bytes>& values) override {
        ++_count;
    }
};

    /**
     * The function used to count the number of rows of a result set. This function is called when COUNT(*) or COUNT(1)
     * is specified.
     */
inline
shared_ptr<aggregate_function>
make_count_rows_function() {
    return make_native_aggregate_function_using<impl_count_function>("countRows", long_type);
}

template <typename Type>
class impl_sum_function_for final : public aggregate_function::aggregate {
   Type _sum{};
public:
    virtual void reset() override {
        _sum = {};
    }
    virtual opt_bytes compute(int protocol_version) override {
        return data_type_for<Type>()->decompose(_sum);
    }
    virtual void add_input(int protocol_version, const std::vector<opt_bytes>& values) override {
        if (!values[0]) {
            return;
        }
        _sum += boost::any_cast<Type>(data_type_for<Type>()->compose(*values[0]));
    }
};

template <typename Type>
class sum_function_for final : public native_aggregate_function {
public:
    sum_function_for() : native_aggregate_function("sum", data_type_for<Type>(), { data_type_for<Type>() }) {}
    virtual std::unique_ptr<aggregate> new_aggregate() override {
        return std::make_unique<impl_sum_function_for<Type>>();
    }
};

template <typename Type>
inline
shared_ptr<aggregate_function>
make_sum_function() {
    return make_shared<sum_function_for<Type>>();
}

template <typename Type>
class impl_avg_function_for final : public aggregate_function::aggregate {
   Type _sum{};
   int64_t _count = 0;
public:
    virtual void reset() override {
        _sum = {};
        _count = 0;
    }
    virtual opt_bytes compute(int protocol_version) override {
        Type ret = 0;
        if (_count) {
            ret = _sum / _count;
        }
        return data_type_for<Type>()->decompose(ret);
    }
    virtual void add_input(int protocol_version, const std::vector<opt_bytes>& values) override {
        if (!values[0]) {
            return;
        }
        ++_count;
        _sum += boost::any_cast<Type>(data_type_for<Type>()->compose(*values[0]));
    }
};

template <typename Type>
class avg_function_for final : public native_aggregate_function {
public:
    avg_function_for() : native_aggregate_function("avg", data_type_for<Type>(), { data_type_for<Type>() }) {}
    virtual std::unique_ptr<aggregate> new_aggregate() override {
        return std::make_unique<impl_avg_function_for<Type>>();
    }
};

template <typename Type>
inline
shared_ptr<aggregate_function>
make_avg_function() {
    return make_shared<avg_function_for<Type>>();
}

template <typename Type>
class impl_max_function_for final : public aggregate_function::aggregate {
   std::experimental::optional<Type> _max{};
public:
    virtual void reset() override {
        _max = {};
    }
    virtual opt_bytes compute() override {
        if (!_max) {
            return {};
        }
        return data_type_for<Type>().decompose(*_max);
    }
    virtual void add_input(int protocol_version, const std::vector<opt_bytes>& values) override {
        if (!values[0]) {
            return;
        }
        if (!_max) {
            _max = values[0];
        } else {
            auto val = boost::any_cast<Type>(data_type_for<Type>().compose(*values[0]));
            _max = std::max(*_max, val);
        }
    }
};

template <typename Type>
class max_function_for final : public native_aggregate_function {
public:
    max_function_for() : native_aggregate_function("max", data_type_for<Type>(), { data_type_for<Type>() }) {}
    virtual std::unique_ptr<aggregate> new_aggregate() override {
        return std::make_unique<impl_max_function_for<Type>>();
    }
};

    /**
     * Creates a MAX function for the specified type.
     *
     * @param inputType the function input and output type
     * @return a MAX function for the specified type.
     */
template <typename Type>
shared_ptr<aggregate_function>
make_max_function() {
    return shared_ptr<max_function_for<Type>>();
}

template <typename Type>
class impl_min_function_for final : public aggregate_function::aggregate {
   std::experimental::optional<Type> _min{};
public:
    virtual void reset() override {
        _min = {};
    }
    virtual opt_bytes compute(int protocol_version) override {
        if (!_min) {
            return {};
        }
        return data_type_for<Type>()->decompose(*_min);
    }
    virtual void add_input(int protocol_version, const std::vector<opt_bytes>& values) override {
        if (!values[0]) {
            return;
        }
        auto val = boost::any_cast<Type>(data_type_for<Type>()->compose(*values[0]));
        if (!_min) {
            _min = val;
        } else {
            _min = std::min(*_min, val);
        }
    }
};

template <typename Type>
class min_function_for final : public native_aggregate_function {
public:
    min_function_for() : native_aggregate_function("min", data_type_for<Type>(), { data_type_for<Type>() }) {}
    virtual std::unique_ptr<aggregate> new_aggregate() override {
        return std::make_unique<impl_min_function_for<Type>>();
    }
};

    /**
     * Creates a MIN function for the specified type.
     *
     * @param inputType the function input and output type
     * @return a MIN function for the specified type.
     */
template <typename Type>
shared_ptr<aggregate_function>
make_min_function() {
    return make_shared<min_function_for<Type>>();
}

template <typename Type>
class impl_count_function_for final : public aggregate_function::aggregate {
   int64_t _count = 0;
public:
    virtual void reset() override {
        _count = 0;
    }
    virtual opt_bytes compute(int protocol_version) override {
        return long_type->decompose(_count);
    }
    virtual void add_input(int protocol_version, const std::vector<opt_bytes>& values) override {
        if (!values[0]) {
            return;
        }
        ++_count;
    }
};

template <typename Type>
class count_function_for final : public native_aggregate_function {
public:
    count_function_for() : native_aggregate_function("count", long_type, { data_type_for<Type>() }) {}
    virtual std::unique_ptr<aggregate> new_aggregate() override {
        return std::make_unique<impl_count_function_for<Type>>();
    }
};

    /**
     * Creates a COUNT function for the specified type.
     *
     * @param inputType the function input type
     * @return a COUNT function for the specified type.
     */
template <typename Type>
shared_ptr<aggregate_function>
make_count_function() {
    return make_shared<count_function_for<Type>>();
}

}
}
}

<|endoftext|>


<commit_before>/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Mobility Components.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qmlmediabase_p.h"

#include <QtCore/qcoreevent.h>
#include <QtCore/qurl.h>

#include <qmediaplayercontrol.h>
#include <qmediaservice.h>
#include <qmediaserviceprovider.h>
#include <qmetadatacontrol.h>
#include <qmetadatacontrolmetaobject_p.h>

QTM_BEGIN_NAMESPACE

class QmlMediaBaseObject : public QMediaObject
{
public:
    QmlMediaBaseObject(QMediaService *service)
        : QMediaObject(0, service)
    {
    }
};

class QmlMediaBasePlayerControl : public QMediaPlayerControl
{
public:
    QmlMediaBasePlayerControl(QObject *parent)
        : QMediaPlayerControl(parent)
    {
    }

    QMediaPlayer::State state() const { return QMediaPlayer::StoppedState; }
    QMediaPlayer::MediaStatus mediaStatus() const { return QMediaPlayer::NoMedia; }

    qint64 duration() const { return 0; }
    qint64 position() const { return 0; }
    void setPosition(qint64) {}
    int volume() const { return 0; }
    void setVolume(int) {}
    bool isMuted() const { return false; }
    void setMuted(bool) {}
    int bufferStatus() const { return 0; }
    bool isAudioAvailable() const { return false; }
    bool isVideoAvailable() const { return false; }
    bool isSeekable() const { return false; }
    QMediaTimeRange availablePlaybackRanges() const { return QMediaTimeRange(); }
    qreal playbackRate() const { return 1; }
    void setPlaybackRate(qreal) {}
    QMediaContent media() const { return QMediaContent(); }
    const QIODevice *mediaStream() const { return 0; }
    void setMedia(const QMediaContent &, QIODevice *) {}

    void play() {}
    void pause() {}
    void stop() {}
};

class QmlMediaBaseAnimation : public QObject
{
public:
    QmlMediaBaseAnimation(QmlMediaBase *media)
        : m_media(media)
    {
    }

    void start() { if (!m_timer.isActive()) m_timer.start(500, this); }
    void stop() { m_timer.stop(); }

protected:
    void timerEvent(QTimerEvent *event)
    {
        if (event->timerId() == m_timer.timerId()) {
            event->accept();

            if (m_media->m_state == QMediaPlayer::PlayingState)
                emit m_media->positionChanged();
            if (m_media->m_status == QMediaPlayer::BufferingMedia || QMediaPlayer::StalledMedia)
                emit m_media->bufferProgressChanged();
        } else {
            QObject::timerEvent(event);
        }
    }

private:
    QmlMediaBase *m_media;
    QBasicTimer m_timer;
};

void QmlMediaBase::_q_stateChanged(QMediaPlayer::State state)
{
    if (state != m_state) {
        QMediaPlayer::State oldState = state;

        m_state = state;

        switch (state) {
        case QMediaPlayer::PlayingState:
            if (oldState == QMediaPlayer::StoppedState)
                emit started();
            else if (oldState == QMediaPlayer::PausedState)
                emit resumed();
            break;
        case QMediaPlayer::PausedState:
            emit paused();
        case QMediaPlayer::StoppedState:
            emit stopped();
            break;
        default:
            break;
        }

        emit playingChanged();
        emit pausedChanged();

        if (m_state == QMediaPlayer::PlayingState
                || m_status == QMediaPlayer::BufferingMedia
                || m_status == QMediaPlayer::StalledMedia) {
            m_animation->start();
        } else {
            m_animation->stop();
        }
    }
}

void QmlMediaBase::_q_mediaStatusChanged(QMediaPlayer::MediaStatus status)
{
    if (status != m_status) {
        m_status = status;

        switch (status) {
        case QMediaPlayer::LoadedMedia:
            emit loaded();
            break;
        case QMediaPlayer::BufferingMedia:
            emit buffering();
            break;
        case QMediaPlayer::BufferedMedia:
            emit buffered();
            break;
        case QMediaPlayer::StalledMedia:
            emit stalled();
            break;
        case QMediaPlayer::EndOfMedia:
            emit endOfMedia();
            break;
        default:
            break;
        }

        emit statusChanged();

        if (m_state == QMediaPlayer::PlayingState
                || m_status == QMediaPlayer::BufferingMedia
                || m_status == QMediaPlayer::StalledMedia) {
            m_animation->start();
        } else {
            m_animation->stop();
        }
    }
}

void QmlMediaBase::_q_metaDataChanged()
{
    m_metaObject->metaDataChanged();
}

QmlMediaBase::QmlMediaBase()
    : m_mediaService(0)
    , m_playerControl(0)
    , m_mediaObject(0)
    , m_mediaProvider(0)
    , m_metaDataControl(0)
    , m_metaObject(0)
    , m_animation(0)
    , m_state(QMediaPlayer::StoppedState)
    , m_status(QMediaPlayer::NoMedia)
    , m_error(QMediaPlayer::NoError)
{
}

QmlMediaBase::~QmlMediaBase()
{
}

void QmlMediaBase::shutdown()
{
    delete m_metaObject;
    delete m_mediaObject;

    if (m_mediaProvider)
        m_mediaProvider->releaseService(m_mediaService);

    delete m_animation;

}

void QmlMediaBase::setObject(QObject *object)
{
    if ((m_mediaProvider = QMediaServiceProvider::defaultServiceProvider())) {
        if ((m_mediaService = m_mediaProvider->requestService(Q_MEDIASERVICE_MEDIAPLAYER))) {
            m_playerControl = qobject_cast<QMediaPlayerControl *>(
                    m_mediaService->control(QMediaPlayerControl_iid));
            m_metaDataControl = qobject_cast<QMetaDataControl *>(
                    m_mediaService->control(QMetaDataControl_iid));
            m_mediaObject = new QmlMediaBaseObject(m_mediaService);
        }
    }

    if (m_playerControl) {
        QObject::connect(m_playerControl, SIGNAL(stateChanged(QMediaPlayer::State)),
                object, SLOT(_q_stateChanged(QMediaPlayer::State)));
        QObject::connect(m_playerControl, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)),
                object, SLOT(_q_mediaStatusChanged(QMediaPlayer::MediaStatus)));
        QObject::connect(m_playerControl, SIGNAL(mediaChanged(QMediaContent)),
                object, SIGNAL(sourceChanged()));
        QObject::connect(m_playerControl, SIGNAL(durationChanged(qint64)),
                object, SIGNAL(durationChanged()));
        QObject::connect(m_playerControl, SIGNAL(positionChanged(qint64)),
                object, SIGNAL(positionChanged()));
        QObject::connect(m_playerControl, SIGNAL(volumeChanged(int)),
                object, SIGNAL(volumeChanged()));
        QObject::connect(m_playerControl, SIGNAL(mutedChanged(bool)),
                object, SIGNAL(mutedChanged()));

        m_animation = new QmlMediaBaseAnimation(this);
    } else {
        m_error = QMediaPlayer::ServiceMissingError;

        m_playerControl = new QmlMediaBasePlayerControl(object);
    }

    if (m_metaDataControl) {
        m_metaObject = new QMetaDataControlMetaObject(m_metaDataControl, object);

        QObject::connect(m_metaDataControl, SIGNAL(metaDataChanged()),
                object, SLOT(_q_metaDataChanged()));
    }
}

QUrl QmlMediaBase::source() const
{
    return m_playerControl->media().canonicalUrl();
}

void QmlMediaBase::setSource(const QUrl &url)
{
    m_playerControl->setMedia(QMediaContent(url), 0);
}

bool QmlMediaBase::isPlaying() const
{
    return m_state != QMediaPlayer::StoppedState;
}

void QmlMediaBase::setPlaying(bool playing)
{
    if (playing && m_state == QMediaPlayer::StoppedState)
        m_playerControl->play();
    else if (!playing)
        m_playerControl->stop();
}

bool QmlMediaBase::isPaused() const
{
    return m_state == QMediaPlayer::PausedState;
}

void QmlMediaBase::setPaused(bool paused)
{
    if (paused && m_state == QMediaPlayer::PlayingState)
        m_playerControl->pause();
    if (!paused && m_state == QMediaPlayer::PausedState)
        m_playerControl->play();
}

int QmlMediaBase::duration() const
{
    return m_playerControl->duration();
}

int QmlMediaBase::position() const
{
    return m_playerControl->position();

}

void QmlMediaBase::setPosition(int position)
{
    m_playerControl->setPosition(position);
}

qreal QmlMediaBase::volume() const
{
    return qreal(m_playerControl->volume()) / 100;
}

void QmlMediaBase::setVolume(qreal volume)
{
    m_playerControl->setVolume(volume * 100);
}

bool QmlMediaBase::isMuted() const
{
    return m_playerControl->isMuted();
}

void QmlMediaBase::setMuted(bool muted)
{
    m_playerControl->setMuted(muted);
}

qreal QmlMediaBase::bufferProgress() const
{
    return qreal(m_playerControl->bufferStatus()) / 100;
}

bool QmlMediaBase::isSeekable() const
{
    return m_playerControl->isSeekable();
}

qreal QmlMediaBase::playbackRate() const
{
    return m_playerControl->playbackRate();
}

void QmlMediaBase::setPlaybackRate(qreal rate)
{
    m_playerControl->setPlaybackRate(rate);
}

QString QmlMediaBase::errorString() const
{
    return m_errorString;
}

QTM_END_NAMESPACE

<commit_msg>Round off the volume when converting from a real to int.<commit_after>/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Mobility Components.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qmlmediabase_p.h"

#include <QtCore/qcoreevent.h>
#include <QtCore/qurl.h>

#include <qmediaplayercontrol.h>
#include <qmediaservice.h>
#include <qmediaserviceprovider.h>
#include <qmetadatacontrol.h>
#include <qmetadatacontrolmetaobject_p.h>

QTM_BEGIN_NAMESPACE

class QmlMediaBaseObject : public QMediaObject
{
public:
    QmlMediaBaseObject(QMediaService *service)
        : QMediaObject(0, service)
    {
    }
};

class QmlMediaBasePlayerControl : public QMediaPlayerControl
{
public:
    QmlMediaBasePlayerControl(QObject *parent)
        : QMediaPlayerControl(parent)
    {
    }

    QMediaPlayer::State state() const { return QMediaPlayer::StoppedState; }
    QMediaPlayer::MediaStatus mediaStatus() const { return QMediaPlayer::NoMedia; }

    qint64 duration() const { return 0; }
    qint64 position() const { return 0; }
    void setPosition(qint64) {}
    int volume() const { return 0; }
    void setVolume(int) {}
    bool isMuted() const { return false; }
    void setMuted(bool) {}
    int bufferStatus() const { return 0; }
    bool isAudioAvailable() const { return false; }
    bool isVideoAvailable() const { return false; }
    bool isSeekable() const { return false; }
    QMediaTimeRange availablePlaybackRanges() const { return QMediaTimeRange(); }
    qreal playbackRate() const { return 1; }
    void setPlaybackRate(qreal) {}
    QMediaContent media() const { return QMediaContent(); }
    const QIODevice *mediaStream() const { return 0; }
    void setMedia(const QMediaContent &, QIODevice *) {}

    void play() {}
    void pause() {}
    void stop() {}
};

class QmlMediaBaseAnimation : public QObject
{
public:
    QmlMediaBaseAnimation(QmlMediaBase *media)
        : m_media(media)
    {
    }

    void start() { if (!m_timer.isActive()) m_timer.start(500, this); }
    void stop() { m_timer.stop(); }

protected:
    void timerEvent(QTimerEvent *event)
    {
        if (event->timerId() == m_timer.timerId()) {
            event->accept();

            if (m_media->m_state == QMediaPlayer::PlayingState)
                emit m_media->positionChanged();
            if (m_media->m_status == QMediaPlayer::BufferingMedia || QMediaPlayer::StalledMedia)
                emit m_media->bufferProgressChanged();
        } else {
            QObject::timerEvent(event);
        }
    }

private:
    QmlMediaBase *m_media;
    QBasicTimer m_timer;
};

void QmlMediaBase::_q_stateChanged(QMediaPlayer::State state)
{
    if (state != m_state) {
        QMediaPlayer::State oldState = state;

        m_state = state;

        switch (state) {
        case QMediaPlayer::PlayingState:
            if (oldState == QMediaPlayer::StoppedState)
                emit started();
            else if (oldState == QMediaPlayer::PausedState)
                emit resumed();
            break;
        case QMediaPlayer::PausedState:
            emit paused();
        case QMediaPlayer::StoppedState:
            emit stopped();
            break;
        default:
            break;
        }

        emit playingChanged();
        emit pausedChanged();

        if (m_state == QMediaPlayer::PlayingState
                || m_status == QMediaPlayer::BufferingMedia
                || m_status == QMediaPlayer::StalledMedia) {
            m_animation->start();
        } else {
            m_animation->stop();
        }
    }
}

void QmlMediaBase::_q_mediaStatusChanged(QMediaPlayer::MediaStatus status)
{
    if (status != m_status) {
        m_status = status;

        switch (status) {
        case QMediaPlayer::LoadedMedia:
            emit loaded();
            break;
        case QMediaPlayer::BufferingMedia:
            emit buffering();
            break;
        case QMediaPlayer::BufferedMedia:
            emit buffered();
            break;
        case QMediaPlayer::StalledMedia:
            emit stalled();
            break;
        case QMediaPlayer::EndOfMedia:
            emit endOfMedia();
            break;
        default:
            break;
        }

        emit statusChanged();

        if (m_state == QMediaPlayer::PlayingState
                || m_status == QMediaPlayer::BufferingMedia
                || m_status == QMediaPlayer::StalledMedia) {
            m_animation->start();
        } else {
            m_animation->stop();
        }
    }
}

void QmlMediaBase::_q_metaDataChanged()
{
    m_metaObject->metaDataChanged();
}

QmlMediaBase::QmlMediaBase()
    : m_mediaService(0)
    , m_playerControl(0)
    , m_mediaObject(0)
    , m_mediaProvider(0)
    , m_metaDataControl(0)
    , m_metaObject(0)
    , m_animation(0)
    , m_state(QMediaPlayer::StoppedState)
    , m_status(QMediaPlayer::NoMedia)
    , m_error(QMediaPlayer::NoError)
{
}

QmlMediaBase::~QmlMediaBase()
{
}

void QmlMediaBase::shutdown()
{
    delete m_metaObject;
    delete m_mediaObject;

    if (m_mediaProvider)
        m_mediaProvider->releaseService(m_mediaService);

    delete m_animation;

}

void QmlMediaBase::setObject(QObject *object)
{
    if ((m_mediaProvider = QMediaServiceProvider::defaultServiceProvider())) {
        if ((m_mediaService = m_mediaProvider->requestService(Q_MEDIASERVICE_MEDIAPLAYER))) {
            m_playerControl = qobject_cast<QMediaPlayerControl *>(
                    m_mediaService->control(QMediaPlayerControl_iid));
            m_metaDataControl = qobject_cast<QMetaDataControl *>(
                    m_mediaService->control(QMetaDataControl_iid));
            m_mediaObject = new QmlMediaBaseObject(m_mediaService);
        }
    }

    if (m_playerControl) {
        QObject::connect(m_playerControl, SIGNAL(stateChanged(QMediaPlayer::State)),
                object, SLOT(_q_stateChanged(QMediaPlayer::State)));
        QObject::connect(m_playerControl, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)),
                object, SLOT(_q_mediaStatusChanged(QMediaPlayer::MediaStatus)));
        QObject::connect(m_playerControl, SIGNAL(mediaChanged(QMediaContent)),
                object, SIGNAL(sourceChanged()));
        QObject::connect(m_playerControl, SIGNAL(durationChanged(qint64)),
                object, SIGNAL(durationChanged()));
        QObject::connect(m_playerControl, SIGNAL(positionChanged(qint64)),
                object, SIGNAL(positionChanged()));
        QObject::connect(m_playerControl, SIGNAL(volumeChanged(int)),
                object, SIGNAL(volumeChanged()));
        QObject::connect(m_playerControl, SIGNAL(mutedChanged(bool)),
                object, SIGNAL(mutedChanged()));

        m_animation = new QmlMediaBaseAnimation(this);
    } else {
        m_error = QMediaPlayer::ServiceMissingError;

        m_playerControl = new QmlMediaBasePlayerControl(object);
    }

    if (m_metaDataControl) {
        m_metaObject = new QMetaDataControlMetaObject(m_metaDataControl, object);

        QObject::connect(m_metaDataControl, SIGNAL(metaDataChanged()),
                object, SLOT(_q_metaDataChanged()));
    }
}

QUrl QmlMediaBase::source() const
{
    return m_playerControl->media().canonicalUrl();
}

void QmlMediaBase::setSource(const QUrl &url)
{
    m_playerControl->setMedia(QMediaContent(url), 0);
}

bool QmlMediaBase::isPlaying() const
{
    return m_state != QMediaPlayer::StoppedState;
}

void QmlMediaBase::setPlaying(bool playing)
{
    if (playing && m_state == QMediaPlayer::StoppedState)
        m_playerControl->play();
    else if (!playing)
        m_playerControl->stop();
}

bool QmlMediaBase::isPaused() const
{
    return m_state == QMediaPlayer::PausedState;
}

void QmlMediaBase::setPaused(bool paused)
{
    if (paused && m_state == QMediaPlayer::PlayingState)
        m_playerControl->pause();
    if (!paused && m_state == QMediaPlayer::PausedState)
        m_playerControl->play();
}

int QmlMediaBase::duration() const
{
    return m_playerControl->duration();
}

int QmlMediaBase::position() const
{
    return m_playerControl->position();

}

void QmlMediaBase::setPosition(int position)
{
    m_playerControl->setPosition(position);
}

qreal QmlMediaBase::volume() const
{
    return qreal(m_playerControl->volume()) / 100;
}

void QmlMediaBase::setVolume(qreal volume)
{
    m_playerControl->setVolume(qRound(volume * 100));
}

bool QmlMediaBase::isMuted() const
{
    return m_playerControl->isMuted();
}

void QmlMediaBase::setMuted(bool muted)
{
    m_playerControl->setMuted(muted);
}

qreal QmlMediaBase::bufferProgress() const
{
    return qreal(m_playerControl->bufferStatus()) / 100;
}

bool QmlMediaBase::isSeekable() const
{
    return m_playerControl->isSeekable();
}

qreal QmlMediaBase::playbackRate() const
{
    return m_playerControl->playbackRate();
}

void QmlMediaBase::setPlaybackRate(qreal rate)
{
    m_playerControl->setPlaybackRate(rate);
}

QString QmlMediaBase::errorString() const
{
    return m_errorString;
}

QTM_END_NAMESPACE

<|endoftext|>


<commit_before>/** \brief Tool for submitting full-text files to an ElsticSearch server.
 *  \author Dr. Johannes Ruscheinski (johannes.ruscheinski@uni-tuebingen.de)
 *
 *  \copyright 2018 Universitätsbibliothek Tübingen.  All rights reserved.
 *
 *  This program 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.
 *
 *  This program 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 Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <iostream>
#include <stdexcept>
#include <cstdio>
#include <cstdlib>
#include <vector>
#include "ControlNumberGuesser.h"
#include "FileUtil.h"
#include "FullTextImport.h"
#include "util.h"

namespace {

[[noreturn]] void Usage() {
    std::cerr << "Usage: " << ::progname << " [--min-log-level=min_verbosity] [--normalise-only] input1 [input2 .. inputN]\n";
    std::exit(EXIT_FAILURE);
}

} // unnamed namespace

int Main(int argc, char *argv[]) {
    if (argc == 0)
        Usage();

    ControlNumberGuesser control_number_guesser;

    unsigned total_count(0), good_count(0);
    for (int arg_no(1); arg_no < argc; ++arg_no) {
        ++total_count;

        const std::string input_filename(argv[arg_no]);
        const auto input_file(FileUtil::OpenInputFileOrDie(input_filename));

        FullTextImport::FullTextData full_text_data;
        FullTextImport::ReadExtractedTextFromDisk(input_file.get(), &full_text_data);

        const auto guessed_control_numbers(control_number_guesser.getGuessedControlNumbers(full_text_data.title_, full_text_data.authors_,
                                                                                full_text_data.year_, full_text_data.doi_,
                                                                                full_text_data.issn_, full_text_data.isbn_));
        if (guessed_control_numbers.empty())
            LOG_WARNING("failed to associate \"" + input_filename + "\" with any control number!");
        else if (guessed_control_numbers.size() > 1)
            LOG_WARNING("we're in a pickle!");
        else {
            ++good_count;
            LOG_INFO("whoohoo!");
        }
    }

    LOG_INFO("Processed " + std::to_string(total_count) + " documents of which \"" + std::to_string(good_count)
             + " could be uniquely associated with a control number.");

    return EXIT_SUCCESS;
}
<commit_msg>Removed an unused flag.<commit_after>/** \brief Tool for submitting full-text files to an ElsticSearch server.
 *  \author Dr. Johannes Ruscheinski (johannes.ruscheinski@uni-tuebingen.de)
 *
 *  \copyright 2018 Universitätsbibliothek Tübingen.  All rights reserved.
 *
 *  This program 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.
 *
 *  This program 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 Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <iostream>
#include <stdexcept>
#include <cstdio>
#include <cstdlib>
#include <vector>
#include "ControlNumberGuesser.h"
#include "FileUtil.h"
#include "FullTextImport.h"
#include "util.h"

namespace {

[[noreturn]] void Usage() {
    std::cerr << "Usage: " << ::progname << " [--min-log-level=min_verbosity] input1 [input2 .. inputN]\n";
    std::exit(EXIT_FAILURE);
}

} // unnamed namespace

int Main(int argc, char *argv[]) {
    if (argc == 0)
        Usage();

    ControlNumberGuesser control_number_guesser;

    unsigned total_count(0), good_count(0);
    for (int arg_no(1); arg_no < argc; ++arg_no) {
        ++total_count;

        const std::string input_filename(argv[arg_no]);
        const auto input_file(FileUtil::OpenInputFileOrDie(input_filename));

        FullTextImport::FullTextData full_text_data;
        FullTextImport::ReadExtractedTextFromDisk(input_file.get(), &full_text_data);

        const auto guessed_control_numbers(control_number_guesser.getGuessedControlNumbers(full_text_data.title_, full_text_data.authors_,
                                                                                full_text_data.year_, full_text_data.doi_,
                                                                                full_text_data.issn_, full_text_data.isbn_));
        if (guessed_control_numbers.empty())
            LOG_WARNING("failed to associate \"" + input_filename + "\" with any control number!");
        else if (guessed_control_numbers.size() > 1)
            LOG_WARNING("we're in a pickle!");
        else {
            ++good_count;
            LOG_INFO("whoohoo!");
        }
    }

    LOG_INFO("Processed " + std::to_string(total_count) + " documents of which \"" + std::to_string(good_count)
             + " could be uniquely associated with a control number.");

    return EXIT_SUCCESS;
}
<|endoftext|>


<commit_before>/** \file mysql_diff_schemas.cc
 *  \brief A tool for listing the differences between two schemas.
 *  \author Dr. Johannes Ruscheinski (johannes.ruscheinski@uni-tuebingen.de)
 *
 *  \copyright 2020 Universitätsbibliothek Tübingen.  All rights reserved.
 *
 *  This program 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.
 *
 *  This program 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 Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include <iostream>
#include <map>
#include <set>
#include <vector>
#include <cstdlib>
#include "FileUtil.h"
#include "StringUtil.h"
#include "util.h"

std::string ExtractBackQuotedString(const std::string &s) {
    auto ch(s.cbegin());
    if (unlikely(*ch != '`'))
        LOG_ERROR("\"" + s + "\" does not start with a backtick!");
    ++ch;

    std::string extracted_string;
    while (ch != s.cend() and *ch != '`')
        extracted_string += *ch++;
    if (unlikely(ch == s.cend()))
        LOG_ERROR("\"" + s + "\" does not end with a backtick!");

    return extracted_string;
}

inline bool SchemaLineIsLessThan(const std::string &line1, const std::string &line2) {
    if ((*line1.c_str() == '`') != (*line2.c_str() == '`'))
        return *line1.c_str() == '`';

    return line1 < line2;
}

void LoadSchema(const std::string &filename, std::map<std::string, std::vector<std::string>> * const table_name_to_schema_map) {
    std::string current_table;
    std::vector<std::string> current_schema;
    for (auto line : FileUtil::ReadLines(filename)) {
        StringUtil::Trim(&line);
        if (StringUtil::StartsWith(line, "CREATE TABLE ")) {
            if (not current_table.empty()) {
                std::sort(current_schema.begin(), current_schema.end(), SchemaLineIsLessThan);
                (*table_name_to_schema_map)[current_table] = current_schema;
            }
            current_table = ExtractBackQuotedString(line.substr(__builtin_strlen("CREATE TABLE ")));
            current_schema.clear();
        } else
            current_schema.emplace_back(line);
    }
    if (not current_schema.empty()) {
        std::sort(current_schema.begin(), current_schema.end(), SchemaLineIsLessThan);
        (*table_name_to_schema_map)[current_table] = current_schema;
    }
}

std::set<std::string> FindLinesStartingWithPrefix(const std::vector<std::string> &lines, const std::string &prefix) {
    std::set<std::string> matching_lines;
    for (const auto &line : lines) {
        if (StringUtil::StartsWith(line, prefix))
            matching_lines.emplace(line);
    }
    return matching_lines;
}

// Reports differences for lines that start w/ "prefix" for tables w/ the identical names.
void CompareTables(const std::string &prefix, const std::map<std::string, std::vector<std::string>> &table_name_to_schema_map1,
                   const std::map<std::string, std::vector<std::string>> &table_name_to_schema_map2)
{
    for (const auto &table_name1_and_schema1 : table_name_to_schema_map1) {
        const auto &table_name1(table_name1_and_schema1.first);
        const auto table_name2_and_schema2(table_name_to_schema_map2.find(table_name1));
        if (table_name2_and_schema2 == table_name_to_schema_map2.cend())
            continue;
        const auto &schema2(table_name2_and_schema2->second);

        const std::set<std::string> matching_lines_in_table1(FindLinesStartingWithPrefix(table_name1_and_schema1.second, prefix));
        for (const auto &matching_line_in_table1 : matching_lines_in_table1) {
            const auto matching_line_in_schema2(std::find(schema2.cbegin(), schema2.cend(), matching_line_in_table1));
            if (matching_line_in_schema2 == schema2.cend())
                std::cout << matching_line_in_table1 << " is missing in 2nd schema for table " << table_name1 << '\n';
        }
        for (const auto &line_in_table2 : schema2) {
            if (StringUtil::StartsWith(line_in_table2, prefix)
                and matching_lines_in_table1.find(line_in_table2) == matching_lines_in_table1.cend())
                std::cout << line_in_table2 << " is missing in 1st schema for table " << table_name1 << '\n';
        }
    }
}

class StartsWith {
    std::string prefix_;
public:
    explicit StartsWith(const std::string &prefix): prefix_(prefix) { }
    bool operator ()(const std::string &line) const { return StringUtil::StartsWith(line, prefix_); }
};

class StartsWithColumnName: public StartsWith {
public:
    explicit StartsWithColumnName(const std::string &reference_column_name): StartsWith("`" + reference_column_name + "`") { }
};

void DiffSchemas(const std::map<std::string, std::vector<std::string>> &table_name_to_schema_map1,
                 const std::map<std::string, std::vector<std::string>> &table_name_to_schema_map2)
{
    std::set<std::string> already_processed_table_names;
    for (const auto &table_name1_and_schema1 : table_name_to_schema_map1) {
        const auto &table_name1(table_name1_and_schema1.first);
        already_processed_table_names.emplace(table_name1);
        const auto table_name2_and_schema2(table_name_to_schema_map2.find(table_name1));
        if (table_name2_and_schema2 == table_name_to_schema_map2.cend()) {
            std::cout << "Table was deleted: " << table_name1 << '\n';
            continue;
        }

        const auto &schema1(table_name1_and_schema1.second);
        const auto &schema2(table_name2_and_schema2->second);

        // Compare column definitions first:
        const auto last_column_def1(std::find_if(schema1.crbegin(), schema1.crend(),
                                                 [](const std::string &line){ return not line.empty() and line[0] == '`'; }));
        const auto last_column_def2(std::find_if(schema2.crbegin(), schema2.crend(),
                                                 [](const std::string &line){ return not line.empty() and line[0] == '`'; }));

        std::set<std::string> already_processed_column_names;
        for (auto column_def1(schema1.cbegin()); column_def1 != last_column_def1.base(); ++column_def1) {
            const auto column_name1(ExtractBackQuotedString(*column_def1));
            already_processed_column_names.emplace(column_name1);
            const auto column_def2(std::find_if(schema2.cbegin(), last_column_def2.base(), StartsWithColumnName(column_name1)));
            if (column_def2 == last_column_def2.base())
                std::cout << "Column does not exist in 1st schema: " << table_name1 << '.' << column_name1 << '\n';
            else if (*column_def1 != *column_def2)
                std::cout << "Column definition differs between the 1st and 2nd schemas: " << *column_def1 << " -> " << *column_def2 << '\n';
        }

        for (auto column_def2(schema2.cbegin()); column_def2 != last_column_def2.base(); ++column_def2) {
            const auto column_name2(ExtractBackQuotedString(*column_def2));
            if (already_processed_column_names.find(column_name2) == already_processed_column_names.cend())
                std::cout << "Column exists only in 2nd schema: " << table_name1 << '.' << column_name2 << '\n';
        }
    }

    std::set<std::string> schema2_tables;
    for (const auto &table_name2_and_schema2 : table_name_to_schema_map2) {
        const auto &table_name2(table_name2_and_schema2.first);
        schema2_tables.emplace(table_name2);
        if (already_processed_table_names.find(table_name2) == already_processed_table_names.cend())
            std::cout << "Table exists only in 1st schema: " << table_name2 << '\n';
    }
    for (const auto &table_name1_and_schema1 : table_name_to_schema_map1) {
        const auto &table_name1(table_name1_and_schema1.first);
        if (schema2_tables.find(table_name1) == schema2_tables.cend())
            std::cout << "Table exist only in 2st schema: " << table_name1 << '\n';
    }

    CompareTables("KEY", table_name_to_schema_map1, table_name_to_schema_map2);
    CompareTables("PRIMARY KEY", table_name_to_schema_map1, table_name_to_schema_map2);
    CompareTables("UNIQUE KEY", table_name_to_schema_map1, table_name_to_schema_map2);
    CompareTables("CONSTRAINT", table_name_to_schema_map1, table_name_to_schema_map2);
}

int Main(int argc, char *argv[]) {
    if (argc != 3)
        ::Usage("schema1 schema2");

    std::map<std::string, std::vector<std::string>> table_name_to_schema_map1;
    LoadSchema(argv[1], &table_name_to_schema_map1);

    std::map<std::string, std::vector<std::string>> table_name_to_schema_map2;
    LoadSchema(argv[2], &table_name_to_schema_map2);

    DiffSchemas(table_name_to_schema_map1, table_name_to_schema_map2);

    return EXIT_SUCCESS;
}
<commit_msg>Added comparison of table options.<commit_after>/** \file mysql_diff_schemas.cc
 *  \brief A tool for listing the differences between two schemas.
 *  \author Dr. Johannes Ruscheinski (johannes.ruscheinski@uni-tuebingen.de)
 *
 *  \copyright 2020 Universitätsbibliothek Tübingen.  All rights reserved.
 *
 *  This program 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.
 *
 *  This program 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 Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include <iostream>
#include <map>
#include <set>
#include <vector>
#include <cstdlib>
#include "FileUtil.h"
#include "StringUtil.h"
#include "util.h"

std::string ExtractBackQuotedString(const std::string &s) {
    auto ch(s.cbegin());
    if (unlikely(*ch != '`'))
        LOG_ERROR("\"" + s + "\" does not start with a backtick!");
    ++ch;

    std::string extracted_string;
    while (ch != s.cend() and *ch != '`')
        extracted_string += *ch++;
    if (unlikely(ch == s.cend()))
        LOG_ERROR("\"" + s + "\" does not end with a backtick!");

    return extracted_string;
}

inline bool SchemaLineIsLessThan(const std::string &line1, const std::string &line2) {
    if ((*line1.c_str() == '`') != (*line2.c_str() == '`'))
        return *line1.c_str() == '`';

    return line1 < line2;
}

void LoadSchema(const std::string &filename, std::map<std::string, std::vector<std::string>> * const table_name_to_schema_map) {
    std::string current_table;
    std::vector<std::string> current_schema;
    for (auto line : FileUtil::ReadLines(filename)) {
        StringUtil::Trim(&line);
        if (StringUtil::StartsWith(line, "CREATE TABLE ")) {
            if (not current_table.empty()) {
                std::sort(current_schema.begin(), current_schema.end(), SchemaLineIsLessThan);
                (*table_name_to_schema_map)[current_table] = current_schema;
            }
            current_table = ExtractBackQuotedString(line.substr(__builtin_strlen("CREATE TABLE ")));
            current_schema.clear();
        } else
            current_schema.emplace_back(line);
    }
    if (not current_schema.empty()) {
        std::sort(current_schema.begin(), current_schema.end(), SchemaLineIsLessThan);
        (*table_name_to_schema_map)[current_table] = current_schema;
    }
}

std::set<std::string> FindLinesStartingWithPrefix(const std::vector<std::string> &lines, const std::string &prefix) {
    std::set<std::string> matching_lines;
    for (const auto &line : lines) {
        if (StringUtil::StartsWith(line, prefix))
            matching_lines.emplace(line);
    }
    return matching_lines;
}

// Reports differences for lines that start w/ "prefix" for tables w/ the identical names.
void CompareTables(const std::string &prefix, const std::map<std::string, std::vector<std::string>> &table_name_to_schema_map1,
                   const std::map<std::string, std::vector<std::string>> &table_name_to_schema_map2)
{
    for (const auto &table_name1_and_schema1 : table_name_to_schema_map1) {
        const auto &table_name1(table_name1_and_schema1.first);
        const auto table_name2_and_schema2(table_name_to_schema_map2.find(table_name1));
        if (table_name2_and_schema2 == table_name_to_schema_map2.cend())
            continue;
        const auto &schema2(table_name2_and_schema2->second);

        const std::set<std::string> matching_lines_in_table1(FindLinesStartingWithPrefix(table_name1_and_schema1.second, prefix));
        for (const auto &matching_line_in_table1 : matching_lines_in_table1) {
            const auto matching_line_in_schema2(std::find(schema2.cbegin(), schema2.cend(), matching_line_in_table1));
            if (matching_line_in_schema2 == schema2.cend())
                std::cout << matching_line_in_table1 << " is missing in 2nd schema for table " << table_name1 << '\n';
        }
        for (const auto &line_in_table2 : schema2) {
            if (StringUtil::StartsWith(line_in_table2, prefix)
                and matching_lines_in_table1.find(line_in_table2) == matching_lines_in_table1.cend())
                std::cout << line_in_table2 << " is missing in 1st schema for table " << table_name1 << '\n';
        }
    }
}

class StartsWith {
    std::string prefix_;
public:
    explicit StartsWith(const std::string &prefix): prefix_(prefix) { }
    bool operator ()(const std::string &line) const { return StringUtil::StartsWith(line, prefix_); }
};

class StartsWithColumnName: public StartsWith {
public:
    explicit StartsWithColumnName(const std::string &reference_column_name): StartsWith("`" + reference_column_name + "`") { }
};

void CompareTableOptions(const std::map<std::string, std::vector<std::string>> &table_name_to_schema_map1,
                         const std::map<std::string, std::vector<std::string>> &table_name_to_schema_map2)
{
    for (const auto &table_name1_and_schema1 : table_name_to_schema_map1) {
        const auto &table_name1(table_name1_and_schema1.first);
        const auto table_name2_and_schema2(table_name_to_schema_map2.find(table_name1));
        if (table_name2_and_schema2 == table_name_to_schema_map2.cend())
            continue;

        const auto &schema1(table_name1_and_schema1.second);
        const auto table_options1(std::find_if(schema1.cbegin(), schema1.cend(), StartsWith(") ")));
        if (unlikely(table_options1 == schema1.cend()))
            LOG_ERROR("No table options line for table \"" + table_name1 + "\" found in 1st schema!");

        const auto &schema2(table_name2_and_schema2->second);
        const auto table_options2(std::find_if(schema2.cbegin(), schema2.cend(), StartsWith(") ")));
        if (unlikely(table_options2 == schema2.cend()))
            LOG_ERROR("No table options line for table \"" + table_name1 + "\" found in 2nd schema!");

        if (*table_options1 != *table_options2)
            std::cerr << "Table options differ for " << table_name1 << ": " << table_options1->substr(2) << " -> "
                      << table_options2->substr(2) << '\n';
    }
}

void DiffSchemas(const std::map<std::string, std::vector<std::string>> &table_name_to_schema_map1,
                 const std::map<std::string, std::vector<std::string>> &table_name_to_schema_map2)
{
    std::set<std::string> already_processed_table_names;
    for (const auto &table_name1_and_schema1 : table_name_to_schema_map1) {
        const auto &table_name1(table_name1_and_schema1.first);
        already_processed_table_names.emplace(table_name1);
        const auto table_name2_and_schema2(table_name_to_schema_map2.find(table_name1));
        if (table_name2_and_schema2 == table_name_to_schema_map2.cend()) {
            std::cout << "Table was deleted: " << table_name1 << '\n';
            continue;
        }

        const auto &schema1(table_name1_and_schema1.second);
        const auto &schema2(table_name2_and_schema2->second);

        // Compare column definitions first:
        const auto last_column_def1(std::find_if(schema1.crbegin(), schema1.crend(),
                                                 [](const std::string &line){ return not line.empty() and line[0] == '`'; }));
        const auto last_column_def2(std::find_if(schema2.crbegin(), schema2.crend(),
                                                 [](const std::string &line){ return not line.empty() and line[0] == '`'; }));

        std::set<std::string> already_processed_column_names;
        for (auto column_def1(schema1.cbegin()); column_def1 != last_column_def1.base(); ++column_def1) {
            const auto column_name1(ExtractBackQuotedString(*column_def1));
            already_processed_column_names.emplace(column_name1);
            const auto column_def2(std::find_if(schema2.cbegin(), last_column_def2.base(), StartsWithColumnName(column_name1)));
            if (column_def2 == last_column_def2.base())
                std::cout << "Column does not exist in 1st schema: " << table_name1 << '.' << column_name1 << '\n';
            else if (*column_def1 != *column_def2)
                std::cout << "Column definition differs between the 1st and 2nd schemas: " << *column_def1 << " -> " << *column_def2 << '\n';
        }

        for (auto column_def2(schema2.cbegin()); column_def2 != last_column_def2.base(); ++column_def2) {
            const auto column_name2(ExtractBackQuotedString(*column_def2));
            if (already_processed_column_names.find(column_name2) == already_processed_column_names.cend())
                std::cout << "Column exists only in 2nd schema: " << table_name1 << '.' << column_name2 << '\n';
        }
    }

    std::set<std::string> schema2_tables;
    for (const auto &table_name2_and_schema2 : table_name_to_schema_map2) {
        const auto &table_name2(table_name2_and_schema2.first);
        schema2_tables.emplace(table_name2);
        if (already_processed_table_names.find(table_name2) == already_processed_table_names.cend())
            std::cout << "Table exists only in 1st schema: " << table_name2 << '\n';
    }
    for (const auto &table_name1_and_schema1 : table_name_to_schema_map1) {
        const auto &table_name1(table_name1_and_schema1.first);
        if (schema2_tables.find(table_name1) == schema2_tables.cend())
            std::cout << "Table exist only in 2st schema: " << table_name1 << '\n';
    }

    CompareTables("KEY", table_name_to_schema_map1, table_name_to_schema_map2);
    CompareTables("PRIMARY KEY", table_name_to_schema_map1, table_name_to_schema_map2);
    CompareTables("UNIQUE KEY", table_name_to_schema_map1, table_name_to_schema_map2);
    CompareTables("CONSTRAINT", table_name_to_schema_map1, table_name_to_schema_map2);
    CompareTableOptions(table_name_to_schema_map1, table_name_to_schema_map2);
}

int Main(int argc, char *argv[]) {
    if (argc != 3)
        ::Usage("schema1 schema2\n"
                "Please note that this tool may not work particularly well if you do not use output from mysql_list_tables");

    std::map<std::string, std::vector<std::string>> table_name_to_schema_map1;
    LoadSchema(argv[1], &table_name_to_schema_map1);

    std::map<std::string, std::vector<std::string>> table_name_to_schema_map2;
    LoadSchema(argv[2], &table_name_to_schema_map2);

    DiffSchemas(table_name_to_schema_map1, table_name_to_schema_map2);

    return EXIT_SUCCESS;
}
<|endoftext|>