LocationCacheAspect.java

/*
** Module   : LocationCacheAspect.java
** Abstract : An aspect which intercepts APIs related to screen location cache.
**
** Copyright (c) 2018-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------Description----------------------------------
** 001 CA  20180208 Created initial version.
** 002 CA  20180209 A fix - iajc compiler requires absolute names in the aspect definition. 
** 003 HC  20240806 Added support for displaying frame widget in an overlay window.
*/ 
/*
** 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.aspects.ui;

import org.aspectj.lang.*;
import org.aspectj.lang.annotation.*;

import com.goldencode.p2j.ui.client.*;
import com.goldencode.p2j.ui.client.widget.*;

/**
 * This aspects takes care of intercepting the APIs which require to either cache the screen
 * location values or disable the caching for them.  This includes:
 * <ul>
 *    <li>disabling and/or reseting the cached values when layout is performed.</li>
 *    <li>caching these values (as appropriate) when the {@link Widget#screenLocation()} and
 *    {@link Widget#screenPhysicalLocation()} APIs are executed.</li>
 * </ul>
 */
@Aspect()
public class LocationCacheAspect
{
   /** 
    * Tracks the depth of layout-related nested API calls.  When is zero, it will force the
    * {@link AbstractWidget#reflectLocationChanged() cached values to be reset.}
    */
   private static int layoutDepth = 0;
   
   /**
    * Executed around the {@link com.goldencode.p2j.ui.client.layout.LayoutManager} APIs, keeping
    * track of the nested depth via {@link #layoutDepth}.
    * <p>
    * Before executing the API, it will reset any cached location values.
    *   
    * @param    thisJoinPoint
    *           A join point reference.
    * @param    container
    *           The container parameter instance.
    *          
    * @return   The value of the join point method.
    *   
    * @throws  Throwable
    *          Any exceptions are untouched propagated out. 
    */
   @Around("args(container) " +
           " && execution(public void com.goldencode.p2j.ui.client.layout.LayoutManager+.*(..))")
   public Object disableLocationCacheForLayout(ProceedingJoinPoint thisJoinPoint, 
                                               Container<?> container)
   throws Throwable
   {
      AbstractWidget<?> widget = (AbstractWidget<?>) container;
      
      if (layoutDepth == 0)
      {
         // ensure nothing is cached
         widget.reflectLocationChanged();
      }

      layoutDepth += 1;
      
      try
      {
         return thisJoinPoint.proceed();
      }
      finally
      {
         // no need to reflectLocationChange, as nothing gets cached while the depth is non-zero
         layoutDepth -= 1;
      }
   }

   /**
    * Executed around the {@link Container#doLayout()} and related APIs, keeping track of the 
    * nested depth via {@link #layoutDepth}.
    * <p>
    * Before executing the API, it will reset any cached location values.
    *   
    * @param    thisJoinPoint
    *           A join point reference.
    *          
    * @return   The value of the join point method.
    *   
    * @throws  Throwable
    *          Any exceptions are untouched propagated out. 
    */
   @Around("execution(public void com.goldencode.p2j.ui.client.widget.Container+.doLayout()) " +
           " || execution(protected void com.goldencode.p2j.ui.client.Frame+.doLayoutWorker(boolean))")
   public Object disableLocationCacheInContainer(ProceedingJoinPoint thisJoinPoint)
   throws Throwable
   {
      AbstractWidget<?> widget = (AbstractWidget<?>) thisJoinPoint.getThis();

      if (layoutDepth == 0)
      {
         // ensure nothing is cached
         widget.reflectLocationChanged();
      }

      layoutDepth += 1;

      try
      {
         return thisJoinPoint.proceed();
      }
      finally
      {
         // no need to reflectLocationChange, as nothing gets cached while the depth is non-zero
         layoutDepth -= 1;
      }
   }
   
   /**
    * Executed around the {@link Widget#screenLocation()} API.  If the widget has an already 
    * cached value and the {@link #layoutDepth} is zero, it will use it.  Otherwise, it will
    * compute it and cache it (if {@link #layoutDepth} is zero).
    * 
    * @param    thisJoinPoint
    *           A join point reference.
    *          
    * @return   The value of the join point method.
    *   
    * @throws  Throwable
    *          Any exceptions are untouched propagated out. 
    */
   @Around("execution(public com.goldencode.p2j.ui.client.Point " + 
           "com.goldencode.p2j.ui.client.widget.Widget+.screenLocation())")
   public Object cacheScreenLocation(ProceedingJoinPoint thisJoinPoint)
   throws Throwable
   {
      AbstractWidget<?> widget = (AbstractWidget<?>) thisJoinPoint.getThis();

      if (layoutDepth == 0)
      {
         Point cached = widget.getCachedScreenLocation();
         if (cached != null)
         {
            return cached;
         }
      }
      
      Point location = (Point) thisJoinPoint.proceed();
      
      if (layoutDepth == 0)
      {
         widget.setCachedScreenLocation(location);
      }
      
      return location;
   }
   
   /**
    * Executed around the {@link Widget#screenPhysicalLocation()} API.  If the widget has an already 
    * cached value and the {@link #layoutDepth} is zero, it will use it.  Otherwise, it will
    * compute it and cache it (if {@link #layoutDepth} is zero).
    * 
    * @param    thisJoinPoint
    *           A join point reference.
    *          
    * @return   The value of the join point method.
    *   
    * @throws  Throwable
    *          Any exceptions are untouched propagated out. 
    */
   @Around("execution(public com.goldencode.p2j.ui.client.NativePoint " +
           "com.goldencode.p2j.ui.client.widget.Widget+.screenPhysicalLocation())")
   public Object cacheScreenPhysicalLocation(ProceedingJoinPoint thisJoinPoint)
   throws Throwable
   {
      AbstractWidget<?> widget = (AbstractWidget<?>) thisJoinPoint.getThis();
      
      if (layoutDepth == 0)
      {
         NativePoint cached = widget.getCachedScreenPhysicalLocation();
         if (cached != null)
         {
            return cached;
         }
      }
      
      NativePoint location = (NativePoint) thisJoinPoint.proceed();
      
      if (layoutDepth == 0)
      {
         widget.setCachedScreenPhysicalLocation(location);
      }
      
      return location;
   }
}