MsgBlaster.java

/*
 ** Module   : MsgBlaster.java
 ** Abstract : An implementation of the MsgBlaster FWD extension.
 **
 ** Copyright (c) 2019-2023, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
 ** 001 OM  20190222 First commit.
 ** 002 HC  20190829 Improvements to life time management.
 ** 003 CA  20231026 The resource registry must be context-local.
 */

/*
 ** This program is free software: you can redistribute it and/or modify
 ** it under the terms of the GNU Affero General Public License as
 ** published by the Free Software Foundation, either version 3 of the
 ** License, or (at your option) any later version.
 **
 ** This program 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 Affero General Public License for more details.
 **
 ** You may find a copy of the GNU Affero GPL version 3 at the following
 ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
 **
 ** Additional terms under GNU Affero GPL version 3 section 7:
 **
 **   Under Section 7 of the GNU Affero GPL version 3, the following additional
 **   terms apply to the works covered under the License.  These additional terms
 **   are non-permissive additional terms allowed under Section 7 of the GNU
 **   Affero GPL version 3 and may not be removed by you.
 **
 **   0. Attribution Requirement.
 **
 **     You must preserve all legal notices or author attributions in the covered
 **     work or Appropriate Legal Notices displayed by works containing the covered
 **     work.  You may not remove from the covered work any author or developer
 **     credit already included within the covered work.
 **
 **   1. No License To Use Trademarks.
 **
 **     This license does not grant any license or rights to use the trademarks
 **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
 **     of Golden Code Development Corporation. You are not authorized to use the
 **     name Golden Code, FWD, or the names of any author or contributor, for
 **     publicity purposes without written authorization.
 **
 **   2. No Misrepresentation of Affiliation.
 **
 **     You may not represent yourself as Golden Code Development Corporation or FWD.
 **
 **     You may not represent yourself for publicity purposes as associated with
 **     Golden Code Development Corporation, FWD, or any author or contributor to
 **     the covered work, without written authorization.
 **
 **   3. No Misrepresentation of Source or Origin.
 **
 **     You may not represent the covered work as solely your work.  All modified
 **     versions of the covered work must be marked in a reasonable way to make it
 **     clear that the modified work is not originating from Golden Code Development
 **     Corporation or FWD.  All modified versions must contain the notices of
 **     attribution required in this license.
 */

package com.goldencode.p2j.extension;

import com.goldencode.p2j.comauto.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.ui.*;
import com.goldencode.p2j.util.*;

import java.util.*;

/**
 * Encapsulates the Java logic for FWD implementation of MsgBlaster32 COM object.
 */
public class MsgBlaster
extends ComObject
{
   /** Stores all known {@code MsgBlaster} instances mapped by their ids. */
   private static final ContextLocal<Map<Long, MsgBlaster>> repository = 
   new ContextLocal<Map<Long, MsgBlaster>>()
   {
      protected Map<Long,MsgBlaster> initialValue()
      {
         return new HashMap<>();
      };
   };

   /** The target hWnd. */
   private Integer hWndTarget = null;
   
   /**
    * Internal data structure.
    * <p>
    * The events that will be captured for the target widget and delivered as COM events.
    * <p>
    * I found a List Of Windows Messages at https://wiki.winehq.org/List_Of_Windows_Messages.
    */
   private final Map<Integer, Integer> msgList = new HashMap<>();
   
   /**
    * Internal data structure.
    * <p>
    * The management of the event after capturing:
    * <ul>
    *    <li>-1 - this event will go back to windows
    *    <li>0 - this event does NOT go back to windows
    * </ul>
    */
   private final Map<Integer, Integer> msgPassage = new HashMap<>();
   
   /**
    * Flags if this object has been yet destroyed and is no longer valid. A {@code MsgBlaster} object
    * is valid throughout it lifetime until it is disposed using {@link #delete()}.
    */
   private boolean invalid = false;

   /**
    * Obtain a specific instance of {@code MsgBlaster} using its {@code id}. If such instance 
    * was not found (never created or already deleted) {@code null} is returned.
    * 
    * @param   id
    *          The {@code id} of the {@code MsgBlaster} to be returned.
    * 
    * @return  The {@code MsgBlaster} with the requested {@code id} or {@code null} if not found.
    */
   public static MsgBlaster getInstanceById(long id) 
   {
      return repository.get().get(id);
   }
   
   /**
    * The constructor. After letting the super class initialize internal data, the object is put
    * in the lookup map for further access by its id. 
    */
   public MsgBlaster()
   {
      Long myId = comhandle.resourceId(this);
      repository.get().put(myId, this);
   }
   
   /**
    * Obtain the Activex name of this COM object.
    *
    * @return  the Activex name of this COM object.
    */
   @Override
   public String getActivexName()
   {
      return "Msgblst32";
   }

   /**
    * Checks whether this object is valid.
    * 
    * @return  {@code true} if this object is valid.
    */
   @Override
   public boolean valid()
   {
      return !invalid;
   }

   /**
    * Perform actual delete of all resources. At the time of this call, it is assumed the resource
    * is valid for deletion (the handle and the resource are both valid).
    */
   @Override
   public void delete()
   {
      if (!valid())
      {
         // already released
         return;
      }

      invalid = true;

      super.delete();

      hWndTarget = null;
      msgList.clear();
      msgPassage.clear();
      
      repository.get().remove(id());
      
      LogicalTerminal.getClient().messageBlasterClearMessages(id(), true);
   }
   
   /**
    * Obtain the {@code hwnd} of the target frame.
    * 
    * @return  the {@code hwnd} of the target frame.
    */
   @ComProperty(name = "hWndTarget")
   public integer getHWndTarget()
   {
      if (!valid())
      {
         return new integer();
      }
      
      return new integer(hWndTarget);
   }
   
   /**
    * Sets the {@code hwnd} of the target frame.
    *
    * @param   newTarget
    *          the new {@code hwnd} of the target frame.
    */
   @ComProperty(name = "hWndTarget", setter = true)
   public void sethWndTarget(integer newTarget)
   {
      if (!valid() || Objects.equals(hWndTarget, newTarget.toJavaIntegerType()))
      {
         return;
      }
      
      hWndTarget = newTarget.toJavaIntegerType();
      
      LogicalTerminal.getClient().messageBlasterSetTarget(id(), hWndTarget);
   }
   
   /**
    * The getter for {@code MsgList} indexed property.
    * 
    * @param   index
    *          The index of the property.
    *
    * @return  The value of the property at requested index.
    */
   @ComProperty(name = "MsgList", indexed = true)
   public integer getMsgList(int index)
   {
      if (!valid())
      {
         return new integer();
      }
      
      return new integer(msgList.get(index));
   }
   
   /**
    * The setter for {@code MsgList} indexed property.
    *
    * @param   val
    *          The value to be set for the property at requested index.
    * @param   index
    *          The index of the property.
    */
   @ComProperty(name = "MsgList", setter = true, indexed = true)
   public void setMsgList(integer val, int index)
   {
      if (!valid())
      {
         return;
      }
      
      msgList.put(index, val.toJavaIntegerType());
   }
   
   /**
    * The getter for {@code MsgPassage} indexed property.
    *
    * @param   index
    *          The index of the property.
    *
    * @return  The value of the property at requested index.
    */
   @ComProperty(name = "MsgPassage", indexed = true)
   public integer getMsgPassage(int index)
   {
      if (!valid())
      {
         return new integer();
      }
      
      return new integer(msgPassage.get(index));
   }
   
   /**
    * The setter for {@code MsgPassage} indexed property.
    * <p>
    * Possible values (extracted from comments in customer's code):
    * <ul>
    *    <li>-1 = Windows has message digested first;
    *    <li>0 = Windows does nothing with message. 
    * </ul>
    *
    * @param   pass
    *          The value to be set for the property at requested index.
    * @param   index
    *          The index of the property.
    */
   @ComProperty(name = "MsgPassage", setter = true, indexed = true)
   public void setMsgPassage(integer pass, int index)
   {
      if (!valid())
      {
         return;
      }
      
      if (index >= 0)
      {
         Integer intVal = pass.toJavaIntegerType();
         msgPassage.put(index, intVal);
         
         Integer msgList = this.msgList.get(index);
         LogicalTerminal.getClient().messageBlasterAddMessage(id(), index, msgList, intVal);
      }
      else
      {
         this.msgList.clear();
         this.msgPassage.clear();
         LogicalTerminal.getClient().messageBlasterClearMessages(id(), false);
      }
   }
   
   /**
    * Incoming notification from client-side. 
    * 
    * @param   msgVal
    *          The message id. Should be one already added to {@code msgList}. 
    * @param   wParam
    *          Message parameter 1.
    * @param   lParam
    *          Message parameter 2.
    * @param   lplRetVal
    *          Message parameter / return value.
    */
   public void incomingMessage(int msgVal, int wParam, int lParam, /*in-out*/ int lplRetVal)
   {
      if (!valid() || hWndTarget == null || !ComServer.checkValid(this))
      {
         return;
      }
      
      if (LogicalTerminal.getWidgetForId(hWndTarget) == null)
      {
         return;
      }
      
      integer _lplRetVal = new integer(lplRetVal);
      ComEvent event = new ComEvent(this, "message", 
            new integer(msgVal), new integer(wParam), new integer(lParam), _lplRetVal);
      ComServer.emit(event, true);
   }
   
   /**Test method. Will be removed after debugging. */
   @ComMethod(name="fire")
   public void fire()
   {
      integer pMsgVal = new integer(512); 
      integer pWParam = new integer(777); 
      integer pLParam = new integer(212 | (199 << 16)); 
      integer pLplRetVal = new integer(0);
      ComServer.emit(new ComEvent(this, "message", pMsgVal, pWParam, pLParam, pLplRetVal), false);
   }
}