QOF 0.8.2

qofbookmerge.c

00001 /*********************************************************************
00002  * QofBookMerge.c -- api for QoFBook merge with collision handling   *
00003  * Copyright (C) 2004-2008                                           *
00004  *    Neil Williams <linux@codehelp.co.uk>                           *
00005  *                                                                   *
00006  * This program is free software; you can redistribute it and/or     *
00007  * modify it under the terms of the GNU General Public License as    *
00008  * published by the Free Software Foundation; either version 2 of    *
00009  * the License, or (at your option) any later version.               *
00010  *                                                                   *
00011  * This program is distributed in the hope that it will be useful,   *
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of    *
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the     *
00014  * GNU General Public License for more details.                      *
00015  *                                                                   *
00016  * You should have received a copy of the GNU General Public License *
00017  * along with this program; if not, contact:                         *
00018  *                                                                   *
00019  * Free Software Foundation           Voice:  +1-617-542-5942        *
00020  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
00021  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
00022  *                                                                   *
00023  ********************************************************************/
00024 
00025 #include "config.h"
00026 #include <glib.h>
00027 #include "qof.h"
00028 
00029 static QofLogModule log_module = QOF_MOD_MERGE;
00030 
00031 /* private rule iteration struct */
00032 struct QofBookMergeRuleIterate
00033 {
00034     QofBookMergeRuleForeachCB fcn;
00035     QofBookMergeData *data;
00036     QofBookMergeRule *rule;
00037     GList *ruleList;
00038     guint remainder;
00039 };
00040 
00041 /* Make string type parameters 3 times more
00042     important in the match than default types.
00043     i.e. even if two other parameters differ, 
00044     a string match will still provide a better target
00045     than when other types match and the string does not.
00046 */
00047 #define DEFAULT_MERGE_WEIGHT    1
00048 #define QOF_STRING_WEIGHT       3
00049 #define QOF_DATE_STRING_LENGTH  MAX_DATE_LENGTH
00050 
00051 static QofBookMergeRule *
00052 qof_book_merge_update_rule (QofBookMergeRule * currentRule, gboolean match,
00053     gint weight)
00054 {
00055     gboolean absolute;
00056 
00057     absolute = currentRule->mergeAbsolute;
00058     if (absolute && match && currentRule->mergeResult == MERGE_UNDEF)
00059         currentRule->mergeResult = MERGE_ABSOLUTE;
00060     if (absolute && !match)
00061         currentRule->mergeResult = MERGE_UPDATE;
00062     if (!absolute && match && currentRule->mergeResult == MERGE_UNDEF)
00063         currentRule->mergeResult = MERGE_DUPLICATE;
00064     if (!absolute && !match)
00065     {
00066         currentRule->difference += weight;
00067         if (currentRule->mergeResult == MERGE_DUPLICATE)
00068             currentRule->mergeResult = MERGE_REPORT;
00069     }
00070     return currentRule;
00071 }
00072 
00073 struct collect_list_s
00074 {
00075     GSList *linkedEntList;
00076 };
00077 
00078 static void
00079 collect_reference_cb (QofEntity * ent, gpointer user_data)
00080 {
00081     struct collect_list_s *s;
00082 
00083     s = (struct collect_list_s *) user_data;
00084     if (!ent || !s)
00085         return;
00086     s->linkedEntList = g_slist_prepend (s->linkedEntList, ent);
00087 }
00088 
00089 static gint
00090 qof_book_merge_compare (QofBookMergeData * mergeData)
00091 {
00092     QofBookMergeRule *currentRule;
00093     QofCollection *mergeColl, *targetColl;
00094     gchar *stringImport, *stringTarget;
00095     QofEntity *mergeEnt, *targetEnt, *referenceEnt;
00096     const GUID *guidImport, *guidTarget;
00097     QofParam *qtparam;
00098     KvpFrame *kvpImport, *kvpTarget;
00099     QofIdType mergeParamName;
00100     QofType mergeType;
00101     GSList *paramList;
00102     gboolean absolute, mergeError, knowntype, mergeMatch, booleanImport,
00103         booleanTarget, (*boolean_getter) (QofEntity *, QofParam *);
00104     QofNumeric numericImport, numericTarget,
00105         (*numeric_getter) (QofEntity *, QofParam *);
00106     gdouble doubleImport, doubleTarget, (*double_getter) (QofEntity *,
00107         QofParam *);
00108     gint32 i32Import, i32Target, (*int32_getter) (QofEntity *, QofParam *);
00109     gint64 i64Import, i64Target, (*int64_getter) (QofEntity *, QofParam *);
00110     gchar charImport, charTarget, (*char_getter) (QofEntity *, QofParam *);
00111 
00112     g_return_val_if_fail ((mergeData != NULL), -1);
00113     currentRule = mergeData->currentRule;
00114     g_return_val_if_fail ((currentRule != NULL), -1);
00115     absolute = currentRule->mergeAbsolute;
00116     mergeEnt = currentRule->importEnt;
00117     targetEnt = currentRule->targetEnt;
00118     paramList = currentRule->mergeParam;
00119     currentRule->difference = 0;
00120     currentRule->mergeResult = MERGE_UNDEF;
00121     currentRule->linkedEntList = NULL;
00122     g_return_val_if_fail ((targetEnt) || (mergeEnt) || (paramList), -1);
00123     kvpImport = kvp_frame_new ();
00124     kvpTarget = kvp_frame_new ();
00125     mergeError = FALSE;
00126     while (paramList != NULL)
00127     {
00128         mergeMatch = FALSE;
00129         knowntype = FALSE;
00130         qtparam = paramList->data;
00131         mergeParamName = qtparam->param_name;
00132         g_return_val_if_fail (mergeParamName != NULL, -1);
00133         mergeType = qtparam->param_type;
00134         if (safe_strcmp (mergeType, QOF_TYPE_STRING) == 0)
00135         {
00136             stringImport = qtparam->param_getfcn (mergeEnt, qtparam);
00137             stringTarget = qtparam->param_getfcn (targetEnt, qtparam);
00138             /* very strict string matches may need to be relaxed. */
00139             if (stringImport == NULL)
00140                 stringImport = "";
00141             if (stringTarget == NULL)
00142                 stringTarget = "";
00143             if (safe_strcmp (stringImport, stringTarget) == 0)
00144                 mergeMatch = TRUE;
00145             /* Give special weight to a string match */
00146             currentRule = qof_book_merge_update_rule (currentRule,
00147                 mergeMatch, QOF_STRING_WEIGHT);
00148             stringImport = stringTarget = NULL;
00149             knowntype = TRUE;
00150         }
00151         if (safe_strcmp (mergeType, QOF_TYPE_TIME) == 0)
00152         {
00153             QofTime *qtImport, *qtTarget;
00154 
00155             qtImport = qtparam->param_getfcn (mergeEnt, qtparam);
00156             qtTarget = qtparam->param_getfcn (targetEnt, qtparam);
00157             if (qof_time_cmp (qtImport, qtTarget) == 0)
00158                 currentRule = qof_book_merge_update_rule (currentRule,
00159                     mergeMatch, DEFAULT_MERGE_WEIGHT);
00160             knowntype = TRUE;
00161         }
00162         if ((safe_strcmp (mergeType, QOF_TYPE_NUMERIC) == 0) ||
00163             (safe_strcmp (mergeType, QOF_TYPE_DEBCRED) == 0))
00164         {
00165             numeric_getter =
00166                 (QofNumeric (*)(QofEntity *, QofParam *)) qtparam->
00167                 param_getfcn;
00168             numericImport = numeric_getter (mergeEnt, qtparam);
00169             numericTarget = numeric_getter (targetEnt, qtparam);
00170             if (qof_numeric_compare (numericImport, numericTarget) == 0)
00171                 mergeMatch = TRUE;
00172             currentRule = qof_book_merge_update_rule (currentRule,
00173                 mergeMatch, DEFAULT_MERGE_WEIGHT);
00174             knowntype = TRUE;
00175         }
00176         if (safe_strcmp (mergeType, QOF_TYPE_GUID) == 0)
00177         {
00178             guidImport = qtparam->param_getfcn (mergeEnt, qtparam);
00179             guidTarget = qtparam->param_getfcn (targetEnt, qtparam);
00180             if (guid_compare (guidImport, guidTarget) == 0)
00181                 mergeMatch = TRUE;
00182             currentRule = qof_book_merge_update_rule (currentRule,
00183                 mergeMatch, DEFAULT_MERGE_WEIGHT);
00184             knowntype = TRUE;
00185         }
00186         if (safe_strcmp (mergeType, QOF_TYPE_INT32) == 0)
00187         {
00188             int32_getter =
00189                 (gint32 (*)(QofEntity *,
00190                     QofParam *)) qtparam->param_getfcn;
00191             i32Import = int32_getter (mergeEnt, qtparam);
00192             i32Target = int32_getter (targetEnt, qtparam);
00193             if (i32Target == i32Import)
00194                 mergeMatch = TRUE;
00195             currentRule = qof_book_merge_update_rule (currentRule,
00196                 mergeMatch, DEFAULT_MERGE_WEIGHT);
00197             knowntype = TRUE;
00198         }
00199         if (safe_strcmp (mergeType, QOF_TYPE_INT64) == 0)
00200         {
00201             int64_getter =
00202                 (gint64 (*)(QofEntity *,
00203                     QofParam *)) qtparam->param_getfcn;
00204             i64Import = int64_getter (mergeEnt, qtparam);
00205             i64Target = int64_getter (targetEnt, qtparam);
00206             if (i64Target == i64Import)
00207                 mergeMatch = TRUE;
00208             currentRule = qof_book_merge_update_rule (currentRule,
00209                 mergeMatch, DEFAULT_MERGE_WEIGHT);
00210             knowntype = TRUE;
00211         }
00212         if (safe_strcmp (mergeType, QOF_TYPE_DOUBLE) == 0)
00213         {
00214             double_getter =
00215                 (double (*)(QofEntity *,
00216                     QofParam *)) qtparam->param_getfcn;
00217             doubleImport = double_getter (mergeEnt, qtparam);
00218             doubleTarget = double_getter (mergeEnt, qtparam);
00219             if (doubleImport == doubleTarget)
00220                 mergeMatch = TRUE;
00221             currentRule = qof_book_merge_update_rule (currentRule,
00222                 mergeMatch, DEFAULT_MERGE_WEIGHT);
00223             knowntype = TRUE;
00224         }
00225         if (safe_strcmp (mergeType, QOF_TYPE_BOOLEAN) == 0)
00226         {
00227             boolean_getter =
00228                 (gboolean (*)(QofEntity *,
00229                     QofParam *)) qtparam->param_getfcn;
00230             booleanImport = boolean_getter (mergeEnt, qtparam);
00231             booleanTarget = boolean_getter (targetEnt, qtparam);
00232             if (booleanImport != FALSE && booleanImport != TRUE)
00233                 booleanImport = FALSE;
00234             if (booleanTarget != FALSE && booleanTarget != TRUE)
00235                 booleanTarget = FALSE;
00236             if (booleanImport == booleanTarget)
00237                 mergeMatch = TRUE;
00238             currentRule = qof_book_merge_update_rule (currentRule,
00239                 mergeMatch, DEFAULT_MERGE_WEIGHT);
00240             knowntype = TRUE;
00241         }
00242         if (safe_strcmp (mergeType, QOF_TYPE_KVP) == 0)
00243         {
00244             kvpImport =
00245                 kvp_frame_copy (qtparam->param_getfcn (mergeEnt, qtparam));
00246             kvpTarget =
00247                 kvp_frame_copy (qtparam->param_getfcn (targetEnt,
00248                     qtparam));
00249             if (kvp_frame_compare (kvpImport, kvpTarget) == 0)
00250                 mergeMatch = TRUE;
00251             currentRule = qof_book_merge_update_rule (currentRule,
00252                 mergeMatch, DEFAULT_MERGE_WEIGHT);
00253             knowntype = TRUE;
00254         }
00255         if (safe_strcmp (mergeType, QOF_TYPE_CHAR) == 0)
00256         {
00257             char_getter =
00258                 (gchar (*)(QofEntity *, QofParam *)) qtparam->param_getfcn;
00259             charImport = char_getter (mergeEnt, qtparam);
00260             charTarget = char_getter (targetEnt, qtparam);
00261             if (charImport == charTarget)
00262                 mergeMatch = TRUE;
00263             currentRule = qof_book_merge_update_rule (currentRule,
00264                 mergeMatch, DEFAULT_MERGE_WEIGHT);
00265             knowntype = TRUE;
00266         }
00267         /* No object should have QofSetterFunc defined for the book,
00268            but just to be safe, do nothing. */
00269         if (safe_strcmp (mergeType, QOF_ID_BOOK) == 0)
00270             knowntype = TRUE;
00271         if (safe_strcmp (mergeType, QOF_TYPE_COLLECT) == 0)
00272         {
00273             struct collect_list_s s;
00274             s.linkedEntList = NULL;
00275             mergeColl = qtparam->param_getfcn (mergeEnt, qtparam);
00276             targetColl = qtparam->param_getfcn (targetEnt, qtparam);
00277             s.linkedEntList = g_slist_copy (currentRule->linkedEntList);
00278             qof_collection_foreach (mergeColl, collect_reference_cb, &s);
00279             currentRule->linkedEntList = g_slist_copy (s.linkedEntList);
00280             if (0 == qof_collection_compare (mergeColl, targetColl))
00281                 mergeMatch = TRUE;
00282             currentRule = qof_book_merge_update_rule (currentRule,
00283                 mergeMatch, DEFAULT_MERGE_WEIGHT);
00284             knowntype = TRUE;
00285         }
00286         if (safe_strcmp (mergeType, QOF_TYPE_CHOICE) == 0)
00287         {
00288             referenceEnt = qtparam->param_getfcn (mergeEnt, qtparam);
00289             currentRule->linkedEntList =
00290                 g_slist_prepend (currentRule->linkedEntList, referenceEnt);
00291             if (referenceEnt == qtparam->param_getfcn (targetEnt, qtparam))
00292                 mergeMatch = TRUE;
00293             knowntype = TRUE;
00294         }
00295         if (knowntype == FALSE)
00296         {
00297             referenceEnt = qtparam->param_getfcn (mergeEnt, qtparam);
00298             if ((referenceEnt != NULL)
00299                 && (safe_strcmp (referenceEnt->e_type, mergeType) == 0))
00300             {
00301                 currentRule->linkedEntList =
00302                     g_slist_prepend (currentRule->linkedEntList,
00303                     referenceEnt);
00304                 if (referenceEnt ==
00305                     qtparam->param_getfcn (targetEnt, qtparam))
00306                     mergeMatch = TRUE;
00307                 currentRule = qof_book_merge_update_rule (currentRule,
00308                     mergeMatch, DEFAULT_MERGE_WEIGHT);
00309             }
00310         }
00311         paramList = g_slist_next (paramList);
00312     }
00313     mergeData->currentRule = currentRule;
00314     g_free (kvpImport);
00315     g_free (kvpTarget);
00316     return 0;
00317 }
00318 
00319 static void
00320 qof_book_merge_commit_foreach_cb (gpointer rule, gpointer arg)
00321 {
00322     struct QofBookMergeRuleIterate *qiter;
00323 
00324     g_return_if_fail (arg != NULL);
00325     qiter = (struct QofBookMergeRuleIterate *) arg;
00326     g_return_if_fail (qiter->data != NULL);
00327     qiter->fcn (qiter->data, (QofBookMergeRule *) rule, 
00328         qiter->remainder);
00329     qiter->remainder--;
00330 }
00331 
00332 static void
00333 qof_book_merge_commit_foreach (QofBookMergeRuleForeachCB cb,
00334     QofBookMergeResult mergeResult, QofBookMergeData * mergeData)
00335 {
00336     struct QofBookMergeRuleIterate qiter;
00337     QofBookMergeRule *currentRule;
00338     GList *subList, *node;
00339 
00340     g_return_if_fail (cb != NULL);
00341     g_return_if_fail (mergeData != NULL);
00342     currentRule = mergeData->currentRule;
00343     g_return_if_fail (currentRule != NULL);
00344     g_return_if_fail (mergeResult > 0);
00345     g_return_if_fail ((mergeResult != MERGE_INVALID) || 
00346         (mergeResult != MERGE_UNDEF) || 
00347         (mergeResult != MERGE_REPORT));
00348 
00349     qiter.fcn = cb;
00350     subList = NULL;
00351     qiter.ruleList = NULL;
00352     for (node = mergeData->mergeList; node != NULL; node = node->next)
00353     {
00354         currentRule = node->data;
00355         if (currentRule->mergeResult == mergeResult)
00356             subList = g_list_prepend (subList, currentRule);
00357     }
00358     qiter.remainder = g_list_length (subList);
00359     qiter.data = mergeData;
00360     g_list_foreach (subList, qof_book_merge_commit_foreach_cb, &qiter);
00361 }
00362 
00363 /* build the table of target comparisons
00364 
00365 This can get confusing, so bear with me. (!)
00366 
00367 Whilst iterating through the entities in the mergeBook, qof_book_mergeForeach assigns
00368 a targetEnt to each mergeEnt (until it runs out of targetEnt or mergeEnt). Each match
00369 is made against the one targetEnt that best matches the mergeEnt. Fine so far.
00370 
00371 Any mergeEnt is only ever assigned a targetEnt if the calculated difference between
00372 the two is less than the difference between that targetEnt and any previous mergeEnt
00373 match.
00374 
00375 The next mergeEnt may be a much better match for that targetEnt and the target_table
00376 is designed to solve the issues that result from this conflict. The previous match
00377 must be re-assigned because if two mergeEnt's are matched with only one targetEnt,
00378 data loss \b WILL follow. Equally, the current mergeEnt must replace the previous
00379 one as it is a better match. qof_entity_rating holds the details required to identify
00380 the correct mergeEnt to be re-assigned and these mergeEnt entities are therefore
00381 orphaned - to be re-matched later.
00382 
00383 Meanwhile, the current mergeEnt is entered into target_table with it's difference and
00384 rule data, in case an even better match is found later in the mergeBook.
00385 
00386 Finally, each mergeEnt in the orphan_list is now put through the comparison again.
00387 
00388 */
00389 static gboolean
00390 qof_book_merge_rule_cmp (gconstpointer a, gconstpointer b)
00391 {
00392     QofBookMergeRule *ra = (QofBookMergeRule *) a;
00393     QofBookMergeRule *rb = (QofBookMergeRule *) b;
00394     if (ra->difference == rb->difference)
00395         return TRUE;
00396     else
00397         return FALSE;
00398 }
00399 
00400 static void
00401 qof_book_merge_orphan_check (double difference,
00402     QofBookMergeRule * mergeRule, QofBookMergeData * mergeData)
00403 {
00404     /* Called when difference is lower than previous
00405        Lookup target to find previous match
00406        and re-assign mergeEnt to orphan_list */
00407     QofBookMergeRule *rule;
00408 
00409     g_return_if_fail (mergeRule != NULL);
00410     g_return_if_fail (mergeData != NULL);
00411     if (g_hash_table_size (mergeData->target_table) == 0)
00412         return;
00413     rule =
00414         (QofBookMergeRule *) g_hash_table_lookup (mergeData->target_table,
00415         mergeRule->targetEnt);
00416     /* If NULL, no match was found. */
00417     if (rule == NULL)
00418         return;
00419     /* Only orphan if this is a better match than already exists. */
00420     if (difference >= rule->difference)
00421         return;
00422     rule->targetEnt = NULL;
00423     rule->mergeResult = MERGE_UNDEF;
00424     mergeData->orphan_list = g_slist_append (mergeData->orphan_list, rule);
00425 }
00426 
00427 static void
00428 qof_book_merge_match_orphans (QofBookMergeData * mergeData)
00429 {
00430     GSList *orphans, *targets;
00431     QofBookMergeRule *rule, *currentRule;
00432     QofEntity *best_matchEnt;
00433     double difference;
00434 
00435     g_return_if_fail (mergeData != NULL);
00436     currentRule = mergeData->currentRule;
00437     g_return_if_fail (currentRule != NULL);
00438     /* This routine does NOT copy the orphan list, it
00439        is used recursively until empty. */
00440     orphans = mergeData->orphan_list;
00441     targets = g_slist_copy (mergeData->targetList);
00442     while (orphans != NULL)
00443     {
00444         rule = orphans->data;
00445         g_return_if_fail (rule != NULL);
00446         difference = g_slist_length (mergeData->mergeObjectParams);
00447         if (rule->targetEnt == NULL)
00448         {
00449             rule->mergeResult = MERGE_NEW;
00450             rule->difference = 0;
00451             mergeData->mergeList =
00452                 g_list_prepend (mergeData->mergeList, rule);
00453             orphans = g_slist_next (orphans);
00454             continue;
00455         }
00456         mergeData->currentRule = rule;
00457         g_return_if_fail (qof_book_merge_compare (mergeData) != -1);
00458         if (difference > mergeData->currentRule->difference)
00459         {
00460             best_matchEnt = currentRule->targetEnt;
00461             difference = currentRule->difference;
00462             rule = currentRule;
00463             mergeData->mergeList =
00464                 g_list_prepend (mergeData->mergeList, rule);
00465             qof_book_merge_orphan_check (difference, rule, mergeData);
00466         }
00467         orphans = g_slist_next (orphans);
00468     }
00469     g_slist_free (mergeData->orphan_list);
00470     g_slist_free (targets);
00471 }
00472 
00473 static void
00474 qof_book_merge_foreach_target (QofEntity * targetEnt, gpointer user_data)
00475 {
00476     QofBookMergeData *mergeData;
00477 
00478     g_return_if_fail (user_data != NULL);
00479     mergeData = (QofBookMergeData *) user_data;
00480     g_return_if_fail (targetEnt != NULL);
00481     mergeData->targetList =
00482         g_slist_prepend (mergeData->targetList, targetEnt);
00483 }
00484 
00485 static void
00486 qof_book_merge_foreach_type_target (QofObject * merge_obj,
00487     gpointer user_data)
00488 {
00489     QofBookMergeData *mergeData;
00490     QofBookMergeRule *currentRule;
00491 
00492     g_return_if_fail (user_data != NULL);
00493     mergeData = (QofBookMergeData *) user_data;
00494     currentRule = mergeData->currentRule;
00495     g_return_if_fail (currentRule != NULL);
00496     g_return_if_fail (merge_obj != NULL);
00497     if (safe_strcmp (merge_obj->e_type,
00498             currentRule->importEnt->e_type) == 0)
00499     {
00500         qof_object_foreach (currentRule->importEnt->e_type,
00501             mergeData->targetBook,
00502             qof_book_merge_foreach_target, user_data);
00503     }
00504 }
00505 
00506 static void
00507 qof_book_merge_foreach (QofEntity * mergeEnt, gpointer user_data)
00508 {
00509     QofBookMergeRule *mergeRule, *currentRule;
00510     QofBookMergeData *mergeData;
00511     QofEntity *targetEnt, *best_matchEnt;
00512     GUID *g;
00513     double difference;
00514     GSList *c;
00515 
00516     g_return_if_fail (user_data != NULL);
00517     mergeData = (QofBookMergeData *) user_data;
00518     g_return_if_fail (mergeEnt != NULL);
00519     currentRule = mergeData->currentRule;
00520     g_return_if_fail (currentRule != NULL);
00521     g = guid_malloc ();
00522     *g = mergeEnt->guid;
00523     mergeRule = g_new0 (QofBookMergeRule, 1);
00524     mergeRule->importEnt = mergeEnt;
00525     mergeRule->difference = difference = 0;
00526     mergeRule->mergeAbsolute = FALSE;
00527     mergeRule->mergeResult = MERGE_UNDEF;
00528     mergeRule->updated = FALSE;
00529     mergeRule->mergeType = mergeEnt->e_type;
00530     mergeRule->mergeLabel = qof_object_get_type_label (mergeEnt->e_type);
00531     mergeRule->mergeParam = g_slist_copy (mergeData->mergeObjectParams);
00532     mergeRule->linkedEntList = NULL;
00533     mergeData->currentRule = mergeRule;
00534     targetEnt = best_matchEnt = NULL;
00535     targetEnt =
00536         qof_collection_lookup_entity (qof_book_get_collection
00537         (mergeData->targetBook, mergeEnt->e_type), g);
00538     if (targetEnt != NULL)
00539     {
00540         mergeRule->mergeAbsolute = TRUE;
00541         mergeRule->targetEnt = targetEnt;
00542         g_return_if_fail (qof_book_merge_compare (mergeData) != -1);
00543         mergeRule->linkedEntList =
00544             g_slist_copy (currentRule->linkedEntList);
00545         mergeData->mergeList =
00546             g_list_prepend (mergeData->mergeList, mergeRule);
00547         return;
00548     }
00549     /* no absolute match exists */
00550     g_slist_free (mergeData->targetList);
00551     mergeData->targetList = NULL;
00552     qof_object_foreach_type (qof_book_merge_foreach_type_target,
00553         mergeData);
00554     if (g_slist_length (mergeData->targetList) == 0)
00555         mergeRule->mergeResult = MERGE_NEW;
00556     difference = g_slist_length (mergeRule->mergeParam);
00557     c = g_slist_copy (mergeData->targetList);
00558     while (c != NULL)
00559     {
00560         mergeRule->targetEnt = c->data;
00561         currentRule = mergeRule;
00562         /* compare two entities and sum the differences */
00563         g_return_if_fail (qof_book_merge_compare (mergeData) != -1);
00564         if (mergeRule->difference == 0)
00565         {
00566             /* check if this is a better match than one already assigned */
00567             best_matchEnt = mergeRule->targetEnt;
00568             mergeRule->mergeResult = MERGE_DUPLICATE;
00569             difference = 0;
00570             mergeRule->linkedEntList =
00571                 g_slist_copy (currentRule->linkedEntList);
00572             g_slist_free (c);
00573             guid_free (g);
00574             /* exact match, return */
00575             return;
00576         }
00577         if (difference > mergeRule->difference)
00578         {
00579             /* The chosen targetEnt determines the parenting of any child object */
00580             /* check if this is a better match than one already assigned */
00581             best_matchEnt = mergeRule->targetEnt;
00582             difference = mergeRule->difference;
00583             /* Use match to lookup the previous entity that matched this targetEnt (if any)
00584                and remove targetEnt from the rule for that mergeEnt.
00585                Add the previous mergeEnt to orphan_list.
00586              */
00587             qof_book_merge_orphan_check (difference, mergeRule, mergeData);
00588         }
00589         c = g_slist_next (c);
00590     }
00591     g_slist_free (c);
00592     if (best_matchEnt != NULL)
00593     {
00594         mergeRule->targetEnt = best_matchEnt;
00595         mergeRule->difference = difference;
00596         /* Set this entity in the target_table in case a better match can be made
00597            with the next mergeEnt. */
00598         g_hash_table_insert (mergeData->target_table, mergeRule->targetEnt,
00599             mergeRule);
00600         /* compare again with the best partial match */
00601         g_return_if_fail (qof_book_merge_compare (mergeData) != -1);
00602         mergeRule->linkedEntList =
00603             g_slist_copy (currentRule->linkedEntList);
00604     }
00605     else
00606     {
00607         mergeRule->targetEnt = NULL;
00608         mergeRule->difference = 0;
00609         mergeRule->mergeResult = MERGE_NEW;
00610         mergeRule->linkedEntList =
00611             g_slist_copy (currentRule->linkedEntList);
00612     }
00613     mergeData->mergeList =
00614         g_list_prepend (mergeData->mergeList, mergeRule);
00615     guid_free (g);
00616     /* return to qof_book_merge_init */
00617 }
00618 
00619 static void
00620 qof_book_merge_foreach_param (QofParam * param, gpointer user_data)
00621 {
00622     QofBookMergeData *mergeData;
00623 
00624     g_return_if_fail (user_data != NULL);
00625     mergeData = (QofBookMergeData *) user_data;
00626     g_return_if_fail (param != NULL);
00627     if ((param->param_getfcn != NULL) && (param->param_setfcn != NULL))
00628     {
00629         mergeData->mergeObjectParams =
00630             g_slist_append (mergeData->mergeObjectParams, param);
00631     }
00632 }
00633 
00634 static void
00635 qof_book_merge_foreach_type (QofObject * merge_obj, gpointer user_data)
00636 {
00637     QofBookMergeData *mergeData;
00638 
00639     g_return_if_fail (user_data != NULL);
00640     mergeData = (QofBookMergeData *) user_data;
00641     g_return_if_fail ((merge_obj != NULL));
00642     /* Skip unsupported objects */
00643     if ((merge_obj->create == NULL) || (merge_obj->foreach == NULL))
00644     {
00645         DEBUG (" merge_obj QOF support failed %s", merge_obj->e_type);
00646         return;
00647     }
00648     if (mergeData->mergeObjectParams != NULL)
00649         g_slist_free (mergeData->mergeObjectParams);
00650     mergeData->mergeObjectParams = NULL;
00651     qof_class_param_foreach (merge_obj->e_type,
00652         qof_book_merge_foreach_param, mergeData);
00653     qof_object_foreach (merge_obj->e_type, mergeData->mergeBook,
00654         qof_book_merge_foreach, mergeData);
00655 }
00656 
00657 static void
00658 qof_book_merge_rule_cb (gpointer rule, gpointer arg)
00659 {
00660     struct QofBookMergeRuleIterate *qiter;
00661     QofBookMergeData *mergeData;
00662 
00663     g_return_if_fail (arg != NULL);
00664     qiter = (struct QofBookMergeRuleIterate *) arg;
00665     mergeData = qiter->data;
00666     g_return_if_fail (mergeData != NULL);
00667     g_return_if_fail (mergeData->abort == FALSE);
00668     qiter->fcn (mergeData, (QofBookMergeRule *) rule, qiter->remainder);
00669     qiter->data = mergeData;
00670     qiter->remainder--;
00671 }
00672 
00673 static void
00674 qof_book_merge_commit_rule_loop (QofBookMergeData * mergeData,
00675     QofBookMergeRule * rule, guint remainder __attribute__ ((unused)))
00676 {
00677     QofInstance *inst;
00678     gboolean registered_type;
00679     QofEntity *referenceEnt;
00680     /* cm_ prefix used for variables that hold the data to commit */
00681     QofCollection *cm_coll;
00682     QofParam *cm_param;
00683     gchar *cm_string;
00684     const GUID *cm_guid;
00685     KvpFrame *cm_kvp;
00686     QofTime *cm_qt;
00687     /* function pointers and variables for parameter getters that don't use pointers normally */
00688     QofNumeric cm_numeric, (*numeric_getter) (QofEntity *, QofParam *);
00689     gdouble cm_double, (*double_getter) (QofEntity *, QofParam *);
00690     gboolean cm_boolean, (*boolean_getter) (QofEntity *, QofParam *);
00691     gint32 cm_i32, (*int32_getter) (QofEntity *, QofParam *);
00692     gint64 cm_i64, (*int64_getter) (QofEntity *, QofParam *);
00693     gchar cm_char, (*char_getter) (QofEntity *, QofParam *);
00694     /* function pointers to the parameter setters */
00695     void (*string_setter) (QofEntity *, const gchar *);
00696     void (*time_setter) (QofEntity *, QofTime *);
00697     void (*numeric_setter) (QofEntity *, QofNumeric);
00698     void (*guid_setter) (QofEntity *, const GUID *);
00699     void (*double_setter) (QofEntity *, double);
00700     void (*boolean_setter) (QofEntity *, gboolean);
00701     void (*i32_setter) (QofEntity *, gint32);
00702     void (*i64_setter) (QofEntity *, gint64);
00703     void (*char_setter) (QofEntity *, gchar);
00704     void (*kvp_frame_setter) (QofEntity *, KvpFrame *);
00705     void (*reference_setter) (QofEntity *, QofEntity *);
00706     void (*collection_setter) (QofEntity *, QofCollection *);
00707 
00708     g_return_if_fail (rule != NULL);
00709     g_return_if_fail (mergeData != NULL);
00710     g_return_if_fail (mergeData->targetBook != NULL);
00711     g_return_if_fail ((rule->mergeResult != MERGE_NEW)
00712         || (rule->mergeResult != MERGE_UPDATE));
00713     /* create a new object for MERGE_NEW */
00714     /* The new object takes the GUID from the import to retain an absolute match */
00715     if (rule->mergeResult == MERGE_NEW)
00716     {
00717         inst =
00718             (QofInstance *) qof_object_new_instance (rule->importEnt->
00719             e_type, mergeData->targetBook);
00720         g_return_if_fail (inst != NULL);
00721         rule->targetEnt = &inst->entity;
00722         qof_entity_set_guid (rule->targetEnt,
00723             qof_entity_get_guid (rule->importEnt));
00724     }
00725     /* currentRule->targetEnt is now set,
00726        1. by an absolute GUID match or
00727        2. by best_matchEnt and difference or
00728        3. by MERGE_NEW.
00729      */
00730     while (rule->mergeParam != NULL)
00731     {
00732         registered_type = FALSE;
00733         g_return_if_fail (rule->mergeParam->data);
00734         cm_param = rule->mergeParam->data;
00735         rule->mergeType = cm_param->param_type;
00736         if (safe_strcmp (rule->mergeType, QOF_TYPE_STRING) == 0)
00737         {
00738             cm_string = cm_param->param_getfcn (rule->importEnt, cm_param);
00739             string_setter =
00740                 (void (*)(QofEntity *,
00741                     const gchar *)) cm_param->param_setfcn;
00742             if (string_setter != NULL)
00743                 string_setter (rule->targetEnt, cm_string);
00744             registered_type = TRUE;
00745         }
00746         if (safe_strcmp (rule->mergeType, QOF_TYPE_TIME) == 0)
00747         {
00748             QofTime *(*time_getter) (QofEntity *, QofParam *);
00749 
00750             time_getter =
00751                 (QofTime* (*)(QofEntity *, QofParam *))cm_param->param_getfcn;
00752             cm_qt = qof_time_copy (
00753                 time_getter (rule->importEnt, cm_param));
00754             time_setter = 
00755                 (void (*)(QofEntity *, QofTime *))
00756                 cm_param->param_setfcn;
00757             if ((time_setter != NULL) && (qof_time_is_valid (cm_qt)))
00758                 time_setter (rule->targetEnt, cm_qt);
00759             registered_type = TRUE;
00760         }
00761         if ((safe_strcmp (rule->mergeType, QOF_TYPE_NUMERIC) == 0) ||
00762             (safe_strcmp (rule->mergeType, QOF_TYPE_DEBCRED) == 0))
00763         {
00764             numeric_getter =
00765                 (QofNumeric (*)(QofEntity *, QofParam *)) cm_param->
00766                 param_getfcn;
00767             cm_numeric = numeric_getter (rule->importEnt, cm_param);
00768             numeric_setter =
00769                 (void (*)(QofEntity *,
00770                     QofNumeric)) cm_param->param_setfcn;
00771             if (numeric_setter != NULL)
00772                 numeric_setter (rule->targetEnt, cm_numeric);
00773             registered_type = TRUE;
00774         }
00775         if (safe_strcmp (rule->mergeType, QOF_TYPE_GUID) == 0)
00776         {
00777             cm_guid = cm_param->param_getfcn (rule->importEnt, cm_param);
00778             guid_setter =
00779                 (void (*)(QofEntity *,
00780                     const GUID *)) cm_param->param_setfcn;
00781             if (guid_setter != NULL)
00782                 guid_setter (rule->targetEnt, cm_guid);
00783             registered_type = TRUE;
00784         }
00785         if (safe_strcmp (rule->mergeType, QOF_TYPE_INT32) == 0)
00786         {
00787             int32_getter =
00788                 (gint32 (*)(QofEntity *,
00789                     QofParam *)) cm_param->param_getfcn;
00790             cm_i32 = int32_getter (rule->importEnt, cm_param);
00791             i32_setter =
00792                 (void (*)(QofEntity *, gint32)) cm_param->param_setfcn;
00793             if (i32_setter != NULL)
00794                 i32_setter (rule->targetEnt, cm_i32);
00795             registered_type = TRUE;
00796         }
00797         if (safe_strcmp (rule->mergeType, QOF_TYPE_INT64) == 0)
00798         {
00799             int64_getter =
00800                 (gint64 (*)(QofEntity *,
00801                     QofParam *)) cm_param->param_getfcn;
00802             cm_i64 = int64_getter (rule->importEnt, cm_param);
00803             i64_setter =
00804                 (void (*)(QofEntity *, gint64)) cm_param->param_setfcn;
00805             if (i64_setter != NULL)
00806                 i64_setter (rule->targetEnt, cm_i64);
00807             registered_type = TRUE;
00808         }
00809         if (safe_strcmp (rule->mergeType, QOF_TYPE_DOUBLE) == 0)
00810         {
00811             double_getter =
00812                 (double (*)(QofEntity *,
00813                     QofParam *)) cm_param->param_getfcn;
00814             cm_double = double_getter (rule->importEnt, cm_param);
00815             double_setter =
00816                 (void (*)(QofEntity *, double)) cm_param->param_setfcn;
00817             if (double_setter != NULL)
00818                 double_setter (rule->targetEnt, cm_double);
00819             registered_type = TRUE;
00820         }
00821         if (safe_strcmp (rule->mergeType, QOF_TYPE_BOOLEAN) == 0)
00822         {
00823             boolean_getter =
00824                 (gboolean (*)(QofEntity *, QofParam *)) cm_param->
00825                 param_getfcn;
00826             cm_boolean = boolean_getter (rule->importEnt, cm_param);
00827             boolean_setter =
00828                 (void (*)(QofEntity *, gboolean)) cm_param->param_setfcn;
00829             if (boolean_setter != NULL)
00830                 boolean_setter (rule->targetEnt, cm_boolean);
00831             registered_type = TRUE;
00832         }
00833         if (safe_strcmp (rule->mergeType, QOF_TYPE_KVP) == 0)
00834         {
00835             cm_kvp =
00836                 kvp_frame_copy (cm_param->
00837                 param_getfcn (rule->importEnt, cm_param));
00838             kvp_frame_setter =
00839                 (void (*)(QofEntity *, KvpFrame *)) cm_param->param_setfcn;
00840             if (kvp_frame_setter != NULL)
00841                 kvp_frame_setter (rule->targetEnt, cm_kvp);
00842             registered_type = TRUE;
00843         }
00844         if (safe_strcmp (rule->mergeType, QOF_TYPE_CHAR) == 0)
00845         {
00846             char_getter =
00847                 (gchar (*)(QofEntity *,
00848                     QofParam *)) cm_param->param_getfcn;
00849             cm_char = char_getter (rule->importEnt, cm_param);
00850             char_setter =
00851                 (void (*)(QofEntity *, gchar)) cm_param->param_setfcn;
00852             if (char_setter != NULL)
00853                 char_setter (rule->targetEnt, cm_char);
00854             registered_type = TRUE;
00855         }
00856         if (safe_strcmp (rule->mergeType, QOF_TYPE_COLLECT) == 0)
00857         {
00858             cm_coll = cm_param->param_getfcn (rule->importEnt, cm_param);
00859             collection_setter =
00860                 (void (*)(QofEntity *, QofCollection *)) cm_param->
00861                 param_setfcn;
00862             if (collection_setter != NULL)
00863                 collection_setter (rule->targetEnt, cm_coll);
00864             registered_type = TRUE;
00865         }
00866         if (safe_strcmp (rule->mergeType, QOF_TYPE_CHOICE) == 0)
00867         {
00868             referenceEnt =
00869                 cm_param->param_getfcn (rule->importEnt, cm_param);
00870             reference_setter =
00871                 (void (*)(QofEntity *,
00872                     QofEntity *)) cm_param->param_setfcn;
00873             if (reference_setter != NULL)
00874                 reference_setter (rule->targetEnt, referenceEnt);
00875             registered_type = TRUE;
00876         }
00877         if (registered_type == FALSE)
00878         {
00879             referenceEnt =
00880                 cm_param->param_getfcn (rule->importEnt, cm_param);
00881             if (referenceEnt)
00882             {
00883                 reference_setter =
00884                     (void (*)(QofEntity *, QofEntity *)) cm_param->
00885                     param_setfcn;
00886                 if (reference_setter != NULL)
00887                 {
00888                     reference_setter (rule->targetEnt, referenceEnt);
00889                 }
00890             }
00891         }
00892         rule->mergeParam = g_slist_next (rule->mergeParam);
00893     }
00894 }
00895 
00896 /* ================================================================ */
00897 /* API functions. */
00898 
00899 QofBookMergeData *
00900 qof_book_merge_init (QofBook * importBook, QofBook * targetBook)
00901 {
00902     QofBookMergeData *mergeData;
00903     QofBookMergeRule *currentRule;
00904     GList *check;
00905 
00906     g_return_val_if_fail ((importBook != NULL)
00907         && (targetBook != NULL), NULL);
00908     mergeData = g_new0 (QofBookMergeData, 1);
00909     mergeData->abort = FALSE;
00910     mergeData->mergeList = NULL;
00911     mergeData->targetList = NULL;
00912     mergeData->mergeBook = importBook;
00913     mergeData->targetBook = targetBook;
00914     mergeData->mergeObjectParams = NULL;
00915     mergeData->orphan_list = NULL;
00916     mergeData->target_table =
00917         g_hash_table_new (g_direct_hash, qof_book_merge_rule_cmp);
00918     currentRule = g_new0 (QofBookMergeRule, 1);
00919     mergeData->currentRule = currentRule;
00920     qof_object_foreach_type (qof_book_merge_foreach_type, mergeData);
00921     g_return_val_if_fail (mergeData->mergeObjectParams, NULL);
00922     if (mergeData->orphan_list != NULL)
00923         qof_book_merge_match_orphans (mergeData);
00924     for (check = mergeData->mergeList; check != NULL; check = check->next)
00925     {
00926         currentRule = check->data;
00927         if (currentRule->mergeResult == MERGE_INVALID)
00928         {
00929             mergeData->abort = TRUE;
00930             return (NULL);
00931         }
00932     }
00933     return mergeData;
00934 }
00935 
00936 void
00937 qof_book_merge_abort (QofBookMergeData * mergeData)
00938 {
00939     QofBookMergeRule *currentRule;
00940 
00941     g_return_if_fail (mergeData != NULL);
00942     while (mergeData->mergeList != NULL)
00943     {
00944         currentRule = mergeData->mergeList->data;
00945         g_slist_free (currentRule->linkedEntList);
00946         g_slist_free (currentRule->mergeParam);
00947         g_free (mergeData->mergeList->data);
00948         if (currentRule)
00949         {
00950             g_slist_free (currentRule->linkedEntList);
00951             g_slist_free (currentRule->mergeParam);
00952             g_free (currentRule);
00953         }
00954         mergeData->mergeList = g_list_next (mergeData->mergeList);
00955     }
00956     g_list_free (mergeData->mergeList);
00957     g_slist_free (mergeData->mergeObjectParams);
00958     g_slist_free (mergeData->targetList);
00959     if (mergeData->orphan_list != NULL)
00960         g_slist_free (mergeData->orphan_list);
00961     g_hash_table_destroy (mergeData->target_table);
00962     g_free (mergeData);
00963 }
00964 
00965 QofBookMergeData *
00966 qof_book_merge_update_result (QofBookMergeData * mergeData,
00967     QofBookMergeResult tag)
00968 {
00969     QofBookMergeRule *resolved;
00970 
00971     g_return_val_if_fail ((mergeData != NULL), NULL);
00972     g_return_val_if_fail ((tag > 0), NULL);
00973     g_return_val_if_fail ((tag != MERGE_REPORT), NULL);
00974     resolved = mergeData->currentRule;
00975     g_return_val_if_fail ((resolved != NULL), NULL);
00976     if ((resolved->mergeAbsolute == TRUE) && (tag == MERGE_DUPLICATE))
00977         tag = MERGE_ABSOLUTE;
00978     if ((resolved->mergeAbsolute == TRUE) && (tag == MERGE_NEW))
00979         tag = MERGE_UPDATE;
00980     if ((resolved->mergeAbsolute == FALSE) && (tag == MERGE_ABSOLUTE))
00981         tag = MERGE_DUPLICATE;
00982     if ((resolved->mergeResult == MERGE_NEW) && (tag == MERGE_UPDATE))
00983         tag = MERGE_NEW;
00984     if (resolved->updated == FALSE)
00985         resolved->mergeResult = tag;
00986     resolved->updated = TRUE;
00987     if (tag >= MERGE_INVALID)
00988     {
00989         mergeData->abort = TRUE;
00990         mergeData->currentRule = resolved;
00991         return NULL;
00992     }
00993     mergeData->currentRule = resolved;
00994     return mergeData;
00995 }
00996 
00997 gint
00998 qof_book_merge_commit (QofBookMergeData * mergeData)
00999 {
01000     QofBookMergeRule *currentRule;
01001     GList *check, *node;
01002 
01003     g_return_val_if_fail (mergeData != NULL, -1);
01004     g_return_val_if_fail (mergeData->mergeList != NULL, -1);
01005     g_return_val_if_fail (mergeData->targetBook != NULL, -1);
01006     if (mergeData->abort == TRUE)
01007         return -1;
01008     check = g_list_copy (mergeData->mergeList);
01009     g_return_val_if_fail (check != NULL, -1);
01010     for (node = check; node != NULL; node = node->next)
01011     {
01012         currentRule = node->data;
01013         if (currentRule->mergeResult == MERGE_INVALID)
01014         {
01015             qof_book_merge_abort (mergeData);
01016             g_list_free (check);
01017             return (-2);
01018         }
01019         if (currentRule->mergeResult == MERGE_REPORT)
01020         {
01021             g_list_free (check);
01022             return 1;
01023         }
01024     }
01025     g_list_free (check);
01026     qof_book_merge_commit_foreach (qof_book_merge_commit_rule_loop,
01027         MERGE_NEW, mergeData);
01028     qof_book_merge_commit_foreach (qof_book_merge_commit_rule_loop,
01029         MERGE_UPDATE, mergeData);
01030     /* Placeholder for QofObject merge_helper_cb - all objects
01031        and all parameters set */
01032     while (mergeData->mergeList != NULL)
01033     {
01034         currentRule = mergeData->mergeList->data;
01035         g_slist_free (currentRule->mergeParam);
01036         g_slist_free (currentRule->linkedEntList);
01037         mergeData->mergeList = g_list_next (mergeData->mergeList);
01038     }
01039     g_list_free (mergeData->mergeList);
01040     g_slist_free (mergeData->mergeObjectParams);
01041     g_slist_free (mergeData->targetList);
01042     if (mergeData->orphan_list != NULL)
01043         g_slist_free (mergeData->orphan_list);
01044     g_hash_table_destroy (mergeData->target_table);
01045     g_free (mergeData);
01046     return 0;
01047 }
01048 
01049 void
01050 qof_book_merge_rule_foreach (QofBookMergeData * mergeData,
01051     QofBookMergeRuleForeachCB cb, QofBookMergeResult mergeResult)
01052 {
01053     struct QofBookMergeRuleIterate qiter;
01054     QofBookMergeRule *currentRule;
01055     GList *matching_rules, *node;
01056 
01057     g_return_if_fail (cb != NULL);
01058     g_return_if_fail (mergeData != NULL);
01059     currentRule = mergeData->currentRule;
01060     g_return_if_fail (mergeResult > 0);
01061     g_return_if_fail (mergeResult != MERGE_INVALID);
01062     g_return_if_fail (mergeData->abort == FALSE);
01063     qiter.fcn = cb;
01064     qiter.data = mergeData;
01065     matching_rules = NULL;
01066     for (node = mergeData->mergeList; node != NULL; node = node->next)
01067     {
01068         currentRule = node->data;
01069         if (currentRule->mergeResult == mergeResult)
01070             matching_rules = g_list_prepend (matching_rules, 
01071                 currentRule);
01072     }
01073     qiter.remainder = g_list_length (matching_rules);
01074     g_list_foreach (matching_rules, qof_book_merge_rule_cb, &qiter);
01075     g_list_free (matching_rules);
01076 }
01077 
01078 /* ============================================================== */