1 /*
2  * Copyright 2016 Collabora Ltd.
3  *
4  * The geocode-glib library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * The geocode-glib library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with the Gnome Library; see the file COPYING.LIB.  If not,
16  * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301  USA.
18  *
19  * Authors: Philip Withnall <philip.withnall@collabora.co.uk>
20  */
21 
22 #include <gio/gio.h>
23 #include <json-glib/json-glib.h>
24 #include <libsoup/soup.h>
25 #include <stdlib.h>
26 #include <string.h>
27 
28 #include "geocode-glib-private.h"
29 #include "geocode-glib.h"
30 #include "geocode-mock-backend.h"
31 
32 /**
33  * SECTION:geocode-mock-backend
34  * @short_description: Geocode mock backend implementation
35  * @include: geocode-glib/geocode-glib.h
36  *
37  * #GeocodeMockBackend is intended to be used in unit tests for applications
38  * which use geocode-glib — it allows them to set the geocode results they
39  * expect their application to query, and check afterwards that the queries
40  * were performed. It works offline, which allows application unit tests to be
41  * run on integration and build machines which are not online. It is not
42  * expected that #GeocodeMockBackend will be used in production code.
43  *
44  * To use it, create the backend instance, add the query results to it which
45  * you want to be returned to your application’s queries, then use it as the
46  * #GeocodeBackend for geocode_forward_set_backend() or
47  * geocode_reverse_set_backend(). After a test has been run, the set of queries
48  * which the code under test actually made on the backend can be checked using
49  * geocode_mock_backend_get_query_log(). The backend can be reset using
50  * geocode_mock_backend_clear() and new queries added for the next test.
51  *
52  * |[<!-- language="C" -->
53  * static void
54  * place_list_free (GList *l)
55  * {
56  *   g_list_free_full (l, g_object_unref);
57  * }
58  *
59  * typedef GList PlaceList;
60  * G_DEFINE_AUTOPTR_CLEANUP_FUNC (PlaceList, place_list_free)
61  *
62  * g_autoptr (GeocodeForward) forward = NULL;
63  * g_autoptr (GeocodeMockBackend) backend = NULL;
64  * g_autoptr (GHashTable) params = NULL;
65  * GValue location = G_VALUE_INIT;
66  * g_autoptr (PlaceList) results = NULL;
67  * g_autoptr (PlaceList) expected_results = NULL;
68  * g_autoptr (GError) error = NULL;
69  * g_autoptr (GeocodePlace) expected_place = NULL;
70  * g_autoptr (GeocodeLocation) expected_location = NULL;
71  * GPtrArray *query_log;  /<!-- -->* (element-type GeocodeMockBackendQuery) *<!-- -->/
72  *
73  * backend = geocode_mock_backend_new ();
74  *
75  * /<!-- -->* Build the set of parameters the mock backend expects to receive from
76  *  * the #GeocodeForward instance. *<!-- -->/
77  * params = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
78  *
79  * g_value_init (&location, G_TYPE_STRING);
80  * g_value_set_static_string (&location, "Bullpot Farm");
81  * g_hash_table_insert (params, (gpointer) "location", &location);
82  *
83  * /<!-- -->* Build the set of results the mock backend should return. *<!-- -->/
84  * expected_location = geocode_location_new_with_description (
85  *     54.22759825, -2.51857179181113, 5.0,
86  *     "Bullpot Farm, Fell Road, South Lakeland, Cumbria, "
87  *     "North West England, England, United Kingdom");
88  * expected_place = geocode_place_new_with_location (
89  *     "Bullpot Farm", GEOCODE_PLACE_TYPE_BUILDING, expected_location);
90  * expected_results = g_list_prepend (expected_results,
91  *                                    g_steal_pointer (&expected_place));
92  *
93  * geocode_mock_backend_add_forward_result (backend, params,
94  *                                          expected_results, NULL);
95  *
96  * /<!-- -->* Do the search. This would typically call the application code
97  *  * under test, rather than geocode-glib directly. *<!-- -->/
98  * forward = geocode_forward_new_for_string ("Bullpot Farm");
99  * geocode_forward_set_backend (forward, GEOCODE_BACKEND (backend));
100  * results = geocode_forward_search (forward, &error);
101  *
102  * g_assert_no_error (error);
103  * assert_place_list_equal (results, expected_results);
104  *
105  * /<!-- -->* Check the application made the expected query. *<!-- -->/
106  * query_log = geocode_mock_backend_get_query_log (backend);
107  * g_assert_cmpuint (query_log->len, ==, 1);
108  * ]|
109  *
110  * Since: 3.23.1
111  */
112 
113 struct _GeocodeMockBackend {
114 	GObject parent;
115 
116 	GPtrArray *forward_results;  /* (owned) (element-type owned GeocodeMockBackendQuery) */
117 	GPtrArray *reverse_results;  /* (owned) (element-type owned GeocodeMockBackendQuery) */
118 	GPtrArray *query_log;  /* (owned) (element-type owned GeocodeMockBackendQuery) */
119 };
120 
121 static void geocode_backend_iface_init (GeocodeBackendInterface *iface);
122 
G_DEFINE_TYPE_WITH_CODE(GeocodeMockBackend,geocode_mock_backend,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (GEOCODE_TYPE_BACKEND,geocode_backend_iface_init))123 G_DEFINE_TYPE_WITH_CODE (GeocodeMockBackend, geocode_mock_backend, G_TYPE_OBJECT,
124                          G_IMPLEMENT_INTERFACE (GEOCODE_TYPE_BACKEND,
125                                                 geocode_backend_iface_init))
126 
127 /******************************************************************************/
128 
129 static void
130 value_free (GValue *value)
131 {
132 	g_value_unset (value);
133 	g_free (value);
134 }
135 
136 static GHashTable *
params_copy_deep(GHashTable * params)137 params_copy_deep (GHashTable *params)
138 {
139 	g_autoptr (GHashTable) output = NULL;
140 	GHashTableIter iter;
141 	const gchar *key;
142 	const GValue *value;
143 
144 	output = g_hash_table_new_full (g_str_hash, g_str_equal,
145 	                                g_free, (GDestroyNotify) value_free);
146 
147 	g_hash_table_iter_init (&iter, params);
148 
149 	while (g_hash_table_iter_next (&iter, (gpointer *) &key,
150 	                               (gpointer *) &value)) {
151 		GValue *value_copy = NULL;
152 
153 		value_copy = g_new0 (GValue, 1);
154 		g_value_init (value_copy, G_VALUE_TYPE (value));
155 		g_value_copy (value, value_copy);
156 
157 		g_hash_table_insert (output, g_strdup (key),
158 		                     g_steal_pointer (&value_copy));
159 	}
160 
161 	return g_steal_pointer (&output);
162 }
163 
164 static GList *
results_copy_deep(GList * results)165 results_copy_deep (GList *results)
166 {
167 	return g_list_copy_deep (results, (GCopyFunc) g_object_ref, NULL);
168 }
169 
170 /******************************************************************************/
171 
172 static void
geocode_mock_backend_query_free(GeocodeMockBackendQuery * query)173 geocode_mock_backend_query_free (GeocodeMockBackendQuery *query)
174 {
175 	if (query == NULL)
176 		return;
177 
178 	g_hash_table_unref (query->params);
179 	g_list_free_full (query->results, g_object_unref);
180 	g_clear_error (&query->error);
181 
182 	g_free (query);
183 }
184 
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GeocodeMockBackendQuery,geocode_mock_backend_query_free)185 G_DEFINE_AUTOPTR_CLEANUP_FUNC (GeocodeMockBackendQuery,
186                                geocode_mock_backend_query_free)
187 
188 static GeocodeMockBackendQuery *
189 geocode_mock_backend_query_new (GHashTable   *params,
190                                 gboolean      is_forward,
191                                 GList        *results,
192                                 const GError *error)
193 {
194 	g_autoptr (GeocodeMockBackendQuery) query = NULL;
195 
196 	g_return_val_if_fail (params != NULL, NULL);
197 	g_return_val_if_fail ((results == NULL) != (error == NULL), NULL);
198 
199 	query = g_new0 (GeocodeMockBackendQuery, 1);
200 
201 	query->params = params_copy_deep (params);
202 	query->is_forward = is_forward;
203 	query->results = results_copy_deep (results);
204 	query->error = (error != NULL) ? g_error_copy (error) : NULL;
205 
206 	return g_steal_pointer (&query);
207 }
208 
209 /******************************************************************************/
210 
211 static gboolean
value_equal(const GValue * a,const GValue * b)212 value_equal (const GValue *a,
213              const GValue *b)
214 {
215 	GValue a_string = G_VALUE_INIT, b_string = G_VALUE_INIT;
216 	gboolean equal;
217 
218 	g_return_val_if_fail (a != NULL, FALSE);
219 	g_return_val_if_fail (b != NULL, FALSE);
220 
221 	if (G_VALUE_TYPE (a) != G_VALUE_TYPE (b))
222 		return FALSE;
223 
224 	/* Doubles can’t be converted to strings, so special-case comparison
225 	 * of them. */
226 	if (G_VALUE_TYPE (a) == G_TYPE_DOUBLE) {
227 		return g_value_get_double (a) == g_value_get_double (b);
228 	}
229 
230 	g_value_init (&a_string, G_TYPE_STRING);
231 	g_value_init (&b_string, G_TYPE_STRING);
232 
233 	/* We assume that all GValue types can be converted to strings for the
234 	 * purpose of comparison. */
235 	if (!g_value_transform (a, &a_string) ||
236 	    !g_value_transform (b, &b_string))
237 		return FALSE;
238 
239 	equal = g_str_equal (g_value_get_string (&a_string),
240 	                     g_value_get_string (&b_string));
241 
242 	g_value_unset (&b_string);
243 	g_value_unset (&a_string);
244 
245 	return equal;
246 }
247 
248 static gboolean
hash_table_equal(GHashTable * a,GHashTable * b)249 hash_table_equal (GHashTable *a,
250                   GHashTable *b)
251 {
252 	GHashTableIter iter_a;
253 	const gchar *key;
254 	const GValue *value_a, *value_b;
255 
256 	if (g_hash_table_size (a) != g_hash_table_size (b))
257 		return FALSE;
258 
259 	g_hash_table_iter_init (&iter_a, a);
260 
261 	while (g_hash_table_iter_next (&iter_a, (gpointer *) &key,
262 	                               (gpointer *) &value_a)) {
263 		if (!g_hash_table_lookup_extended (b, key, NULL,
264 		                                   (gpointer *) &value_b) ||
265 		    !value_equal (value_a, value_b))
266 			return FALSE;
267 	}
268 
269 	return TRUE;
270 }
271 
272 static const GeocodeMockBackendQuery *
find_query(GPtrArray * queries,GHashTable * params,gsize * index)273 find_query (GPtrArray  *queries,
274             GHashTable *params,
275             gsize      *index)
276 {
277 	gsize i;
278 
279 	for (i = 0; i < queries->len; i++) {
280 		const GeocodeMockBackendQuery *query = queries->pdata[i];
281 
282 		if (hash_table_equal (query->params, params)) {
283 			if (index != NULL)
284 				*index = i;
285 
286 			return query;
287 		}
288 	}
289 
290 	return NULL;
291 }
292 
293 static void
debug_print_params(GHashTable * params)294 debug_print_params (GHashTable *params)
295 {
296 	GHashTableIter iter;
297 	const gchar *key;
298 	const GValue *value;
299 	g_autoptr (GString) output = NULL;
300 	g_autofree gchar *output_str = NULL;
301 	gboolean non_empty = FALSE;
302 
303 	g_hash_table_iter_init (&iter, params);
304 	output = g_string_new ("");
305 
306 	while (g_hash_table_iter_next (&iter, (gpointer *) &key,
307 	                               (gpointer *) &value)) {
308 		g_autofree gchar *value_str = NULL;
309 
310 		value_str = g_strdup_value_contents (value);
311 		g_string_append_printf (output, " • %s = %s\n", key, value_str);
312 
313 		non_empty = TRUE;
314 	}
315 
316 	if (non_empty)
317 		g_string_prepend (output, "Parameters:\n");
318 	else
319 		g_string_append (output, "Parameters: (none)\n");
320 
321 	/* Strip off the trailing newline. */
322 	g_string_truncate (output, output->len - 1);
323 
324 	output_str = g_string_free (g_steal_pointer (&output), FALSE);
325 	g_debug ("%s", output_str);
326 }
327 
328 static GList *
forward_or_reverse(GeocodeMockBackend * self,GPtrArray * results,GeocodeError no_results_error,GHashTable * params,GCancellable * cancellable,GError ** error)329 forward_or_reverse (GeocodeMockBackend  *self,
330                     GPtrArray           *results,
331                     GeocodeError         no_results_error,
332                     GHashTable          *params,
333                     GCancellable        *cancellable,
334                     GError             **error)
335 {
336 	const GeocodeMockBackendQuery *query;
337 	g_autoptr (GeocodeMockBackendQuery) logged_query = NULL;
338 	GList *output_results = NULL;  /* (element-type GeocodePlace) */
339 	g_autoptr (GError) output_error = NULL;
340 
341 	/* Log the query; helpful during development. */
342 	debug_print_params (params);
343 
344 	/* Do we have a mock result for this query? */
345 	query = find_query (results, params, NULL);
346 
347 	if (query == NULL) {
348 		output_error = g_error_new (GEOCODE_ERROR, no_results_error,
349 		                            "No matches found for request");
350 	} else if (query->error != NULL) {
351 		output_error = g_error_copy (query->error);
352 	} else {
353 		output_results = results_copy_deep (query->results);
354 	}
355 
356 	/* Log the query. */
357 	logged_query = geocode_mock_backend_query_new (params, TRUE,
358 	                                               output_results,
359 	                                               output_error);
360 	g_ptr_array_add (self->query_log, g_steal_pointer (&logged_query));
361 
362 	/* Output either the results or the error. */
363 	g_assert ((output_results == NULL) != (output_error == NULL));
364 
365 	if (output_error != NULL)
366 		g_propagate_error (error, g_steal_pointer (&output_error));
367 
368 	return g_steal_pointer (&output_results);
369 }
370 
371 static GList *
geocode_mock_backend_forward_search(GeocodeBackend * backend,GHashTable * params,GCancellable * cancellable,GError ** error)372 geocode_mock_backend_forward_search (GeocodeBackend  *backend,
373                                      GHashTable      *params,
374                                      GCancellable    *cancellable,
375                                      GError         **error)
376 {
377 	GeocodeMockBackend *self = GEOCODE_MOCK_BACKEND (backend);
378 
379 	return forward_or_reverse (self, self->forward_results,
380 	                           GEOCODE_ERROR_NO_MATCHES, params,
381 	                           cancellable, error);
382 }
383 
384 static GList *
geocode_mock_backend_reverse_resolve(GeocodeBackend * backend,GHashTable * params,GCancellable * cancellable,GError ** error)385 geocode_mock_backend_reverse_resolve (GeocodeBackend  *backend,
386                                       GHashTable      *params,
387                                       GCancellable    *cancellable,
388                                       GError         **error)
389 {
390 	GeocodeMockBackend *self = GEOCODE_MOCK_BACKEND (backend);
391 
392 	return forward_or_reverse (self, self->reverse_results,
393 	                           GEOCODE_ERROR_NOT_SUPPORTED,
394 	                           params, cancellable, error);
395 }
396 
397 /******************************************************************************/
398 
399 /**
400  * geocode_mock_backend_new:
401  *
402  * Creates a new mock backend implementation with no initial forward or reverse
403  * query results (so it will return an empty result set for all queries).
404  *
405  * Returns: (transfer full): a new #GeocodeMockBackend
406  *
407  * Since: 3.23.1
408  */
409 GeocodeMockBackend *
geocode_mock_backend_new(void)410 geocode_mock_backend_new (void)
411 {
412 	return GEOCODE_MOCK_BACKEND (g_object_new (GEOCODE_TYPE_MOCK_BACKEND,
413 	                                           NULL));
414 }
415 
416 /**
417  * geocode_mock_backend_add_forward_result:
418  * @self: a #GeocodeMockBackend
419  * @params: (transfer none) (element-type utf8 GValue): query parameters to
420  *     respond to, in the same format as accepted by geocode_forward_search()
421  * @results: (transfer none) (nullable) (element-type GeocodePlace): result set
422  *     to return for the query, or %NULL if @error is non-%NULL; result sets
423  *     must be in the same format as returned by geocode_forward_search()
424  * @error: (nullable): error to return for the query, or %NULL if @results
425  *     should be returned instead; errors must match those returned by
426  *     geocode_forward_search()
427  *
428  * Add a query and corresponding result (or error) to the mock backend, meaning
429  * that if it receives a forward search for @params through
430  * geocode_backend_forward_search() (or its asynchronous variants), the mock
431  * backend will return the given @results or @error to the caller.
432  *
433  * If a set of @params is added to the backend multiple times, the most
434  * recently provided @results and @error will be used.
435  *
436  * Exactly one of @results and @error must be set. Empty result sets are
437  * represented as a %GEOCODE_ERROR_NO_MATCHES error.
438  *
439  * Since: 3.23.1
440  */
441 void
geocode_mock_backend_add_forward_result(GeocodeMockBackend * self,GHashTable * params,GList * results,const GError * error)442 geocode_mock_backend_add_forward_result (GeocodeMockBackend *self,
443                                          GHashTable         *params,
444                                          GList              *results,
445                                          const GError       *error)
446 {
447 	g_autoptr (GeocodeMockBackendQuery) query = NULL;
448 	gsize idx;
449 
450 	g_return_if_fail (GEOCODE_IS_MOCK_BACKEND (self));
451 	g_return_if_fail (params != NULL);
452 	g_return_if_fail (results == NULL || error == NULL);
453 
454 	if (find_query (self->forward_results, params, &idx))
455 		g_ptr_array_remove_index_fast (self->forward_results, idx);
456 
457 	query = geocode_mock_backend_query_new (params, TRUE, results, error);
458 	g_ptr_array_add (self->forward_results, g_steal_pointer (&query));
459 }
460 
461 /**
462  * geocode_mock_backend_add_reverse_result:
463  * @self: a #GeocodeMockBackend
464  * @params: (transfer none) (element-type utf8 GValue): query parameters to
465  *     respond to, in the same format as accepted by geocode_reverse_resolve()
466  * @results: (transfer none) (nullable) (element-type GeocodePlace): result set
467  *     to return for the query, or %NULL if @error is non-%NULL; result sets
468  *     must be in the same format as returned by geocode_reverse_resolve()
469  * @error: (nullable): error to return for the query, or %NULL if @results
470  *     should be returned instead; errors must match those returned by
471  *     geocode_reverse_resolve()
472  *
473  * Add a query and corresponding result (or error) to the mock backend, meaning
474  * that if it receives a reverse search for @params through
475  * geocode_backend_reverse_resolve() (or its asynchronous variants), the mock
476  * backend will return the given @results or @error to the caller.
477  *
478  * If a set of @params is added to the backend multiple times, the most
479  * recently provided @results and @error will be used.
480  *
481  * Exactly one of @results and @error must be set. Empty result sets are
482  * represented as a %GEOCODE_ERROR_NOT_SUPPORTED error.
483  *
484  * Since: 3.23.1
485  */
486 void
geocode_mock_backend_add_reverse_result(GeocodeMockBackend * self,GHashTable * params,GList * results,const GError * error)487 geocode_mock_backend_add_reverse_result (GeocodeMockBackend *self,
488                                          GHashTable         *params,
489                                          GList              *results,
490                                          const GError       *error)
491 {
492 	g_autoptr (GeocodeMockBackendQuery) query = NULL;
493 	gsize idx;
494 
495 	g_return_if_fail (GEOCODE_IS_MOCK_BACKEND (self));
496 	g_return_if_fail (params != NULL);
497 	g_return_if_fail (results == NULL || error == NULL);
498 
499 	if (find_query (self->reverse_results, params, &idx))
500 		g_ptr_array_remove_index_fast (self->reverse_results, idx);
501 
502 	query = geocode_mock_backend_query_new (params, FALSE, results, error);
503 	g_ptr_array_add (self->reverse_results, g_steal_pointer (&query));
504 }
505 
506 /**
507  * geocode_mock_backend_clear:
508  * @self: a #GeocodeMockBackend
509  *
510  * Clear the set of stored results in the mock backend which have been added
511  * using geocode_mock_backend_add_forward_result() and
512  * geocode_mock_backend_add_reverse_result(). Additionally, clear the query log
513  * so far (see geocode_mock_backend_get_query_log()).
514  *
515  * This effectively resets the mock backend to its initial state.
516  *
517  * Since: 3.23.1
518  */
519 void
geocode_mock_backend_clear(GeocodeMockBackend * self)520 geocode_mock_backend_clear (GeocodeMockBackend *self)
521 {
522 	g_return_if_fail (GEOCODE_MOCK_BACKEND (self));
523 
524 	g_ptr_array_set_size (self->query_log, 0);
525 	g_ptr_array_set_size (self->forward_results, 0);
526 	g_ptr_array_set_size (self->reverse_results, 0);
527 }
528 
529 /**
530  * geocode_mock_backend_get_query_log:
531  * @self: a #GeocodeMockBackend
532  *
533  * Get the details of the forward and reverse queries which have been requested
534  * of the mock backend since the most recent call to
535  * geocode_mock_backend_clear(). The query details are provided as
536  * #GeocodeMockBackendQuery structures, which map the query parameters to
537  * either the result set or the error which geocode_backend_forward_search()
538  * or geocode_backend_reverse_resolve() (or their asynchronous variants)
539  * returned to the caller.
540  *
541  * The results are provided in the order in which calls were made to
542  * geocode_backend_forward_search() and geocode_backend_reverse_resolve().
543  * Results for forward and reverse queries may be interleaved.
544  *
545  * Returns: (transfer none) (element-type GeocodeMockBackendQuery): potentially
546  *     empty sequence of forward and reverse query details
547  * Since: 3.23.1
548  */
549 GPtrArray *
geocode_mock_backend_get_query_log(GeocodeMockBackend * self)550 geocode_mock_backend_get_query_log (GeocodeMockBackend *self)
551 {
552 	g_return_val_if_fail (GEOCODE_IS_MOCK_BACKEND (self), NULL);
553 
554 	return self->query_log;
555 }
556 
557 static void
geocode_mock_backend_init(GeocodeMockBackend * self)558 geocode_mock_backend_init (GeocodeMockBackend *self)
559 {
560 	self->query_log =
561 	    g_ptr_array_new_with_free_func ((GDestroyNotify) geocode_mock_backend_query_free);
562 	self->forward_results =
563 	    g_ptr_array_new_with_free_func ((GDestroyNotify) geocode_mock_backend_query_free);
564 	self->reverse_results =
565 	    g_ptr_array_new_with_free_func ((GDestroyNotify) geocode_mock_backend_query_free);
566 }
567 
568 static void
geocode_mock_backend_finalize(GObject * object)569 geocode_mock_backend_finalize (GObject *object)
570 {
571 	GeocodeMockBackend *self = GEOCODE_MOCK_BACKEND (object);
572 
573 	g_clear_pointer (&self->forward_results, g_ptr_array_unref);
574 	g_clear_pointer (&self->reverse_results, g_ptr_array_unref);
575 
576 	G_OBJECT_CLASS (geocode_mock_backend_parent_class)->finalize (object);
577 }
578 
579 static void
geocode_backend_iface_init(GeocodeBackendInterface * iface)580 geocode_backend_iface_init (GeocodeBackendInterface *iface)
581 {
582 	/* We use the default implementation of the asynchronous methods, which
583 	 * runs the synchronous version in a thread. */
584 	iface->forward_search = geocode_mock_backend_forward_search;
585 	iface->reverse_resolve = geocode_mock_backend_reverse_resolve;
586 }
587 
588 static void
geocode_mock_backend_class_init(GeocodeMockBackendClass * klass)589 geocode_mock_backend_class_init (GeocodeMockBackendClass *klass)
590 {
591 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
592 
593 	object_class->finalize = geocode_mock_backend_finalize;
594 }
595