/*
 * The contents of this file is dual-licensed under 2 
 * alternative Open Source/Free licenses: LGPL 2.1 or later and 
 * Apache License 2.0. (starting with JNA version 4.0.0).
 * 
 * You can freely decide which license you want to apply to 
 * the project.
 * 
 * You may obtain a copy of the LGPL License at:
 * 
 * http://www.gnu.org/licenses/licenses.html
 * 
 * A copy is also included in the downloadable source code package
 * containing JNA, in file "LGPL2.1".
 * 
 * You may obtain a copy of the Apache License at:
 * 
 * http://www.apache.org/licenses/
 * 
 * A copy is also included in the downloadable source code package
 * containing JNA, in file "AL2.0".
 */
package com.sun.jna.platform.win32.COM;

/*
 * @author L W Ahonen, lwahonen@iki.fi
 */

import com.sun.jna.Function;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Guid.IID;
import com.sun.jna.platform.win32.Guid.REFIID;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.platform.win32.WinNT.HRESULT;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;

public interface IShellFolder {


    /**
     * The interface IID for QueryInterface et al
     */
    public final static IID IID_ISHELLFOLDER = new IID(
            "{000214E6-0000-0000-C000-000000000046}");

    /**
     *
     * Retrieves pointers to the supported interfaces on an object.
     * This method calls IUnknown::AddRef on the pointer it returns.
     *
     * @param riid
     *            The identifier of the interface being requested.
     *
     * @param ppvObject
     *            The address of a pointer variable that receives the interface pointer requested in the riid parameter. Upon successful
     *            return, *ppvObject contains the requested interface pointer to the object. If the object does not support the
     *            interface, *ppvObject is set to NULL.
     *
     * @return
     *            This method returns S_OK if the interface is supported, and E_NOINTERFACE otherwise. If ppvObject is NULL, this method returns E_POINTER.
     *            For any one object, a specific query for the IUnknown interface on any of the object's interfaces must always return the same pointer value.
     *            This enables a client to determine whether two pointers point to the same component by calling QueryInterfacewith IID_IUnknown
     *            and comparing the results. It is specifically not the case that queries for interfaces other than IUnknown (even the same interface
     *            through the same pointer) must return the same pointer value.
     *
     *            There are four requirements for implementations of QueryInterface (In these cases, "must succeed" means "must succeed barring
     *            catastrophic failure."):
     *            The set of interfaces accessible on an object through QueryInterface must be static, not dynamic. This means that if a call
     *            toQueryInterface for a pointer to a specified interface succeeds the first time, it must succeed again, and if it fails
     *            the first time, it must fail on all subsequent queries. 
     *
     *            It must be reflexive: if a client holds a pointer to an interface on an object, and queries for that interface, the call must succeed. 
     *
     *            It must be symmetric: if a client holding a pointer to one interface queries successfully for another, a query through
     *            the obtained pointer for the first interface must succeed. 
     *
     *            It must be transitive: if a client holding a pointer to one interface queries successfully for a second, and through that
     *            pointer queries successfully for a third interface, a query for the first interface through the pointer for the
     *            third interface must succeed. 
     *            Notes to Implementers
     *            Implementations of QueryInterface must never check ACLs. The main reason for this rule is that COM requires that an object supporting a
     *            particular interface always return success when queried for that interface. Another reason is that checking ACLs on QueryInterface
     *            does not provide any real security because any client who has access to a particular interface can hand it directly to another
     *            client without any calls back to the server. Also, because COM caches interface pointers, it does not callQueryInterface on
     *            the server every time a client does a query.
     */
    HRESULT QueryInterface(
            REFIID riid,
            PointerByReference ppvObject);

    /**
     *
     * Increments the reference count for an interface on an object. This method should be called for every new copy of a pointer to an interface on an object.
     * @return
     *            The method returns the new reference count. This value is intended to be used only for test purposes.
     *
     *            Objects use a reference counting mechanism to ensure that the lifetime of the object includes the lifetime of references to it. You use AddRef
     *            to stabilize a copy of an interface pointer. It can also be called when the life of a cloned pointer must extend beyond the
     *            lifetime of the original pointer. The cloned pointer must be released by calling IUnknown::Release.
     *
     *            The internal reference counter that AddRef maintains should be a 32-bit unsigned integer.
     *            Notes to Callers
     *            Call this method for every new copy of an interface pointer that you make. For example, if you are passing a copy of a pointer
     *            back from a method, you must call AddRef on that pointer. You must also call AddRef on a pointer before passing it as an in-out
     *            parameter to a method; the method will call IUnknown::Release before copying the out-value on top of it.
     */
    int AddRef();

    /**
     * Decrements the reference count for an interface on an object.
     *
     * @return
     *            The method returns the new reference count. This value is intended to be used only for test purposes.
     *
     *            When the reference count on an object reaches zero, Release must cause the interface pointer to free itself. When the released
     *            pointer is the only existing reference to an object (whether the object supports single or multiple interfaces), the
     *            implementation must free the object.
     *
     *            Note that aggregation of objects restricts the ability to recover interface pointers.
     *            Notes to Callers
     *            Call this method when you no longer need to use an interface pointer. If you are writing a method that takes an in-out
     *            parameter, call Release on the pointer you are passing in before copying the out-value on top of it.
     */
    int Release();

    /**
     * Translates the display name of a file object or a folder into an item identifier list
     *
     * @param hwnd
     *            A window handle. The client should provide a window handle if it displays a dialog or message box. Otherwise set hwnd to NULL.
     *
     * @param pbc
     *            Optional. A pointer to a bind context used to pass parameters as inputs and outputs to the parsing function. These passed parameters
     *            are often specific to the data source and are documented by the data source owners. For example, the file system data source accepts
     *            the name being parsed (as a WIN32_FIND_DATA structure), using the STR_FILE_SYS_BIND_DATA bind context parameter.
     *            STR_PARSE_PREFER_FOLDER_BROWSING can be passed to indicate that URLs are parsed using the file system data source when possible.
     *            Construct a bind context object using CreateBindCtx and populate the values using IBindCtx::RegisterObjectParam. See Bind Context
     *            String Keys for a complete list of these.
     *
     *            If no data is being passed to or received from the parsing function, this value can be NULL.
     *
     * @param pszDisplayName
     *            A null-terminated Unicode string with the display name. Because each Shell folder defines its own parsing syntax, the
     *            form this string can take may vary. The desktop folder, for instance, accepts paths such as "C:\My Docs\My File.txt".
     *            It also will accept references to items in the namespace that have a GUID associated with them using the "::{GUID}" syntax.
     *            For example, to retrieve a fully qualified identifier list for the control panel from the desktop folder, you can use the following:
     *            "::{CLSID for Control Panel}\::{CLSID for printers folder}"
     *
     * @param pchEaten
     *            A pointer to a ULONG value that receives the number of characters of the display name that was parsed. If your application
     *            does not need this information, set pchEaten to NULL, and no value will be returned.
     *
     * @param ppidl
     *            When this method returns, contains a pointer to the PIDL for the object. The returned item identifier list specifies the item
     *            relative to the parsing folder. If the object associated with pszDisplayName is within the parsing folder, the returned item
     *            identifier list will contain only one SHITEMID structure. If the object is in a subfolder of the parsing folder, the returned
     *            item identifier list will contain multiple SHITEMID structures. If an error occurs, NULL is returned in this address.
     *
     *            When it is no longer needed, it is the responsibility of the caller to free this resource by calling CoTaskMemFree.
     *
     * @param pdwAttributes
     *            The value used to query for file attributes. If not used, it should be set to NULL. To query for one or more attributes, initialize
     *            this parameter with the SFGAO flags that represent the attributes of interest. On return, those attributes that are true and were requested will be set.
     * @return
     *            If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.
     */
    HRESULT ParseDisplayName(
            WinDef.HWND hwnd,
            Pointer pbc,
            String pszDisplayName,
            IntByReference pchEaten,
            PointerByReference ppidl,
            IntByReference pdwAttributes);

    /**
     * Enables a client to determine the contents of a folder by creating an item identifier enumeration object and returning its IEnumIDList interface.
     * The methods supported by that interface can then be used to enumerate the folder's contents.
     *
     * @param hwnd
     *            If user input is required to perform the enumeration, this window handle should be used by the enumeration object as the parent window
     *            to take user input. An example would be a dialog box to ask for a password or prompt the user to insert a CD or floppy disk.
     *            If hwndOwner is set to NULL, the enumerator should not post any messages, and if user input is required, it should silently fail.
     *
     * @param grfFlags
     *            Flags indicating which items to include in the enumeration. For a list of possible values, see the SHCONTF enumerated type.
     *
     * @param ppenumIDList
     *            The address that receives a pointer to the IEnumIDList interface of the enumeration object created by this method.
     *            If an error occurs or no suitable subobjects are found, ppenumIDList is set to NULL.
     *
     * @return
     *            Returns S_OK if successful, or an error value otherwise. Some implementations may also return S_FALSE, indicating that there
     *            are no children matching the grfFlags that were passed in. If S_FALSE is returned, ppenumIDList is set to NULL.
     *
     */
    HRESULT EnumObjects(
            WinDef.HWND hwnd,
            int grfFlags,
            PointerByReference ppenumIDList);

    /**
     *
     *            Retrieves a handler, typically the Shell folder object that implements IShellFolder for a particular item. Optional
     *            parameters that control the construction of the handler are passed in the bind context.
     * @param pidl
     *
     *            The address of an ITEMIDLIST structure (PIDL) that identifies the subfolder. This value can refer to an item at any level below
     *            the parent folder in the namespace hierarchy. The structure contains one or more SHITEMID structures, followed by a terminating NULL.
     *
     * @param pbc
     *
     *            A pointer to an IBindCtx interface on a bind context object that can be used to pass parameters to the construction of the handler.
     *            If this parameter is not used, set it to NULL. Because support for this parameter is optional for folder object implementations,
     *            some folders may not support the use of bind contexts.
     *            Information that can be provided in the bind context includes a BIND_OPTS structure that includes a grfMode member that indicates
     *            the access mode when binding to a stream handler. Other parameters can be set and discovered using IBindCtx::RegisterObjectParam
     *            and IBindCtx::GetObjectParam.
     *
     * @param riid
     *            The identifier of the interface to return. This may be IID_IShellFolder, IID_IStream, or any other interface that identifies a particular handler.
     *
     * @param ppv
     *            When this method returns, contains the address of a pointer to the requested interface. If an error occurs, a NULL pointer is returned at this address.
     *
     * @return
     *            If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.
     *
     */
    HRESULT BindToObject(
            Pointer pidl,
            Pointer pbc,
            REFIID riid,
            PointerByReference ppv);

    /**
     * Requests a pointer to an object's storage interface.
     * @param pidl
     *            The address of an ITEMIDLIST structure that identifies the subfolder relative to its parent folder. The structure must contain exactly one SHITEMID structure followed by a terminating zero.
     *
     * @param pbc
     *            The optional address of an IBindCtx interface on a bind context object to be used during this operation. If this parameter is
     *            not used, set it to NULL. Because support for pbc is optional for folder object implementations, some folders may not support the use of bind contexts.
     *
     * @param riid
     *            The IID of the requested storage interface. To retrieve an IStream, IStorage, or IPropertySetStorage interface pointer, set
     *            riid to IID_IStream, IID_IStorage, or IID_IPropertySetStorage, respectively.
     *
     * @param ppv
     *            The address that receives the interface pointer specified by riid. If an error occurs, a NULL pointer is returned in this address.
     *
     * @return
     *            If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.
     *
     */
    HRESULT BindToStorage(
            Pointer pidl,
            Pointer pbc,
            REFIID riid,
            PointerByReference ppv);

    /**
     * Determines the relative order of two file objects or folders, given their item identifier lists.
     * @param lParam
     *            A value that specifies how the comparison should be performed.
     *            The lower sixteen bits of lParam define the sorting rule. Most applications set the sorting rule to the default value of zero, indicating that the
     *            two items should be compared by name. The system does not define any other sorting rules. Some folder objects might allow calling applications to
     *            use the lower sixteen bits of lParam to specify folder-specific sorting rules. The rules and their associated lParam values are defined by the folder.
     *
     *            When the system folder view object calls IShellFolder::CompareIDs, the lower sixteen bits of lParam are used to specify the column to be used for
     *            the comparison.
     *            The upper sixteen bits of lParam are used for flags that modify the sorting rule. The system currently defines these modifier flags.
     *
     *            SHCIDS_ALLFIELDS
     *            Version 5.0. Compare all the information contained in the ITEMIDLIST structure, not just the display names. This flag is valid only for folder objects that support
     *            the IShellFolder2 interface. For instance, if the two items are files, the folder should compare their names, sizes, file times, attributes, and any other information
     *            in the structures. If this flag is set, the lower sixteen bits of lParam must be zero.
     *
     *            SHCIDS_CANONICALONLY
     *            Version 5.0. When comparing by name, compare the system names but not the display names. When this flag is passed, the two items are compared by whatever criteria the
     *            Shell folder determines are most efficient, as long as it implements a consistent sort function. This flag is useful when comparing for equality or when the results of
     *            the sort are not displayed to the user. This flag cannot be combined with other flags.
     *
     * @param pidl1
     *            A pointer to the first item's ITEMIDLIST structure. It will be relative to the folder. This ITEMIDLIST structure can contain more than one
     *            element; therefore, the entire structure must be compared, not just the first element.
     *
     * @param pidl2
     *            A pointer to the second item's ITEMIDLIST structure. It will be relative to the folder. This ITEMIDLIST structure can contain more than one
     *            element; therefore, the entire structure must be compared, not just the first element.
     *
     * @return
     *            If this method is successful, the CODE field of the HRESULT contains one of the following values. For information regarding the extraction of
     *            the CODE field from the returned HRESULT, see Remarks. If this method is unsuccessful, it returns a COM error code.
     *            Negative
     *            A negative return value indicates that the first item should precede the second (pidl1 < pidl2).
     *            Positive
     *            A positive return value indicates that the first item should follow the second (pidl1 > pidl2).
     *            Zero
     *            A return value of zero indicates that the two items are the same (pidl1 = pidl2).
     *            Use the HRESULT_CODE macro to extract the CODE field from the HRESULT, then cast the result as a short.
     *            #define HRESULT_CODE(hr)    ((hr) & 0xFFFF)
     *
     */
    HRESULT CompareIDs(
            WinDef.LPARAM lParam,
            Pointer pidl1,
            Pointer pidl2);


    /**
     * Requests an object that can be used to obtain information from or interact with a folder object.
     *
     * @param hwndOwner
     *            A handle to the owner window. If you have implemented a custom folder view object, your folder view window should be created as a child of hwndOwner.
     *
     * @param riid
     *            A reference to the IID of the interface to retrieve through ppv, typically IID_IShellView.
     *
     * @param ppv
     *            When this method returns successfully, contains the interface pointer requested in riid. This is typically IShellView. See the Remarks section for more details.
     *
     * @return
     *            If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.
     *
     */
    HRESULT CreateViewObject(
            WinDef.HWND hwndOwner,
            REFIID riid,
            PointerByReference ppv);

    /**
     * Gets the attributes of one or more file or folder objects contained in the object represented by IShellFolder.
     *
     * @param cidl
     *            The number of items from which to retrieve attributes.
     *
     * @param apidl
     *            The address of an array of pointers to ITEMIDLIST structures, each of which uniquely identifies an item relative to the parent folder.
     *            Each ITEMIDLIST structure must contain exactly one SHITEMID structure followed by a terminating zero.
     *
     * @param rgfInOut
     *            Pointer to a single ULONG value that, on entry, contains the bitwise SFGAO attributes that the calling application is requesting. On
     *            exit, this value contains the requested attributes that are common to all of the specified items.
     *
     * @return
     *            If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.
     *
     */
    HRESULT GetAttributesOf(
            int cidl,
            Pointer apidl,
            IntByReference rgfInOut);

    /**
     * If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.
     *
     * @param hwndOwner
     *            A handle to the owner window that the client should specify if it displays a dialog box or message box.
     *
     * @param cidl
     *            The number of file objects or subfolders specified in the apidl parameter.
     *
     * @param apidl
     *            The address of an array of pointers to ITEMIDLIST structures, each of which uniquely identifies a file object or subfolder relative to
     *            the parent folder. Each item identifier list must contain exactly one SHITEMID structure followed by a terminating zero.
     *
     * @param riid
     *            A reference to the IID of the interface to retrieve through ppv. This can be any valid interface identifier that can be created for an
     *            item. The most common identifiers used by the Shell are listed in the comments at the end of this reference.
     *
     * @param rgfReserved
     *            Reserved.
     *
     * @param ppv
     *            When this method returns successfully, contains the interface pointer requested in riid.
     *
     * @return
     *            If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.
     *
     */
    HRESULT GetUIObjectOf(
            WinDef.HWND hwndOwner,
            int cidl,
            Pointer apidl,
            REFIID riid,
            IntByReference rgfReserved,
            PointerByReference ppv);

    /**
     * Retrieves the display name for the specified file object or subfolder.
     *
     * @param pidl
     *            PIDL that uniquely identifies the file object or subfolder relative to the parent folder.
     *
     * @param flags
     *            Flags used to request the type of display name to return. For a list of possible values, see the SHGDNF enumerated type.
     *
     * @param pName
     *            When this method returns, contains a pointer to a STRRET structure in which to return the display name. The type of name returned in this structure can be the requested type, but the Shell folder might return a different type.
     *
     * @return
     *            If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.
     *            It is the caller's responsibility to free resources allocated by this function.
     *
     */
    HRESULT GetDisplayNameOf(
            Pointer pidl,
            int flags,
            PointerByReference pName);

    /**
     * Sets the display name of a file object or subfolder, changing the item identifier in the process.
     *
     * @param hwnd
     *            A handle to the owner window of any dialog or message box that the client displays.
     *
     * @param pidl
     *            A pointer to an ITEMIDLIST structure that uniquely identifies the file object or subfolder relative to the parent folder.
     *            The structure must contain exactly one SHITEMID structure followed by a terminating zero.
     *
     * @param pszName
     *            A pointer to a null-terminated string that specifies the new display name.
     *
     * @param uFlags
     *            Flags that indicate the type of name specified by the pszName parameter. For a list of possible values and combinations of values, see SHGDNF.
     *
     * @param ppidlOut
     *            Optional. If specified, the address of a pointer to an ITEMIDLIST structure that receives the ITEMIDLIST of the renamed item. The
     *            caller requests this value by passing a non-null ppidlOut. Implementations of IShellFolder::SetNameOf must return a pointer to the
     *            new ITEMIDLIST in the ppidlOut parameter.
     *
     * @return
     *            If this method succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code.
     *
     */
    HRESULT SetNameOf(
            WinDef.HWND hwnd,
            Pointer pidl,
            String pszName,
            int uFlags,
            PointerByReference ppidlOut);

    /*
    Use this like:
    PointerByReference pbr=new PointerByReference();
    HRESULT result=SomeCOMObject.QueryInterface(IID_ISHELLFOLDER, pbr);
    if(COMUtils.SUCCEEDED(result)) IShellFolder isf=IShellFolder.Converter.PointerToIShellFolder(pbr);
     */

    public static class Converter
    {
        public static IShellFolder PointerToIShellFolder(final PointerByReference ptr)
        {
            final Pointer interfacePointer = ptr.getValue();
            final Pointer vTablePointer = interfacePointer.getPointer(0);
            final Pointer[] vTable = new Pointer[13];
            vTablePointer.read(0, vTable, 0, 13);
            return new IShellFolder() {

                @Override
                public WinNT.HRESULT QueryInterface(REFIID byValue, PointerByReference pointerByReference) {
                    Function f = Function.getFunction(vTable[0], Function.ALT_CONVENTION);
                    return new WinNT.HRESULT(f.invokeInt(new Object[]{interfacePointer, byValue, pointerByReference}));
                }

                @Override
                public int AddRef() {
                    Function f = Function.getFunction(vTable[1], Function.ALT_CONVENTION);
                    return f.invokeInt(new Object[]{interfacePointer});
                }

                public int Release() {
                    Function f = Function.getFunction(vTable[2], Function.ALT_CONVENTION);
                    return f.invokeInt(new Object[]{interfacePointer});
                }

                @Override
                public WinNT.HRESULT ParseDisplayName(WinDef.HWND hwnd, Pointer pbc, String pszDisplayName, IntByReference pchEaten, PointerByReference ppidl, IntByReference pdwAttributes) {
                    Function f = Function.getFunction(vTable[3], Function.ALT_CONVENTION);
                    return new WinNT.HRESULT(f.invokeInt(new Object[]{interfacePointer, hwnd, pbc, pszDisplayName, pchEaten, ppidl, pdwAttributes}));
                }

                @Override
                public WinNT.HRESULT EnumObjects(WinDef.HWND hwnd, int grfFlags, PointerByReference ppenumIDList) {
                    Function f = Function.getFunction(vTable[4], Function.ALT_CONVENTION);
                    return new WinNT.HRESULT( f.invokeInt(new Object[]{interfacePointer, hwnd, grfFlags, ppenumIDList}));
                }

                public WinNT.HRESULT BindToObject(Pointer pidl, Pointer pbc, REFIID riid, PointerByReference ppv) {
                    Function f = Function.getFunction(vTable[5], Function.ALT_CONVENTION);
                    return new WinNT.HRESULT( f.invokeInt(new Object[]{interfacePointer, pidl, pbc, riid, ppv}));
                }

                @Override
                public HRESULT BindToStorage(Pointer pidl, Pointer pbc, REFIID riid, PointerByReference ppv) {
                    Function f = Function.getFunction(vTable[6], Function.ALT_CONVENTION);
                    return new WinNT.HRESULT( f.invokeInt(new Object[]{interfacePointer, pidl, pbc, riid, ppv}));
                }

                @Override
                public HRESULT CompareIDs(WinDef.LPARAM lParam, Pointer pidl1, Pointer pidl2) {
                    Function f = Function.getFunction(vTable[7], Function.ALT_CONVENTION);
                    return new WinNT.HRESULT( f.invokeInt(new Object[]{interfacePointer, lParam, pidl1, pidl2}));
                }

                @Override
                public HRESULT CreateViewObject(WinDef.HWND hwndOwner, REFIID riid, PointerByReference ppv) {
                    Function f = Function.getFunction(vTable[8], Function.ALT_CONVENTION);
                    return new WinNT.HRESULT( f.invokeInt(new Object[]{interfacePointer, hwndOwner, riid, ppv}));
                }

                @Override
                public HRESULT GetAttributesOf(int cidl, Pointer apidl, IntByReference rgfInOut) {
                    Function f = Function.getFunction(vTable[9], Function.ALT_CONVENTION);
                    return new WinNT.HRESULT( f.invokeInt(new Object[]{interfacePointer, cidl, apidl, rgfInOut}));
                }

                @Override
                public HRESULT GetUIObjectOf(WinDef.HWND hwndOwner, int cidl, Pointer apidl, REFIID riid, IntByReference rgfReserved, PointerByReference ppv) {
                    Function f = Function.getFunction(vTable[10], Function.ALT_CONVENTION);
                    return new WinNT.HRESULT( f.invokeInt(new Object[]{interfacePointer, hwndOwner, cidl, apidl, riid, rgfReserved, ppv}));
                }

                public WinNT.HRESULT GetDisplayNameOf(Pointer pidl, int flags, PointerByReference pName){
                    Function f = Function.getFunction(vTable[11], Function.ALT_CONVENTION);
                    return new WinNT.HRESULT( f.invokeInt(new Object[]{interfacePointer, pidl, flags, pName}));
                }

                @Override
                public HRESULT SetNameOf(WinDef.HWND hwnd, Pointer pidl, String pszName, int uFlags, PointerByReference ppidlOut) {
                    Function f = Function.getFunction(vTable[12], Function.ALT_CONVENTION);
                    return new WinNT.HRESULT( f.invokeInt(new Object[]{interfacePointer, hwnd, pidl, pszName, uFlags, ppidlOut}));
                }
            };
        }
    }
}
