GnuCash  4.14+
kvp-frame.cpp
1 /********************************************************************
2  * kvp_frame.cpp -- Implements a key-value frame system *
3  * Copyright (C) 2000 Bill Gribble *
4  * Copyright (C) 2001,2003 Linas Vepstas <linas@linas.org> *
5  * *
6  * This program is free software; you can redistribute it and/or *
7  * modify it under the terms of the GNU General Public License as *
8  * published by the Free Software Foundation; either version 2 of *
9  * the License, or (at your option) any later version. *
10  * *
11  * This program is distributed in the hope that it will be useful, *
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14  * GNU General Public License for more details. *
15  * *
16  * You should have received a copy of the GNU General Public License*
17  * along with this program; if not, contact: *
18  * *
19  * Free Software Foundation Voice: +1-617-542-5942 *
20  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
21  * Boston, MA 02110-1301, USA gnu@gnu.org *
22  * *
23  ********************************************************************/
24 
25 extern "C"
26 {
27 #include <config.h>
28 #include "qof.h"
29 #include <glib.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <string.h>
33 }
34 
35 #include "kvp-value.hpp"
36 #include "kvp-frame.hpp"
37 #include <typeinfo>
38 #include <sstream>
39 #include <algorithm>
40 #include <vector>
41 #include <numeric>
42 
43 /* This static indicates the debugging module that this .o belongs to. */
44 static QofLogModule log_module = "qof.kvp";
45 
46 static const char delim = '/';
47 
48 KvpFrameImpl::KvpFrameImpl(const KvpFrameImpl & rhs) noexcept
49 {
50  std::for_each(rhs.m_valuemap.begin(), rhs.m_valuemap.end(),
51  [this](const map_type::value_type & a)
52  {
53  auto key = qof_string_cache_insert(a.first);
54  auto val = new KvpValueImpl(*a.second);
55  this->m_valuemap.insert({key,val});
56  }
57  );
58 }
59 
61 {
62  std::for_each(m_valuemap.begin(), m_valuemap.end(),
63  [](const map_type::value_type &a){
64  qof_string_cache_remove(a.first);
65  delete a.second;
66  }
67  );
68  m_valuemap.clear();
69 }
70 
71 KvpFrame *
72 KvpFrame::get_child_frame_or_nullptr (Path const & path) noexcept
73 {
74  if (!path.size ())
75  return this;
76  auto key = path.front ();
77  auto map_iter = m_valuemap.find (key.c_str ());
78  if (map_iter == m_valuemap.end ())
79  return nullptr;
80  auto child = map_iter->second->get <KvpFrame *> ();
81  if (!child)
82  return nullptr;
83  Path send;
84  std::copy (path.begin () + 1, path.end (), std::back_inserter (send));
85  return child->get_child_frame_or_nullptr (send);
86 }
87 
88 KvpFrame *
89 KvpFrame::get_child_frame_or_create (Path const & path) noexcept
90 {
91  if (!path.size ())
92  return this;
93  auto key = path.front ();
94  auto spot = m_valuemap.find (key.c_str ());
95  if (spot == m_valuemap.end () || spot->second->get_type () != KvpValue::Type::FRAME)
96  delete set_impl (key.c_str (), new KvpValue {new KvpFrame});
97  Path send;
98  std::copy (path.begin () + 1, path.end (), std::back_inserter (send));
99  auto child_val = m_valuemap.at (key.c_str ());
100  auto child = child_val->get <KvpFrame *> ();
101  return child->get_child_frame_or_create (send);
102 }
103 
104 
105 KvpValue *
106 KvpFrame::set_impl (std::string const & key, KvpValue * value) noexcept
107 {
108  KvpValue * ret {};
109  auto spot = m_valuemap.find (key.c_str ());
110  if (spot != m_valuemap.end ())
111  {
112  qof_string_cache_remove (spot->first);
113  ret = spot->second;
114  m_valuemap.erase (spot);
115  }
116  if (value)
117  {
118  auto cachedkey = static_cast <char const *> (qof_string_cache_insert (key.c_str ()));
119  m_valuemap.emplace (cachedkey, value);
120  }
121  return ret;
122 }
123 
124 KvpValue *
125 KvpFrameImpl::set (Path path, KvpValue* value) noexcept
126 {
127  if (path.empty())
128  return nullptr;
129  auto key = path.back ();
130  path.pop_back ();
131  auto target = get_child_frame_or_nullptr (path);
132  if (!target)
133  return nullptr;
134  return target->set_impl (key, value);
135 }
136 
137 KvpValue *
138 KvpFrameImpl::set_path (Path path, KvpValue* value) noexcept
139 {
140  auto key = path.back();
141  path.pop_back();
142  auto target = get_child_frame_or_create (path);
143  if (!target)
144  return nullptr;
145  return target->set_impl (key, value);
146 }
147 
148 KvpValue *
149 KvpFrameImpl::get_slot (Path path) noexcept
150 {
151  auto key = path.back();
152  path.pop_back();
153  auto target = get_child_frame_or_nullptr (path);
154  if (!target)
155  return nullptr;
156  auto spot = target->m_valuemap.find (key.c_str ());
157  if (spot != target->m_valuemap.end ())
158  return spot->second;
159  return nullptr;
160 }
161 
162 std::string
163 KvpFrameImpl::to_string() const noexcept
164 {
165  return to_string("");
166 }
167 
168 std::string
169 KvpFrameImpl::to_string(std::string const & prefix) const noexcept
170 {
171  if (!m_valuemap.size())
172  return prefix;
173  std::ostringstream ret;
174  std::for_each(m_valuemap.begin(), m_valuemap.end(),
175  [this,&ret,&prefix](const map_type::value_type &a)
176  {
177  std::string new_prefix {prefix};
178  if (a.first)
179  {
180  new_prefix += a.first;
181  new_prefix += "/";
182  }
183  if (a.second)
184  ret << a.second->to_string(new_prefix) << "\n";
185  else
186  ret << new_prefix << "(null)\n";
187  }
188  );
189  return ret.str();
190 }
191 
192 std::vector<std::string>
193 KvpFrameImpl::get_keys() const noexcept
194 {
195  std::vector<std::string> ret;
196  ret.reserve (m_valuemap.size());
197  std::for_each(m_valuemap.begin(), m_valuemap.end(),
198  [&ret](const KvpFrameImpl::map_type::value_type &a)
199  {
200  ret.push_back(a.first);
201  }
202  );
203  return ret;
204 }
205 
206 int compare(const KvpFrameImpl * one, const KvpFrameImpl * two) noexcept
207 {
208  if (one && !two) return 1;
209  if (!one && two) return -1;
210  if (!one && !two) return 0;
211  return compare(*one, *two);
212 }
213 
221 int compare(const KvpFrameImpl & one, const KvpFrameImpl & two) noexcept
222 {
223  for (const auto & a : one.m_valuemap)
224  {
225  auto otherspot = two.m_valuemap.find(a.first);
226  if (otherspot == two.m_valuemap.end())
227  {
228  return 1;
229  }
230  auto comparison = compare(a.second,otherspot->second);
231 
232  if (comparison != 0)
233  return comparison;
234  }
235 
236  if (one.m_valuemap.size() < two.m_valuemap.size())
237  return -1;
238  return 0;
239 }
240 
241 static void
242 kvp_value_list_from_gvalue (GValue *gval, gpointer pList)
243 {
244  GList **kvplist = (GList**)pList;
245  KvpValue *kvp;
246  if (!(gval && G_VALUE_TYPE (gval)))
247  return;
248  kvp = kvp_value_from_gvalue (gval);
249  *kvplist = g_list_prepend (*kvplist, kvp);
250 }
251 
252 GValue*
253 gvalue_from_kvp_value (const KvpValue *kval, GValue* val)
254 {
255  if (kval == NULL) return NULL;
256  if (!val)
257  val = g_slice_new0 (GValue);
258  else
259  g_value_unset(val);
260 
261  switch (kval->get_type())
262  {
263  case KvpValue::Type::INT64:
264  g_value_init (val, G_TYPE_INT64);
265  g_value_set_int64 (val, kval->get<int64_t>());
266  break;
267  case KvpValue::Type::DOUBLE:
268  g_value_init (val, G_TYPE_DOUBLE);
269  g_value_set_double (val, kval->get<double>());
270  break;
271  case KvpValue::Type::NUMERIC:
272  g_value_init (val, GNC_TYPE_NUMERIC);
273  g_value_set_static_boxed (val, kval->get_ptr<gnc_numeric>());
274  break;
275  case KvpValue::Type::STRING:
276  g_value_init (val, G_TYPE_STRING);
277  g_value_set_static_string (val, kval->get<const char*>());
278  break;
279  case KvpValue::Type::GUID:
280  g_value_init (val, GNC_TYPE_GUID);
281  g_value_set_static_boxed (val, kval->get<GncGUID*>());
282  break;
283  case KvpValue::Type::TIME64:
284  g_value_init (val, GNC_TYPE_TIME64);
285  g_value_set_boxed (val, kval->get_ptr<Time64>());
286  break;
287  case KvpValue::Type::GDATE:
288  g_value_init (val, G_TYPE_DATE);
289  g_value_set_static_boxed (val, kval->get_ptr<GDate>());
290  break;
291  case KvpValue::Type::GLIST:
292  {
293  g_value_init (val, GNC_TYPE_VALUE_LIST);
294  g_value_set_static_boxed (val, kval->get<GList*>());
295  break;
296  }
297 /* No transfer of KVP frames outside of QofInstance-derived classes! */
298  case KvpValue::Type::FRAME:
299  PWARN ("Error! Attempt to transfer KvpFrame!");
300  default:
301  PWARN ("Error! Invalid KVP Transfer Request!");
302  g_slice_free (GValue, val);
303  val = NULL;
304  break;
305  }
306  return val;
307 }
308 
309 KvpValue*
310 kvp_value_from_gvalue (const GValue *gval)
311 {
312  KvpValue *val = NULL;
313  GType type;
314  if (gval == NULL)
315  return NULL;
316  type = G_VALUE_TYPE (gval);
317  g_return_val_if_fail (G_VALUE_TYPE (gval), NULL);
318 
319  if (type == G_TYPE_INT64)
320  val = new KvpValue(g_value_get_int64 (gval));
321  else if (type == G_TYPE_DOUBLE)
322  val = new KvpValue(g_value_get_double (gval));
323  else if (type == G_TYPE_BOOLEAN)
324  {
325  auto bval = g_value_get_boolean(gval);
326  if (bval)
327  val = new KvpValue(g_strdup("true"));
328  }
329  else if (type == GNC_TYPE_NUMERIC)
330  val = new KvpValue(*(gnc_numeric*)g_value_get_boxed (gval));
331  else if (type == G_TYPE_STRING)
332  {
333  auto string = g_value_get_string(gval);
334  if (string != nullptr)
335  val = new KvpValue(g_strdup(string));
336  }
337  else if (type == GNC_TYPE_GUID)
338  {
339  auto boxed = g_value_get_boxed(gval);
340  if (boxed != nullptr)
341  val = new KvpValue(guid_copy(static_cast<GncGUID*>(boxed)));
342  }
343  else if (type == GNC_TYPE_TIME64)
344  val = new KvpValue(*(Time64*)g_value_get_boxed (gval));
345  else if (type == G_TYPE_DATE)
346  val = new KvpValue(*(GDate*)g_value_get_boxed (gval));
347  else if (type == GNC_TYPE_VALUE_LIST)
348  {
349  GList *gvalue_list = (GList*)g_value_get_boxed (gval);
350  GList *kvp_list = NULL;
351  g_list_foreach (gvalue_list, (GFunc)kvp_value_list_from_gvalue,
352  &kvp_list);
353  kvp_list = g_list_reverse (kvp_list);
354  val = new KvpValue(kvp_list);
355 // g_list_free_full (gvalue_list, (GDestroyNotify)g_value_unset);
356 // gvalue_list = NULL;
357  }
358  else
359  PWARN ("Error! Don't know how to make a KvpValue from a %s",
360  G_VALUE_TYPE_NAME (gval));
361 
362  return val;
363 }
364 /* The following are required for using KvpValue GLists as GValues */
365 static void
366 gnc_gvalue_copy (GValue *src, gpointer uData)
367 {
368  GList **new_list = (GList**)uData;
369  GValue *dest = g_value_init (g_slice_new0 (GValue), G_VALUE_TYPE (src));
370  g_value_copy (src, dest);
371  *new_list = g_list_prepend(*new_list, dest);
372 }
373 
374 void
375 gnc_gvalue_free (GValue *val)
376 {
377  if (val == NULL || ! G_IS_VALUE (val)) return;
378  g_value_unset (val);
379  g_slice_free (GValue, val);
380 }
381 
382 static GList*
383 gnc_value_list_copy (GList *list)
384 {
385  GList *new_list = NULL;
386  g_list_foreach (list, (GFunc)gnc_gvalue_copy, &new_list);
387  new_list = g_list_reverse (new_list);
388  return new_list;
389 }
390 
391 static void
392 gnc_value_list_free (GList *list)
393 {
394  g_list_free_full (list, (GDestroyNotify)gnc_gvalue_free);
395 }
396 
397 GType
398 gnc_value_list_get_type (void)
399 {
400  static GType type = 0;
401  if (type == 0)
402  {
403  type = g_boxed_type_register_static ("gnc_value_list",
404  (GBoxedCopyFunc)gnc_value_list_copy,
405  (GBoxedFreeFunc)gnc_value_list_free);
406  }
407  return type;
408 }
409 
410 void
411 KvpFrame::flatten_kvp_impl(std::vector <std::string> path, std::vector <KvpEntry> & entries) const noexcept
412 {
413  for (auto const & entry : m_valuemap)
414  {
415  std::vector<std::string> new_path {path};
416  new_path.push_back("/");
417  if (entry.second->get_type() == KvpValue::Type::FRAME)
418  {
419  new_path.push_back(entry.first);
420  entry.second->get<KvpFrame*>()->flatten_kvp_impl(new_path, entries);
421  }
422  else
423  {
424  new_path.emplace_back (entry.first);
425  entries.emplace_back (new_path, entry.second);
426  }
427  }
428 }
429 
430 std::vector <KvpEntry>
431 KvpFrame::flatten_kvp(void) const noexcept
432 {
433  std::vector <KvpEntry> ret;
434  flatten_kvp_impl({}, ret);
435  return ret;
436 }
~KvpFrameImpl() noexcept
Perform a deep delete.
Definition: kvp-frame.cpp:60
Implements KvpFrame.
Definition: kvp-frame.hpp:109
GncGUID * guid_copy(const GncGUID *guid)
Returns a newly allocated GncGUID that matches the passed-in GUID.
Definition: guid.cpp:121
std::string to_string() const noexcept
Make a string representation of the frame.
Definition: kvp-frame.cpp:163
KvpValue * kvp_value_from_gvalue(const GValue *gval)
Convert a gvalue into a kvpvalue.
Definition: kvp-frame.cpp:310
KvpValue * set(Path path, KvpValue *newvalue) noexcept
Set the value with the key in the immediate frame, replacing and returning the old value if it exists...
Definition: kvp-frame.cpp:125
const char * qof_string_cache_insert(const char *key)
You can use this function with g_hash_table_insert(), for the key (or value), as long as you use the ...
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250
void gnc_gvalue_free(GValue *value)
Convenience function to release the value in a GValue acquired by kvp_frame_get_gvalue and to free th...
Definition: kvp-frame.cpp:375
int compare(const KvpFrameImpl &one, const KvpFrameImpl &two) noexcept
If the first KvpFrameImpl has an item that the second does not, 1 is returned.
Definition: kvp-frame.cpp:221
std::vector< std::string > get_keys() const noexcept
Report the keys in the immediate frame.
Definition: kvp-frame.cpp:193
Implements KvpValue using boost::variant.
Definition: kvp-value.hpp:57
KvpValue * get_slot(Path keys) noexcept
Get the value for the tail of the path or nullptr if it doesn&#39;t exist.
Definition: kvp-frame.cpp:149
GValue * gvalue_from_kvp_value(const KvpValue *kval, GValue *val)
Convert a kvp_value into a GValue.
Definition: kvp-frame.cpp:253
KvpValue * set_path(Path path, KvpValue *newvalue) noexcept
Set the value with the key in a subframe following the keys in path, replacing and returning the old ...
Definition: kvp-frame.cpp:138
The type used to store guids in C.
Definition: guid.h:75
void qof_string_cache_remove(const char *key)
You can use this function as a destroy notifier for a GHashTable that uses common strings as keys (or...