<commit_before>/* abstract base class for all vips objects
 *
 * Edited from nip's base class, 15/10/08
 */

/*

    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 <vips/intl.h>

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

#include <vips/vips.h>
#include <vips/internal.h>
#include <vips/debug.h>

#include <gobject/gvaluecollector.h>

#include "vipsmarshal.h"

/**
 * SECTION: object
 * @short_description: the VIPS base object class
 * @stability: Stable
 * @see_also: <link linkend="libvips-operation">operation</link>
 * @include: vips/vips.h
 *
 * The #VipsObject class and associated types and macros.
 *
 * #VipsObject is the base class for all objects in libvips. It has the
 * following major features:
 *
 * <emphasis>Functional class creation</emphasis> Vips objects have a very 
 * regular
 * lifecycle: initialise, build, use, destroy. They behave rather like
 * function calls and are free of side-effects. 
 *
 * <emphasis>Run-time introspection</emphasis> Vips objects can be fully 
 * introspected at
 * run-time. There is not need for a separate source-code analysis. 
 *
 * <emphasis>Command-line interface</emphasis> Vips objects have an 
 * automatic command-line
 * line interface with a set of virtual methods. 
 *
 */

/** 
 * 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 
 *
 * 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.
 *
 * @VIPS_ARGUMENT_SET_ALWAYS is handy for arguments which are set from C. For
 * example, VipsImage::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.
 *
 * @VIPS_ARGUMENT_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.
 */

/* 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 = NULL;

static guint vips_object_signals[SIG_LAST] = { 0 };

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 );

/* 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 *object_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( object_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)
 *
 */
void
vips_object_summary_class( VipsObjectClass *class, VipsBuf *buf )
{
	class->summary_class( class, buf );
}

/**
 * vips_object_summary: (skip)
 *
 */
void
vips_object_summary( VipsObject *object, VipsBuf *buf )
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );

	class->summary( object, buf );
}

/**
 * vips_object_dump: (skip)
 *
 */
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 *class )
{
	char str[2048];
	VipsBuf buf = VIPS_BUF_STATIC( str );

	vips_object_summary_class( class, &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 )
{
	VipsObjectClass *class;
	char str[1000];
	VipsBuf buf = VIPS_BUF_STATIC( str );

	if( !object ) {
		printf( "vips_object_sanity: null object\n" );

		return( FALSE );
	}

	class = VIPS_OBJECT_GET_CLASS( object );
	class->sanity( object, &buf );
	if( !vips_buf_is_empty( &buf ) ) {
		printf( "sanity failure: " );
		vips_object_print_name( object );
		printf( " %s\n", 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;

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

	if( argument_instance->invalidate_id ) {
		if( g_signal_handler_is_connected( object,
			argument_instance->invalidate_id ) )
			g_signal_handler_disconnect( object,
				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: (skip)
 * @object: object whose args should be enumerated
 * @fn: call this function for every argument
 * @a: client data
 * @b: client data
 *
 * Loop over the vips_arguments to 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_class &&
		!(*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 #VipsArgmentFlags 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( VipsObject *object, GParamSpec *pspec, 
	GObject **member )
{
	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 );

	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 )
{
	vips_operation_invalidate( VIPS_OPERATION( argument ) ); 
}

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 );

	g_assert( argument_instance );

	vips_object_clear_member( object, pspec, member );

	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 );

			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 ) {
#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 );

			/* FIXME ... could use a NULLing weakref
			 */
			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 );

	if( !argument_class ) {
		G_OBJECT_WARN_INVALID_PROPERTY_ID( gobject,
			property_id, pspec );
		return;
	}

#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 commenmt 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 );
	}<commit_msg>fix up operation invalidate<commit_after>/* abstract base class for all vips objects
 *
 * Edited from nip's base class, 15/10/08
 */

/*

    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 <vips/intl.h>

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

#include <vips/vips.h>
#include <vips/internal.h>
#include <vips/debug.h>

#include <gobject/gvaluecollector.h>

#include "vipsmarshal.h"

/**
 * SECTION: object
 * @short_description: the VIPS base object class
 * @stability: Stable
 * @see_also: <link linkend="libvips-operation">operation</link>
 * @include: vips/vips.h
 *
 * The #VipsObject class and associated types and macros.
 *
 * #VipsObject is the base class for all objects in libvips. It has the
 * following major features:
 *
 * <emphasis>Functional class creation</emphasis> Vips objects have a very 
 * regular
 * lifecycle: initialise, build, use, destroy. They behave rather like
 * function calls and are free of side-effects. 
 *
 * <emphasis>Run-time introspection</emphasis> Vips objects can be fully 
 * introspected at
 * run-time. There is not need for a separate source-code analysis. 
 *
 * <emphasis>Command-line interface</emphasis> Vips objects have an 
 * automatic command-line
 * line interface with a set of virtual methods. 
 *
 */

/** 
 * 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 
 *
 * 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.
 *
 * @VIPS_ARGUMENT_SET_ALWAYS is handy for arguments which are set from C. For
 * example, VipsImage::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.
 *
 * @VIPS_ARGUMENT_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.
 */

/* 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 = NULL;

static guint vips_object_signals[SIG_LAST] = { 0 };

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 );

/* 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 *object_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( object_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)
 *
 */
void
vips_object_summary_class( VipsObjectClass *class, VipsBuf *buf )
{
	class->summary_class( class, buf );
}

/**
 * vips_object_summary: (skip)
 *
 */
void
vips_object_summary( VipsObject *object, VipsBuf *buf )
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );

	class->summary( object, buf );
}

/**
 * vips_object_dump: (skip)
 *
 */
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 *class )
{
	char str[2048];
	VipsBuf buf = VIPS_BUF_STATIC( str );

	vips_object_summary_class( class, &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 )
{
	VipsObjectClass *class;
	char str[1000];
	VipsBuf buf = VIPS_BUF_STATIC( str );

	if( !object ) {
		printf( "vips_object_sanity: null object\n" );

		return( FALSE );
	}

	class = VIPS_OBJECT_GET_CLASS( object );
	class->sanity( object, &buf );
	if( !vips_buf_is_empty( &buf ) ) {
		printf( "sanity failure: " );
		vips_object_print_name( object );
		printf( " %s\n", 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;
	GObject *member = G_STRUCT_MEMBER( GObject *, object,
		argument_class->offset );

	if( argument_instance->close_id ) {
		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 ) {
		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: (skip)
 * @object: object whose args should be enumerated
 * @fn: call this function for every argument
 * @a: client data
 * @b: client data
 *
 * Loop over the vips_arguments to 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_class &&
		!(*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 #VipsArgmentFlags 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 );

	if( !argument_class ) {
		G_OBJECT_WARN_INVALID_PROPERTY_ID( gobject,
			property_id, pspec );
		return;
	}

#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 commenmt 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 );
	}
{'fraction_non_alphanumeric': 0.11116738414788555, 'fraction_numerical': 0.0023297037224613826, 'mean_word_length': 4.315029161058771, 'pattern_counts': {'":': 0, '<': 39, '<?xml version=': 0, '>': 237, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4858740', 'n_tokens_mistral': 21616, 'n_tokens_neox': 20262, 'n_words': 5407}