FunctionSample.java

/*
** Module   : FunctionSample.java
** Abstract : sample for user defined function execution in the Progress
**            expression evaluator
**
** Copyright (c) 2004-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- -T- --JPRM-- ----------------Description-----------------
** 001 GES 20041116 NEW   @18716 First version showing examples of:
**                               - function w 1 String parm, int return
**                               - function w null parms, int return
**                               - static function w 1 String parm, int
**                                 return
**                               - int variable
**                               - command line test driver
** 002 GES 20070319 CHG   @32499 Reworked code to match newer expression
**                               processing implementation.
** 003 GBB 20230512              Logging methods replaced by CentralLogger/ConversionStatus.
*/ 
/*
** 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.uast;

import com.goldencode.p2j.convert.*;
import com.goldencode.p2j.util.*;

import java.util.logging.*;

/**
 * Provides an example of how to implement user-defined functions and 
 * variables while using the standard Progress expression evaluator on a
 * given Progress 4GL expression. 
 * <p>
 * This sample shows that all necessary code to implement user-defined
 * functions can be implemented in a single class if desired. The 
 * implementation can also be split into multiple files.
 * <p>
 * Steps in the process of implementing user-defined functions:
 * <ul>
 *    <li> Create a static or instance method that implements the function
 *         required. In particular, the parameters and return value must
 *         match one of the supported data types (which map from Java to 
 *         Progress 4GL):
 *       <ul>
 *          <li> <code>String</code>
 *          <li> <code>int</code>
 *          <li> <code>double</code>
 *          <li> <code>boolean</code>
 *       </ul>
 *    <li> In the class that is going to call the 
 *         <code>{@link ExpressionEvaluator#evaluateExpr}</code>
 *         method: 
 *       <ol>
 *          <li> Instantiate a <code>ExpressionEvaluator</code>.
 *          <li> Instantiate a <code>CallbackResolver</code>.
 *          <li> If your method is not static, instantiate the class that
 *               contains the callback method.
 *          <li> Instantiate a <code>Keyword</code> object with the text
 *               that matches the Progress function name being implemented.
 *          <li> Ensure that the class that instantiates the
 *               <code>Keyword</code> objects also implements the 
 *               <code>ProgressParserTokenTypes</code> Interface. This is
 *               required to provide access to the token types constant
 *               definitions.
 *          <li> Call <code>{@link CallbackResolver#addCallback}</code> to
 *               add the definition of this callback to the resolver.
 *          <li> Invoke <code>evaluateExpr</code> with the expression,
 *               the <code>CallbackResolver</code> and a boolean debug flag.
 *       </ol>
 *    <li> Voila!  At this point you can use the returned result.
 * </ul>
 * Some general notes:
 * <ol>
 *    <li> The method name does not have to match the Progress 4GL function
 *         name. This is because it is the <code>Keyword</code> object that
 *         defines the matching criteria by which the Progress function name
 *         is matched to a token type.  This token type is the type of the
 *         return data and the expression evaluator triggers all of its
 *         processing off of this.  Since the same <code>Keyword</code>
 *         object is the mechanism by which the <code>CallbackResolver</code>
 *         looks up the corresponding <code>Callback</code> which defines
 *         the name of the class and method, the two names are properly
 *         associated but do not need to be the same.
 *    <li> Variables can be implemented using a function returning the
 *         right data type and taking no parameters.  In addition, when you
 *         instantiate the <code>Keyword</code> object, you must set the
 *         token type to the correct value (e.g. <code>VAR_INT</code> instead
 *         of <code>FUNC_INT</code>.
 *    <li> The signature of the method that is found must match with the
 *         signature found in the Progress 4GL function call. The parameters
 *         are dynamically examined and converted to Java equivalents. Then
 *         the array of corresponding class objects (in the exact order in
 *         which the parameters were positioned in the Progress function
 *         call) is used in the J2SE reflection APIs to lookup the method. 
 *         In the case of variables, it is the same except there are no
 *         parameters.
 *    <li> If you see a <code>NoSuchMethodException</code>, check the
 *         following:
 *       <ul>
 *          <li> The class name is fully qualified (includes the package) 
 *               and each component is properly separated by '.' characters.
 *          <li> The class name is correct.
 *          <li> The method name is correct.
 *          <li> The type and order of parameters in the Progress 4GL
 *               expression exactly match those of the callback method.
 *          <li> The method MUST be public!
 *       </ul>
 * </ol>
 * <p>
 * This program provides a command line interface.  See {@link #main} for the
 * syntax. 
 *
 * @author GES
 */
public class FunctionSample
implements ProgressParserTokenTypes
{
   /** Logger. */
   private static final ConversionStatus LOG = ConversionStatus.get(FunctionSample.class);
   
   /**
    * Example private variable whose value will be returned as a user-defined
    * variable.
    */
   private int myVar = 100;
   
   /**
    * Example user-defined function taking a single String parameter and 
    * returning an integer.
    *
    * @param    name
    *           String passed as a parameter in the Progress 4GL expression. 
    * @return   The length of the string.
    */
   public int myDefinedFunction(String name)
   {
      return name.length();
   }   
   
   /**
    * Example user-defined function taking a no parameters and returning an
    * integer.
    *
    * @return   Example integer return data.
    */
   public int myNullParmDefinedFunction()
   {
      return 0;
   }   
   
   /**
    * Example user-defined static function taking a single String parameter 
    * and returning an integer.
    *
    * @param    name
    *           String passed as a parameter in the Progress 4GL expression. 
    * @return   The length of the string.
    */
   public static int myStaticDefinedFunction(String name)
   {
      return name.length();
   }  
   
   /**
    * Example user-defined function that implements a variable by taking no 
    * parameters and returning an integer.
    *
    * @return   The value of the variable.
    */
   public int myVariable()
   {
      return myVar;
   }   
   
   /**
    * Provides a command line interface for an end user to drive and/or test
    * the FunctionSample class.
    * <p>
    * This method is also the primary example of how to setup and evaluate
    * a Progress 4GL expression with user-defined functions and variables.
    * <p>
    * Syntax:
    * <pre>
    *   java.exe FunctionSample &quot;quoted_expression&quot; [debug]
    * </pre>
    * Where:
    * <ul>
    *   <li> &quot;quoted_expression&quot; is a valid Progress 4GL 
    *        expression that is surrounded by double quotes. Note: if strings
    *        must be specified as part of the expression, specify those
    *        strings using the single quote character. 
    * </ul>
    *
    * @param   args 
    *          List of command line arguments.
    */
   public static void main(String[] args) 
   {
      boolean debug  = false;
      String  syntax = "Syntax: java.exe FunctionSample " +
                       "<quoted_expression> [debug]"; 
      
      if (args.length < 1)
      {
         LOG.log(Level.SEVERE, syntax);
         System.exit(-1);
      }
      
      if (args.length == 2)
      {
         debug = true;
      }
      
      try
      {
         CallbackResolver cr = new CallbackResolver();
         FunctionSample   fs = new FunctionSample();
         
         Keyword defined1 = new Keyword("defined",  0, FUNC_INT, false);
         Keyword defined2 = new Keyword("defined2", 0, FUNC_INT, false);         
         Keyword defined3 = new Keyword("defined3", 0, FUNC_INT, false);
         Keyword myvar    = new Keyword("myVar",    0, VAR_INT,  false);
         
         cr.addCallback(defined1,
                        fs,
                        "com.goldencode.p2j.uast.FunctionSample",
                        "myDefinedFunction");
         cr.addCallback(defined2,
                        fs,
                        "com.goldencode.p2j.uast.FunctionSample",
                        "myNullParmDefinedFunction");
         cr.addCallback(defined3,
                        null,
                        "com.goldencode.p2j.uast.FunctionSample",
                        "myStaticDefinedFunction");
         cr.addCallback(myvar,
                        fs,
                        "com.goldencode.p2j.uast.FunctionSample",
                        "myVariable");
          
         BaseDataType result = ExpressionEvaluator.evaluateExpr(args[0],
                                                                cr,
                                                                debug);
         System.out.println("Result = " + result);         
      }
      
      catch (Exception exc)
      {
         LOG.log(Level.SEVERE, "", exc);
      }
   }
}