| // Copyright 2016 The Fuchsia Authors |
| // |
| // 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. |
| |
| #ifndef COBALT_CONFIG_CONFIG_H_ |
| #define COBALT_CONFIG_CONFIG_H_ |
| |
| #include <fcntl.h> |
| #include <google/protobuf/io/tokenizer.h> |
| #include <google/protobuf/io/zero_copy_stream_impl.h> |
| #include <google/protobuf/text_format.h> |
| |
| #include <memory> |
| #include <string> |
| #include <unordered_map> |
| #include <utility> |
| |
| namespace cobalt { |
| namespace config { |
| |
| enum Status { |
| kOK = 0, |
| |
| // The specified file could not be opened. |
| kFileOpenError = 1, |
| |
| // The specified file could not be parsed as the appropriate type |
| // of protocol message. |
| kParsingError = 2, |
| |
| // The specified file could be parsed but it contained two different |
| // objects with the same fully-qualified ID. |
| kDuplicateRegistration = 3 |
| }; |
| |
| // A template for a Registry. |
| // |
| // RT should be one of: |
| // RegisteredEncodings, RegisteredReports, RegisteredMetrics. |
| // |
| // We then define |T| to be the corresponding one of: |
| // EncodingConfig, ReportConfig, Metric. |
| // |
| // A Registry<RT> is then a container for all of the |T|s registered in Cobalt. |
| template<class RT> class Registry { |
| public: |
| // This is some template meta-programming magic that has the effect of |
| // defining |T| to be the type of the objects contained in a registry |
| // of type |RT|. |
| typedef typename std::remove_pointer<decltype( |
| (reinterpret_cast<RT*>(0))->mutable_element(0))>::type T; |
| |
| private: |
| // The container for the registry. The keys in this map are strings that |
| // encode ID triples of the form (customer_id, project_id, id). |
| typedef std::unordered_map<std::string, std::unique_ptr<T>> Map; |
| |
| public: |
| // Iterator for going through registry items. We just use the Map iterator |
| // but return only values (without keys). |
| class RegistryIterator { |
| public: |
| explicit RegistryIterator(const typename Map::iterator& iter) |
| : iter_(iter) {} |
| |
| const T& operator*() const { return *iter_->second; } |
| |
| bool operator!=(const RegistryIterator& rhs) const { |
| return iter_ != rhs.iter_; |
| } |
| |
| const RegistryIterator& operator++() { |
| ++iter_; |
| return *this; |
| } |
| |
| private: |
| typename Map::iterator iter_; |
| }; |
| typedef RegistryIterator iterator; |
| |
| // Populates a new instance of Registry<T> by reading and parsing the |
| // specified file. Returns a pair consisting of a pointer to the result and a |
| // Status. |
| // |
| // If the operation is successful then the status is kOK. Otherwise the |
| // Status indicates the error. |
| // |
| // If |error_collector| is not null then it will be notified of any parsing |
| // errors or warnings. |
| static std::pair<std::unique_ptr<Registry<RT>>, Status> |
| FromFile(const std::string& file_path, |
| google::protobuf::io::ErrorCollector* error_collector); |
| |
| // Populates a new instance of Registry<T> by reading and parsing the |
| // specified string. Returns a pair consisting of a pointer to the result and |
| // a Status. |
| // |
| // If the operation is successful then the status is kOK. Otherwise the |
| // Status indicates the error. |
| // |
| // If |error_collector| is not null then it will be notified of any parsing |
| // errors or warnings. |
| static std::pair<std::unique_ptr<Registry<RT>>, Status> |
| FromString(const std::string& contents, |
| google::protobuf::io::ErrorCollector* error_collector); |
| |
| // Returns the number of |T| in this registry. |
| size_t size(); |
| |
| // Returns the |T| with the given ID triple, or nullptr if there is |
| // no such |T|. The caller does not take ownership of the returned |
| // pointer. |
| const T* const Get(uint32_t customer_id, |
| uint32_t project_id, |
| uint32_t id) { |
| auto iterator = map_.find(MakeKey(customer_id, project_id, id)); |
| if (iterator == map_.end()) { |
| return nullptr; |
| } |
| return iterator->second.get(); |
| } |
| |
| // Provide a mechansim to iterate through all registry items. |
| RegistryIterator begin() { return RegistryIterator(map_.begin()); } |
| RegistryIterator end() { return RegistryIterator(map_.end()); } |
| |
| private: |
| // Builds a map key that encodes the triple (customer_id, project_id, id) |
| static std::string MakeKey(uint32_t customer_id, uint32_t project_id, |
| uint32_t id); |
| |
| // Builds a map key from the ids in |config_proto|. |
| static std::string MakeKey(const T& config_proto); |
| |
| // The keys in this map are strings that encode ID triples of the form |
| // (customer_id, project_id, id) |
| Map map_; |
| }; |
| |
| ////////////////////////////////////////////////////////////////// |
| /// IMPLEMENTATION BELOW |
| ////////////////////////////////////////////////////////////////// |
| |
| template<class RT> |
| std::string Registry<RT>::MakeKey(uint32_t customer_id, uint32_t project_id, |
| uint32_t id) { |
| // Three 32-bit positive ints (at most 10 digits each) plus 3 colons plus a |
| // trailing null is <= 34 bytes. |
| char out[34]; |
| int size = snprintf(out, sizeof(out), "%u:%u:%u", |
| customer_id, project_id, id); |
| if (size <= 0) { |
| return ""; |
| } |
| return std::string(out, size); |
| } |
| |
| template<class RT> |
| std::string Registry<RT>::MakeKey(const T& config_proto) { |
| return MakeKey(config_proto.customer_id(), config_proto.project_id(), |
| config_proto.id()); |
| } |
| |
| template<class RT> |
| std::pair<std::unique_ptr<Registry<RT>>, Status> |
| Registry<RT>::FromFile(const std::string& file_path, |
| google::protobuf::io::ErrorCollector* error_collector) { |
| // Make an empty registry to return; |
| std::unique_ptr<Registry<RT>> registry(new Registry<RT>()); |
| |
| // Try to open the specified file. |
| int fd = open(file_path.c_str(), O_RDONLY); |
| if (fd < 0) { |
| return std::make_pair(std::move(registry), kFileOpenError); |
| } |
| |
| // Try to parse the specified file. |
| google::protobuf::io::FileInputStream file_input_stream(fd); |
| file_input_stream.SetCloseOnDelete(true); |
| // The contents of the file should be a serialized |RT|. |
| RT registered_configs; |
| google::protobuf::TextFormat::Parser parser; |
| if (error_collector) { |
| parser.RecordErrorsTo(error_collector); |
| } |
| if (!parser.Parse(&file_input_stream, ®istered_configs)) { |
| return std::make_pair(std::move(registry), kParsingError); |
| } |
| |
| // Put all of the T's into the map, ensuring that the id triples |
| // are unique. |
| int num_configs = registered_configs.element_size(); |
| for (int i = 0; i < num_configs; i++) { |
| T* config_proto = registered_configs.mutable_element(i); |
| // First build the key and insert an empty Tg into the map |
| // at that key. |
| auto pair = registry->map_.insert(std::make_pair(MakeKey(*config_proto), |
| std::unique_ptr<T>(new T()))); |
| const bool& success = pair.second; |
| auto& inserted_pair = pair.first; |
| if (!success) { |
| return std::make_pair(std::move(registry), kDuplicateRegistration); |
| } |
| // Then swap in the data from the T; |
| inserted_pair->second->Swap(config_proto); |
| } |
| return std::make_pair(std::move(registry), kOK); |
| } |
| |
| template<class RT> |
| std::pair<std::unique_ptr<Registry<RT>>, Status> |
| Registry<RT>::FromString(const std::string& input, |
| google::protobuf::io::ErrorCollector* error_collector) { |
| // Make an empty registry to return; |
| std::unique_ptr<Registry<RT>> registry(new Registry<RT>()); |
| |
| // Try to parse the specified string |
| // The contents of the string should be a serialized |RT|. |
| RT registered_configs; |
| google::protobuf::TextFormat::Parser parser; |
| if (error_collector) { |
| parser.RecordErrorsTo(error_collector); |
| } |
| if (!parser.ParseFromString(input, ®istered_configs)) { |
| return std::make_pair(std::move(registry), kParsingError); |
| } |
| |
| // Put all of the T's into the map, ensuring that the id triples |
| // are unique. |
| int num_configs = registered_configs.element_size(); |
| for (int i = 0; i < num_configs; i++) { |
| T* config_proto = registered_configs.mutable_element(i); |
| // First build the key and insert an empty Tg into the map |
| // at that key. |
| auto pair = registry->map_.insert(std::make_pair(MakeKey(*config_proto), |
| std::unique_ptr<T>(new T()))); |
| const bool& success = pair.second; |
| auto& inserted_pair = pair.first; |
| if (!success) { |
| return std::make_pair(std::move(registry), kDuplicateRegistration); |
| } |
| // Then swap in the data from the T; |
| inserted_pair->second->Swap(config_proto); |
| } |
| return std::make_pair(std::move(registry), kOK); |
| } |
| |
| template<class RT> |
| size_t Registry<RT>::size() { |
| return map_.size(); |
| } |
| |
| } // namespace config |
| } // namespace cobalt |
| |
| #endif // COBALT_CONFIG_CONFIG_H_ |