Goby v2
dccl_transitional.cpp
1 // Copyright 2009-2018 Toby Schneider (http://gobysoft.org/index.wt/people/toby)
2 // GobySoft, LLC (2013-)
3 // Massachusetts Institute of Technology (2007-2014)
4 // Community contributors (see AUTHORS file)
5 //
6 //
7 // This file is part of the Goby Underwater Autonomy Project Libraries
8 // ("The Goby Libraries").
9 //
10 // The Goby Libraries are free software: you can redistribute them and/or modify
11 // them under the terms of the GNU Lesser General Public License as published by
12 // the Free Software Foundation, either version 2.1 of the License, or
13 // (at your option) any later version.
14 //
15 // The Goby Libraries are distributed in the hope that they will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU Lesser General Public License for more details.
19 //
20 // You should have received a copy of the GNU Lesser General Public License
21 // along with Goby. If not, see <http://www.gnu.org/licenses/>.
22 
23 #include <boost/filesystem.hpp>
24 #include <boost/foreach.hpp>
25 
26 #include "dccl_transitional.h"
27 #include "goby/common/logger.h"
28 #include "goby/util/as.h"
29 #include "goby/util/dynamic_protobuf_manager.h"
30 #include "message_xml_callbacks.h"
31 #include "queue_xml_callbacks.h"
32 #include <boost/regex.hpp>
33 #include <google/protobuf/descriptor.pb.h>
34 
36 using goby::util::as;
37 using goby::acomms::operator<<;
38 using namespace goby::common::logger;
39 
41 // public methods (general use)
44  : log_(0), dccl_(goby::acomms::DCCLCodec::get()), start_time_(goby_time())
45 {
46  goby::util::DynamicProtobufManager::enable_compilation();
47 }
48 
49 void goby::transitional::DCCLTransitionalCodec::convert_to_v2_representation(
51 {
52  cfg_ = cfg->transitional_cfg();
53 
54  for (int i = 0, n = cfg->transitional_cfg().message_file_size(); i < n; ++i)
55  {
56  convert_xml_message_file(cfg->transitional_cfg().message_file(i),
57  cfg->add_load_proto_file(), cfg->mutable_translator_entry(),
58  cfg->mutable_queue_cfg());
59  }
60 }
61 
62 void goby::transitional::DCCLTransitionalCodec::convert_xml_message_file(
63  const goby::transitional::protobuf::MessageFile& message_file, std::string* proto_file,
64  google::protobuf::RepeatedPtrField<goby::moos::protobuf::TranslatorEntry>* translator_entries,
66 {
67  const std::string& xml_file = message_file.path();
68 
69  size_t begin_size = messages_.size();
70 
71  // Register handlers for XML parsing
72  DCCLMessageContentHandler message_content(messages_);
73  DCCLMessageErrorHandler message_error;
74  // instantiate a parser for the xml message files
75  XMLParser message_parser(message_content, message_error);
76 
77  std::vector<goby::transitional::protobuf::QueueConfig> old_queue_cfg;
78  QueueContentHandler queue_content(old_queue_cfg);
79  QueueErrorHandler queue_error;
80  // instantiate a parser for the xml message files
81  XMLParser queue_parser(queue_content, queue_error);
82 
83  message_parser.parse(xml_file);
84  queue_parser.parse(xml_file);
85 
86  size_t end_size = messages_.size();
87 
88  check_duplicates();
89 
90  std::vector<unsigned> added_ids;
91  std::set<unsigned> set_added_ids;
92 
93  boost::filesystem::path xml_file_path(xml_file);
94 
95  std::string generated_proto_dir = cfg_.generated_proto_dir();
96  if (!generated_proto_dir.empty() && generated_proto_dir[generated_proto_dir.size() - 1] != '/')
97  generated_proto_dir += "/";
98 
99 #if BOOST_FILESYSTEM_VERSION == 3
100  std::string xml_path_stem = xml_file_path.stem().string();
101  boost::filesystem::path proto_file_path =
102  boost::filesystem::absolute(generated_proto_dir + xml_path_stem + ".proto");
103 #else
104  std::string xml_path_stem = xml_file_path.stem();
105  boost::filesystem::path proto_file_path =
106  boost::filesystem::complete(generated_proto_dir + xml_path_stem + ".proto");
107 #endif
108 
109  proto_file_path.normalize();
110 
111  for (size_t i = 0, n = end_size - begin_size; i < n; ++i)
112  {
113  // map name/id to position in messages_ vector for later use
114  size_t new_index = messages_.size() - (n - i);
115  name2messages_.insert(
116  std::pair<std::string, size_t>(messages_[new_index].name(), new_index));
117  id2messages_.insert(std::pair<unsigned, size_t>(messages_[new_index].id(), new_index));
118 
119  set_added_ids.insert(messages_[new_index].id());
120  added_ids.push_back(messages_[new_index].id());
121  }
122 
123  *proto_file = proto_file_path.string();
124 
125  std::ofstream fout(proto_file->c_str(), std::ios::out);
126 
127  if (!fout.is_open())
128  throw(goby::acomms::DCCLException("Could not open " + *proto_file + " for writing"));
129 
130  fout << "import \"dccl/option_extensions.proto\";" << std::endl;
131  fout << "import \"goby/common/protobuf/option_extensions.proto\";" << std::endl;
132 
133  for (int i = 0, n = added_ids.size(); i < n; ++i)
134  {
135  to_iterator(added_ids[i])->write_schema_to_dccl2(&fout);
136 
137  goby::acomms::protobuf::QueuedMessageEntry* queue_entry = queue_cfg->add_message_entry();
138  queue_entry->set_protobuf_name(to_iterator(added_ids[i])->name());
139  if (old_queue_cfg[i].has_ack())
140  queue_entry->set_ack(old_queue_cfg[i].ack());
141  if (old_queue_cfg[i].has_blackout_time())
142  queue_entry->set_blackout_time(old_queue_cfg[i].blackout_time());
143  if (old_queue_cfg[i].has_max_queue())
144  queue_entry->set_max_queue(old_queue_cfg[i].max_queue());
145  if (old_queue_cfg[i].has_newest_first())
146  queue_entry->set_newest_first(old_queue_cfg[i].newest_first());
147  if (old_queue_cfg[i].has_value_base())
148  queue_entry->set_value_base(old_queue_cfg[i].value_base());
149  if (old_queue_cfg[i].has_ttl())
150  queue_entry->set_ttl(old_queue_cfg[i].ttl());
151 
152  goby::acomms::protobuf::QueuedMessageEntry::Role* src_role = queue_entry->add_role();
153  src_role->set_type(goby::acomms::protobuf::QueuedMessageEntry::SOURCE_ID);
154  src_role->set_field(to_iterator(added_ids[i])->header_var(HEAD_SRC_ID).name());
155 
156  goby::acomms::protobuf::QueuedMessageEntry::Role* dest_role = queue_entry->add_role();
157  dest_role->set_type(goby::acomms::protobuf::QueuedMessageEntry::DESTINATION_ID);
158  dest_role->set_field(to_iterator(added_ids[i])->header_var(HEAD_DEST_ID).name());
159 
160  goby::acomms::protobuf::QueuedMessageEntry::Role* time_role = queue_entry->add_role();
161  time_role->set_type(goby::acomms::protobuf::QueuedMessageEntry::TIMESTAMP);
162  time_role->set_field(to_iterator(added_ids[i])->header_var(HEAD_TIME).name());
163 
164  for (int i = 0, n = message_file.manipulator_size(); i < n; ++i)
165  queue_entry->add_manipulator(message_file.manipulator(i));
166  }
167 
168  fout.close();
169 
170  const google::protobuf::FileDescriptor* file_desc =
171  goby::util::DynamicProtobufManager::user_descriptor_pool().FindFileByName(*proto_file);
172 
173  if (file_desc)
174  {
175  glog.is(DEBUG2) && glog << file_desc->DebugString() << std::flush;
176 
177  BOOST_FOREACH (unsigned id, added_ids)
178  {
179  const google::protobuf::Descriptor* desc =
180  file_desc->FindMessageTypeByName(to_iterator(id)->name());
181 
182  if (desc)
183  dccl_->validate(desc);
184  else
185  {
186  glog << die << "No descriptor with name " << to_iterator(id)->name() << " found!"
187  << std::endl;
188  }
189 
190  to_iterator(id)->set_descriptor(desc);
191  }
192  }
193  else
194  {
195  glog << die << "No file_descriptor with name " << *proto_file << " found!" << std::endl;
196  }
197 
198  BOOST_FOREACH (unsigned id, added_ids)
199  {
200  goby::moos::protobuf::TranslatorEntry* entry = translator_entries->Add();
201  entry->set_use_short_enum(true);
202  std::vector<DCCLMessage>::const_iterator msg_it = to_iterator(id);
203  entry->set_protobuf_name(msg_it->name());
204 
205  if (msg_it->trigger_type() == "time")
206  {
207  entry->mutable_trigger()->set_type(
208  goby::moos::protobuf::TranslatorEntry::Trigger::TRIGGER_TIME);
209  entry->mutable_trigger()->set_period(msg_it->trigger_time());
210  }
211  else if (msg_it->trigger_type() == "publish")
212  {
213  entry->mutable_trigger()->set_type(
214  goby::moos::protobuf::TranslatorEntry::Trigger::TRIGGER_PUBLISH);
215  entry->mutable_trigger()->set_moos_var(msg_it->trigger_var());
216  if (!msg_it->trigger_mandatory().empty())
217  entry->mutable_trigger()->set_mandatory_content(msg_it->trigger_mandatory());
218  }
219 
220  std::map<std::string, goby::moos::protobuf::TranslatorEntry::CreateParser*> parser_map;
221  std::map<std::string, int> sequence_map;
222  int max_sequence = 0;
223  BOOST_FOREACH (boost::shared_ptr<DCCLMessageVar> var, msg_it->header_const())
224  {
225  sequence_map[var->name()] = var->sequence_number();
226  max_sequence =
227  (var->sequence_number() > max_sequence) ? var->sequence_number() : max_sequence;
228 
229  fill_create(var, &parser_map, entry);
230  }
231  BOOST_FOREACH (boost::shared_ptr<DCCLMessageVar> var, msg_it->layout_const())
232  {
233  sequence_map[var->name()] = var->sequence_number();
234  max_sequence =
235  (var->sequence_number() > max_sequence) ? var->sequence_number() : max_sequence;
236 
237  fill_create(var, &parser_map, entry);
238  }
239 
240  max_sequence = ((max_sequence + 100) / 100) * 100;
241 
242  for (int i = 0, n = msg_it->publishes_const().size(); i < n; ++i)
243  {
244  const DCCLPublish& publish = msg_it->publishes_const()[i];
245 
247  entry->add_publish();
248  serializer->set_technique(goby::moos::protobuf::TranslatorEntry::TECHNIQUE_FORMAT);
249 
250  // renumber format numbers
251  boost::regex pattern("%([0-9]+)",
252  boost::regex_constants::icase | boost::regex_constants::perl);
253  std::string replace("%_GOBY1TEMP_\\1_");
254 
255  std::string new_moos_var = boost::regex_replace(publish.var(), pattern, replace);
256 
257  std::string old_format = publish.format();
258  std::string new_format = boost::regex_replace(old_format, pattern, replace);
259 
260  int v1_index = 1;
261  for (int j = 0, m = publish.message_vars().size(); j < m; ++j)
262  {
263  for (int k = 0, o = publish.message_vars()[j]->array_length(); k < o; ++k)
264  {
265  int replacement_field = sequence_map[publish.message_vars()[j]->name()];
266 
267  // add any algorithms
268  bool added_algorithms = false;
269  BOOST_FOREACH (const std::string& algorithm, publish.algorithms()[j])
270  {
271  added_algorithms = true;
273  new_alg = serializer->add_algorithm();
274  std::vector<std::string> algorithm_parts;
275  boost::split(algorithm_parts, algorithm, boost::is_any_of(":"));
276 
277  new_alg->set_name(algorithm_parts[0]);
278  new_alg->set_primary_field(sequence_map[publish.message_vars()[j]->name()]);
279 
280  for (int l = 1, p = algorithm_parts.size(); l < p; ++l)
281  new_alg->add_reference_field(sequence_map[algorithm_parts[l]]);
282 
283  new_alg->set_output_virtual_field(max_sequence);
284  }
285 
286  if (added_algorithms)
287  {
288  replacement_field = max_sequence;
289  ++max_sequence;
290  }
291 
292  std::string v2_index = goby::util::as<std::string>(replacement_field);
293  if (o > 1)
294  v2_index += "." + goby::util::as<std::string>(k);
295 
296  boost::algorithm::replace_all(
297  new_moos_var, "%_GOBY1TEMP_" + goby::util::as<std::string>(v1_index) + "_",
298  "%" + v2_index);
299  boost::algorithm::replace_all(
300  new_format, "%_GOBY1TEMP_" + goby::util::as<std::string>(v1_index) + "_",
301  "%" + v2_index);
302 
303  ++v1_index;
304  }
305  }
306  serializer->set_moos_var(new_moos_var);
307  serializer->set_format(new_format);
308  }
309  }
310 }
311 
312 void goby::transitional::DCCLTransitionalCodec::fill_create(
313  boost::shared_ptr<DCCLMessageVar> var,
314  std::map<std::string, goby::moos::protobuf::TranslatorEntry::CreateParser*>* parser_map,
316 {
317  // entry already exists for this type
318  if ((*parser_map).count(var->source_var()))
319  {
320  // use key=value parsing
321  (*parser_map)[var->source_var()]->set_technique(
323  TECHNIQUE_COMMA_SEPARATED_KEY_EQUALS_VALUE_PAIRS);
324  (*parser_map)[var->source_var()]->clear_format();
325  }
326  else if (!var->source_var().empty())
327  {
328  (*parser_map)[var->source_var()] = entry->add_create();
329  (*parser_map)[var->source_var()]->set_format(
330  "%" + goby::util::as<std::string>(var->sequence_number()) + "%");
331  (*parser_map)[var->source_var()]->set_technique(
332  goby::moos::protobuf::TranslatorEntry::TECHNIQUE_FORMAT);
333  (*parser_map)[var->source_var()]->set_moos_var(var->source_var());
334  }
335 
336  // add any algorithms
337  BOOST_FOREACH (const std::string& algorithm, var->algorithms())
338  {
339  if (var->source_var().empty())
340  throw(std::runtime_error("Algorithms without a corresponding source variable are not "
341  "permitted in Goby v2 --> v1 backwards compatibility as the "
342  "MOOS Translator does all algorithm conversions in Goby2"));
343 
345  (*parser_map)[var->source_var()]->add_algorithm();
346 
347  if (algorithm.find(':') != std::string::npos)
348  {
349  throw(std::runtime_error(
350  "Algorithms with reference fields (e.g. subtract:timestamp) are not permitted in "
351  "Goby v2 --> v1 backwards compatibility. Please redesign the XML message to remove "
352  "such algorithms. Offending algorithm: " +
353  algorithm + " used in variable: " + var->name()));
354  }
355 
356  new_alg->set_name(algorithm);
357  new_alg->set_primary_field(var->sequence_number());
358  }
359 }
360 
361 std::ostream& goby::transitional::operator<<(std::ostream& out, const std::set<std::string>& s)
362 {
363  out << "std::set<std::string>:" << std::endl;
364  for (std::set<std::string>::const_iterator it = s.begin(), n = s.end(); it != n; ++it)
365  out << (*it) << std::endl;
366  return out;
367 }
368 
369 std::ostream& goby::transitional::operator<<(std::ostream& out, const std::set<unsigned>& s)
370 {
371  out << "std::set<unsigned>:" << std::endl;
372  for (std::set<unsigned>::const_iterator it = s.begin(), n = s.end(); it != n; ++it)
373  out << (*it) << std::endl;
374  return out;
375 }
376 
378 // private methods
380 
381 void goby::transitional::DCCLTransitionalCodec::check_duplicates()
382 {
383  std::map<unsigned, std::vector<DCCLMessage>::iterator> all_ids;
384  for (std::vector<DCCLMessage>::iterator it = messages_.begin(), n = messages_.end(); it != n;
385  ++it)
386  {
387  unsigned id = it->id();
388 
389  std::map<unsigned, std::vector<DCCLMessage>::iterator>::const_iterator id_it =
390  all_ids.find(id);
391  if (id_it != all_ids.end())
392  throw goby::acomms::DCCLException(
393  std::string("DCCL: duplicate variable id " + as<std::string>(id) +
394  " specified for " + it->name() + " and " + id_it->second->name()));
395 
396  all_ids.insert(std::pair<unsigned, std::vector<DCCLMessage>::iterator>(id, it));
397  }
398 }
399 
400 std::vector<goby::transitional::DCCLMessage>::const_iterator
401 goby::transitional::DCCLTransitionalCodec::to_iterator(const std::string& message_name) const
402 {
403  if (name2messages_.count(message_name))
404  return messages_.begin() + name2messages_.find(message_name)->second;
405  else
406  throw goby::acomms::DCCLException(std::string("DCCL: attempted an operation on message [" +
407  message_name + "] which is not loaded"));
408 }
409 std::vector<goby::transitional::DCCLMessage>::iterator
410 goby::transitional::DCCLTransitionalCodec::to_iterator(const std::string& message_name)
411 {
412  if (name2messages_.count(message_name))
413  return messages_.begin() + name2messages_.find(message_name)->second;
414  else
415  throw goby::acomms::DCCLException(std::string("DCCL: attempted an operation on message [" +
416  message_name + "] which is not loaded"));
417 }
418 std::vector<goby::transitional::DCCLMessage>::const_iterator
419 goby::transitional::DCCLTransitionalCodec::to_iterator(const unsigned& id) const
420 {
421  if (id2messages_.count(id))
422  return messages_.begin() + id2messages_.find(id)->second;
423  else
424  throw goby::acomms::DCCLException(std::string("DCCL: attempted an operation on message [" +
425  as<std::string>(id) +
426  "] which is not loaded"));
427 }
428 
429 std::vector<goby::transitional::DCCLMessage>::iterator
430 goby::transitional::DCCLTransitionalCodec::to_iterator(const unsigned& id)
431 {
432  if (id2messages_.count(id))
433  return messages_.begin() + id2messages_.find(id)->second;
434  else
435  throw goby::acomms::DCCLException(std::string("DCCL: attempted an operation on message [" +
436  as<std::string>(id) +
437  "] which is not loaded"));
438 }
ReturnType goby_time()
Returns current UTC time as a boost::posix_time::ptime.
Definition: time.h:104
DCCLTransitionalCodec()
Instantiate optionally with a ostream logger (for human readable output)
common::FlexOstream glog
Access the Goby logger through this object.
The global namespace for the Goby project.