docs: @SuppressWarnings("unchecked")
      public static MVELAnalysisResult analyzeExpression(final PackageBuildContext context,
                                                         final String expr,
                                                         final BoundIdentifiers availableIdentifiers,
                                                         final Map<String, Class< ? >> localTypes,
                                                         String contextIdentifier,
                                                         Class kcontextClass) {
          if ( expr.trim().length() <= 0 ) {
              MVELAnalysisResult result = analyze( (Set<String>) Collections.EMPTY_SET, availableIdentifiers );
              result.setMvelVariables( new HashMap<String, Class< ? >>() );
              result.setTypesafe( true );
              return result;
          }
  
          MVEL.COMPILER_OPT_ALLOW_NAKED_METH_CALL = true;
          MVEL.COMPILER_OPT_ALLOW_OVERRIDE_ALL_PROPHANDLING = true;
          MVEL.COMPILER_OPT_ALLOW_RESOLVE_INNERCLASSES_WITH_DOTNOTATION = true;
          MVEL.COMPILER_OPT_SUPPORT_JAVA_STYLE_CLASS_LITERALS = true;
  
          MVELDialect dialect = (MVELDialect) context.getDialect( "mvel" );
  
          ParserConfiguration conf = context.getMVELDialectRuntimeData().getParserConfiguration();
  
          conf.setClassLoader( context.getKnowledgeBuilder().getRootClassLoader() );
  
          // first compilation is for verification only
          // @todo proper source file name
          final ParserContext parserContext1 = new ParserContext( conf );
          if ( localTypes != null ) {
              for ( Entry entry : localTypes.entrySet() ) {
                  parserContext1.addInput( (String) entry.getKey(),
                                           (Class) entry.getValue() );
              }
          }
          if ( availableIdentifiers.getThisClass() != null ) {
              parserContext1.addInput( "this",
                                       availableIdentifiers.getThisClass() );
          }
  
          if ( availableIdentifiers.getOperators() != null ) {
              for ( Entry<String, EvaluatorWrapper> opEntry : availableIdentifiers.getOperators().entrySet() ) {
                  parserContext1.addInput( opEntry.getKey(), opEntry.getValue().getClass() );
              }
          }
  
          parserContext1.setStrictTypeEnforcement( false );
          parserContext1.setStrongTyping( false );
          parserContext1.setInterceptors( dialect.getInterceptors() );
          Class< ? > returnType;
  
          try {
              returnType = MVEL.analyze( expr,
                                         parserContext1 );
          } catch ( Exception e ) {
              BaseDescr base = (context instanceof RuleBuildContext) ? ((RuleBuildContext)context).getRuleDescr() : context.getParentDescr();
              if ( e instanceof CompileException && e.getCause() != null && e.getMessage().startsWith( "[Error: null]" )) {
                  // rewrite error message in cause original message is null
                  e = new CompileException(e.getCause().toString(), ( (CompileException) e ).getExpr(), ( (CompileException) e ).getCursor(), e.getCause());
              }
              DialectUtil.copyErrorLocation(e, context.getParentDescr());
              context.addError( new DescrBuildError( base,
                                                     context.getParentDescr(),
                                                     null,
                                                     "Unable to Analyse Expression " + expr + ":\n" + e.getMessage() ) );
              return null;
          }
  
          Set<String> requiredInputs = new HashSet<String>();
          requiredInputs.addAll( parserContext1.getInputs().keySet() );
          HashMap<String, Class< ? >> variables = (HashMap<String, Class< ? >>) ((Map) parserContext1.getVariables());
          if ( localTypes != null ) {
              for ( String str : localTypes.keySet() ) {
                  // we have to do this due to mvel regressions on detecting true local vars
                  variables.remove( str );
              }
          }
  
          // MVEL includes direct fields of context object in non-strict mode. so we need to strip those
          if ( availableIdentifiers.getThisClass() != null ) {
              requiredInputs.removeIf( s -> PropertyTools.getFieldOrAccessor( availableIdentifiers.getThisClass(), s ) != null );
          }
  
          // now, set the required input types and compile again
          final ParserContext parserContext2 = new ParserContext( conf );
          parserContext2.setStrictTypeEnforcement( true );
          parserContext2.setStrongTyping( true );
          parserContext2.setInterceptors( dialect.getInterceptors() );
  
          for ( String input : requiredInputs ) {
              if ("this".equals( input )) {
                  continue;
              }
              if (WM_ARGUMENT.equals( input )) {
                  parserContext2.addInput( input, InternalWorkingMemory.class );
                  continue;
              }
  
              Class< ? > cls = availableIdentifiers.resolveType( input );
              if ( cls == null ) {
                  if ( input.equals( contextIdentifier ) || input.equals( "kcontext" ) ) {
                      cls = kcontextClass;
                  } else if ( input.equals( "rule" ) ) {
                      cls = Rule.class;
                  } else if ( localTypes != null ) {
                      cls = localTypes.get( input );
                  }
              }
              if ( cls != null ) {
                  parserContext2.addInput( input, cls );
              }
          }
  
          if ( availableIdentifiers.getThisClass() != null ) {
              parserContext2.addInput( "this", availableIdentifiers.getThisClass() );
          }
  
          boolean typesafe = context.isTypesafe();
  
          try {
              returnType = MVEL.analyze( expr,
                                         parserContext2 );
              typesafe = true;
          } catch ( Exception e ) {
              // is this an error, or can we fall back to non-typesafe mode?
              if ( typesafe ) {
                  BaseDescr base = (context instanceof RuleBuildContext) ? ((RuleBuildContext)context).getRuleDescr() : context.getParentDescr();
                  DialectUtil.copyErrorLocation(e, context.getParentDescr());
                  context.addError( new DescrBuildError( base,
                                                         context.getParentDescr(),
                                                         null,
                                                         "Unable to Analyse Expression " + expr + ":\n" + e.getMessage() ) );
                  return null;
              }
          }
  
          if ( typesafe ) {
              requiredInputs = new HashSet<String>();
              requiredInputs.addAll( parserContext2.getInputs().keySet() );
              requiredInputs.addAll( variables.keySet() );
              variables = (HashMap<String, Class< ? >>) ((Map) parserContext2.getVariables());
              if ( localTypes != null ) {
                  for ( String str : localTypes.keySet() ) {
                      // we have to do this due to mvel regressions on detecting true local vars
                      variables.remove( str );
                  }
              }
          }
  
          MVELAnalysisResult result = analyze( requiredInputs, availableIdentifiers );
          result.setReturnType( returnType );
          result.setMvelVariables( variables );
          result.setTypesafe( typesafe );
          return result;
      }