/* abstract base class for all vips objects
 *
 * Edited from nip's base class, 15/10/08
 *
 * 29/5/18
 * 	- added vips_argument_get_id()
 */

/*

	Copyright (C) 1991-2003 The National Gallery

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 2.1 of the License, or (at your option) any later version.

	This library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
	02110-1301  USA

 */

/*

	These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk

 */

/*
#define DEBUG
#define VIPS_DEBUG
#define DEBUG_REF
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <glib/gi18n-lib.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

/* For VIPS_OPERATION_SEQUENTIAL_UNBUFFERED
 */
#define VIPS_DISABLE_DEPRECATION_WARNINGS
#include <vips/vips.h>
#include <vips/internal.h>
#include <vips/debug.h>

#include <gobject/gvaluecollector.h>

#include "vipsmarshal.h"

/**
 * VipsObject:
 *
 * An abstract base class for all objects in libvips.
 *
 * It has the following major features:
 *
 * - **Functional class creation**: libvips objects have a very regular
 *   lifecycle: initialise, build, use, destroy. They behave rather like
 *   function calls and are free of side-effects.
 *
 * - **Run-time introspection**: libvips objects can be fully introspected
 *   at run-time. There is no need for separate source-code analysis.
 *
 * - **Command-line interface**: Any vips object can be run from the
 *   command-line with the `vips` driver program.
 *
 * ## The [class@Object] lifecycle
 *
 * [class@Object]'s have a strictly defined lifecycle, split broadly as
 * construct and then use. In detail, the stages are:
 *
 * 1. [ctor@GObject.Object.new]. The [class@Object] is created with
 *   [ctor@GObject.Object.new]. Objects in this state are blank slates and
 *   need to have their various parameters set.
 *
 * 2. [method@GObject.Object.set]. You loop over the [struct@Argument] that
 *   the object has defined with [func@Argument.map]. Arguments have a set of
 *   flags attached to them for required, optional, input, output, type, and
 *   so on. You must set all required arguments.
 *
 * 3. [method@Object.build]. Call this to construct the object and get it
 *   ready for use. Building an object happens in four stages, see below.
 *
 * 4. [method@GObject.Object.get]. The object has now been built. You can
 *   read out any computed values.
 *
 * 5. [method@GObject.Object.unref]. When you are done with an object, you
 *   can unref it. See the section on reference counting for an explanation
 *   of the convention that [class@Object] uses. When the last ref to an
 *   object is released, the object is closed. Objects close in three stages,
 *   see below.
 *
 * The stages inside [method@Object.build] are:
 *
 * 1. Chain up through the object's `build` class methods. At each stage,
 *   each class does any initial setup and checking, then chains up to its
 *   superclass.
 *
 * 2. The innermost `build` method inside [class@Object] itself checks that
 *   all input arguments have been set and then returns.
 *
 * 3. All object `build` methods now finish executing, from innermost to
 *   outermost. They know all input arguments have been checked and supplied,
 *   so now they set all output arguments.
 *
 * 4. [method@Object.build] finishes the process by checking that all output
 *   objects have been set, and then triggering the [signal@Object::postbuild]
 *   signal. [signal@Object::postbuild] only runs if the object has constructed
 *   successfully.
 *
 * [class@Operation] has a cache of recent operation objects, see that class for
 * an explanation of [func@cache_operation_build].
 *
 * Finally, the stages inside close are:
 *
 * 1. [signal@Object::preclose]. This is emitted at the start of the
 *   [class@Object] dispose. The object is still functioning.
 *
 * 2. [signal@Object::close]. This runs just after all [struct@Argument] held
 *   by the object have been released.
 *
 * 3. [signal@Object::postclose]. This runs right at the end. The object
 *   pointer is still valid, but nothing else is.
 *
 * ## The [class@Object] reference counting convention
 *
 * [class@Object] has a set of conventions to simplify reference counting.
 *
 * 1. All input [class@GObject.Object] have a ref added to them, owned by the
 *   object. When a [class@Object] is unreffed, all of these refs to input
 *   objects are automatically dropped.
 *
 * 2. All output [class@GObject.Object] hold a ref to the object. When a
 *   [class@GObject.Object] which is an output of a [class@Object] is
 *   disposed, it must drop this reference. [class@Object] which are outputs
 *   of other [class@Object]'s will do this automatically.
 *
 * See [class@Operation] for an example of [class@Object] reference counting.
 */

/**
 * VipsArgument:
 *
 * libvips has a simple mechanism for automating at least some aspects of
 * [class@GObject.Object] properties. You add a set of macros to your
 * `_class_init()` which describe the arguments, and set the get and set
 * functions to the libvips ones.
 *
 * See [extending](extending.html) for a complete example.
 */

/**
 * VipsArgumentFlags:
 * @VIPS_ARGUMENT_NONE: no flags
 * @VIPS_ARGUMENT_REQUIRED: must be set in the constructor
 * @VIPS_ARGUMENT_CONSTRUCT: can only be set in the constructor
 * @VIPS_ARGUMENT_SET_ONCE: can only be set once
 * @VIPS_ARGUMENT_SET_ALWAYS: don't do use-before-set checks
 * @VIPS_ARGUMENT_INPUT: is an input argument (one we depend on)
 * @VIPS_ARGUMENT_OUTPUT: is an output argument (depends on us)
 * @VIPS_ARGUMENT_DEPRECATED: just there for back-compat, hide
 * @VIPS_ARGUMENT_MODIFY: the input argument will be modified
 * @VIPS_ARGUMENT_NON_HASHABLE: the argument is non-hashable
 *
 * Flags we associate with each object argument.
 *
 * Have separate input & output flags. Both set is an error; neither set is OK.
 *
 * Input gobjects are automatically reffed, output gobjects automatically ref
 * us. We also automatically watch for "destroy" and unlink.
 *
 * [flags@Vips.ArgumentFlags.SET_ALWAYS] is handy for arguments which are set
 * from C. For example, [property@Image:width] is a property that gives
 * access to the Xsize member of struct _VipsImage. We default its
 * 'assigned' to `TRUE` since the field is always set directly by C.
 *
 * [flags@Vips.ArgumentFlags.DEPRECATED] arguments are not shown in help text,
 * are not looked for if required, are not checked for "have-been-set". You can
 * deprecate a required argument, but you must obviously add a new required
 * argument if you do.
 *
 * Input args with [flags@Vips.ArgumentFlags.MODIFY] will be modified by the
 * operation. This is used for things like the in-place drawing operations.
 *
 * [flags@Vips.ArgumentFlags.NON_HASHABLE] stops the argument being used in
 * hash and equality tests. It's useful for arguments like `revalidate` which
 * control the behaviour of the operator cache.
 */

/* Our signals.
 */
enum {
	SIG_POSTBUILD,
	SIG_PRECLOSE,
	SIG_CLOSE,
	SIG_POSTCLOSE,
	SIG_LAST
};

/* Table of all objects, handy for debugging.
 */
static GHashTable *vips__object_all = NULL;
static GMutex vips__object_all_lock;

static guint vips_object_signals[SIG_LAST] = { 0 };

/* This has to be externally visible for compatibility with older libvipses.
 */
int _vips__argument_id = 1;

/* Keep a cache of nickname -> GType lookups.
 */
static GHashTable *vips__object_nickname_table = NULL;

G_DEFINE_ABSTRACT_TYPE(VipsObject, vips_object, G_TYPE_OBJECT);

/**
 * vips_argument_get_id: (skip)
 *
 * Allocate a new property id. See
 * [method@GObject.ObjectClass.install_property].
 *
 * Returns: a new property id > 0
 */
int
vips_argument_get_id(void)
{
	int id;

	/* We probably don't need to lock: glib seems to single-thread class
	 * creation.
	 */
	id = _vips__argument_id++;

	return id;
}

/* Don't call this directly, see vips_object_build().
 */
static int
vips_object_postbuild(VipsObject *object)
{
	int result;

#ifdef DEBUG
	printf("vips_object_postbuild: ");
	vips_object_print_name(object);
	printf("\n");
#endif /*DEBUG*/

	g_signal_emit(object, vips_object_signals[SIG_POSTBUILD], 0, &result);

	return result;
}

void
vips_object_preclose(VipsObject *object)
{
	if (!object->preclose) {
		object->preclose = TRUE;

#ifdef DEBUG
		printf("vips_object_preclose: ");
		vips_object_print_name(object);
		printf("\n");
#endif /*DEBUG*/

		g_signal_emit(object, vips_object_signals[SIG_PRECLOSE], 0);
	}
}

static void
vips_object_close(VipsObject *object)
{
	if (!object->close) {
		object->close = TRUE;

#ifdef DEBUG
		printf("vips_object_close: ");
		vips_object_print_name(object);
		printf("\n");
#endif /*DEBUG*/

		g_signal_emit(object, vips_object_signals[SIG_CLOSE], 0);
	}
}

static void
vips_object_postclose(VipsObject *object)
{
	if (!object->postclose) {
		object->postclose = TRUE;

#ifdef DEBUG
		printf("vips_object_postclose: ");
		vips_object_print_name(object);
		printf("\n");
#endif /*DEBUG*/

		g_signal_emit(object, vips_object_signals[SIG_POSTCLOSE], 0);
	}
}

static void *
vips_object_check_required(VipsObject *object, GParamSpec *pspec,
	VipsArgumentClass *argument_class,
	VipsArgumentInstance *argument_instance,
	void *a, void *b)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);

	int *result = (int *) a;
	VipsArgumentFlags *iomask = (VipsArgumentFlags *) b;

	VIPS_DEBUG_MSG("vips_object_check_required: %s\n",
		g_param_spec_get_name(pspec));
	VIPS_DEBUG_MSG("\trequired: %d\n",
		argument_class->flags & VIPS_ARGUMENT_REQUIRED);
	VIPS_DEBUG_MSG("\tconstruct: %d\n",
		argument_class->flags & VIPS_ARGUMENT_CONSTRUCT);
	VIPS_DEBUG_MSG("\tinput: %d\n",
		argument_class->flags & VIPS_ARGUMENT_INPUT);
	VIPS_DEBUG_MSG("\toutput: %d\n",
		argument_class->flags & VIPS_ARGUMENT_OUTPUT);
	VIPS_DEBUG_MSG("\tassigned: %d\n",
		argument_instance->assigned);

	if ((argument_class->flags & VIPS_ARGUMENT_REQUIRED) &&
		(argument_class->flags & VIPS_ARGUMENT_CONSTRUCT) &&
		!(argument_class->flags & VIPS_ARGUMENT_DEPRECATED) &&
		(argument_class->flags & *iomask) &&
		!argument_instance->assigned) {
		vips_error(class->nickname,
			_("parameter %s not set"),
			g_param_spec_get_name(pspec));
		*result = -1;
	}

	return NULL;
}

int
vips_object_build(VipsObject *object)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);

	/* Input and output args must both be set.
	 */
	VipsArgumentFlags iomask = VIPS_ARGUMENT_INPUT | VIPS_ARGUMENT_OUTPUT;

	int result;

#ifdef DEBUG
	printf("vips_object_build: ");
	vips_object_print_name(object);
	printf("\n");
#endif /*DEBUG*/

	if (class->build(object))
		return -1;

	/* Check all required arguments have been supplied, don't stop on 1st
	 * error.
	 */
	result = 0;
	(void) vips_argument_map(object,
		vips_object_check_required, &result, &iomask);

	/* ... more checks go here.
	 */
	object->constructed = TRUE;

	/* Only postbuild on success.
	 */
	if (!result)
		result = vips_object_postbuild(object);

	return result;
}

/**
 * vips_object_summary_class: (skip)
 * @klass: class to summarise
 * @buf: write summary here
 *
 * Generate a human-readable summary for a class.
 */
void
vips_object_summary_class(VipsObjectClass *klass, VipsBuf *buf)
{
	klass->summary_class(klass, buf);
}

/**
 * vips_object_summary: (skip)
 * @object: object to summarise
 * @buf: write summary here
 *
 * Generate a human-readable summary for an object.
 */
void
vips_object_summary(VipsObject *object, VipsBuf *buf)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);

	class->summary(object, buf);
}

/**
 * vips_object_dump: (skip)
 * @object: object to dump
 * @buf: write dump here
 *
 * Dump everything that vips knows about an object to a string.
 */
void
vips_object_dump(VipsObject *object, VipsBuf *buf)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);

	class->dump(object, buf);
}

void
vips_object_print_summary_class(VipsObjectClass *klass)
{
	char str[2048];
	VipsBuf buf = VIPS_BUF_STATIC(str);

	vips_object_summary_class(klass, &buf);
	printf("%s\n", vips_buf_all(&buf));
}

void
vips_object_print_summary(VipsObject *object)
{
	char str[2048];
	VipsBuf buf = VIPS_BUF_STATIC(str);

	vips_object_summary(object, &buf);
	printf("%s\n", vips_buf_all(&buf));
}

void
vips_object_print_dump(VipsObject *object)
{
	char str[32768];
	VipsBuf buf = VIPS_BUF_STATIC(str);

	vips_object_dump(object, &buf);
	printf("%s\n", vips_buf_all(&buf));
}

void
vips_object_print_name(VipsObject *object)
{
	printf("%s (%p)", G_OBJECT_TYPE_NAME(object), object);
}

gboolean
vips_object_sanity(VipsObject *object)
{
	if (!object) {
		vips_error("vips_object_sanity", _("null object"));
		return FALSE;
	}

	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);

	char str[1000];
	VipsBuf buf = VIPS_BUF_STATIC(str);
	class->sanity(object, &buf);
	if (!vips_buf_is_empty(&buf)) {
		vips_error(class->nickname, "%s", vips_buf_all(&buf));
		return FALSE;
	}

	return TRUE;
}

/* On a rewind, we dispose the old contents of the object and
 * reconstruct. This is used in things like im_pincheck() where a "w"
 * image has to be rewound and become a "p" image.
 *
 * Override in subclasses if you want to preserve some fields, see image.c.
 */
void
vips_object_rewind(VipsObject *object)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);

	class->rewind(object);
}

/* Extra stuff we track for properties to do our argument handling.
 */

static void
vips_argument_instance_detach(VipsArgumentInstance *argument_instance)
{
	VipsObject *object = argument_instance->object;
	VipsArgumentClass *argument_class = argument_instance->argument_class;

	if (argument_instance->close_id) {
		/* If close_id is set, the argument must be a gobject of some
		 * sort, so we can fetch it.
		 */
		GObject *member = G_STRUCT_MEMBER(GObject *, object,
			argument_class->offset);

		if (g_signal_handler_is_connected(member,
				argument_instance->close_id))
			g_signal_handler_disconnect(member,
				argument_instance->close_id);
		argument_instance->close_id = 0;
	}

	if (argument_instance->invalidate_id) {
		GObject *member = G_STRUCT_MEMBER(GObject *, object,
			argument_class->offset);

		if (g_signal_handler_is_connected(member,
				argument_instance->invalidate_id))
			g_signal_handler_disconnect(member,
				argument_instance->invalidate_id);
		argument_instance->invalidate_id = 0;
	}
}

/* Free a VipsArgumentInstance ... VipsArgumentClass can just be g_free()d.
 */
static void
vips_argument_instance_free(VipsArgumentInstance *argument_instance)
{
	vips_argument_instance_detach(argument_instance);
	g_free(argument_instance);
}

VipsArgument *
vips__argument_table_lookup(VipsArgumentTable *table, GParamSpec *pspec)
{
	VipsArgument *argument;

	g_mutex_lock(&vips__global_lock);
	argument = (VipsArgument *) g_hash_table_lookup(table, pspec);
	g_mutex_unlock(&vips__global_lock);

	return argument;
}

static void
vips_argument_table_replace(VipsArgumentTable *table, VipsArgument *argument)
{
	g_hash_table_replace(table, argument->pspec, argument);
}

static void
vips_argument_table_destroy(VipsArgumentTable *table)
{
	g_hash_table_destroy(table);
}

/**
 * vips_argument_map:
 * @object: object whose args should be enumerated
 * @fn: (scope call) (closure a): call this function for every argument
 * @a: client data
 * @b: client data
 *
 * Loop over the [struct@Argument] of an object. Stop when @fn returns non-`NULL`
 * and return that value.
 *
 * Returns: `NULL` if @fn returns `NULL` for all arguments, otherwise the first
 * non-`NULL` value from @fn.
 */
void *
vips_argument_map(VipsObject *object,
	VipsArgumentMapFn fn, void *a, void *b)
{
	/* Make sure we can't go during the loop. This can happen if eg. we
	 * flush an arg that refs us.
	 */
	g_object_ref(object);

	VIPS_ARGUMENT_FOR_ALL(object,
		pspec, argument_class, argument_instance)
	{
		void *result;

		/* argument_instance should not be NULL.
		 */
		g_assert(argument_instance);

		if ((result = fn(object, pspec,
				 argument_class, argument_instance, a, b))) {
			g_object_unref(object);
			return result;
		}
	}
	VIPS_ARGUMENT_FOR_ALL_END

	g_object_unref(object);

	return NULL;
}

/**
 * vips_argument_class_map: (skip)
 *
 * And loop over a class. Same as ^^, but with no VipsArgumentInstance.
 */
void *
vips_argument_class_map(VipsObjectClass *object_class,
	VipsArgumentClassMapFn fn, void *a, void *b)
{
	GSList *p;

	for (p = object_class->argument_table_traverse; p; p = p->next) {
		VipsArgumentClass *arg_class =
			(VipsArgumentClass *) p->data;
		VipsArgument *argument = (VipsArgument *) arg_class;
		GParamSpec *pspec = argument->pspec;

		void *result;

		if ((result =
					fn(object_class, pspec, arg_class, a, b)))
			return result;
	}

	return NULL;
}

/* Does an vipsargument need an argument to write to? For example, an image
 * output needs a filename, a double output just prints.
 */
gboolean
vips_argument_class_needsstring(VipsArgumentClass *argument_class)
{
	GParamSpec *pspec = ((VipsArgument *) argument_class)->pspec;

	GType otype;
	VipsObjectClass *oclass;

	if (G_IS_PARAM_SPEC_BOOLEAN(pspec))
		/* Bools, input or output, don't need args.
		 */
		return FALSE;

	if (argument_class->flags & VIPS_ARGUMENT_INPUT)
		/* All other inputs need something.
		 */
		return TRUE;

	/* Just output objects.
	 */

	if ((otype = G_PARAM_SPEC_VALUE_TYPE(pspec)) &&
		g_type_is_a(otype, VIPS_TYPE_OBJECT) &&
		(oclass = g_type_class_ref(otype)))
		/* For now, only vipsobject subclasses can ask for args.
		 */
		return oclass->output_needs_arg;
	else
		return FALSE;
}

/* Create a VipsArgumentInstance for each installed argument property. Ideally
 * we'd do this during _init() but g_object_class_find_property() does not seem
 * to work then :-( so we have to delay it until first access. See
 * vips__argument_get_instance().
 */
static void
vips_argument_init(VipsObject *object)
{
	if (!object->argument_table) {
#ifdef DEBUG
		printf("vips_argument_init: ");
		vips_object_print_name(object);
		printf("\n");
#endif /*DEBUG*/

		object->argument_table = g_hash_table_new_full(g_direct_hash,
			g_direct_equal, NULL,
			(GDestroyNotify) vips_argument_instance_free);

		/* Make a VipsArgumentInstance for each installed argument
		 * property. We can't use vips_argument_map() since that does
		 * some sanity checks that won't pass until all arg instance
		 * are built.
		 */
		VIPS_ARGUMENT_FOR_ALL(object,
			pspec, argument_class, argument_instance)
		{
#ifdef DEBUG
			printf("vips_argument_init: adding instance argument for %s\n",
				g_param_spec_get_name(pspec));
#endif /*DEBUG*/

			/* argument_instance should be NULL since we've not
			 * set it yet.
			 */
			g_assert(argument_instance == NULL);

			argument_instance = g_new(VipsArgumentInstance, 1);

			((VipsArgument *) argument_instance)->pspec = pspec;
			argument_instance->argument_class = argument_class;
			argument_instance->object = object;
			/* SET_ALWAYS args default to assigned.
			 */
			argument_instance->assigned =
				argument_class->flags &
				VIPS_ARGUMENT_SET_ALWAYS;
			argument_instance->close_id = 0;
			argument_instance->invalidate_id = 0;

			vips_argument_table_replace(object->argument_table,
				(VipsArgument *) argument_instance);
		}
		VIPS_ARGUMENT_FOR_ALL_END
	}
}

/**
 * vips__argument_get_instance: (skip)
 *
 * Convenience ... given the VipsArgumentClass, get the VipsArgumentInstance.
 */
VipsArgumentInstance *
vips__argument_get_instance(VipsArgumentClass *argument_class,
	VipsObject *object)
{
	/* Make sure the instance args are built.
	 */
	vips_argument_init(object);

	return (VipsArgumentInstance *)
		vips__argument_table_lookup(object->argument_table,
			((VipsArgument *) argument_class)->pspec);
}

/**
 * vips_object_get_argument: (skip)
 * @object: the object to fetch the args from
 * @name: arg to fetch
 * @pspec: (transfer none): the pspec for this arg
 * @argument_class: (transfer none): the argument_class for this arg
 * @argument_instance: (transfer none): the argument_instance for this arg
 *
 * Look up the three things you need to work with a vips argument.
 *
 * Returns: 0 on success, or -1 on error.
 */
int
vips_object_get_argument(VipsObject *object, const char *name,
	GParamSpec **pspec,
	VipsArgumentClass **argument_class,
	VipsArgumentInstance **argument_instance)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);

	if (!(*pspec = g_object_class_find_property(G_OBJECT_CLASS(class), name))) {
		vips_error(class->nickname, _("no property named `%s'"), name);
		return -1;
	}

	if (!(*argument_class = (VipsArgumentClass *)
			vips__argument_table_lookup(class->argument_table, *pspec))) {
		vips_error(class->nickname, _("no vips argument named `%s'"), name);
		return -1;
	}

	if (!(*argument_instance = vips__argument_get_instance(
		  *argument_class, object))) {
		vips_error(class->nickname, _("argument `%s' has no instance"), name);
		return -1;
	}

	return 0;
}

/**
 * vips_object_argument_isset:
 * @object: the object to fetch the args from
 * @name: arg to fetch
 *
 * Convenience: has an argument been assigned. Useful for bindings.
 *
 * Returns: `TRUE` if the argument has been assigned.
 */
gboolean
vips_object_argument_isset(VipsObject *object, const char *name)
{
	GParamSpec *pspec;
	VipsArgumentClass *argument_class;
	VipsArgumentInstance *argument_instance;

	if (vips_object_get_argument(object, name,
			&pspec, &argument_class, &argument_instance))
		return FALSE;

	return argument_instance->assigned;
}

/**
 * vips_object_get_argument_flags:
 * @object: the object to fetch the args from
 * @name: arg to fetch
 *
 * Convenience: get the flags for an argument. Useful for bindings.
 *
 * Returns: The [flags@ArgumentFlags] for this argument.
 */
VipsArgumentFlags
vips_object_get_argument_flags(VipsObject *object, const char *name)
{
	GParamSpec *pspec;
	VipsArgumentClass *argument_class;
	VipsArgumentInstance *argument_instance;

	if (vips_object_get_argument(object, name,
			&pspec, &argument_class, &argument_instance))
		return 0;

	return argument_class->flags;
}

/**
 * vips_object_get_argument_priority:
 * @object: the object to fetch the args from
 * @name: arg to fetch
 *
 * Convenience: get the priority for an argument. Useful for bindings.
 *
 * Returns: The priority of this argument.
 */
int
vips_object_get_argument_priority(VipsObject *object, const char *name)
{
	GParamSpec *pspec;
	VipsArgumentClass *argument_class;
	VipsArgumentInstance *argument_instance;

	if (vips_object_get_argument(object, name,
			&pspec, &argument_class, &argument_instance))
		return 0;

	return argument_class->priority;
}

static void
vips_object_clear_member(VipsArgumentInstance *argument_instance)
{
	VipsObject *object = argument_instance->object;
	VipsArgumentClass *argument_class = argument_instance->argument_class;
	GObject **member = &G_STRUCT_MEMBER(GObject *, object,
		argument_class->offset);

	vips_argument_instance_detach(argument_instance);

	if (*member) {
		if (argument_class->flags & VIPS_ARGUMENT_INPUT) {
#ifdef DEBUG_REF
			printf("vips_object_clear_member: vips object: ");
			vips_object_print_name(object);
			printf("  no longer refers to gobject %s (%p)\n",
				G_OBJECT_TYPE_NAME(*member), *member);
			printf("  count down to %d\n", G_OBJECT(*member)->ref_count - 1);
#endif /*DEBUG_REF*/

			/* We reffed the object.
			 */
			g_object_unref(*member);
		}
		else if (argument_class->flags & VIPS_ARGUMENT_OUTPUT) {
#ifdef DEBUG_REF
			printf("vips_object_clear_member: gobject %s (%p)\n",
				G_OBJECT_TYPE_NAME(*member), *member);
			printf("  no longer refers to vips object: ");
			vips_object_print_name(object);
			printf("  count down to %d\n", G_OBJECT(object)->ref_count - 1);
#endif /*DEBUG_REF*/

			g_object_unref(object);
		}

		*member = NULL;
	}
}

/* Free any args which are holding resources.
 */
static void *
vips_object_dispose_argument(VipsObject *object, GParamSpec *pspec,
	VipsArgumentClass *argument_class,
	VipsArgumentInstance *argument_instance,
	void *a, void *b)
{
	g_assert(((VipsArgument *) argument_class)->pspec == pspec);
	g_assert(((VipsArgument *) argument_instance)->pspec == pspec);

	if (G_IS_PARAM_SPEC_OBJECT(pspec) ||
		G_IS_PARAM_SPEC_BOXED(pspec)) {
#ifdef DEBUG
		printf("vips_object_dispose_argument: ");
		vips_object_print_name(object);
		printf(".%s\n", g_param_spec_get_name(pspec));
#endif /*DEBUG*/

		g_object_set(object,
			g_param_spec_get_name(pspec), NULL,
			NULL);
	}

	return NULL;
}

/* Free all args on this object which may be holding resources.
 *
 * Note that this is not the same as vips_object_unref_outputs(). That
 * looks for output objects which may have been created during _build() which
 * hold refs to this object and unrefs them.
 *
 * This function looks for objects which this object holds refs to and which
 * may be holding sub-resources and zaps them.
 */
static void
vips_argument_dispose_all(VipsObject *object)
{
#ifdef DEBUG
	printf("vips_argument_dispose_all: ");
	vips_object_print_name(object);
	printf("\n");
#endif /*DEBUG*/

	vips_argument_map(object, vips_object_dispose_argument, NULL, NULL);
}

/* Free any args which are holding memory.
 */
static void *
vips_object_free_argument(VipsObject *object, GParamSpec *pspec,
	VipsArgumentClass *argument_class,
	VipsArgumentInstance *argument_instance,
	void *a, void *b)
{
	g_assert(((VipsArgument *) argument_class)->pspec == pspec);
	g_assert(((VipsArgument *) argument_instance)->pspec == pspec);

	if (G_IS_PARAM_SPEC_STRING(pspec)) {
#ifdef DEBUG
		printf("vips_object_free_argument: ");
		vips_object_print_name(object);
		printf(".%s\n", g_param_spec_get_name(pspec));
#endif /*DEBUG*/

		g_object_set(object,
			g_param_spec_get_name(pspec), NULL,
			NULL);
	}

	return NULL;
}

/* Free args which hold memory. Things like strings need to be freed right at
 * the end in case anyone is still using them.
 */
static void
vips_argument_free_all(VipsObject *object)
{
#ifdef DEBUG
	printf("vips_argument_free_all: ");
	vips_object_print_name(object);
	printf("\n");
#endif /*DEBUG*/

	vips_argument_map(object, vips_object_free_argument, NULL, NULL);
}

static void
vips_object_dispose(GObject *gobject)
{
	VipsObject *object = VIPS_OBJECT(gobject);

#ifdef DEBUG
	printf("vips_object_dispose: ");
	vips_object_print_name(object);
	printf("\n");
#endif /*DEBUG*/

	/* Our subclasses should have already called this. Run it again, just
	 * in case.
	 */
#ifdef DEBUG
	if (!object->preclose)
		printf("vips_object_dispose: pre-close missing!\n");
#endif /*DEBUG*/
	vips_object_preclose(object);

	/* Clear all our arguments: they may be holding resources we should
	 * drop.
	 */
	vips_argument_dispose_all(object);

	vips_object_close(object);

	vips_object_postclose(object);

	vips_argument_free_all(object);

	VIPS_FREEF(vips_argument_table_destroy, object->argument_table);

	G_OBJECT_CLASS(vips_object_parent_class)->dispose(gobject);
}

static void
vips_object_finalize(GObject *gobject)
{
	VipsObject *object = VIPS_OBJECT(gobject);

#ifdef DEBUG
	printf("vips_object_finalize: ");
	vips_object_print_name(object);
	printf("\n");
#endif /*DEBUG*/

	/* I'd like to have post-close in here, but you can't emit signals
	 * from finalize, sadly.
	 */

	g_mutex_lock(&vips__object_all_lock);
	g_hash_table_remove(vips__object_all, object);
	g_mutex_unlock(&vips__object_all_lock);

	G_OBJECT_CLASS(vips_object_parent_class)->finalize(gobject);
}

static void
vips_object_arg_invalidate(GObject *argument,
	VipsArgumentInstance *argument_instance)
{
	/* Image @argument has signalled "invalidate" ... resignal on our
	 * operation.
	 */
	if (VIPS_IS_OPERATION(argument_instance->object))
		vips_operation_invalidate(VIPS_OPERATION(argument_instance->object));
}

static void
vips_object_arg_close(GObject *argument,
	VipsArgumentInstance *argument_instance)
{
	VipsObject *object = argument_instance->object;
	GParamSpec *pspec = ((VipsArgument *) argument_instance)->pspec;

	/* Argument had reffed us ... now it's being closed, so we NULL out
	 * the pointer to unref.
	 */
	g_object_set(object,
		g_param_spec_get_name(pspec), NULL,
		NULL);
}

/* Set a member to an object. Handle the ref counts and signal
 * connect/disconnect.
 */
void
vips__object_set_member(VipsObject *object, GParamSpec *pspec,
	GObject **member, GObject *argument)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);
	VipsArgumentClass *argument_class = (VipsArgumentClass *)
		vips__argument_table_lookup(class->argument_table, pspec);
	VipsArgumentInstance *argument_instance =
		vips__argument_get_instance(argument_class, object);
	GType otype = G_PARAM_SPEC_VALUE_TYPE(pspec);

	g_assert(argument_instance);

	vips_object_clear_member(argument_instance);

	g_assert(!*member);
	*member = argument;

	if (*member) {
		if (argument_class->flags & VIPS_ARGUMENT_INPUT) {
#ifdef DEBUG_REF
			printf("vips__object_set_member: vips object: ");
			vips_object_print_name(object);
			printf("  refers to gobject %s (%p)\n",
				G_OBJECT_TYPE_NAME(*member), *member);
			printf("  count up to %d\n", G_OBJECT(*member)->ref_count);
#endif /*DEBUG_REF*/

			/* Ref the argument.
			 */
			g_object_ref(*member);
		}
		else if (argument_class->flags & VIPS_ARGUMENT_OUTPUT) {
#ifdef DEBUG_REF
			printf("vips__object_set_member: gobject %s (%p)\n",
				G_OBJECT_TYPE_NAME(*member), *member);
			printf("  refers to vips object: ");
			vips_object_print_name(object);
			printf("  count up to %d\n", G_OBJECT(object)->ref_count);
#endif /*DEBUG_REF*/

			/* The argument reffs us.
			 */
			g_object_ref(object);
		}
	}

	if (*member &&
		g_type_is_a(otype, VIPS_TYPE_IMAGE)) {
		if (argument_class->flags & VIPS_ARGUMENT_INPUT) {
			g_assert(!argument_instance->invalidate_id);

			argument_instance->invalidate_id =
				g_signal_connect(*member, "invalidate",
					G_CALLBACK(vips_object_arg_invalidate),
					argument_instance);
		}
		else if (argument_class->flags & VIPS_ARGUMENT_OUTPUT) {
			g_assert(!argument_instance->close_id);

			argument_instance->close_id =
				g_signal_connect(*member, "close",
					G_CALLBACK(vips_object_arg_close),
					argument_instance);
		}
	}
}

/* Is a value NULL? We allow multiple sets of NULL so props can be cleared.
 * The pspec gives the value type, for consistency with the way value types
 * are detected in set and get.
 */
gboolean
vips_value_is_null(GParamSpec *pspec, const GValue *value)
{
	if (G_IS_PARAM_SPEC_STRING(pspec) &&
		!g_value_get_string(value))
		return TRUE;
	if (G_IS_PARAM_SPEC_OBJECT(pspec) &&
		!g_value_get_object(value))
		return TRUE;
	if (G_IS_PARAM_SPEC_POINTER(pspec) &&
		!g_value_get_pointer(value))
		return TRUE;
	if (G_IS_PARAM_SPEC_BOXED(pspec) &&
		!g_value_get_boxed(value))
		return TRUE;

	return FALSE;
}

/* Also used by subclasses, so not static.
 */
void
vips_object_set_property(GObject *gobject,
	guint property_id, const GValue *value, GParamSpec *pspec)
{
	VipsObject *object = VIPS_OBJECT(gobject);
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(gobject);
	VipsArgumentClass *argument_class = (VipsArgumentClass *)
		vips__argument_table_lookup(class->argument_table, pspec);
	VipsArgumentInstance *argument_instance =
		vips__argument_get_instance(argument_class, object);

	g_assert(argument_instance);

#ifdef DEBUG
	printf("vips_object_set_property: ");
	vips_object_print_name(object);
	printf(".%s\n", g_param_spec_get_name(pspec));

	/* This can crash horribly with some values, have it as a separate
	 * chunk so we can easily comment it out.
	 */
	{
		char *str_value;

		str_value = g_strdup_value_contents(value);
		printf("\t%s\n", str_value);
		g_free(str_value);
	}
#endif /*DEBUG*/

	g_assert(((VipsArgument *) argument_class)->pspec == pspec);
	g_assert(((VipsArgument *) argument_instance)->pspec == pspec);

	/* If this is a construct-only argument, we can only set before we've
	 * built.
	 */
	if (argument_class->flags & VIPS_ARGUMENT_CONSTRUCT &&
		object->constructed &&
		!vips_value_is_null(pspec, value)) {
		g_warning("%s: %s can't assign '%s' after construct",
			G_STRLOC,
			G_OBJECT_TYPE_NAME(gobject),
			g_param_spec_get_name(pspec));
		return;
	}

	/* If this is a set-once argument, check we've not set it before.
	 */
	if (argument_class->flags & VIPS_ARGUMENT_SET_ONCE &&
		argument_instance->assigned &&
		!vips_value_is_null(pspec, value)) {
		g_warning("%s: %s can only assign '%s' once",
			G_STRLOC,
			G_OBJECT_TYPE_NAME(gobject),
			g_param_spec_get_name(pspec));
		return;
	}

	/* We can't use a switch since some param specs don't have fundamental
	 * types and are hence not compile-time constants, argh.
	 */
	if (G_IS_PARAM_SPEC_STRING(pspec)) {
		char **member = &G_STRUCT_MEMBER(char *, object,
			argument_class->offset);

		if (*member)
			g_free(*member);
		*member = g_value_dup_string(value);
	}
	else if (G_IS_PARAM_SPEC_OBJECT(pspec)) {
		GObject **member = &G_STRUCT_MEMBER(GObject *, object,
			argument_class->offset);

		vips__object_set_member(object, pspec, member,
			g_value_get_object(value));
	}
	else if (G_IS_PARAM_SPEC_INT(pspec)) {
		int *member = &G_STRUCT_MEMBER(int, object,
			argument_class->offset);

		*member = g_value_get_int(value);
	}
	else if (G_IS_PARAM_SPEC_UINT64(pspec)) {
		guint64 *member = &G_STRUCT_MEMBER(guint64, object,
			argument_class->offset);

		*member = g_value_get_uint64(value);
	}
	else if (G_IS_PARAM_SPEC_BOOLEAN(pspec)) {
		gboolean *member = &G_STRUCT_MEMBER(gboolean, object,
			argument_class->offset);

		*member = g_value_get_boolean(value);
	}
	else if (G_IS_PARAM_SPEC_ENUM(pspec)) {
		int *member = &G_STRUCT_MEMBER(int, object,
			argument_class->offset);

		*member = g_value_get_enum(value);
	}
	else if (G_IS_PARAM_SPEC_FLAGS(pspec)) {
		int *member = &G_STRUCT_MEMBER(int, object,
			argument_class->offset);

		*member = g_value_get_flags(value);
	}
	else if (G_IS_PARAM_SPEC_POINTER(pspec)) {
		gpointer *member = &G_STRUCT_MEMBER(gpointer, object,
			argument_class->offset);

		*member = g_value_get_pointer(value);
	}
	else if (G_IS_PARAM_SPEC_DOUBLE(pspec)) {
		double *member = &G_STRUCT_MEMBER(double, object,
			argument_class->offset);

		*member = g_value_get_double(value);
	}
	else if (G_IS_PARAM_SPEC_BOXED(pspec)) {
		gpointer *member = &G_STRUCT_MEMBER(gpointer, object,
			argument_class->offset);

		if (*member) {
			g_boxed_free(G_PARAM_SPEC_VALUE_TYPE(pspec),
				*member);
			*member = NULL;
		}

		/* Copy the boxed into our pointer (will use eg.
		 * vips__object_vector_dup()).
		 */
		*member = g_value_dup_boxed(value);
	}
	else {
		g_warning("%s: %s.%s unimplemented property type %s",
			G_STRLOC,
			G_OBJECT_TYPE_NAME(gobject),
			g_param_spec_get_name(pspec),
			g_type_name(G_PARAM_SPEC_VALUE_TYPE(pspec)));
	}

	/* Note that it's now been set.
	 */
	argument_instance->assigned = TRUE;
}

/* Also used by subclasses, so not static.
 */
void
vips_object_get_property(GObject *gobject,
	guint property_id, GValue *value, GParamSpec *pspec)
{
	VipsObject *object = VIPS_OBJECT(gobject);
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(gobject);
	VipsArgumentClass *argument_class = (VipsArgumentClass *)
		vips__argument_table_lookup(class->argument_table, pspec);
	VipsArgumentInstance *argument_instance =
		vips__argument_get_instance(argument_class, object);

	g_assert(((VipsArgument *) argument_class)->pspec == pspec);

	if (!argument_instance->assigned) {
		/* Set the value to the default. Things like Ruby
		 * gobject-introspection will walk objects during GC, and we
		 * can find ourselves fetching object values between init and
		 * build.
		 */
		g_param_value_set_default(pspec, value);
		return;
	}

	if (G_IS_PARAM_SPEC_STRING(pspec)) {
		char *member = G_STRUCT_MEMBER(char *, object,
			argument_class->offset);

		g_value_set_string(value, member);
	}
	else if (G_IS_PARAM_SPEC_OBJECT(pspec)) {
		GObject **member = &G_STRUCT_MEMBER(GObject *, object,
			argument_class->offset);

		g_value_set_object(value, *member);
	}
	else if (G_IS_PARAM_SPEC_INT(pspec)) {
		int *member = &G_STRUCT_MEMBER(int, object,
			argument_class->offset);

		g_value_set_int(value, *member);
	}
	else if (G_IS_PARAM_SPEC_UINT64(pspec)) {
		guint64 *member = &G_STRUCT_MEMBER(guint64, object,
			argument_class->offset);

		g_value_set_uint64(value, *member);
	}
	else if (G_IS_PARAM_SPEC_BOOLEAN(pspec)) {
		gboolean *member = &G_STRUCT_MEMBER(gboolean, object,
			argument_class->offset);

		g_value_set_boolean(value, *member);
	}
	else if (G_IS_PARAM_SPEC_ENUM(pspec)) {
		int *member = &G_STRUCT_MEMBER(int, object,
			argument_class->offset);

		g_value_set_enum(value, *member);
	}
	else if (G_IS_PARAM_SPEC_FLAGS(pspec)) {
		int *member = &G_STRUCT_MEMBER(int, object,
			argument_class->offset);

		g_value_set_flags(value, *member);
	}
	else if (G_IS_PARAM_SPEC_POINTER(pspec)) {
		gpointer *member = &G_STRUCT_MEMBER(gpointer, object,
			argument_class->offset);

		g_value_set_pointer(value, *member);
	}
	else if (G_IS_PARAM_SPEC_DOUBLE(pspec)) {
		double *member = &G_STRUCT_MEMBER(double, object,
			argument_class->offset);

		g_value_set_double(value, *member);
	}
	else if (G_IS_PARAM_SPEC_BOXED(pspec)) {
		gpointer *member = &G_STRUCT_MEMBER(gpointer, object,
			argument_class->offset);

		/* Copy the boxed into our pointer (will use eg.
		 * vips__object_vector_dup()).
		 */
		g_value_set_boxed(value, *member);
	}
	else {
		g_warning("%s: %s.%s unimplemented property type %s",
			G_STRLOC,
			G_OBJECT_TYPE_NAME(gobject),
			g_param_spec_get_name(pspec),
			g_type_name(G_PARAM_SPEC_VALUE_TYPE(pspec)));
	}
}

static int
vips_object_real_build(VipsObject *object)
{
	VipsObjectClass *object_class = VIPS_OBJECT_GET_CLASS(object);

	/* Only test input args, output ones can be set by our subclasses as
	 * they build. See vips_object_build() above.
	 */
	VipsArgumentFlags iomask = VIPS_ARGUMENT_INPUT;

	int result;

#ifdef DEBUG
	printf("vips_object_real_build: ");
	vips_object_print_name(object);
	printf("\n");
#endif /*DEBUG*/

	g_assert(!object->constructed);

	/* It'd be nice if this just copied a pointer rather than did a
	 * strdup(). Set these here rather than in object_init, so that the
	 * class gets a chance to set them.
	 */
	g_object_set(object,
		"nickname", object_class->nickname,
		"description", object_class->description, NULL);

	/* Check all required input arguments have been supplied, don't stop
	 * on 1st error.
	 */
	result = 0;
	(void) vips_argument_map(object,
		vips_object_check_required, &result, &iomask);

	if (!vips_object_sanity(object))
		return -1;

	return result;
}

static int
vips_object_real_postbuild(VipsObject *object, void *data)
{
#ifdef DEBUG
	printf("vips_object_real_postbuild: ");
	vips_object_print_name(object);
	printf("\n");
#endif /*DEBUG*/

	g_assert(object->constructed);

	return 0;
}

static void
vips_object_real_summary_class(VipsObjectClass *class, VipsBuf *buf)
{
	vips_buf_appendf(buf, "%s", G_OBJECT_CLASS_NAME(class));
	if (class->nickname)
		vips_buf_appendf(buf, " (%s)", class->nickname);
	if (class->description)
		vips_buf_appendf(buf, ", %s", class->description);
}

static void
vips_object_real_summary(VipsObject *object, VipsBuf *buf)
{
}

static void
vips_object_real_dump(VipsObject *object, VipsBuf *buf)
{
	vips_buf_appendf(buf, " %s (%p) count=%d",
		G_OBJECT_TYPE_NAME(object),
		object,
		G_OBJECT(object)->ref_count);

	if (object->local_memory)
		vips_buf_appendf(buf, " %zd bytes", object->local_memory);
}

static void
vips_object_real_sanity(VipsObject *object, VipsBuf *buf)
{
}

static void
vips_object_real_rewind(VipsObject *object)
{
#ifdef DEBUG
	printf("vips_object_real_rewind\n");
	vips_object_print_name(object);
	printf("\n");
#endif /*DEBUG*/

	g_object_run_dispose(G_OBJECT(object));

	object->constructed = FALSE;
	object->preclose = FALSE;
	object->close = FALSE;
	object->postclose = FALSE;
}

static VipsObject *
vips_object_real_new_from_string(const char *string)
{
	GType type;

	vips_check_init();

	/* The main arg selects the subclass.
	 */
	if (!(type = vips_type_find(NULL, string))) {
		vips_error("VipsObject",
			_("class \"%s\" not found"), string);
		return NULL;
	}

	return VIPS_OBJECT(g_object_new(type, NULL));
}

static void
vips_object_real_to_string(VipsObject *object, VipsBuf *buf)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);

	/* Just "bicubic" or whatever.
	 */
	vips_buf_appends(buf, class->nickname);
}

static void
transform_string_double(const GValue *src_value, GValue *dest_value)
{
	g_value_set_double(dest_value,
		g_ascii_strtod(g_value_get_string(src_value), NULL));
}

static void
vips_object_class_init(VipsObjectClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);

	/* We must have threads set up before we can process.
	 */
	vips_check_init();

	if (!vips__object_all)
		vips__object_all = g_hash_table_new(g_direct_hash, g_direct_equal);

	gobject_class->dispose = vips_object_dispose;
	gobject_class->finalize = vips_object_finalize;
	gobject_class->set_property = vips_object_set_property;
	gobject_class->get_property = vips_object_get_property;

	class->build = vips_object_real_build;
	class->postbuild = vips_object_real_postbuild;
	class->summary_class = vips_object_real_summary_class;
	class->summary = vips_object_real_summary;
	class->dump = vips_object_real_dump;
	class->sanity = vips_object_real_sanity;
	class->rewind = vips_object_real_rewind;
	class->new_from_string = vips_object_real_new_from_string;
	class->to_string = vips_object_real_to_string;
	class->nickname = "object";
	class->description = _("base class");

	/* Table of VipsArgumentClass ... we can just g_free() them.
	 */
	class->argument_table = g_hash_table_new_full(
		g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) g_free);
	class->argument_table_traverse = NULL;

	/* For setting double arguments from the command-line.
	 */
	g_value_register_transform_func(G_TYPE_STRING, G_TYPE_DOUBLE,
		transform_string_double);

	VIPS_ARG_STRING(class, "nickname", 1,
		_("Nickname"),
		_("Class nickname"),
		VIPS_ARGUMENT_SET_ONCE,
		G_STRUCT_OFFSET(VipsObject, nickname),
		"");

	VIPS_ARG_STRING(class, "description", 2,
		_("Description"),
		_("Class description"),
		VIPS_ARGUMENT_SET_ONCE,
		G_STRUCT_OFFSET(VipsObject, description),
		"");

	/**
	 * VipsObject::postbuild:
	 * @object: the object that has been built
	 *
	 * The ::postbuild signal is emitted once just after successful object
	 * construction. Return non-zero to cause object construction to fail.
	 */
	vips_object_signals[SIG_POSTBUILD] = g_signal_new("postbuild",
		G_TYPE_FROM_CLASS(class),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET(VipsObjectClass, postbuild),
		NULL, NULL,
		vips_INT__VOID,
		G_TYPE_INT, 0);

	/**
	 * VipsObject::preclose:
	 * @object: the object that is to close
	 *
	 * The ::preclose signal is emitted once just before object close
	 * starts. The object is still alive.
	 */
	vips_object_signals[SIG_PRECLOSE] = g_signal_new("preclose",
		G_TYPE_FROM_CLASS(class),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET(VipsObjectClass, preclose),
		NULL, NULL,
		g_cclosure_marshal_VOID__VOID,
		G_TYPE_NONE, 0);

	/**
	 * VipsObject::close:
	 * @object: the object that is closing
	 *
	 * The ::close signal is emitted once during object close. The object
	 * is dying and may not work.
	 */
	vips_object_signals[SIG_CLOSE] = g_signal_new("close",
		G_TYPE_FROM_CLASS(class),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET(VipsObjectClass, close),
		NULL, NULL,
		g_cclosure_marshal_VOID__VOID,
		G_TYPE_NONE, 0);

	/**
	 * VipsObject::postclose:
	 * @object: the object that has closed
	 *
	 * The ::postclose signal is emitted once after object close. The
	 * object pointer is still valid, but nothing else.
	 */
	vips_object_signals[SIG_POSTCLOSE] = g_signal_new("postclose",
		G_TYPE_FROM_CLASS(class),
		G_SIGNAL_RUN_LAST,
		G_STRUCT_OFFSET(VipsObjectClass, postclose),
		NULL, NULL,
		g_cclosure_marshal_VOID__VOID,
		G_TYPE_NONE, 0);
}

static void
vips_object_init(VipsObject *object)
{
#ifdef DEBUG
	printf("vips_object_init: ");
	vips_object_print_name(object);
	printf("\n");
#endif /*DEBUG*/

	g_mutex_lock(&vips__object_all_lock);
	g_hash_table_insert(vips__object_all, object, object);
	g_mutex_unlock(&vips__object_all_lock);
}

static void *
traverse_find_required_priority(void *data, void *a, void *b)
{
	VipsArgumentClass *argument_class = (VipsArgumentClass *) data;
	int priority = GPOINTER_TO_INT(a);

	if ((argument_class->flags & VIPS_ARGUMENT_REQUIRED) &&
		!(argument_class->flags & VIPS_ARGUMENT_DEPRECATED) &&
		argument_class->priority == priority)
		return argument_class;

	return NULL;
}

static int
traverse_sort(VipsArgumentClass *class1, VipsArgumentClass *class2,
	void *user_data)
{
	return class1->priority - class2->priority;
}

/* Add a vipsargument ... automate some stuff with this.
 */
void
vips_object_class_install_argument(VipsObjectClass *object_class,
	GParamSpec *pspec, VipsArgumentFlags flags, int priority, guint offset)
{
	VipsArgumentClass *argument_class = g_new(VipsArgumentClass, 1);

	GSList *argument_table_traverse;
	VipsArgumentClass *ac;

#ifdef DEBUG
	printf("vips_object_class_install_argument: %p %s %s\n",
		object_class,
		g_type_name(G_TYPE_FROM_CLASS(object_class)),
		g_param_spec_get_name(pspec));
#endif /*DEBUG*/

	/* object_class->argument* is shared, so we must lock.
	 */
	g_mutex_lock(&vips__global_lock);

	/* Must be a new one.
	 */
	g_assert(!g_hash_table_lookup(object_class->argument_table, pspec));

	/* Mustn't have INPUT and OUTPUT both set.
	 */
	g_assert(!(
		(flags & VIPS_ARGUMENT_INPUT) &&
		(flags & VIPS_ARGUMENT_OUTPUT)));

	((VipsArgument *) argument_class)->pspec = pspec;
	argument_class->object_class = object_class;
	argument_class->flags = flags;
	argument_class->priority = priority;
	argument_class->offset = offset;

	vips_argument_table_replace(object_class->argument_table,
		(VipsArgument *) argument_class);

	/* If this is the first argument for a new subclass, we need to clone
	 * the traverse list we inherit.
	 */
	if (object_class->argument_table_traverse_gtype !=
		G_TYPE_FROM_CLASS(object_class)) {
#ifdef DEBUG
		printf("vips_object_class_install_argument: cloning traverse\n");
#endif /*DEBUG*/

		object_class->argument_table_traverse =
			g_slist_copy(object_class->argument_table_traverse);
		object_class->argument_table_traverse_gtype =
			G_TYPE_FROM_CLASS(object_class);
	}

	/* We read argument_table_traverse without a lock (eg. see
	 * vips_argument_map()), so we must be very careful updating it.
	 */
	argument_table_traverse =
		g_slist_copy(object_class->argument_table_traverse);

	/* We keep traverse sorted by priority, so we mustn't have duplicate
	 * priority values in required args.
	 */
	if ((flags & VIPS_ARGUMENT_REQUIRED) &&
		!(flags & VIPS_ARGUMENT_DEPRECATED) &&
		(ac = vips_slist_map2(argument_table_traverse,
			 traverse_find_required_priority,
			 GINT_TO_POINTER(priority), NULL)))
		g_warning("vips_object_class_install_argument: "
				  "%s.%s, %s.%s duplicate priority",
			g_type_name(G_TYPE_FROM_CLASS(object_class)),
			g_param_spec_get_name(pspec),
			g_type_name(G_TYPE_FROM_CLASS(ac->object_class)),
			g_param_spec_get_name(((VipsArgument *) ac)->pspec));

	/* Warn about optional boolean args which default TRUE. These won't
	 * work from the CLI, since simple GOption switches don't allow
	 * `=false`.
	 */
	if (!(flags & VIPS_ARGUMENT_REQUIRED) &&
		!(flags & VIPS_ARGUMENT_DEPRECATED) &&
		G_IS_PARAM_SPEC_BOOLEAN(pspec) &&
		G_PARAM_SPEC_BOOLEAN(pspec)->default_value)
		g_warning("vips_object_class_install_argument: "
				  "default TRUE BOOL arg %s.%s",
			g_type_name(G_TYPE_FROM_CLASS(object_class)),
			g_param_spec_get_name(pspec));

	argument_table_traverse = g_slist_prepend(
		argument_table_traverse, argument_class);
	argument_table_traverse = g_slist_sort(
		argument_table_traverse, (GCompareFunc) traverse_sort);
	VIPS_SWAP(GSList *,
		argument_table_traverse,
		object_class->argument_table_traverse);

	g_slist_free(argument_table_traverse);

#ifdef DEBUG
	{
		GSList *p;

		printf("%d items on traverse %p\n",
			g_slist_length(object_class->argument_table_traverse),
			&object_class->argument_table_traverse);
		for (p = object_class->argument_table_traverse; p; p = p->next) {
			VipsArgumentClass *argument_class =
				(VipsArgumentClass *) p->data;

			printf("\t%p %s\n",
				argument_class,
				g_param_spec_get_name(
					((VipsArgument *) argument_class)->pspec));
		}
	}
#endif /*DEBUG*/

	g_mutex_unlock(&vips__global_lock);
}

static void
vips_object_no_value(VipsObject *object, const char *name)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);

	GParamSpec *pspec;
	VipsArgumentClass *argument_class;
	VipsArgumentInstance *argument_instance;

	if (vips_object_get_argument(object, name,
			&pspec, &argument_class, &argument_instance))
		g_assert_not_reached();

	if (strcmp(name, g_param_spec_get_name(pspec)) == 0)
		vips_error(class->nickname,
			_("no value supplied for argument '%s'"), name);
	else
		vips_error(class->nickname,
			_("no value supplied for argument '%s' ('%s')"),
			name,
			g_param_spec_get_name(pspec));
}

/* Set a named arg from a string.
 */
int
vips_object_set_argument_from_string(VipsObject *object,
	const char *name, const char *value)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);

	GParamSpec *pspec;
	VipsArgumentClass *argument_class;
	VipsArgumentInstance *argument_instance;
	VipsObjectClass *oclass;
	GType otype;

	GValue gvalue = G_VALUE_INIT;

	VIPS_DEBUG_MSG("vips_object_set_argument_from_string: %s = %s\n",
		name, value);

	if (vips_object_get_argument(object, name,
			&pspec, &argument_class, &argument_instance))
		return -1;

	otype = G_PARAM_SPEC_VALUE_TYPE(pspec);

	g_assert(argument_class->flags & VIPS_ARGUMENT_INPUT);

	if (g_type_is_a(otype, VIPS_TYPE_IMAGE)) {
		VipsImage *out;
		VipsOperationFlags flags;
		VipsAccess access;
		char filename[VIPS_PATH_MAX];
		char option_string[VIPS_PATH_MAX];

		flags = 0;
		if (VIPS_IS_OPERATION(object))
			flags = vips_operation_get_flags(VIPS_OPERATION(object));

		if (flags &
			(VIPS_OPERATION_SEQUENTIAL_UNBUFFERED | VIPS_OPERATION_SEQUENTIAL))
			access = VIPS_ACCESS_SEQUENTIAL;
		else
			access = VIPS_ACCESS_RANDOM;

		if (!value) {
			vips_object_no_value(object, name);
			return -1;
		}
		vips__filename_split8(value, filename, option_string);

		if (strcmp("stdin", filename) == 0) {
			VipsSource *source;

			if (!(source = vips_source_new_from_descriptor(0)))
				return -1;
			if (!(out = vips_image_new_from_source(source,
					  option_string,
					  "access", access,
					  NULL))) {
				VIPS_UNREF(source);
				return -1;
			}
			VIPS_UNREF(source);
		}
		else {
			if (!(out = vips_image_new_from_file(value,
					  "access", access,
					  NULL)))
				return -1;
		}

		g_value_init(&gvalue, VIPS_TYPE_IMAGE);
		g_value_set_object(&gvalue, out);

		/* Setting gvalue will have upped @out's count again,
		 * go back to 1 so that gvalue has the only ref.
		 */
		g_object_unref(out);
	}
	else if (g_type_is_a(otype, VIPS_TYPE_SOURCE)) {
		VipsSource *source;

		if (!value) {
			vips_object_no_value(object, name);
			return -1;
		}

		if (!(source = vips_source_new_from_options(value)))
			return -1;

		g_value_init(&gvalue, VIPS_TYPE_SOURCE);
		g_value_set_object(&gvalue, source);

		/* Setting gvalue will have upped @source's count again,
		 * go back to 1 so that gvalue has the only ref.
		 */
		g_object_unref(source);
	}
	else if (g_type_is_a(otype, VIPS_TYPE_INTERPOLATE)) {
		VipsInterpolate *interpolate;

		if (!value) {
			vips_object_no_value(object, name);
			return -1;
		}

		if (!(interpolate = vips_interpolate_new(value)))
			return -1;

		g_value_init(&gvalue, VIPS_TYPE_INTERPOLATE);
		g_value_set_object(&gvalue, interpolate);

		/* Setting gvalue will have upped @interpolate's count again,
		 * go back to 1 so that gvalue has the only ref.
		 */
		g_object_unref(interpolate);
	}
	else if (g_type_is_a(otype, VIPS_TYPE_ARRAY_IMAGE)) {
		/* We have to have a special case for this, we can't just rely
		 * on transform_g_string_array_image(), since we need to be
		 * able to set the access hint on the image.
		 */
		VipsArrayImage *array_image;
		VipsOperationFlags flags;
		VipsAccess access;

		if (!value) {
			vips_object_no_value(object, name);
			return -1;
		}

		flags = 0;
		if (VIPS_IS_OPERATION(object))
			flags = vips_operation_get_flags(
				VIPS_OPERATION(object));

		if (flags &
			(VIPS_OPERATION_SEQUENTIAL_UNBUFFERED | VIPS_OPERATION_SEQUENTIAL))
			access = VIPS_ACCESS_SEQUENTIAL;
		else
			access = VIPS_ACCESS_RANDOM;

		if (!(array_image =
					vips_array_image_new_from_string(value, access)))
			return -1;

		g_value_init(&gvalue, VIPS_TYPE_ARRAY_IMAGE);
		g_value_set_boxed(&gvalue, array_image);

		/* Setting gvalue will have upped @array_image's count again,
		 * go back to 1 so that gvalue has the only ref.
		 */
		vips_area_unref(VIPS_AREA(array_image));
	}
	else if (g_type_is_a(otype, VIPS_TYPE_OBJECT) &&
		(oclass = g_type_class_ref(otype))) {
		VipsObject *new_object;

		if (!value) {
			vips_object_no_value(object, name);
			return -1;
		}

		if (!(new_object =
					vips_object_new_from_string(oclass, value)))
			return -1;

		/* Not necessarily a VipsOperation subclass so we don't use
		 * the cache. We could have a separate case for this.
		 */
		if (vips_object_build(new_object)) {
			g_object_unref(new_object);
			return -1;
		}

		g_value_init(&gvalue, G_TYPE_OBJECT);
		g_value_set_object(&gvalue, new_object);

		/* The GValue now has a ref, we can drop ours.
		 */
		g_object_unref(new_object);
	}
	else if (G_IS_PARAM_SPEC_BOOLEAN(pspec)) {
		gboolean b;

		b = TRUE;
		if (value &&
			(g_ascii_strcasecmp(value, "false") == 0 ||
				g_ascii_strcasecmp(value, "no") == 0 ||
				strcmp(value, "0") == 0))
			b = FALSE;

		g_value_init(&gvalue, G_TYPE_BOOLEAN);
		g_value_set_boolean(&gvalue, b);
	}
	else if (G_IS_PARAM_SPEC_INT(pspec)) {
		int i;

		if (!value) {
			vips_object_no_value(object, name);
			return -1;
		}

		if (sscanf(value, "%d", &i) != 1) {
			vips_error(class->nickname,
				_("'%s' is not an integer"), value);
			return -1;
		}

		g_value_init(&gvalue, G_TYPE_INT);
		g_value_set_int(&gvalue, i);
	}
	else if (G_IS_PARAM_SPEC_UINT64(pspec)) {
		/* Not always the same as guint64 :-( argh.
		 */
		long long l;

		if (!value) {
			vips_object_no_value(object, name);
			return -1;
		}

		if (sscanf(value, "%lld", &l) != 1) {
			vips_error(class->nickname,
				_("'%s' is not an integer"), value);
			return -1;
		}

		g_value_init(&gvalue, G_TYPE_UINT64);
		g_value_set_uint64(&gvalue, l);
	}
	else if (G_IS_PARAM_SPEC_DOUBLE(pspec)) {
		double d;

		if (!value) {
			vips_object_no_value(object, name);
			return -1;
		}

		if (vips_strtod(value, &d))
			return -1;

		g_value_init(&gvalue, G_TYPE_DOUBLE);
		g_value_set_double(&gvalue, d);
	}
	else if (G_IS_PARAM_SPEC_ENUM(pspec)) {
		int i;

		if (!value) {
			vips_object_no_value(object, name);
			return -1;
		}

		if ((i = vips_enum_from_nick(class->nickname, otype, value)) < 0)
			return -1;

		g_value_init(&gvalue, otype);
		g_value_set_enum(&gvalue, i);
	}

	else if (G_IS_PARAM_SPEC_FLAGS(pspec)) {
		int i;

		if (!value) {
			vips_object_no_value(object, name);
			return -1;
		}

		if ((i = vips_flags_from_nick(class->nickname, otype, value)) < 0)
			return -1;

		g_value_init(&gvalue, otype);
		g_value_set_flags(&gvalue, i);
	}

	else {
		if (!value) {
			vips_object_no_value(object, name);
			return -1;
		}

		g_value_init(&gvalue, G_TYPE_STRING);
		g_value_set_string(&gvalue, value);
	}

	g_object_set_property(G_OBJECT(object), name, &gvalue);
	g_value_unset(&gvalue);

	return 0;
}

/* Does an vipsargument need an argument to write to? For example, an image
 * output needs a filename, a double output just prints.
 */
gboolean
vips_object_argument_needsstring(VipsObject *object, const char *name)
{
	GParamSpec *pspec;
	VipsArgumentClass *argument_class;
	VipsArgumentInstance *argument_instance;

#ifdef DEBUG
	printf("vips_object_argument_needsstring: %s\n", name);
#endif /*DEBUG*/

	if (vips_object_get_argument(object, name,
			&pspec, &argument_class, &argument_instance))
		return -1;

	return vips_argument_class_needsstring(argument_class);
}

static void
vips_object_print_arg(VipsObject *object, GParamSpec *pspec, VipsBuf *buf)
{
	GType type = G_PARAM_SPEC_VALUE_TYPE(pspec);
	const char *name = g_param_spec_get_name(pspec);
	GValue value = G_VALUE_INIT;
	char *str_value;

	g_value_init(&value, type);
	g_object_get_property(G_OBJECT(object), name, &value);
	str_value = g_strdup_value_contents(&value);
	vips_buf_appends(buf, str_value);
	g_free(str_value);
	g_value_unset(&value);
}

/* Is a filename a target, ie. it is of the form ".jpg". Any trailing options
 * have already been stripped. Watch out for cases like "./x.jpg".
 */
static gboolean
vips_filename_istarget(const char *filename)
{
	const char *p;

	return (p = strrchr(filename, '.')) && p == filename;
}

/* Write a named arg to the string. If the arg does not need a string (see
 * above), arg will be NULL.
 */
int
vips_object_get_argument_to_string(VipsObject *object,
	const char *name, const char *arg)
{
	GParamSpec *pspec;
	GType otype;
	VipsArgumentClass *argument_class;
	VipsArgumentInstance *argument_instance;
	VipsObjectClass *oclass;

#ifdef DEBUG
	printf("vips_object_get_argument_to_string: %s -> %s\n",
		name, arg);
#endif /*DEBUG*/

	if (vips_object_get_argument(object, name,
			&pspec, &argument_class, &argument_instance))
		return -1;

	otype = G_PARAM_SPEC_VALUE_TYPE(pspec);

	g_assert(argument_class->flags & VIPS_ARGUMENT_OUTPUT);

	if (g_type_is_a(otype, VIPS_TYPE_IMAGE)) {
		VipsImage *in;
		char filename[VIPS_PATH_MAX];
		char option_string[VIPS_PATH_MAX];

		vips__filename_split8(arg, filename, option_string);

		if (vips_filename_istarget(filename)) {
			VipsTarget *target;

			if (!(target = vips_target_new_to_descriptor(1)))
				return -1;
			g_object_get(object, name, &in, NULL);
			if (vips_image_write_to_target(in,
					arg, target, NULL)) {
				VIPS_UNREF(in);
				VIPS_UNREF(target);
				return -1;
			}
			VIPS_UNREF(in);
			VIPS_UNREF(target);
		}
		else {
			/* Pull out the image and write it.
			 */
			g_object_get(object, name, &in, NULL);
			if (vips_image_write_to_file(in, arg, NULL)) {
				VIPS_UNREF(in);
				return -1;
			}
			VIPS_UNREF(in);
		}
	}
	else if (g_type_is_a(otype, VIPS_TYPE_OBJECT) &&
		(oclass = g_type_class_ref(otype)) &&
		oclass->output_to_arg) {
		VipsObject *value;

		g_object_get(object, name, &value, NULL);
		if (oclass->output_to_arg(value, arg)) {
			g_object_unref(value);
			return -1;
		}
		g_object_unref(value);
	}
	else {
		char str[1000];
		VipsBuf buf = VIPS_BUF_STATIC(str);

		vips_object_print_arg(object, pspec, &buf);
		printf("%s\n", vips_buf_all(&buf));
	}

	return 0;
}

static void *
vips_argument_is_required(VipsObject *object,
	GParamSpec *pspec,
	VipsArgumentClass *argument_class,
	VipsArgumentInstance *argument_instance,
	void *a, void *b)
{
	if ((argument_class->flags & VIPS_ARGUMENT_REQUIRED) &&
		(argument_class->flags & VIPS_ARGUMENT_CONSTRUCT) &&
		(argument_class->flags & VIPS_ARGUMENT_INPUT) &&
		!argument_instance->assigned)
		return pspec;

	return NULL;
}

/* Find the first unassigned required input arg.
 */
static GParamSpec *
vips_object_find_required(VipsObject *object)
{
	return (GParamSpec *) vips_argument_map(object,
		vips_argument_is_required, NULL, NULL);
}

typedef struct _VipsNameFlagsPair {
	const char **names;
	int *flags;
} VipsNameFlagsPair;

static void *
vips_object_find_args(VipsObject *object,
	GParamSpec *pspec,
	VipsArgumentClass *argument_class,
	VipsArgumentInstance *argument_instance,
	void *a, void *b)
{
	VipsNameFlagsPair *pair = (VipsNameFlagsPair *) a;
	int *i = (int *) b;

	pair->names[*i] = g_param_spec_get_name(pspec);
	pair->flags[*i] = (int) argument_class->flags;

	*i += 1;

	return NULL;
}

/**
 * vips_object_get_args: (skip)
 * @object: object whose args should be retrieved
 * @names: (transfer none) (array length=n_args) (allow-none): output array of [class@GObject.ParamSpec] names
 * @flags: (transfer none) (array length=n_args) (allow-none): output array of [flags@ArgumentFlags]
 * @n_args: (allow-none): length of output arrays
 *
 * Get all [class@GObject.ParamSpec] names and [flags@ArgumentFlags] for an object.
 *
 * This is handy for language bindings. From C, it's usually more convenient to
 * use [func@Argument.map].
 *
 * Returns: 0 on success, -1 on error
 */
int
vips_object_get_args(VipsObject *object,
	const char ***names, int **flags, int *n_args)
{
	VipsObjectClass *object_class = VIPS_OBJECT_GET_CLASS(object);
	int n = g_slist_length(object_class->argument_table_traverse);

	VipsNameFlagsPair pair;
	int i;

	pair.names = VIPS_ARRAY(object, n, const char *);
	pair.flags = VIPS_ARRAY(object, n, int);
	if (!pair.names ||
		!pair.flags)
		return -1;

	i = 0;
	(void) vips_argument_map(object,
		vips_object_find_args, &pair, &i);

	if (names)
		*names = pair.names;
	if (flags)
		*flags = pair.flags;
	if (n_args)
		*n_args = n;

	return 0;
}

/**
 * vips_object_new: (skip)
 * @type: object to create
 * @set: set arguments with this
 * @a: client data
 * @b: client data
 *
 * [ctor@GObject.Object.new] the object, set any arguments with @set, call
 * [method@Object.build] and return the complete object.
 *
 * Returns: the new object
 */
VipsObject *
vips_object_new(GType type, VipsObjectSetArguments set, void *a, void *b)
{
	VipsObject *object;

	vips_check_init();

	object = VIPS_OBJECT(g_object_new(type, NULL));

	if (set && set(object, a, b)) {
		g_object_unref(object);
		return NULL;
	}

	if (vips_object_build(object)) {
		g_object_unref(object);
		return NULL;
	}

	return object;
}

/**
 * vips_object_set_valist:
 * @object: object to set arguments on
 * @ap: `NULL`-terminated list of argument/value pairs
 *
 * See [method@Object.set].
 *
 * Returns: 0 on success, -1 on error
 */
int
vips_object_set_valist(VipsObject *object, va_list ap)
{
	char *name;

	VIPS_DEBUG_MSG("vips_object_set_valist:\n");

	for (name = va_arg(ap, char *); name; name = va_arg(ap, char *)) {
		GParamSpec *pspec;
		VipsArgumentClass *argument_class;
		VipsArgumentInstance *argument_instance;

		VIPS_DEBUG_MSG("\tname = '%s' (%p)\n", name, name);

		if (vips_object_get_argument(VIPS_OBJECT(object), name,
				&pspec, &argument_class, &argument_instance))
			return -1;

		VIPS_ARGUMENT_COLLECT_SET(pspec, argument_class, ap);

		g_object_set_property(G_OBJECT(object), name, &value);

		VIPS_ARGUMENT_COLLECT_GET(pspec, argument_class, ap);

		VIPS_ARGUMENT_COLLECT_END
	}

	return 0;
}

/**
 * vips_object_set:
 * @object: object to set arguments on
 * @...: `NULL`-terminated list of argument/value pairs
 *
 * Set a list of vips object arguments. For example:
 *
 * ```c
 * vips_object_set(operation,
 *     "input", in,
 *     "output", &out,
 *     NULL);
 * ```
 *
 * Input arguments are given in-line, output arguments are given as pointers
 * to where the output value should be written.
 *
 * ::: seealso
 *     [method@Object.set_valist], [method@Object.set_from_string].
 *
 * Returns: 0 on success, -1 on error
 */
int
vips_object_set(VipsObject *object, ...)
{
	va_list ap;
	int result;

	va_start(ap, object);
	result = vips_object_set_valist(object, ap);
	va_end(ap);

	return result;
}

/* Set object args from a string. @p should be the initial left bracket and
 * there should be no tokens after the matching right bracket. @p is modified.
 */
static int
vips_object_set_args(VipsObject *object, const char *p)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);

	VipsToken token;
	char string[VIPS_PATH_MAX];
	char string2[VIPS_PATH_MAX];
	GParamSpec *pspec;
	VipsArgumentClass *argument_class;
	VipsArgumentInstance *argument_instance;

	if (!(p = vips__token_need(p, VIPS_TOKEN_LEFT,
			  string, VIPS_PATH_MAX)))
		return -1;

	if (!(p = vips__token_segment(p, &token, string, VIPS_PATH_MAX)))
		return -1;

	for (;;) {
		if (token == VIPS_TOKEN_RIGHT)
			break;
		if (token != VIPS_TOKEN_STRING) {
			vips_error(class->nickname,
				_("expected string or ), saw %s"),
				vips_enum_nick(VIPS_TYPE_TOKEN, token));
			return -1;
		}

		/* We have to look for a '=', ']' or a ',' to see if string is
		 * a param name or a value.
		 */
		if (!(p = vips__token_segment(p, &token,
				  string2, VIPS_PATH_MAX)))
			return -1;
		if (token == VIPS_TOKEN_EQUALS) {
			if (!(p = vips__token_segment_need(p, VIPS_TOKEN_STRING,
					  string2, VIPS_PATH_MAX)))
				return -1;
			if (vips_object_set_argument_from_string(object,
					string, string2))
				return -1;

			if (!(p = vips__token_must(p, &token,
					  string2, VIPS_PATH_MAX)))
				return -1;
		}
		else if (g_object_class_find_property(
					 G_OBJECT_GET_CLASS(object), string) &&
			!vips_object_get_argument(object, string,
				&pspec, &argument_class, &argument_instance) &&
			(argument_class->flags & VIPS_ARGUMENT_CONSTRUCT) &&
			(argument_class->flags & VIPS_ARGUMENT_INPUT) &&
			G_IS_PARAM_SPEC_BOOLEAN(pspec)) {
			/* The string is the name of an optional
			 * input boolean ... set it!
			 */
			if (!argument_instance->assigned)
				g_object_set(object, string, TRUE, NULL);
		}
		else if ((pspec = vips_object_find_required(object))) {
			if (vips_object_set_argument_from_string(object,
					g_param_spec_get_name(pspec), string))
				return -1;
		}
		else {
			vips_error(class->nickname,
				_("unable to set '%s'"), string);
			return -1;
		}

		/* Now must be a , or a ).
		 */
		if (token == VIPS_TOKEN_COMMA) {
			if (!(p = vips__token_must(p, &token,
					  string, VIPS_PATH_MAX)))
				return -1;
		}
		else if (token != VIPS_TOKEN_RIGHT) {
			vips_error(class->nickname,
				"%s", _("not , or ) after parameter"));
			return -1;
		}
	}

	if ((p = vips__token_get(p, &token, string, VIPS_PATH_MAX))) {
		vips_error(class->nickname,
			"%s", _("extra tokens after ')'"));
		return -1;
	}

	return 0;
}

/**
 * vips_object_set_from_string:
 * @object: object to set arguments on
 * @string: arguments as a string
 *
 * Set object arguments from a string. The string can be something like
 * "a=12", or "a = 12, b = 13", or "fred". The string can optionally be
 * enclosed in brackets.
 *
 * You'd typically use this between creating the object and building it.
 *
 * ::: seealso
 *     [method@Object.set], [method@Object.build],
 *     [func@cache_operation_buildp].
 *
 * Returns: 0 on success, -1 on error
 */
int
vips_object_set_from_string(VipsObject *object, const char *string)
{
	const char *q;
	VipsToken token;
	char buffer[VIPS_PATH_MAX];
	char str[VIPS_PATH_MAX];

	g_strlcpy(buffer, string, VIPS_PATH_MAX);

	/* Does string start with a bracket? If it doesn't, enclose the whole
	 * thing in [].
	 */
	if (!(q = vips__token_get(buffer, &token, str, VIPS_PATH_MAX)) ||
		token != VIPS_TOKEN_LEFT)
		g_snprintf(buffer, VIPS_PATH_MAX, "[%s]", string);
	else
		g_strlcpy(buffer, string, VIPS_PATH_MAX);

	return vips_object_set_args(object, buffer);
}

VipsObject *
vips_object_new_from_string(VipsObjectClass *object_class, const char *p)
{
	const char *q;
	char str[VIPS_PATH_MAX];
	VipsObject *object;

	g_assert(object_class);
	g_assert(object_class->new_from_string);

	/* Find the start of the optional args on the end of the string, take
	 * everything before that as the principal arg for the constructor.
	 */
	if ((q = vips__find_rightmost_brackets(p)))
		g_strlcpy(str, p, VIPS_MIN(VIPS_PATH_MAX, q - p + 1));
	else
		g_strlcpy(str, p, VIPS_PATH_MAX);
	if (!(object = object_class->new_from_string(str)))
		return NULL;

	/* More tokens there? Set any other args.
	 */
	if (q &&
		vips_object_set_from_string(object, q)) {
		g_object_unref(object);
		return NULL;
	}

	return object;
}

static void *
vips_object_to_string_required(VipsObject *object,
	GParamSpec *pspec,
	VipsArgumentClass *argument_class,
	VipsArgumentInstance *argument_instance,
	void *a, void *b)
{
	VipsBuf *buf = (VipsBuf *) a;
	gboolean *first = (gboolean *) b;

	if ((argument_class->flags & VIPS_ARGUMENT_REQUIRED)) {
		if (*first) {
			vips_buf_appends(buf, "(");
			*first = FALSE;
		}
		else {
			vips_buf_appends(buf, ",");
		}

		vips_object_print_arg(object, pspec, buf);
	}

	return NULL;
}

static void *
vips_object_to_string_optional(VipsObject *object,
	GParamSpec *pspec,
	VipsArgumentClass *argument_class,
	VipsArgumentInstance *argument_instance,
	void *a, void *b)
{
	VipsBuf *buf = (VipsBuf *) a;
	gboolean *first = (gboolean *) b;

	if (!(argument_class->flags & VIPS_ARGUMENT_REQUIRED) &&
		argument_instance->assigned) {
		if (*first) {
			vips_buf_appends(buf, "(");
			*first = FALSE;
		}
		else {
			vips_buf_appends(buf, ",");
		}

		vips_buf_appends(buf, g_param_spec_get_name(pspec));
		vips_buf_appends(buf, "=");
		vips_object_print_arg(object, pspec, buf);
	}

	return NULL;
}

/**
 * vips_object_to_string:
 * @object: object to stringify
 * @buf: write string here
 *
 * The inverse of [ctor@Object.new_from_string]: turn @object into eg.
 * `"bicubic"`.
 */
void
vips_object_to_string(VipsObject *object, VipsBuf *buf)
{
	VipsObjectClass *object_class = VIPS_OBJECT_GET_CLASS(object);

	gboolean first;

	g_assert(object_class->to_string);

	/* Nicknames are not guaranteed to be unique, so use the full type
	 * name.
	 */
	object_class->to_string(object, buf);
	first = TRUE;
	(void) vips_argument_map(object,
		vips_object_to_string_required, buf, &first);
	(void) vips_argument_map(object,
		vips_object_to_string_optional, buf, &first);
	if (!first)
		vips_buf_appends(buf, ")");
}

typedef struct {
	VipsSListMap2Fn fn;
	void *a;
	void *b;
	void *result;
} VipsObjectMapArgs;

static void
vips_object_map_sub(VipsObject *key, VipsObject *value,
	VipsObjectMapArgs *args)
{
	if (!args->result)
		args->result = args->fn(key, args->a, args->b);
}

/**
 * vips_object_map: (skip)
 * @fn: function to call for all objects
 * @a: client data
 * @b: client data
 *
 * Call a function for all alive objects.
 * Stop when @fn returns non-`NULL` and return that value.
 *
 * Returns: `NULL` if @fn returns `NULL` for all arguments, otherwise the first
 * non-`NULL` value from @fn.
 */
void *
vips_object_map(VipsSListMap2Fn fn, void *a, void *b)
{
	VipsObjectMapArgs args;

	args.fn = fn;
	args.a = a;
	args.b = b;
	args.result = NULL;

	/* We must test vips__object_all before we lock because the lock is
	 * only created when the first object is created.
	 */
	if (vips__object_all) {
		g_mutex_lock(&vips__object_all_lock);
		g_hash_table_foreach(vips__object_all,
			(GHFunc) vips_object_map_sub, &args);
		g_mutex_unlock(&vips__object_all_lock);
	}

	return args.result;
}

/**
 * vips_type_map: (skip)
 * @base: base type
 * @fn: call this function for every type
 * @a: client data
 * @b: client data
 *
 * Map over a type's children. Stop when @fn returns non-`NULL`
 * and return that value.
 *
 * Returns: `NULL` if @fn returns `NULL` for all arguments, otherwise the first
 * non-`NULL` value from @fn.
 */
void *
vips_type_map(GType base, VipsTypeMap2Fn fn, void *a, void *b)
{
	GType *child;
	guint n_children;
	unsigned int i;
	void *result;

	child = g_type_children(base, &n_children);
	result = NULL;
	for (i = 0; i < n_children && !result; i++)
		result = fn(child[i], a, b);
	g_free(child);

	return result;
}

/**
 * vips_type_map_all: (skip)
 * @base: base type
 * @fn: call this function for every type
 * @a: client data
 *
 * Map over a type's children, direct and indirect. Stop when @fn returns
 * non-`NULL` and return that value.
 *
 * Returns: `NULL` if @fn returns `NULL` for all arguments, otherwise the first
 * non-`NULL` value from @fn.
 */
void *
vips_type_map_all(GType base, VipsTypeMapFn fn, void *a)
{
	void *result;

	if (!(result = fn(base, a)))
		result = vips_type_map(base,
			(VipsTypeMap2Fn) vips_type_map_all, fn, a);

	return result;
}

/**
 * vips_class_map_all: (skip)
 * @type: base type
 * @fn: call this function for every type
 * @a: client data
 *
 * Loop over all the subclasses of @type. Non-abstract classes only.
 * Stop when @fn returns
 * non-`NULL` and return that value.
 *
 * Returns: `NULL` if @fn returns `NULL` for all arguments, otherwise the first
 * non-`NULL` value from @fn.
 */
void *
vips_class_map_all(GType type, VipsClassMapFn fn, void *a)
{
	void *result;

	/* Avoid abstract classes. Use type_map_all for them.
	 */
	if (!G_TYPE_IS_ABSTRACT(type)) {
		/* We never unref this ref, but we never unload classes
		 * anyway, so so what.
		 */
		if ((result = fn(
				 VIPS_OBJECT_CLASS(g_type_class_ref(type)), a)))
			return result;
	}

	if ((result = vips_type_map(type,
			 (VipsTypeMap2Fn) vips_class_map_all, fn, a)))
		return result;

	return NULL;
}

/* How deeply nested is a class ... used to indent class lists.
 */
int
vips_type_depth(GType type)
{
	int depth;

	depth = 0;
	while (type != VIPS_TYPE_OBJECT &&
		(type = g_type_parent(type)))
		depth += 1;

	return depth;
}

static void *
test_name(VipsObjectClass *class, const char *nickname)
{
	if (g_ascii_strcasecmp(class->nickname, nickname) == 0)
		return class;

	/* Check the class name too, why not.
	 */
	if (g_ascii_strcasecmp(G_OBJECT_CLASS_NAME(class), nickname) == 0)
		return class;

	return NULL;
}

/**
 * vips_class_find:
 * @basename: name of base class
 * @nickname: search for a class with this nickname
 *
 * Search below @basename, return the first class whose name or @nickname
 * matches.
 *
 * ::: seealso
 *     [func@type_find]
 *
 * Returns: (transfer none): the found class.
 */
const VipsObjectClass *
vips_class_find(const char *basename, const char *nickname)
{
	const char *classname = basename ? basename : "VipsObject";

	VipsObjectClass *class;
	GType base;

	if (!(base = g_type_from_name(classname)))
		return NULL;
	class = vips_class_map_all(base,
		(VipsClassMapFn) test_name, (void *) nickname);

	return class;
}

/* What we store for each nickname. We can't just store the type with
 * GINT_TO_POINTER() since GType is 64 bits on some platforms.
 */
typedef struct _NicknameGType {
	const char *nickname;
	GType type;
	gboolean duplicate;
} NicknameGType;

static void *
vips_class_add_hash(VipsObjectClass *class, GHashTable *table)
{
	GType type = G_OBJECT_CLASS_TYPE(class);
	NicknameGType *hit;

	hit = (NicknameGType *)
		g_hash_table_lookup(table, (void *) class->nickname);

	/* If this is not a unique name, mark as a duplicate. In this case
	 * we'll need to fall back to a search.
	 */
	if (hit)
		hit->duplicate = TRUE;
	else {
		hit = g_new(NicknameGType, 1);
		hit->nickname = class->nickname;
		hit->type = type;
		hit->duplicate = FALSE;
		g_hash_table_insert(table, (void *) hit->nickname, hit);
	}

	return NULL;
}

static void *
vips_class_build_hash_cb(void *dummy)
{
	GType base;

	vips__object_nickname_table = g_hash_table_new(g_str_hash, g_str_equal);

	base = g_type_from_name("VipsObject");
	g_assert(base);

	vips_class_map_all(base,
		(VipsClassMapFn) vips_class_add_hash,
		(void *) vips__object_nickname_table);

	return NULL;
}

/**
 * vips_type_find:
 * @basename: name of base class
 * @nickname: search for a class with this nickname
 *
 * Search below @basename, return the [alias@GObject.Type] of the class
 * whose name or @nickname matches, or 0 for not found.
 * If @basename is `NULL`, the whole of [class@Object] is searched.
 *
 * This function uses a cache, so it should be quick.
 *
 * ::: seealso
 *     [func@class_find]
 *
 * Returns: the [alias@GObject.Type] of the class, or 0 if the class is not found.
 */
GType
vips_type_find(const char *basename, const char *nickname)
{
	static GOnce once = G_ONCE_INIT;

	const char *classname = basename ? basename : "VipsObject";

	NicknameGType *hit;
	GType base;
	GType type;

	VIPS_ONCE(&once, vips_class_build_hash_cb, NULL);

	hit = (NicknameGType *)
		g_hash_table_lookup(vips__object_nickname_table, (void *) nickname);

	/* We must only search below basename ... check that the cache hit is
	 * in the right part of the tree.
	 */
	if (!(base = g_type_from_name(classname)))
		return 0;
	if (hit &&
		!hit->duplicate &&
		g_type_is_a(hit->type, base))
		type = hit->type;
	else {
		const VipsObjectClass *class;

		if (!(class = vips_class_find(basename, nickname)))
			return 0;

		type = G_OBJECT_CLASS_TYPE(class);
	}

	return type;
}

/**
 * vips_nickname_find:
 * @type: [alias@GObject.Type] to search for
 *
 * Return the VIPS nickname for a [alias@GObject.Type]. Handy for language bindings.
 *
 * Returns: (transfer none): the class nickname.
 */
const char *
vips_nickname_find(GType type)
{
	gpointer p;
	VipsObjectClass *class;

	if (type &&
		(p = g_type_class_ref(type)) &&
		VIPS_IS_OBJECT_CLASS(p) &&
		(class = VIPS_OBJECT_CLASS(p)))
		return class->nickname;

	return NULL;
}

/* The vips_object_local_array() macro uses this as its callback.
 */
void
vips_object_local_cb(VipsObject *vobject, GObject *gobject)
{
	VIPS_FREEF(g_object_unref, gobject);
}

typedef struct {
	VipsObject **array;
	int n;
} VipsObjectLocal;

static void
vips_object_local_array_cb(VipsObject *parent, VipsObjectLocal *local)
{
	int i;

	for (i = 0; i < local->n; i++)
		VIPS_FREEF(g_object_unref, local->array[i]);

	VIPS_FREEF(g_free, local->array);
	VIPS_FREEF(g_free, local);
}

/**
 * vips_object_local_array: (skip)
 * @parent: objects unref when this object unrefs
 * @n: array size
 *
 * Make an array of `NULL` [class@Object] pointers. When @parent closes, every
 * non-`NULL` pointer in the array will be unreffed and the array will be
 * freed. Handy for creating a set of temporary images for a function.
 *
 * The array is `NULL`-terminated, ie. contains an extra `NULL` element at the
 * end.
 *
 * Example:
 *
 * ```c
 * VipsObject **t;
 *
 * t = vips_object_local_array(parent, 5);
 * if (vips_add(a, b, &t[0], NULL) ||
 *     vips_invert(t[0], &t[1], NULL) ||
 *     vips_add(t[1], t[0], &t[2], NULL) ||
 *     vips_costra(t[2], out, NULL))
 *     return -1;
 * ```
 *
 * Returns: an array of `NULL` pointers of length @n
 */
VipsObject **
vips_object_local_array(VipsObject *parent, int n)
{
	VipsObjectLocal *local;

	local = g_new(VipsObjectLocal, 1);
	local->n = n;
	/* Make the array 1 too long so we can be sure there's a NULL
	 * terminator.
	 */
	local->array = g_new0(VipsObject *, n + 1);

	g_signal_connect(parent, "close",
		G_CALLBACK(vips_object_local_array_cb), local);

	return local->array;
}

void
vips_object_set_static(VipsObject *object, gboolean static_object)
{
	object->static_object = static_object;
}

static void *
vips_object_n_static_cb(VipsObject *object, int *n, void *b)
{
	if (object->static_object)
		*n += 1;

	return NULL;
}

static int
vips_object_n_static(void)
{
	int n;

	n = 0;
	vips_object_map(
		(VipsSListMap2Fn) vips_object_n_static_cb, &n, NULL);

	return n;
}

static void *
vips_object_print_all_cb(VipsObject *object, int *n, void *b)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);

	char str[32768];
	VipsBuf buf = VIPS_BUF_STATIC(str);

	fprintf(stderr, "%d) %s (%p)",
		*n, G_OBJECT_TYPE_NAME(object), object);
	if (object->local_memory)
		fprintf(stderr, " %zd bytes", object->local_memory);
	fprintf(stderr, ", count=%d", G_OBJECT(object)->ref_count);
	fprintf(stderr, "\n");

	vips_object_summary_class(class, &buf);
	vips_buf_appends(&buf, ", ");
	vips_object_summary(object, &buf);
	fprintf(stderr, "%s\n", vips_buf_all(&buf));

	*n += 1;

	return NULL;
}

int
vips__object_leak(void)
{
	int n_leaks;

	n_leaks = 0;

	/* Don't count static objects.
	 */
	if (vips__object_all &&
		g_hash_table_size(vips__object_all) > vips_object_n_static()) {
		fprintf(stderr, "%d objects alive:\n",
			g_hash_table_size(vips__object_all));

		vips_object_map(
			(VipsSListMap2Fn) vips_object_print_all_cb, &n_leaks, NULL);
	}

	return n_leaks;
}

void
vips_object_print_all(void)
{
	(void) vips__object_leak();
	(void) vips__type_leak();
}

static void *
vips_object_sanity_all_cb(VipsObject *object, void *a, void *b)
{
	(void) vips_object_sanity(object);

	return NULL;
}

void
vips_object_sanity_all(void)
{
	vips_object_map(
		(VipsSListMap2Fn) vips_object_sanity_all_cb, NULL, NULL);
}

static void *
vips_object_unref_outputs_sub(VipsObject *object,
	GParamSpec *pspec,
	VipsArgumentClass *argument_class,
	VipsArgumentInstance *argument_instance,
	void *a, void *b)
{
	if ((argument_class->flags & VIPS_ARGUMENT_OUTPUT) &&
		G_IS_PARAM_SPEC_OBJECT(pspec) &&
		argument_instance->assigned) {
		GObject *value;

		g_object_get(object,
			g_param_spec_get_name(pspec), &value, NULL);

		/* Doing the get refs the object, so unref the get, then unref
		 * again since this an an output object of the operation.
		 */
		g_object_unref(value);
		g_object_unref(value);
	}

	return NULL;
}

/**
 * vips_object_unref_outputs:
 * @object: object to drop output refs from
 *
 * Unref all assigned output objects. Useful for language bindings.
 *
 * After an object is built, all output args are owned by the caller. If
 * something goes wrong before then, we have to unref the outputs that have
 * been made so far. This function can also be useful for callers when
 * they've finished processing outputs themselves.
 *
 * ::: seealso
 *     [func@cache_operation_build].
 */
void
vips_object_unref_outputs(VipsObject *object)
{
	(void) vips_argument_map(object,
		vips_object_unref_outputs_sub, NULL, NULL);
}

/**
 * vips_object_get_description:
 * @object: object to fetch description from
 *
 * Fetch the object description. Useful for language bindings.
 *
 * [property@Object:description] is only available after `_build()`, which can be too
 * late. This function fetches from the instance, if possible, but falls back
 * to the class description if we are too early.
 *
 * Returns: the object description
 */
const char *
vips_object_get_description(VipsObject *object)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);

	if (object->description)
		return object->description;
	else
		return class->description;
}
