using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using NetVips.Internal;
namespace NetVips;
///
/// Build introspection data for operations.
///
///
/// Make an operation, introspect it, and build a structure representing
/// everything we know about it.
///
public class Introspect
{
///
/// A cache for introspection data.
///
private static readonly ConcurrentDictionary IntrospectCache = new();
///
/// An object structure that encapsulates the metadata
/// required to specify arguments.
///
public struct Argument
{
///
/// Name of this argument.
///
public string Name;
///
/// Flags for this argument.
///
public Enums.ArgumentFlags Flags;
///
/// The GType for this argument.
///
public nint Type;
}
///
/// The first required input image or .
///
public Argument? MemberX;
///
/// A bool indicating if this operation is mutable.
///
public readonly bool Mutable;
///
/// The required input for this operation.
///
public readonly List RequiredInput = new();
///
/// The optional input for this operation.
///
public readonly Dictionary OptionalInput = new();
///
/// The required output for this operation.
///
public readonly List RequiredOutput = new();
///
/// The optional output for this operation.
///
public readonly Dictionary OptionalOutput = new();
///
/// Build introspection data for a specified operation name.
///
/// The operation name to introspect.
private Introspect(string operationName)
{
using var op = Operation.NewFromName(operationName);
var arguments = GetArgs(op);
foreach (var entry in arguments)
{
var name = entry.Key;
var flag = entry.Value;
var gtype = op.GetTypeOf(name);
var details = new Argument
{
Name = name,
Flags = flag,
Type = gtype
};
if ((flag & Enums.ArgumentFlags.INPUT) != 0)
{
if ((flag & Enums.ArgumentFlags.REQUIRED) != 0 &&
(flag & Enums.ArgumentFlags.DEPRECATED) == 0)
{
// the first required input image arg will be self
if (!MemberX.HasValue && gtype == GValue.ImageType)
{
MemberX = details;
}
else
{
RequiredInput.Add(details);
}
}
else
{
// we allow deprecated optional args
OptionalInput[name] = details;
}
// modified input arguments count as mutable.
if ((flag & Enums.ArgumentFlags.MODIFY) != 0 &&
(flag & Enums.ArgumentFlags.REQUIRED) != 0 &&
(flag & Enums.ArgumentFlags.DEPRECATED) == 0)
{
Mutable = true;
}
}
else if ((flag & Enums.ArgumentFlags.OUTPUT) != 0)
{
if ((flag & Enums.ArgumentFlags.REQUIRED) != 0 &&
(flag & Enums.ArgumentFlags.DEPRECATED) == 0)
{
RequiredOutput.Add(details);
}
else
{
// again, allow deprecated optional args
OptionalOutput[name] = details;
}
}
}
}
///
/// Get all arguments for an operation.
///
///
/// Not quick! Try to call this infrequently.
///
/// Operation to lookup.
/// Arguments for the operation.
private IEnumerable> GetArgs(Operation operation)
{
var args = new List>();
void AddArg(string name, Enums.ArgumentFlags flags)
{
// libvips uses '-' to separate parts of arg names, but we
// need '_' for C#
name = name.Replace("-", "_");
args.Add(new KeyValuePair(name, flags));
}
// vips_object_get_args was added in 8.7
if (NetVips.AtLeastLibvips(8, 7))
{
var result = Internal.VipsObject.GetArgs(operation, out var names, out var flags, out var nArgs);
if (result != 0)
{
throw new VipsException("unable to get arguments from operation");
}
for (var i = 0; i < nArgs; i++)
{
var flag = (Enums.ArgumentFlags)Marshal.PtrToStructure(flags + i * sizeof(int));
if ((flag & Enums.ArgumentFlags.CONSTRUCT) == 0)
{
continue;
}
var name = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(names, i * IntPtr.Size));
AddArg(name, flag);
}
}
else
{
nint AddConstruct(nint self, nint pspec, nint argumentClass, nint argumentInstance,
nint a, nint b)
{
var flags = Marshal.PtrToStructure(argumentClass).Flags;
if ((flags & Enums.ArgumentFlags.CONSTRUCT) == 0)
{
return IntPtr.Zero;
}
var name = Marshal.PtrToStringAnsi(Marshal.PtrToStructure(pspec).Name);
AddArg(name, flags);
return IntPtr.Zero;
}
Vips.ArgumentMap(operation, AddConstruct, IntPtr.Zero, IntPtr.Zero);
}
return args;
}
///
/// Get introspection data for a specified operation name.
///
/// Operation name.
/// Introspection data.
public static Introspect Get(string operationName)
{
return IntrospectCache.GetOrAdd(operationName, name => new Introspect(name));
}
}