JournalStream.hh 17.8 KB
Newer Older
1
// Copyright 2020 Autodesk, Inc. All rights reserved.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//
// This computer source code and related instructions and comments are the
// unpublished confidential and proprietary information of Autodesk, Inc.
// and are protected under applicable copyright and trade secret law. They
// may not be disclosed to, copied or used by any third party without the
// prior written consent of Autodesk, Inc.

#ifndef BASE_JOURNAL_STREAM_HH_INCLUDED
#define BASE_JOURNAL_STREAM_HH_INCLUDED

#ifdef JOURNAL_ON 

#include <Base/Utils/IOutputStream.hh>

#include <string>
#include <vector>
18
#include <type_traits>
19

20
21
namespace Journal 
{
22
23
typedef std::string String;

24
using Base::ENDL;
25
26
using Base::IOutputStream;

27
28
29
typedef size_t Key;
const Key INVALID_KEY = 0;

30
31
32
33
template <class T> Key key(const T* const _pntr)
{
  return reinterpret_cast<Key>(_pntr);
}
34
35
36
37

// Wrapper for data as text from any type, returned from the Journal::define()
struct Data
{
38
39
  String str; // reference string

40
41
42
43
44
45
46
47
48
49
  enum class Type
  {
    INTERNAL,
    ASSIGN,
    CONSTRUCT
  };

  Type type = Type::INTERNAL; // data type 

  Data() {} // opaque data
50

51
52
53
54
55
56
  explicit Data(const String& _str, const Type _type = Type::ASSIGN)
      : str(_str), type(_type)
  {
  }

  bool valid() const { return !str.empty(); }
57
58
59
60
};

typedef std::vector<Data> DataVector;

61
62
63
64
65
66
67
68
69
70
71
72
73
//! Enumerate the Languages supported by Journal
enum class Language
{
  CPP,
  SCHEME
};

class Stream;

/*!
Include one of the Journal[Cpp|Scheme]Defs.hh files to get an implementation
of this function for the selected output language.
*/
74
template <typename T> Data define_in_language(Stream& _strm, const T& _arg);
75

76
77
78
79
//! the journal stream class, used when streaming from define() specializations
class Stream
{
public:
80
81
82
  // do not call constructor/destructor directly, use \ref set_on() instead
  Stream(const Language _lngg, const char* const _cmpn_name,
      const char* const _base_path);
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
  ~Stream();

  // disable copy & assignment
  Stream(const Stream&) = delete;
  Stream& operator=(const Stream&) = delete;

  //! Get the journal output path
  String output_path() const;

  //! Get the next available filename based on _name to use by the journal
  String next_filename(const char* const _name);

  //! Put the "end line" combination for the journal, e.g., ";\n" for C/C++
  void end_line();

  //! Make an unique journal name which can be linked to a pointer/key later 
  String make_unique_name(const char* const _name);

  //! Book an unique journal name which can be linked to a pointer/key later 
  const char* book_unique_name(const char* const _name);

  //! Link to a pointer/key to a pre-booked unique journal name
  void link_unique_name(const Key _key, const char* const _name);

107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
  /*!
  Search for the unique name corresponding to the key, return nullptr if the key 
  has not been linked.
  */
  const char* search_unique_name(const Key _key) const;

  /*!
  Same as \ref search_unique_name(), but emits an error if no name is found and
  returns a pointer to an invalid journal name. Useful when expecting the key to
  be linked already and signals a journal or client error.
  */
  const char* retrieve_unique_name(const Key _key);

  /*!
  Same as \ref search_unique_name(), but allocates a new unique name based on
  _name and links it to the key if no name has been linked to this key already.
  \return true if the name has been added or false otherwise.
  */
  bool obtain_unique_name(
      const Key _key, const char* const _name_base, const char*& _name);
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163

  //! Journal a constructor
  template <class ObjectT, typename... ArgT> 
  void constructor(const char* const _fnct, const ObjectT* _obj,
    const ArgT&... _args)
  {
    emit_constructor(_fnct, object(_obj), arguments(_args...));
  }

  //! Journal a destructor
  template <class ObjectT> 
  void destructor(const char* const _fnct, const ObjectT* _obj)
  {
    emit_destructor(_fnct, object(_obj));
  }

  //! Journal a method returning outcome
  template <class ObjectT, typename... ArgT> 
  void method_outcome(const char* const _fnct, const ObjectT* _obj, 
    const ArgT&... _args)
  {
    emit_method_outcome(_fnct, object(_obj), arguments(_args...));
  }

  //! Journal a void method
  template <class ObjectT, typename... ArgT> 
  void method_void(const char* const _fnct, const ObjectT* _obj, 
    const ArgT&... _args)
  {
    emit_method_void(_fnct, object(_obj), arguments(_args...));
  }

  //! Journal a method returning any type
  template <typename ReturnT, class ObjectT, typename... ArgT>
  void method(const char* const _fnct, const ReturnT& _rtrn,
      const ObjectT* _obj, const ArgT&... _args)
  {
164
    emit_method(_fnct, dispatch_define(_rtrn), object(_obj),
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
        arguments(_args...));
  }

  //! Journal a function returning outcome
  template <typename... ArgT> 
  void function_outcome(const char* const _fnct, const ArgT&... _args)
  {
    emit_function_outcome(_fnct, arguments(_args...));
  }

  //! Journal a void function
  template <typename... ArgT> 
  void function_void(const char* const _fnct, const ArgT&... _args)
  {
    emit_function_void(_fnct, arguments(_args...));
  }

  //! Journal a function returning any type
  template <typename ReturnT, typename... ArgT>
  void function(
      const char* const _fnct, const ReturnT& _rtrn, const ArgT&... _args)
  {
187
    emit_function(_fnct, dispatch_define(_rtrn), arguments(_args...));
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
  }

  //! Retrieve the key for any data 
  template <class ObjectT> 
  Key object(const ObjectT* _obj) const
  {// don't need a type name here as we can extract this from the key
    return key(_obj);
  }

  //! Journal a comment start, and return os() to complete it
  Base::IOutputStream& comment_os();

  //! Journal an include file
  void include(const char* const _flnm);

  //! Journal a blank line
  void blank_line();

  /*! 
  Journal a path transform function which allows all filenames to be
  transformed before passed as arguments to journaled functions.
  This allows journal data stored as files to be loaded from other locations.
  
  In C/C++ journals the transform is #define PATH(FLNM) _path_trns.
  If no path transform is journaled, the transform is identity.
  */
  void set_path_transform(const char* const _trns);

  /*!
  Transforms the path with a previously set path transform, or leaves it as
  it is if no transform is set.
  */
  void transform_path(String& _path) const;

222
223
224
225
  /*!
  Journal the start of the main() function (C++ specific). The signature can be
  customized or if left blank is set to int main(). 
  \optionally specify if the main function is void, or returns int (default). 
226
  */
227
228
  void main(
      const char* const _main_fnct = nullptr, const bool _rtrn_int = true);
229
230
231
232
233
234

  //! Journal a custom code line.
  void code_line(const char* const _line);

  //! Direct access to the underlying stream, use with caution
  Base::IOutputStream& os();
235
  
236
237
private:
  class Impl;
238
239
  class CppImpl;
  class SchemeImpl;
240
241
242
  Impl* impl_;

private:
243
  template <typename... ArgT> DataVector arguments(const ArgT&... _args)
244
  {
245
246
247
    return {dispatch_define(_args)...};
  }

248
  template <typename T> Data dispatch_define(const T& _arg)
249
250
  {
    return define_in_language(*this, _arg);
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
  }

  void emit_constructor(
      const char* const _fnct, const Key& _obj, const DataVector& _args);
  void emit_destructor(const char* const _fnct, const Key& _obj);
  void emit_method_outcome(
      const char* const _fnct, const Key& _obj, const DataVector& _args);
  void emit_method_void(
      const char* const _fnct, const Key& _obj, const DataVector& _args);
  void emit_method(const char* const _fnct, const Data& _rtrn, const Key& _obj,
      const DataVector& _args);
  void emit_function_outcome(const char* const _fnct, const DataVector& _args);
  void emit_function_void(const char* const _fnct, const DataVector& _args);
  void emit_function(
      const char* const _fnct, const Data& _rtrn, const DataVector& _args);
};

268
269
270
271
272
273
274
275
276
277
/*! 
Customized pointer that ensures only a single object is ever retained and 
prevents overrides when enabling the journal on application startup within the
static data initialization phase.
\note Exposed for efficiency only (inline access), please do *not* use it.
*/
class StreamPtr 
{ 
public:
  ~StreamPtr();
278

279
280
281
282
283
284
285
  static bool valid() { return ptr_ != nullptr; }
  static Stream& stream() { return *ptr_; }
  static void reset(Stream* _ptr);

private:
  static Stream* ptr_;
};
286
287
288
289
290
291
292
293
294
295
296

/*!
Turn on/off the journal, set the base path optionally. If no base path is
provided, the base path will be composed from the component name as follows:
  * from the environment variable toupper(cmpn_name)_JOURNAL_BASE if defined,
  * from the local home data directory of the current user/cmpn_name, e.g.,
    %LOCALAPP_DATA%/cmpn_name on Windows, or $HOME/.cmpn_name on Linux and OS X,
  * the current folder sub-folder ./cmpn_name.
\note If _on == true and for some reason the journal output folder and files
cannot be made, the journaling is disabled and this function returns false.
*/
297
298
299
300
bool set_on(const Language _lngg, //!< output journal language
    const bool _on,                    //!< turn on or off
    const char* const _cmpn_name, //!< component name in desired capitalization
    const char* const _base_path  //!< base path
301
302
303
304
305
306
);

/*!
Check if the journal is on or off, as efficient as possible as it is the only
function which is called when the journal is turned off!
*/
307
inline bool on() { return StreamPtr::valid(); }
308
309

//! Access the stream directly, this is not recommended in general
310
inline Stream& stream() { return StreamPtr::stream(); }
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336

//! Get the journal output path, or an empty string if the journal is off
inline String output_path() { return on() ? stream().output_path() : String(); }

// Basic types define()
struct Name
{
  Name(const char* const _name) : name(_name) {}

  const char* const name;
};

// Wrapper for an C-style array of known length
template <typename T> class CArrayT : public Name
{
public:
  const size_t size;
  const T* const pntr;

  CArrayT(const char* const _name, const size_t _size, const T* const _pntr)
      : Name(_name), size(_size), pntr(_pntr)
  {
  }
};

template <typename T>
337
338
CArrayT<T> c_array(
    const char* const _name, const size_t _size, const T* const _pntr)
339
{
340
  return CArrayT<T>(_name, _size, _pntr);
341
342
}

343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
/*!
Contains a plain filename w/o a path to be loaded during replay, allows for
a specialized define() implementation.
*/
struct Filename
{
  Filename(const String& _str) : str(_str) {}
  String str;
};

enum class ArgQ
{
  VALUE,
  REFERENCE,
  POINTER
};

//! Contains a reference to a function/method argument and its type from ArgQ
template <typename T, ArgQ Q> struct ArgT
{
  using Type = T;

Martin Marinov's avatar
Martin Marinov committed
365
  constexpr static ArgQ quality() { return Q; }
366
367
368
369
370
371
372
373
374
375
376
  typedef ArgT<T, Q> Self;

  T& vrbl;
  const char* const name;

  ArgT(T& _vrbl, const char* const _name) : vrbl(_vrbl), name(_name) {}
  Self& operator=(const Self&) = delete;
};

template <typename T> auto argument(T& _vrbl, const char* const _name)
{
Martin Marinov's avatar
Martin Marinov committed
377
  using Arg = typename
378
      std::conditional<std::is_pointer<T>::value, ArgT<T, ArgQ::POINTER>,
Martin Marinov's avatar
Martin Marinov committed
379
          typename std::conditional<std::is_reference<T>::value,
380
381
382
383
384
385
386
387
388
389
390
391
              ArgT<T, ArgQ::REFERENCE>, ArgT<T, ArgQ::VALUE>>::type>::type;
  return Arg(_vrbl, _name);
}

/*!
Helper to allow generic modification of ArgT.vrbl by ArgLinkT, cannot be made
private due to partial specialization rules.
 */
template <typename T, ArgQ Q> struct ArgSetT;

template <typename T>
struct ArgSetT<T, ArgQ::REFERENCE> : public ArgT<T, ArgQ::REFERENCE>
392
{
393
394
395
396
protected:
  using Arg = ArgT<T, ArgQ::REFERENCE>;
  using rT = typename std::remove_reference<T>::type;
  static_assert(std::is_pointer<rT>::value, "Base type should be a pointer");
397

398
399
  ArgSetT(const Arg& _arg) : Arg(_arg) {}

Martin Marinov's avatar
Martin Marinov committed
400
  template <typename ValueT> void assign(ValueT _val) { Arg::vrbl = _val; }
401
402
403
404

  void init() { assign(nullptr); }

  void make_link(const char* const _name)
405
  {
406
407
    if (on() && Arg::vrbl != nullptr)
      stream().link_unique_name(key(Arg::vrbl), _name);
408
  }
409
};
410

411
412
413
414
415
416
417
418
419
template <typename T>
struct ArgSetT<T, ArgQ::POINTER> : public ArgT<T, ArgQ::POINTER>
{
protected:
  using Arg = ArgT<T, ArgQ::POINTER>;
  using rT = typename std::remove_pointer<T>::type;
  static_assert(std::is_pointer<rT>::value, "Base type should be a pointer");

  ArgSetT(const Arg& _arg) : Arg(_arg) {}
420

421
422
423
  template <typename ValueT> void assign(ValueT _val) { *Arg::vrbl = _val; }

  void init()
424
  {
425
426
    if (Arg::vrbl != nullptr)
      assign(nullptr);
427
428
  }

429
430
431
432
433
  void make_link(const char* const _name)
  {
    if (on() && Arg::vrbl != nullptr && *Arg::vrbl != nullptr)
      stream().link_unique_name(key(*Arg::vrbl), _name);
  }
434
435
};

436
437
438
439
440
441
442
443
444
/*!
Enumerates the types of argument links, public to allow access without using 
the template arguments.
*/ 
enum class LinkMode
{
  MANUAL,
  AUTO
};
445

446
447
//! Link an argument to a variable to allow reuse in the journal 
template <typename T, ArgQ Q> struct ArgLinkT : public ArgSetT<T, Q>
448
{
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
  using Arg = ArgT<T, Q>;
  using ArgSet = ArgSetT<T, Q>;
  
  Name link;
  LinkMode mode;

  explicit ArgLinkT(const Arg& _arg, const LinkMode _mode = LinkMode::MANUAL)
      : ArgSet(_arg),
        link(on() ? stream().book_unique_name(Arg::name) : Arg::name),
        mode(_mode)
  {
    if (mode == LinkMode::AUTO) // if AUTO mode?
      ArgSet::init();           // always set the underlying pointer to nullptr
  }

  // output arguments need explicit linking on successful assignment only
  template <typename ValueT> ValueT operator=(ValueT _val)
  {
    ArgSet::assign(_val);
    ArgSet::make_link(link.name);
    mode = LinkMode::MANUAL; // explicitly assigned value, switch the mode
    return _val;
  }

  ~ArgLinkT()
  {
    if (mode == LinkMode::AUTO)
Martin Marinov's avatar
Martin Marinov committed
476
      ArgSet::make_link(link.name);
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
  }
};

template <typename T> struct ReturnLinkT
{
  static_assert(std::is_pointer<T>::value, "Return value should be a pointer");
  Name link;

  explicit ReturnLinkT(const char* const _name)
      : link(on() ? stream().book_unique_name(_name) : _name)
  {
  }

  T operator=(T pntr)
  {
    if (on() && pntr != nullptr)
      stream().link_unique_name(key(pntr), link.name);
    return pntr;
  }
496
497
};

498
499
500
501
502
503
504
template <typename T> auto argument_link(T& _vrbl, const char* const _name, 
  const LinkMode _mode = LinkMode::MANUAL)
{
  auto arg = argument<T>(_vrbl, _name);
  return ArgLinkT<T, arg.quality()>(arg, _mode);
}

505
} // namespace Journal
506

507
508
509
#if defined(_MSC_VER)
#define __JOURNAL_FUNCTION__ __FUNCTION__ // works in VC well
#else
510
#define __JOURNAL_FUNCTION__ __PRETTY_FUNCTION__ // needed for gcc & xcode
511
#endif                                           // _MSC_VER
512
513
514
515
516
517

#define JOURNAL_CHECK if (::Journal::on()) 

#define JOURNAL_CALL(CALL, ...) \
  ::Journal::stream().CALL(__JOURNAL_FUNCTION__, ##__VA_ARGS__)

518
#ifdef JOURNAL_AUTO_INCLUDE
519
#define JOURNAL_INCLUDE_THIS ::Journal::stream().include(__FILE__)
520
521
522
#else
#define JOURNAL_INCLUDE_THIS
#endif//JOURNAL_AUTO_INCLUDE
523
524
525
526
527
528
529
530
531
532
533
534
535
536

#define JOURNAL_INCLUDE(FILE) \
  { JOURNAL_CHECK ::Journal::stream().include(FILE); }

#define JOURNAL_COMMENT(CMNT) \
  { JOURNAL_CHECK { ::Journal::stream().comment_os() << CMNT << Base::ENDL; } }

#define JOURNAL_AS_COMMENT(EXPR) \
  { JOURNAL_CHECK{ ::Journal::stream().comment_os(); EXPR; } }
  
#define JOURNAL(CALL, ...) { JOURNAL_CHECK JOURNAL_CALL(CALL, ##__VA_ARGS__); }
#define JOURNAL_INC(CALL, ...) { JOURNAL_CHECK \
  { JOURNAL_INCLUDE_THIS; JOURNAL_CALL(CALL, ##__VA_ARGS__); } }

537
#define JOURNAL_CONSTRUCTOR(...) JOURNAL_INC(constructor, this, ##__VA_ARGS__)
538
539
540
541
542
543
#define JOURNAL_DESTRUCTOR JOURNAL(destructor, this)
#define JOURNAL_METHOD_OUTCOME(...) JOURNAL(method_outcome, this, ##__VA_ARGS__)
#define JOURNAL_METHOD_VOID(...) JOURNAL(method_void, this, ##__VA_ARGS__)

#define JOURNAL_FUNCTION_VOID(...) JOURNAL_INC(function_void, ##__VA_ARGS__)
#define JOURNAL_FUNCTION_OUTCOME(...) \
544
  JOURNAL_INC(function_outcome, ##__VA_ARGS__)
545
546
547

#define JOURNAL_C_ARRAY(SIZE, PNTR) ::Journal::c_array(#PNTR, SIZE, PNTR)

548
549
550
551
552
553
554
555
556
557
#define JL(ARG) _jl_##ARG
#define JOURNAL_LINK_MANUAL(ARG) \
  auto JL(ARG) = ::Journal::argument_link<decltype(ARG)>( \
      ARG, #ARG, ::Journal::LinkMode::MANUAL)
#define JOURNAL_LINK_AUTO(ARG) \
  auto JL(ARG) = ::Journal::argument_link<decltype(ARG)>( \
      ARG, #ARG, ::Journal::LinkMode::AUTO)

#define JOURNAL_METHOD_RETURN_LINK(NAME, IMPL, ...) \
  ::Journal::ReturnLinkT<decltype(IMPL)> JL(rtrn)(NAME); \
558
559
  JOURNAL(method, JL(rtrn), this, ##__VA_ARGS__); \
  return JL(rtrn) = IMPL
560
561
562

#define JOURNAL_FUNCTION_RETURN_LINK(NAME, IMPL, ...) \
  ::Journal::ReturnLinkT<decltype(IMPL)> JL(rtrn)(NAME); \
563
564
  JOURNAL(function, JL(rtrn), ##__VA_ARGS__); \
  return JL(rtrn) = IMPL
565
566
567
568

#define JOURNAL_PATH_TRANSFORM(TRNS) \
  ::Journal::stream().set_path_transform(TRNS)
#define JOURNAL_MAIN(MAIN) ::Journal::stream().main(MAIN)
569
#define JOURNAL_MAIN_VOID(MAIN) ::Journal::stream().main(MAIN, false)
570
571
572
573
574
575
576
577
#define JOURNAL_CODE_LINE(CODE) ::Journal::stream().code_line(CODE)
#define JOURNAL_BLANK_LINE ::Journal::stream().blank_line()

#define JOURNAL_ERROR(MSG) JOURNAL_COMMENT("Journal ERROR: " << MSG)
#define JOURNAL_ERROR_if(CND, MSG) if (CND) JOURNAL_ERROR(MSG)
#define JOURNAL_ERROR_if_do(CND, MSG, ACTN) \
  if (CND) { JOURNAL_ERROR(MSG); ACTN; }

578
579
#define JT(ARG) ::Journal::argument<decltype(ARG)>(ARG, #ARG)

580
#else
581

582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
#define JOURNAL_INCLUDE_THIS
#define JOURNAL_INCLUDE(FILE)
#define JOURNAL_COMMENT(CMNT)

#define JOURNAL(CMNT, ...)

#define JOURNAL_CONSTRUCTOR(...)
#define JOURNAL_DESTRUCTOR
#define JOURNAL_METHOD_OUTCOME(...)
#define JOURNAL_METHOD_VOID(...)
#define JOURNAL_FUNCTION(VRBL, ...)
#define JOURNAL_FUNCTION_VOID(...)
#define JOURNAL_FUNCTION_OUTCOME(...)

#define JOURNAL_C_ARRAY(SIZE, PNTR)
597
598
#define JOURNAL_LINK(VRBL)
#define JOURNAL_LINK_AUTO(VRBL)
599
600
601

#define JOURNAL_PATH_TRANSFORM(TRNS)
#define JOURNAL_MAIN(MAIN)
602
#define JOURNAL_MAIN_VOID(MAIN)
603
604
605
606
607
608
609
#define JOURNAL_CODE_LINE(CODE)
#define JOURNAL_BLANK_LINE

#define JOURNAL_ERROR(MSG)
#define JOURNAL_ERROR_if(CND, MSG)
#define JOURNAL_ERROR_if_do(CND, MSG, ACTN)

610
611
#define JOURNAL_TYPE_NAME(TYPE)

612
613
614
#define JOURNAL_METHOD_RETURN_LINK(NAME, IMPL, ...)  return IMPL
#define JOURNAL_FUNCTION_RETURN_LINK(NAME, IMPL, ...) return IMPL

615
616
#endif // JOURNAL_ON
#endif // BASE_JOURNAL_STREAM_HH_INCLUDED