text:
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

//----------------------------------------------------------
// CommandLine.cpp - tiny very specific command line parser
//----------------------------------------------------------

#include "standardpch.h"
#include "lightweightmap.h"
#include "commandline.h"
#include "superpmi.h"
#include "mclist.h"
#include "methodcontext.h"
#include "logging.h"

// NOTE: this is parsed by parallelsuperpmi.cpp::ProcessChildStdOut() to determine if an incorrect
// argument usage error has occurred.
const char* const g_SuperPMIUsageFirstLine = "SuperPMI is a JIT compiler testing tool.";

void CommandLine::DumpHelp(const char* program)
{
    printf("%s\n", g_SuperPMIUsageFirstLine);
    printf("\n");
    printf("Usage: %s [options] jitname [jitname2] filename.mc\n", program);
    printf(" jitname" PLATFORM_SHARED_LIB_SUFFIX_A " - path of jit to be tested\n");
    printf(" jitname2" PLATFORM_SHARED_LIB_SUFFIX_A " - optional path of second jit to be tested\n");
    printf(" filename.mc - load method contexts from filename.mc\n");
    printf(" -j[it] Name - optionally -jit can be used to specify jits\n");
    printf(" -l[oad] filename - optionally -load can be used to specify method contexts\n");
    printf("\n");
    printf("Options:\n");
    printf("\n");
    printf(" -boe\n");
    printf("     Break on error return from compileMethod\n");
    printf("\n");
    printf(" -boa\n");
    printf("     Break on assert from the JIT\n");
    printf("\n");
    printf(" -v[erbosity] messagetypes\n");
    printf("     Controls which types of messages SuperPMI logs. Specify a string of\n");
    printf("     characters representing message categories to enable, where:\n");
    printf("         e - errors (internal fatal errors that are non-recoverable)\n");
    printf("         w - warnings (internal conditions that are unusual, but not serious)\n");
    printf("         m - missing (failures due to missing JIT-EE interface details)\n");
    printf("         i - issues (issues found with the JIT, e.g. asm diffs, asserts)\n");
    printf("         n - information (notifications/summaries, e.g. 'Loaded 5  Jitted 4  FailedCompile 1')\n");
    printf("         v - verbose (status messages, e.g. 'Jit startup took '151.12ms')\n");
    printf("         d - debug (lots of detailed output)\n");
    printf("         a - all (enable all message types; overrides other enable message types)\n");
    printf("         q - quiet (disable all output; overrides all others)\n");
    printf("     e.g. '-v ew' only writes error and warning messages to the console.\n");
    printf("     'q' takes precedence over any other message type specified.\n");
    printf("     Default set of messages enabled is 'ewminv'.\n");
    printf("\n");
    printf(" -w[riteLogFile] logfile\n");
    printf("     Write log messages to the specified file.\n");
    printf("\n");
    printf(" -c[ompile] <indices>\n");
    printf("     Compile only those method contexts whose indices are specified.\n");
    printf("     Indices can be either a single index, comma separated values,\n");
    printf("     a range, or the name of a .MCL file with newline delimited indices.\n");
    printf("     e.g. -compile 20\n");
    printf("     e.g. -compile 20,25,30,32\n");
    printf("     e.g. -compile 10-99\n");
    printf("     e.g. -compile 5,10-99,101,201-300\n");
    printf("     e.g. -compile failed.mcl\n");
    printf("\n");
    printf(" -m[atchHash] <MD5 Hash>\n");
    printf("     Compile only method context with specific MD5 hash\n");
    printf("\n");
    printf(" -e[mitMethodStats] <stats-types>\n");
    printf("     Emit method statistics in CSV format to filename.mc.stats.\n");
    printf("     Specify a string of characters representing statistics to emit, where:\n");
    printf("         i - method IL code size\n");
    printf("         a - method compiled ASM code size\n");
    printf("         h - method hash to uniquely identify a method across MCH files\n");
    printf("         n - method number inside the source MCH\n");
    printf("         t - method throughput time\n");
    printf("         * - all available method stats\n");
    printf("\n");
    printf(" -a[pplyDiff]\n");
    printf("     Compare the compile result generated from the provided JIT with the\n");
    printf("     compile result stored with the MC. If two JITs are provided, this\n");
    printf("     compares the compile results generated by the two JITs.\n");
    printf("\n");
    printf(" -r[eproName] prefix\n");
    printf("     Write out failing methods to prefix-n.mc\n");
    printf("\n");
    printf(" -f[ailingMCList] mclfilename\n");
    printf("     Write out failing methods to mclfilename.\n");
    printf("     If using -applyDiff and no -diffMCList is specified,\n");
    printf("     comparison failures also get written to mclfilename.\n");
    printf("\n");
    printf(" -diffMCList diffMCLfilename\n");
    printf("     Write out methods that differ between compilations to diffMCLfilename.\n");
    printf("     This only works with -applyDiff.\n");
    printf("\n");
    printf(" -p[arallel] [workerCount]\n");
    printf("     Run in parallel mode by spawning 'workerCount' processes to do processing.\n");
    printf("     If 'workerCount' is not specified, the number of workers used is\n");
    printf("     the number of processors on the machine.\n");
    printf("\n");
    printf(" -skipCleanup\n");
    printf("     Skip deletion of temporary files created by child SuperPMI processes with -parallel.\n");
    printf("\n");
    printf(" -target <target>\n");
    printf("     Used by the assembly differences calculator. This specifies the target\n");
    printf("     architecture for cross-compilation. Currently allowed <target> value: arm64\n");
    printf("\n");
    printf(" -coredistools\n");
    printf("     Use disassembly tools from the CoreDisTools library\n");
#if defined(USE_MSVCDIS)
    printf("     Default: use MSVCDIS.\n");
#elif defined(USE_COREDISTOOLS)
    printf("     Ignored: MSVCDIS is not available, so CoreDisTools will be used.\n");
#else
    printf("     Ignored: neither MSVCDIS nor CoreDisTools is available.\n");
#endif // USE_COREDISTOOLS
    printf("\n");
    printf(" -jitoption [force] key=value\n");
    printf("     Set the JIT option named \"key\" to \"value\" for JIT 1 if the option was not set.");
    printf("     With optional force flag overwrites the existing value if it was already set. NOTE: do not use a "
           "\"COMPlus_\" prefix!\n");
    printf("\n");
    printf(" -jit2option [force] key=value\n");
    printf("     Set the JIT option named \"key\" to \"value\" for JIT 2 if the option was not set.");
    printf("     With optional force flag overwrites the existing value if it was already set. NOTE: do not use a "
           "\"COMPlus_\" prefix!\n");
    printf("\n");
    printf("Inputs are case sensitive.\n");
    printf("\n");
    printf("SuperPMI method contexts are stored in files with extension .MC, implying\n");
    printf("a single method context, or .MCH, implying a set of method contexts. Either\n");
    printf("extension works equivalently.\n");
    printf("\n");
    printf("Exit codes:\n");
    printf("0  : success\n");
    printf("-1 : general fatal error (e.g., failed to initialize, failed to read files)\n");
    printf("-2 : JIT failed to initialize\n");
    printf("1  : there were compilation failures\n");
    printf("2  : there were assembly diffs\n");
    printf("\n");
    printf("Examples:\n");
    printf(" %s " MAKEDLLNAME_A("clrjit") " test.mch\n", program);
    printf("     ; compile all functions in test.mch using " MAKEDLLNAME_A("clrjit") "\n");
    printf(" %s -p " MAKEDLLNAME_A("clrjit") " test.mch\n", program);
    printf("     ; same as above, but use all available processors to compile in parallel\n");
    printf(" %s -f fail.mcl " MAKEDLLNAME_A("clrjit") " test.mch\n", program);
    printf("     ; if there are any failures, record their MC numbers in the file fail.mcl\n");
}

static bool ParseJitOption(const char* optionString, wchar_t** key, wchar_t** value)
{
    char tempKey[1024];

    unsigned i;
    for (i = 0; optionString[i] != '='; i++)
    {
        if ((i >= 1023) || (optionString[i] == '\0'))
        {
            return false;
        }
        tempKey[i] = optionString[i];
    }
    tempKey[i] = '\0';

    const char* tempVal = &optionString[i + 1];

    const unsigned keyLen = i;
    wchar_t*       keyBuf = new wchar_t[keyLen + 1];
    MultiByteToWideChar(CP_UTF8, 0, tempKey, keyLen + 1, keyBuf, keyLen + 1);

    const unsigned valLen = (unsigned)strlen(tempVal);
    wchar_t*       valBuf = new wchar_t[valLen + 1];
    MultiByteToWideChar(CP_UTF8, 0, tempVal, valLen + 1, valBuf, valLen + 1);

    *key   = keyBuf;
    *value = valBuf;
    return true;
}

// Assumption: All inputs are initialized to default or real value.  we'll just set the stuff we see on the command
// line. Assumption: Single byte names are passed in.. mb stuff doesnt cause an obvious problem... but it might have
// issues... Assumption: Values larger than 2^31 aren't expressible from the commandline.... (atoi) Unless you pass in
// negatives.. :-|
bool CommandLine::Parse(int argc, char* argv[], /* OUT */ Options* o)
{
    size_t argLen    = 0;
    size_t tempLen   = 0;
    bool   foundJit  = false;
    bool   foundFile = false;

    if (argc == 1) // Print help when no args are passed
    {
        DumpHelp(argv[0]);
        return false;
    }

    for (int i = 1; i < argc; i++)
    {
        bool isASwitch = (argv[i][0] == '-');
#ifndef FEATURE_PAL
        if (argv[i][0] == '/') // Also accept "/" on Windows
        {
            isASwitch = true;
        }
#endif // !FEATURE_PAL

        // Process a switch
        if (isASwitch)
        {
            argLen = strlen(argv[i]);

            if (argLen > 1)
                argLen--; // adjust for leading switch
            else
            {
                DumpHelp(argv[0]);
                return false;
            }

            if ((_strnicmp(&argv[i][1], "help", argLen) == 0) || (_strnicmp(&argv[i][1], "HELP", argLen) == 0) ||
                (_strnicmp(&argv[i][1], "?", argLen) == 0))
            {
                DumpHelp(argv[0]);
                return false;
            }
            else if ((_strnicmp(&argv[i][1], "load", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

            processMethodContext:

                tempLen = strlen(argv[i]);
                if (tempLen == 0)
                {
                    LogError("Arg '%s' is invalid, name of file missing.", argv[i]);
                    DumpHelp(argv[0]);
                    return false;
                }
                o->nameOfInputMethodContextFile = new char[tempLen + 1];
                strcpy_s(o->nameOfInputMethodContextFile, tempLen + 1, argv[i]);
                foundFile = true;
            }
            else if ((_strnicmp(&argv[i][1], "jit", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

            processJit:

                tempLen = strlen(argv[i]);
                if (tempLen == 0)
                {
                    LogError("Arg '%s' is invalid, name of jit missing.", argv[i]);
                    DumpHelp(argv[0]);
                    return false;
                }
                char* tempStr = new char[tempLen + 1];
                strcpy_s(tempStr, tempLen + 1, argv[i]);
                if (!foundJit)
                {
                    o->nameOfJit = tempStr;
                    foundJit     = true;
                }
                else
                {
                    o->nameOfJit2 = tempStr;
                }
            }
            else if ((_strnicmp(&argv[i][1], "reproName", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                tempLen = strlen(argv[i]);
                if (tempLen == 0)
                {
                    LogError("Arg '%s' is invalid, name of prefix missing.", argv[i]);
                    DumpHelp(argv[0]);
                    return false;
                }
                char* tempStr = new char[tempLen + 1];
                strcpy_s(tempStr, tempLen + 1, argv[i]);
                o->reproName = tempStr;
            }
            else if ((_strnicmp(&argv[i][1], "failingMCList", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                o->mclFilename = argv[i];
            }
            else if ((_strnicmp(&argv[i][1], "diffMCList", 10) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                o->diffMCLFilename = argv[i];
            }
            else if ((_strnicmp(&argv[i][1], "target", 6) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                o->targetArchitecture = argv[i];
            }
            else if ((_strnicmp(&argv[i][1], "boe", 3) == 0))
            {
                o->breakOnError = true;
            }
            else if ((_strnicmp(&argv[i][1], "boa", 3) == 0))
            {
                o->breakOnAssert = true;
            }
            else if ((_strnicmp(&argv[i][1], "verbosity", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                Logger::SetLogLevel(Logger::ParseLogLevelString(argv[i]));
            }
            else if ((_strnicmp(&argv[i][1], "writeLogFile", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                o->writeLogFile = argv[i];
                Logger::OpenLogFile(argv[i]);
            }
            else if ((_strnicmp(&argv[i][1], "emitMethodStats", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                o->methodStatsTypes = argv[i];
            }
            else if ((_strnicmp(&argv[i][1], "applyDiff", argLen) == 0))
            {
                o->applyDiff = true;
            }
            else if ((_strnicmp(&argv[i][1], "compile", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                bool isValidList = MCList::processArgAsMCL(argv[i], &o->indexCount, &o->indexes);
                if (!isValidList)
                {
                    LogError("Arg '%s' is invalid, needed method context list.", argv[i]);
                    DumpHelp(argv[0]);
                    return false;
                }
                if (o->hash != nullptr)
                {
                    LogError("Cannot use both method context list and method context hash.");
                    DumpHelp(argv[0]);
                    return false;
                }
                if (o->offset > 0 && o->increment > 0)
                {
                    LogError("Cannot use method context list in parallel mode.");
                    DumpHelp(argv[0]);
                    return false;
                }

                o->compileList = argv[i]; // Save this in case we need it for -parallel.
            }
            else if ((_strnicmp(&argv[i][1], "coredistools", argLen) == 0))
            {
#ifndef USE_COREDISTOOLS // If USE_COREDISTOOLS is not defined, then allow the switch, but ignore it.
                o->useCoreDisTools = true;
#endif // USE_COREDISTOOLS
            }
            else if ((_strnicmp(&argv[i][1], "matchHash", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                if (strlen(argv[i]) != (MD5_HASH_BUFFER_SIZE - 1))
                {
                    LogError("Arg '%s' is invalid, needed a valid method context hash.", argv[i]);
                    DumpHelp(argv[0]);
                    return false;
                }
                if (o->indexCount > 0)
                {
                    LogError("Cannot use both method context list and method context hash.");
                    DumpHelp(argv[0]);
                    return false;
                }
                if (o->offset > 0 && o->increment > 0)
                {
                    LogError("Cannot use method context hash in parallel mode.");
                    DumpHelp(argv[0]);
                    return false;
                }
                o->hash = argv[i];
            }
            else if ((_strnicmp(&argv[i][1], "parallel", argLen) == 0))
            {
                o->parallel = true;

                // Is there another argument?
                if (i + 1 < argc)
                {
                    // If so, does it look like a worker count?
                    bool   isWorkerCount = true;
                    size_t nextlen       = strlen(argv[i + 1]);
                    for (size_t j = 0; j < nextlen; j++)
                    {
                        if (!isdigit(argv[i + 1][j]))
                        {
                            isWorkerCount =
                                false; // Doesn't look like a worker count; bail out and let someone else handle it.
                            break;
                        }
                    }
                    if (isWorkerCount)
                    {
                        ++i;
                        o->workerCount = atoi(argv[i]);

                        if (o->workerCount < 1)
                        {
                            LogError("Invalid workers count specified, workers count must be at least 1.");
                            DumpHelp(argv[0]);
                            return false;
                        }
                        if (o->workerCount > MAXIMUM_WAIT_OBJECTS)
                        {
                            LogError("Invalid workers count specified, workers count cannot be more than %d.",
                                     MAXIMUM_WAIT_OBJECTS);
                            DumpHelp(argv[0]);
                            return false;
                        }
                    }
                }
            }
            else if ((_stricmp(&argv[i][1], "skipCleanup") == 0))
            {
                o->skipCleanup = true;
            }
            else if ((_strnicmp(&argv[i][1], "stride", argLen) == 0))
            {
                // "-stride" is an internal switch used by -parallel. Usage is:
                //
                // -stride offset increment
                //
                // It compiles methods in this series until end-of-file:
                //      offset, offset+increment, offset+2*increment, offset+3*increment, ...

                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                o->offset = atoi(argv[i]);

                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                o->increment = atoi(argv[i]);

                if (o->offset < 1 || o->increment < 1)
                {
                    LogError(
                        "Incorrect offset/increment specified for -stride. Offset and increment both must be > 0.");
                    DumpHelp(argv[0]);
                    return false;
                }
                if (o->indexCount > 0)
                {
                    LogError("Cannot use method context list in parallel mode.");
                    DumpHelp(argv[0]);
                    return false;
                }
                if (o->hash != nullptr)
                {
                    LogError("Cannot use method context hash in parallel mode.");
                    DumpHelp(argv[0]);
                    return false;
                }
            }
            else if (_strnicmp(&argv[i][1], "jitoption", argLen) == 0)
            {
                i++;
                if (!AddJitOption(i, argc, argv, &o->jitOptions, &o->forceJitOptions))
                {
                    return false;
                }
            }
            else if (_strnicmp(&argv[i][1], "jit2option", argLen) == 0)
            {
                i++;
                if (!AddJitOption(i, argc, argv, &o->jit2Options, &o->forceJit2Options))
                {
                    return false;
                }
            }
            else
            {
                LogError("Unknown switch '%s' passed as argument.", argv[i]);
                DumpHelp(argv[0]);
                return false;
            }
        }
        // Process an input filename
        // String comparisons on file extensions must be case-insensitive since we run on Windows
        else
        {
            char* lastdot = strrchr(argv[i], '.');
            if (lastdot == nullptr)
            {
                DumpHelp(argv[0]);
                return false;
            }

            if (_stricmp(lastdot, PLATFORM_SHARED_LIB_SUFFIX_A) == 0)
                goto processJit;
            else if (_stricmp(lastdot, ".mc") == 0)
                goto processMethodContext;
            else if (_stricmp(lastdot, ".mch") == 0)
                goto processMethodContext;
            else if (_stricmp(lastdot, ".mct") == 0)
                goto processMethodContext;
            else
            {
                LogError("Unknown file type passed as argument, '%s'.", argv[i]);
                DumpHelp(argv[0]);
                return false;
            }
        }
    }

    // Do some argument validation.

    if (o->nameOfJit == nullptr)
    {
        LogError("Missing name of a Jit.");
        DumpHelp(argv[0]);
        return false;
    }
    if (o->nameOfInputMethodContextFile == nullptr)
    {
        LogError("Missing name of an input file.");
        DumpHelp(argv[0]);
        return false;
    }
    if (o->diffMCLFilename != nullptr && !o->applyDiff)
    {
        LogError("-diffMCList specified without -applyDiff.");
        DumpHelp(argv[0]);
        return false;
    }
    if (o->targetArchitecture != nullptr && (0 != _stricmp(o->targetArchitecture, "arm64")))
    {
        LogError("Illegal target architecture specified with -target (only arm64 is supported).");
        DumpHelp(argv[0]);
        return false;
    }
    if (o->skipCleanup && !o->parallel)
    {
        LogError("-skipCleanup requires -parallel.");
        DumpHelp(argv[0]);
        return false;
    }
    return true;
}

//-------------------------------------------------------------
// AddJitOption: Parse the value that was passed with -jitOption flag.
//
// Arguments:
//    currArgument     - current argument number. Points to the token next to -jitOption. After the function
//                       points to the last parsed token
//    argc             - number of all arguments
//    argv             - arguments as array of char*
//    pJitOptions      - a jit options map, that sets the option, if it was not already set.
//    pForceJitOptions - a jit options map, that forces the option even if it was already set.
//
// Returns:
//    False if an error occurred, true if the option was parsed and added.
bool CommandLine::AddJitOption(int&  currArgument,
                               int   argc,
                               char* argv[],
                               LightWeightMap<DWORD, DWORD>** pJitOptions,
                               LightWeightMap<DWORD, DWORD>** pForceJitOptions)
{
    if (currArgument >= argc)
    {
        DumpHelp(argv[0]);
        return false;
    }

    LightWeightMap<DWORD, DWORD>* targetjitOptions = nullptr;

    if (_strnicmp(argv[currArgument], "force", strlen(argv[currArgument])) == 0)
    {
        if (*pForceJitOptions == nullptr)
        {
            *pForceJitOptions = new LightWeightMap<DWORD, DWORD>();
        }
        targetjitOptions = *pForceJitOptions;
        currArgument++;
    }
    else
    {
        if (*pJitOptions == nullptr)
        {
            *pJitOptions = new LightWeightMap<DWORD, DWORD>();
        }
        targetjitOptions = *pJitOptions;
    }

    wchar_t* key;
    wchar_t* value;
    if ((currArgument >= argc) || !ParseJitOption(argv[currArgument], &key, &value))
    {
        DumpHelp(argv[0]);
        return false;
    }

    DWORD keyIndex =
        (DWORD)targetjitOptions->AddBuffer((unsigned char*)key, sizeof(wchar_t) * ((unsigned int)wcslen(key) + 1));
    DWORD valueIndex =
        (DWORD)targetjitOptions->AddBuffer((unsigned char*)value, sizeof(wchar_t) * ((unsigned int)wcslen(value) + 1));
    targetjitOptions->Add(keyIndex, valueIndex);

    delete[] key;
    delete[] value;
    return true;
}


text:
require 'github_api'

module PullRequestTester

  # figure out if a certain pull request can be tested.
  # Pull requests can only be tested if they have a comment
  # that contains the speficied expected_body in a PR comment
  # created by one of the approved admin.
  #
  # Parameters:
  #   pr::
  #     pull request object to be verified.
  #   admin_users::
  #     array of users who can approve pull requests for testing
  #   expected_body::
  #     expected body of a message that means a PR can be tested.
  def testable_pull_request?(
    pr,
    admin_users,
    expected_body = 'test_it',
    options       = {}
  )
    if ! pr['merged']
      if pr['mergeable']
        if pr['comments'] > 0
          comments = ::Github.new(options).issues.comments.all(
            pr['base']['user']['login'],
            pr['base']['repo']['name'],
            pr['number']
          )
          #puts 'going through comments'
          comments.each do |comment|
            if admin_users.include?(comment['user']['login'])
              if comment['body'] == expected_body
                return true
              end
            end
            puts "Did not find comment matching #{expected_body} for #{pr['number']}"
          end
        else
          puts "PR: #{pr['number']} from #{pr['base']['repo']['name']} has no issue commments.\
          I will not test it. We only test things approved."
        end
      else
        puts "PR: #{pr['number']} from #{pr['base']['repo']['name']} cannot be merged, will not test"
      end
    else
      puts "PR: #{pr['number']} from #{pr['base']['repo']['name']} was already merged, will not test"
    end
    return false
  end

  # publish a string as a gist.
  # publish a link to that gist as a issue comment.
  def publish_results(
    project_name,
    number,
    outcome,
    body,
    options,
    account     = 'puppetlabs',
    repo_prefix = 'puppetlabs-'
  )
    require 'github_api'
    github = ::Github.new(options)
    gist_response = github.gists.create(
      'description' => "#{project_name}/#{number}@#{Time.now.strftime("%Y%m%dT%H%M%S%z")}",
      'public'      => true,
      'files' => {
        'file1' => {'content' => body}
      }
    )
    comments = github.issues.comments.create(
      account,
      "#{repo_prefix}#{project_name}",
      number,
      'body' => "Test #{outcome}. Results can be found here: #{gist_response.html_url}"
    )
  end

  # scans a list of github repos, looking for open, mergable pull requests where one of the
  # 'admin' users has added a comment that contains the 'test_message'.
  def testable_pull_requests(
    project_names,
    admin_users,
    github_login,
    github_password,
    test_message = 'schedule_for_testing',
    account      = 'puppetlabs',
    repo_prefix  = 'puppetlabs-'
  )
    testable_pull_requests = {}
    project_names.each do |repo_name|
      options = { :login => github_login, :password => github_password }
      prs = ::Github.new(options).pull_requests.list(account, "#{repo_prefix}#{repo_name}")
      prs.each do |pr|
        # the data structure of pr returned from list (above) appears to be in a different format
        # than this get call, therefor, I need to get the number, and make this extra call.
        # this seems to justify my experience so far that this github_api plugin may not be worth using.
        number = pr['number']
        pr = ::Github.new(options).pull_requests.get(account, "#{repo_prefix}#{repo_name}", number)
        # I know this is lazy to do b/c it means every pull request will be validated twice based
        # on the current workflow with jenkins (where this will populate parameterized builds
        # that also check if the pull request is valid
        if testable_pull_request?(pr, admin_users + Array(github_login), test_message, options)
          if testable_pull_requests[repo_name]
            testable_pull_requests[repo_name].push(number)
          else
            testable_pull_requests[repo_name] = [number]
          end
        end
      end
    end
    testable_pull_requests
  end
end

redpajama_set_name: RedPajamaGithub


text:
package cache

import (
	"context"
	"sort"
	"sync"
	"time"

	"github.com/containerd/containerd/content"
	"github.com/containerd/containerd/diff"
	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/filters"
	"github.com/containerd/containerd/gc"
	"github.com/containerd/containerd/leases"
	"github.com/docker/docker/pkg/idtools"
	"github.com/moby/buildkit/cache/metadata"
	"github.com/moby/buildkit/client"
	"github.com/moby/buildkit/identity"
	"github.com/moby/buildkit/session"
	"github.com/moby/buildkit/snapshot"
	"github.com/moby/buildkit/util/flightcontrol"
	digest "github.com/opencontainers/go-digest"
	imagespecidentity "github.com/opencontainers/image-spec/identity"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"golang.org/x/sync/errgroup"
)

var (
	ErrLocked   = errors.New("locked")
	errNotFound = errors.New("not found")
	errInvalid  = errors.New("invalid")
)

type ManagerOpt struct {
	Snapshotter     snapshot.Snapshotter
	MetadataStore   *metadata.Store
	ContentStore    content.Store
	LeaseManager    leases.Manager
	PruneRefChecker ExternalRefCheckerFunc
	GarbageCollect  func(ctx context.Context) (gc.Stats, error)
	Applier         diff.Applier
	Differ          diff.Comparer
}

type Accessor interface {
	GetByBlob(ctx context.Context, desc ocispec.Descriptor, parent ImmutableRef, opts ...RefOption) (ImmutableRef, error)
	Get(ctx context.Context, id string, opts ...RefOption) (ImmutableRef, error)

	New(ctx context.Context, parent ImmutableRef, s session.Group, opts ...RefOption) (MutableRef, error)
	GetMutable(ctx context.Context, id string, opts ...RefOption) (MutableRef, error) // Rebase?
	IdentityMapping() *idtools.IdentityMapping
	Metadata(string) *metadata.StorageItem
}

type Controller interface {
	DiskUsage(ctx context.Context, info client.DiskUsageInfo) ([]*client.UsageInfo, error)
	Prune(ctx context.Context, ch chan client.UsageInfo, info ...client.PruneInfo) error
}

type Manager interface {
	Accessor
	Controller
	Close() error
}

type ExternalRefCheckerFunc func() (ExternalRefChecker, error)

type ExternalRefChecker interface {
	Exists(string, []digest.Digest) bool
}

type cacheManager struct {
	records map[string]*cacheRecord
	mu      sync.Mutex
	ManagerOpt
	md *metadata.Store

	muPrune sync.Mutex // make sure parallel prune is not allowed so there will not be inconsistent results
	unlazyG flightcontrol.Group
}

func NewManager(opt ManagerOpt) (Manager, error) {
	cm := &cacheManager{
		ManagerOpt: opt,
		md:         opt.MetadataStore,
		records:    make(map[string]*cacheRecord),
	}

	if err := cm.init(context.TODO()); err != nil {
		return nil, err
	}

	// cm.scheduleGC(5 * time.Minute)

	return cm, nil
}

func (cm *cacheManager) GetByBlob(ctx context.Context, desc ocispec.Descriptor, parent ImmutableRef, opts ...RefOption) (ir ImmutableRef, rerr error) {
	diffID, err := diffIDFromDescriptor(desc)
	if err != nil {
		return nil, err
	}
	chainID := diffID
	blobChainID := imagespecidentity.ChainID([]digest.Digest{desc.Digest, diffID})

	descHandlers := descHandlersOf(opts...)
	if desc.Digest != "" && (descHandlers == nil || descHandlers[desc.Digest] == nil) {
		if _, err := cm.ContentStore.Info(ctx, desc.Digest); errors.Is(err, errdefs.ErrNotFound) {
			return nil, NeedsRemoteProvidersError([]digest.Digest{desc.Digest})
		} else if err != nil {
			return nil, err
		}
	}

	var p *immutableRef
	var parentID string
	if parent != nil {
		pInfo := parent.Info()
		if pInfo.ChainID == "" || pInfo.BlobChainID == "" {
			return nil, errors.Errorf("failed to get ref by blob on non-addressable parent")
		}
		chainID = imagespecidentity.ChainID([]digest.Digest{pInfo.ChainID, chainID})
		blobChainID = imagespecidentity.ChainID([]digest.Digest{pInfo.BlobChainID, blobChainID})

		p2, err := cm.Get(ctx, parent.ID(), NoUpdateLastUsed, descHandlers)
		if err != nil {
			return nil, err
		}
		if err := p2.Finalize(ctx, true); err != nil {
			return nil, err
		}
		parentID = p2.ID()
		p = p2.(*immutableRef)
	}

	releaseParent := false
	defer func() {
		if releaseParent || rerr != nil && p != nil {
			p.Release(context.TODO())
		}
	}()

	cm.mu.Lock()
	defer cm.mu.Unlock()

	sis, err := cm.MetadataStore.Search("blobchainid:" + blobChainID.String())
	if err != nil {
		return nil, err
	}

	for _, si := range sis {
		ref, err := cm.get(ctx, si.ID(), opts...)
		if err != nil && !IsNotFound(err) {
			return nil, errors.Wrapf(err, "failed to get record %s by blobchainid", sis[0].ID())
		}
		if ref == nil {
			continue
		}
		if p != nil {
			releaseParent = true
		}
		if err := setImageRefMetadata(ref, opts...); err != nil {
			return nil, errors.Wrapf(err, "failed to append image ref metadata to ref %s", ref.ID())
		}
		return ref, nil
	}

	sis, err = cm.MetadataStore.Search("chainid:" + chainID.String())
	if err != nil {
		return nil, err
	}

	var link ImmutableRef
	for _, si := range sis {
		ref, err := cm.get(ctx, si.ID(), opts...)
		if err != nil && !IsNotFound(err) {
			return nil, errors.Wrapf(err, "failed to get record %s by chainid", sis[0].ID())
		}
		if ref != nil {
			link = ref
			break
		}
	}

	id := identity.NewID()
	snapshotID := chainID.String()
	blobOnly := true
	if link != nil {
		snapshotID = getSnapshotID(link.Metadata())
		blobOnly = getBlobOnly(link.Metadata())
		go link.Release(context.TODO())
	}

	l, err := cm.ManagerOpt.LeaseManager.Create(ctx, func(l *leases.Lease) error {
		l.ID = id
		l.Labels = map[string]string{
			"containerd.io/gc.flat": time.Now().UTC().Format(time.RFC3339Nano),
		}
		return nil
	})
	if err != nil {
		return nil, errors.Wrap(err, "failed to create lease")
	}

	defer func() {
		if rerr != nil {
			if err := cm.ManagerOpt.LeaseManager.Delete(context.TODO(), leases.Lease{
				ID: l.ID,
			}); err != nil {
				logrus.Errorf("failed to remove lease: %+v", err)
			}
		}
	}()

	if err := cm.ManagerOpt.LeaseManager.AddResource(ctx, l, leases.Resource{
		ID:   snapshotID,
		Type: "snapshots/" + cm.ManagerOpt.Snapshotter.Name(),
	}); err != nil {
		return nil, errors.Wrapf(err, "failed to add snapshot %s to lease", id)
	}

	if desc.Digest != "" {
		if err := cm.ManagerOpt.LeaseManager.AddResource(ctx, leases.Lease{ID: id}, leases.Resource{
			ID:   desc.Digest.String(),
			Type: "content",
		}); err != nil {
			return nil, errors.Wrapf(err, "failed to add blob %s to lease", id)
		}
	}

	md, _ := cm.md.Get(id)

	rec := &cacheRecord{
		mu:     &sync.Mutex{},
		cm:     cm,
		refs:   make(map[ref]struct{}),
		parent: p,
		md:     md,
	}

	if err := initializeMetadata(rec, parentID, opts...); err != nil {
		return nil, err
	}

	if err := setImageRefMetadata(rec, opts...); err != nil {
		return nil, errors.Wrapf(err, "failed to append image ref metadata to ref %s", rec.ID())
	}

	queueDiffID(rec.md, diffID.String())
	queueBlob(rec.md, desc.Digest.String())
	queueChainID(rec.md, chainID.String())
	queueBlobChainID(rec.md, blobChainID.String())
	queueSnapshotID(rec.md, snapshotID)
	queueBlobOnly(rec.md, blobOnly)
	queueMediaType(rec.md, desc.MediaType)
	queueBlobSize(rec.md, desc.Size)
	queueCommitted(rec.md)

	if err := rec.md.Commit(); err != nil {
		return nil, err
	}

	cm.records[id] = rec

	return rec.ref(true, descHandlers), nil
}

// init loads all snapshots from metadata state and tries to load the records
// from the snapshotter. If snaphot can't be found, metadata is deleted as well.
func (cm *cacheManager) init(ctx context.Context) error {
	items, err := cm.md.All()
	if err != nil {
		return err
	}

	for _, si := range items {
		if _, err := cm.getRecord(ctx, si.ID()); err != nil {
			logrus.Debugf("could not load snapshot %s: %+v", si.ID(), err)
			cm.md.Clear(si.ID())
			cm.LeaseManager.Delete(ctx, leases.Lease{ID: si.ID()})
		}
	}
	return nil
}

// IdentityMapping returns the userns remapping used for refs
func (cm *cacheManager) IdentityMapping() *idtools.IdentityMapping {
	return cm.Snapshotter.IdentityMapping()
}

// Close closes the manager and releases the metadata database lock. No other
// method should be called after Close.
func (cm *cacheManager) Close() error {
	// TODO: allocate internal context and cancel it here
	return cm.md.Close()
}

// Get returns an immutable snapshot reference for ID
func (cm *cacheManager) Get(ctx context.Context, id string, opts ...RefOption) (ImmutableRef, error) {
	cm.mu.Lock()
	defer cm.mu.Unlock()
	return cm.get(ctx, id, opts...)
}

func (cm *cacheManager) Metadata(id string) *metadata.StorageItem {
	cm.mu.Lock()
	defer cm.mu.Unlock()
	r, ok := cm.records[id]
	if !ok {
		return nil
	}
	return r.Metadata()
}

// get requires manager lock to be taken
func (cm *cacheManager) get(ctx context.Context, id string, opts ...RefOption) (*immutableRef, error) {
	rec, err := cm.getRecord(ctx, id, opts...)
	if err != nil {
		return nil, err
	}
	rec.mu.Lock()
	defer rec.mu.Unlock()

	triggerUpdate := true
	for _, o := range opts {
		if o == NoUpdateLastUsed {
			triggerUpdate = false
		}
	}

	descHandlers := descHandlersOf(opts...)

	if rec.mutable {
		if len(rec.refs) != 0 {
			return nil, errors.Wrapf(ErrLocked, "%s is locked", id)
		}
		if rec.equalImmutable != nil {
			return rec.equalImmutable.ref(triggerUpdate, descHandlers), nil
		}
		return rec.mref(triggerUpdate, descHandlers).commit(ctx)
	}

	return rec.ref(triggerUpdate, descHandlers), nil
}

// getRecord returns record for id. Requires manager lock.
func (cm *cacheManager) getRecord(ctx context.Context, id string, opts ...RefOption) (cr *cacheRecord, retErr error) {
	checkLazyProviders := func(rec *cacheRecord) error {
		missing := NeedsRemoteProvidersError(nil)
		dhs := descHandlersOf(opts...)
		for {
			blob := digest.Digest(getBlob(rec.md))
			if isLazy, err := rec.isLazy(ctx); err != nil {
				return err
			} else if isLazy && dhs[blob] == nil {
				missing = append(missing, blob)
			}

			if rec.parent == nil {
				break
			}
			rec = rec.parent.cacheRecord
		}
		if len(missing) > 0 {
			return missing
		}
		return nil
	}

	if rec, ok := cm.records[id]; ok {
		if rec.isDead() {
			return nil, errors.Wrapf(errNotFound, "failed to get dead record %s", id)
		}
		if err := checkLazyProviders(rec); err != nil {
			return nil, err
		}
		return rec, nil
	}

	md, ok := cm.md.Get(id)
	if !ok {
		return nil, errors.Wrapf(errNotFound, "%s not found", id)
	}
	if mutableID := getEqualMutable(md); mutableID != "" {
		mutable, err := cm.getRecord(ctx, mutableID)
		if err != nil {
			// check loading mutable deleted record from disk
			if IsNotFound(err) {
				cm.md.Clear(id)
			}
			return nil, err
		}

		// parent refs are possibly lazy so keep it hold the description handlers.
		var dhs DescHandlers
		if mutable.parent != nil {
			dhs = mutable.parent.descHandlers
		}
		rec := &cacheRecord{
			mu:           &sync.Mutex{},
			cm:           cm,
			refs:         make(map[ref]struct{}),
			parent:       mutable.parentRef(false, dhs),
			md:           md,
			equalMutable: &mutableRef{cacheRecord: mutable},
		}
		mutable.equalImmutable = &immutableRef{cacheRecord: rec}
		cm.records[id] = rec
		return rec, nil
	}

	var parent *immutableRef
	if parentID := getParent(md); parentID != "" {
		var err error
		parent, err = cm.get(ctx, parentID, append(opts, NoUpdateLastUsed)...)
		if err != nil {
			return nil, err
		}
		defer func() {
			if retErr != nil {
				parent.mu.Lock()
				parent.release(context.TODO())
				parent.mu.Unlock()
			}
		}()
	}

	rec := &cacheRecord{
		mu:      &sync.Mutex{},
		mutable: !getCommitted(md),
		cm:      cm,
		refs:    make(map[ref]struct{}),
		parent:  parent,
		md:      md,
	}

	// the record was deleted but we crashed before data on disk was removed
	if getDeleted(md) {
		if err := rec.remove(ctx, true); err != nil {
			return nil, err
		}
		return nil, errors.Wrapf(errNotFound, "failed to get deleted record %s", id)
	}

	if err := initializeMetadata(rec, getParent(md), opts...); err != nil {
		return nil, err
	}

	if err := setImageRefMetadata(rec, opts...); err != nil {
		return nil, errors.Wrapf(err, "failed to append image ref metadata to ref %s", rec.ID())
	}

	cm.records[id] = rec
	if err := checkLazyProviders(rec); err != nil {
		return nil, err
	}
	return rec, nil
}

func (cm *cacheManager) New(ctx context.Context, s ImmutableRef, sess session.Group, opts ...RefOption) (mr MutableRef, err error) {
	id := identity.NewID()

	var parent *immutableRef
	var parentID string
	var parentSnapshotID string
	if s != nil {
		if _, ok := s.(*immutableRef); ok {
			parent = s.Clone().(*immutableRef)
		} else {
			p, err := cm.Get(ctx, s.ID(), append(opts, NoUpdateLastUsed)...)
			if err != nil {
				return nil, err
			}
			parent = p.(*immutableRef)
		}
		if err := parent.Finalize(ctx, true); err != nil {
			return nil, err
		}
		if err := parent.Extract(ctx, sess); err != nil {
			return nil, err
		}
		parentSnapshotID = getSnapshotID(parent.md)
		parentID = parent.ID()
	}

	defer func() {
		if err != nil && parent != nil {
			parent.Release(context.TODO())
		}
	}()

	l, err := cm.ManagerOpt.LeaseManager.Create(ctx, func(l *leases.Lease) error {
		l.ID = id
		l.Labels = map[string]string{
			"containerd.io/gc.flat": time.Now().UTC().Format(time.RFC3339Nano),
		}
		return nil
	})
	if err != nil {
		return nil, errors.Wrap(err, "failed to create lease")
	}

	defer func() {
		if err != nil {
			if err := cm.ManagerOpt.LeaseManager.Delete(context.TODO(), leases.Lease{
				ID: l.ID,
			}); err != nil {
				logrus.Errorf("failed to remove lease: %+v", err)
			}
		}
	}()

	if err := cm.ManagerOpt.LeaseManager.AddResource(ctx, l, leases.Resource{
		ID:   id,
		Type: "snapshots/" + cm.ManagerOpt.Snapshotter.Name(),
	}); err != nil {
		return nil, errors.Wrapf(err, "failed to add snapshot %s to lease", id)
	}

	if cm.Snapshotter.Name() == "stargz" && parent != nil {
		if rerr := parent.withRemoteSnapshotLabelsStargzMode(ctx, sess, func() {
			err = cm.Snapshotter.Prepare(ctx, id, parentSnapshotID)
		}); rerr != nil {
			return nil, rerr
		}
	} else {
		err = cm.Snapshotter.Prepare(ctx, id, parentSnapshotID)
	}
	if err != nil {
		return nil, errors.Wrapf(err, "failed to prepare %s", id)
	}

	md, _ := cm.md.Get(id)

	rec := &cacheRecord{
		mu:      &sync.Mutex{},
		mutable: true,
		cm:      cm,
		refs:    make(map[ref]struct{}),
		parent:  parent,
		md:      md,
	}

	if err := initializeMetadata(rec, parentID, opts...); err != nil {
		return nil, err
	}

	if err := setImageRefMetadata(rec, opts...); err != nil {
		return nil, errors.Wrapf(err, "failed to append image ref metadata to ref %s", rec.ID())
	}

	cm.mu.Lock()
	defer cm.mu.Unlock()

	cm.records[id] = rec // TODO: save to db

	// parent refs are possibly lazy so keep it hold the description handlers.
	var dhs DescHandlers
	if parent != nil {
		dhs = parent.descHandlers
	}
	return rec.mref(true, dhs), nil
}

func (cm *cacheManager) GetMutable(ctx context.Context, id string, opts ...RefOption) (MutableRef, error) {
	cm.mu.Lock()
	defer cm.mu.Unlock()

	rec, err := cm.getRecord(ctx, id, opts...)
	if err != nil {
		return nil, err
	}

	rec.mu.Lock()
	defer rec.mu.Unlock()
	if !rec.mutable {
		return nil, errors.Wrapf(errInvalid, "%s is not mutable", id)
	}

	if len(rec.refs) != 0 {
		return nil, errors.Wrapf(ErrLocked, "%s is locked", id)
	}

	if rec.equalImmutable != nil {
		if len(rec.equalImmutable.refs) != 0 {
			return nil, errors.Wrapf(ErrLocked, "%s is locked", id)
		}
		delete(cm.records, rec.equalImmutable.ID())
		if err := rec.equalImmutable.remove(ctx, false); err != nil {
			return nil, err
		}
		rec.equalImmutable = nil
	}

	return rec.mref(true, descHandlersOf(opts...)), nil
}

func (cm *cacheManager) Prune(ctx context.Context, ch chan client.UsageInfo, opts ...client.PruneInfo) error {
	cm.muPrune.Lock()

	for _, opt := range opts {
		if err := cm.pruneOnce(ctx, ch, opt); err != nil {
			cm.muPrune.Unlock()
			return err
		}
	}

	cm.muPrune.Unlock()

	if cm.GarbageCollect != nil {
		if _, err := cm.GarbageCollect(ctx); err != nil {
			return err
		}
	}

	return nil
}

func (cm *cacheManager) pruneOnce(ctx context.Context, ch chan client.UsageInfo, opt client.PruneInfo) error {
	filter, err := filters.ParseAll(opt.Filter...)
	if err != nil {
		return errors.Wrapf(err, "failed to parse prune filters %v", opt.Filter)
	}

	var check ExternalRefChecker
	if f := cm.PruneRefChecker; f != nil && (!opt.All || len(opt.Filter) > 0) {
		c, err := f()
		if err != nil {
			return errors.WithStack(err)
		}
		check = c
	}

	totalSize := int64(0)
	if opt.KeepBytes != 0 {
		du, err := cm.DiskUsage(ctx, client.DiskUsageInfo{})
		if err != nil {
			return err
		}
		for _, ui := range du {
			if ui.Shared {
				continue
			}
			totalSize += ui.Size
		}
	}

	return cm.prune(ctx, ch, pruneOpt{
		filter:       filter,
		all:          opt.All,
		checkShared:  check,
		keepDuration: opt.KeepDuration,
		keepBytes:    opt.KeepBytes,
		totalSize:    totalSize,
	})
}

func (cm *cacheManager) prune(ctx context.Context, ch chan client.UsageInfo, opt pruneOpt) error {
	var toDelete []*deleteRecord

	if opt.keepBytes != 0 && opt.totalSize < opt.keepBytes {
		return nil
	}

	cm.mu.Lock()

	gcMode := opt.keepBytes != 0
	cutOff := time.Now().Add(-opt.keepDuration)

	locked := map[*sync.Mutex]struct{}{}

	for _, cr := range cm.records {
		if _, ok := locked[cr.mu]; ok {
			continue
		}
		cr.mu.Lock()

		// ignore duplicates that share data
		if cr.equalImmutable != nil && len(cr.equalImmutable.refs) > 0 || cr.equalMutable != nil && len(cr.refs) == 0 {
			cr.mu.Unlock()
			continue
		}

		if cr.isDead() {
			cr.mu.Unlock()
			continue
		}

		if len(cr.refs) == 0 {
			recordType := GetRecordType(cr)
			if recordType == "" {
				recordType = client.UsageRecordTypeRegular
			}

			shared := false
			if opt.checkShared != nil {
				shared = opt.checkShared.Exists(cr.ID(), cr.parentChain())
			}

			if !opt.all {
				if recordType == client.UsageRecordTypeInternal || recordType == client.UsageRecordTypeFrontend || shared {
					cr.mu.Unlock()
					continue
				}
			}

			c := &client.UsageInfo{
				ID:         cr.ID(),
				Mutable:    cr.mutable,
				RecordType: recordType,
				Shared:     shared,
			}

			usageCount, lastUsedAt := getLastUsed(cr.md)
			c.LastUsedAt = lastUsedAt
			c.UsageCount = usageCount

			if opt.keepDuration != 0 {
				if lastUsedAt != nil && lastUsedAt.After(cutOff) {
					cr.mu.Unlock()
					continue
				}
			}

			if opt.filter.Match(adaptUsageInfo(c)) {
				toDelete = append(toDelete, &deleteRecord{
					cacheRecord: cr,
					lastUsedAt:  c.LastUsedAt,
					usageCount:  c.UsageCount,
				})
				if !gcMode {
					cr.dead = true

					// mark metadata as deleted in case we crash before cleanup finished
					if err := setDeleted(cr.md); err != nil {
						cr.mu.Unlock()
						cm.mu.Unlock()
						return err
					}
				} else {
					locked[cr.mu] = struct{}{}
					continue // leave the record locked
				}
			}
		}
		cr.mu.Unlock()
	}

	if gcMode && len(toDelete) > 0 {
		sortDeleteRecords(toDelete)
		var err error
		for i, cr := range toDelete {
			// only remove single record at a time
			if i == 0 {
				cr.dead = true
				err = setDeleted(cr.md)
			}
			cr.mu.Unlock()
		}
		if err != nil {
			return err
		}
		toDelete = toDelete[:1]
	}

	cm.mu.Unlock()

	if len(toDelete) == 0 {
		return nil
	}

	// calculate sizes here so that lock does not need to be held for slow process
	for _, cr := range toDelete {
		size := getSize(cr.md)

		if size == sizeUnknown && cr.equalImmutable != nil {
			size = getSize(cr.equalImmutable.md) // benefit from DiskUsage calc
		}
		if size == sizeUnknown {
			// calling size will warm cache for next call
			if _, err := cr.Size(ctx); err != nil {
				return err
			}
		}
	}

	cm.mu.Lock()
	var err error
	for _, cr := range toDelete {
		cr.mu.Lock()

		usageCount, lastUsedAt := getLastUsed(cr.md)

		c := client.UsageInfo{
			ID:          cr.ID(),
			Mutable:     cr.mutable,
			InUse:       len(cr.refs) > 0,
			Size:        getSize(cr.md),
			CreatedAt:   GetCreatedAt(cr.md),
			Description: GetDescription(cr.md),
			LastUsedAt:  lastUsedAt,
			UsageCount:  usageCount,
		}

		if cr.parent != nil {
			c.Parent = cr.parent.ID()
		}
		if c.Size == sizeUnknown && cr.equalImmutable != nil {
			c.Size = getSize(cr.equalImmutable.md) // benefit from DiskUsage calc
		}

		opt.totalSize -= c.Size

		if cr.equalImmutable != nil {
			if err1 := cr.equalImmutable.remove(ctx, false); err == nil {
				err = err1
			}
		}
		if err1 := cr.remove(ctx, true); err == nil {
			err = err1
		}

		if err == nil && ch != nil {
			ch <- c
		}
		cr.mu.Unlock()
	}
	cm.mu.Unlock()
	if err != nil {
		return err
	}

	select {
	case <-ctx.Done():
		return ctx.Err()
	default:
		return cm.prune(ctx, ch, opt)
	}
}

func (cm *cacheManager) markShared(m map[string]*cacheUsageInfo) error {
	if cm.PruneRefChecker == nil {
		return nil
	}
	c, err := cm.PruneRefChecker()
	if err != nil {
		return errors.WithStack(err)
	}

	var markAllParentsShared func(string)
	markAllParentsShared = func(id string) {
		if v, ok := m[id]; ok {
			v.shared = true
			if v.parent != "" {
				markAllParentsShared(v.parent)
			}
		}
	}

	for id := range m {
		if m[id].shared {
			continue
		}
		if b := c.Exists(id, m[id].parentChain); b {
			markAllParentsShared(id)
		}
	}
	return nil
}

type cacheUsageInfo struct {
	refs        int
	parent      string
	size        int64
	mutable     bool
	createdAt   time.Time
	usageCount  int
	lastUsedAt  *time.Time
	description string
	doubleRef   bool
	recordType  client.UsageRecordType
	shared      bool
	parentChain []digest.Digest
}

func (cm *cacheManager) DiskUsage(ctx context.Context, opt client.DiskUsageInfo) ([]*client.UsageInfo, error) {
	filter, err := filters.ParseAll(opt.Filter...)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to parse diskusage filters %v", opt.Filter)
	}

	cm.mu.Lock()

	m := make(map[string]*cacheUsageInfo, len(cm.records))
	rescan := make(map[string]struct{}, len(cm.records))

	for id, cr := range cm.records {
		cr.mu.Lock()
		// ignore duplicates that share data
		if cr.equalImmutable != nil && len(cr.equalImmutable.refs) > 0 || cr.equalMutable != nil && len(cr.refs) == 0 {
			cr.mu.Unlock()
			continue
		}

		usageCount, lastUsedAt := getLastUsed(cr.md)
		c := &cacheUsageInfo{
			refs:        len(cr.refs),
			mutable:     cr.mutable,
			size:        getSize(cr.md),
			createdAt:   GetCreatedAt(cr.md),
			usageCount:  usageCount,
			lastUsedAt:  lastUsedAt,
			description: GetDescription(cr.md),
			doubleRef:   cr.equalImmutable != nil,
			recordType:  GetRecordType(cr),
			parentChain: cr.parentChain(),
		}
		if c.recordType == "" {
			c.recordType = client.UsageRecordTypeRegular
		}
		if cr.parent != nil {
			c.parent = cr.parent.ID()
		}
		if cr.mutable && c.refs > 0 {
			c.size = 0 // size can not be determined because it is changing
		}
		m[id] = c
		rescan[id] = struct{}{}
		cr.mu.Unlock()
	}
	cm.mu.Unlock()

	for {
		if len(rescan) == 0 {
			break
		}
		for id := range rescan {
			v := m[id]
			if v.refs == 0 && v.parent != "" {
				m[v.parent].refs--
				if v.doubleRef {
					m[v.parent].refs--
				}
				rescan[v.parent] = struct{}{}
			}
			delete(rescan, id)
		}
	}

	if err := cm.markShared(m); err != nil {
		return nil, err
	}

	var du []*client.UsageInfo
	for id, cr := range m {
		c := &client.UsageInfo{
			ID:          id,
			Mutable:     cr.mutable,
			InUse:       cr.refs > 0,
			Size:        cr.size,
			Parent:      cr.parent,
			CreatedAt:   cr.createdAt,
			Description: cr.description,
			LastUsedAt:  cr.lastUsedAt,
			UsageCount:  cr.usageCount,
			RecordType:  cr.recordType,
			Shared:      cr.shared,
		}
		if filter.Match(adaptUsageInfo(c)) {
			du = append(du, c)
		}
	}

	eg, ctx := errgroup.WithContext(ctx)

	for _, d := range du {
		if d.Size == sizeUnknown {
			func(d *client.UsageInfo) {
				eg.Go(func() error {
					ref, err := cm.Get(ctx, d.ID, NoUpdateLastUsed)
					if err != nil {
						d.Size = 0
						return nil
					}
					s, err := ref.Size(ctx)
					if err != nil {
						return err
					}
					d.Size = s
					return ref.Release(context.TODO())
				})
			}(d)
		}
	}

	if err := eg.Wait(); err != nil {
		return du, err
	}

	return du, nil
}

func IsNotFound(err error) bool {
	return errors.Is(err, errNotFound)
}

type RefOption interface{}

type cachePolicy int

const (
	cachePolicyDefault cachePolicy = iota
	cachePolicyRetain
)

type withMetadata interface {
	Metadata() *metadata.StorageItem
}

type noUpdateLastUsed struct{}

var NoUpdateLastUsed noUpdateLastUsed

func HasCachePolicyRetain(m withMetadata) bool {
	return getCachePolicy(m.Metadata()) == cachePolicyRetain
}

func CachePolicyRetain(m withMetadata) error {
	return queueCachePolicy(m.Metadata(), cachePolicyRetain)
}

func CachePolicyDefault(m withMetadata) error {
	return queueCachePolicy(m.Metadata(), cachePolicyDefault)
}

func WithDescription(descr string) RefOption {
	return func(m withMetadata) error {
		return queueDescription(m.Metadata(), descr)
	}
}

func WithRecordType(t client.UsageRecordType) RefOption {
	return func(m withMetadata) error {
		return queueRecordType(m.Metadata(), t)
	}
}

func WithCreationTime(tm time.Time) RefOption {
	return func(m withMetadata) error {
		return queueCreatedAt(m.Metadata(), tm)
	}
}

// Need a separate type for imageRef because it needs to be called outside
// initializeMetadata while still being a RefOption, so wrapping it in a
// different type ensures initializeMetadata won't catch it too and duplicate
// setting the metadata.
type imageRefOption func(m withMetadata) error

// WithImageRef appends the given imageRef to the cache ref's metadata
func WithImageRef(imageRef string) RefOption {
	return imageRefOption(func(m withMetadata) error {
		return appendImageRef(m.Metadata(), imageRef)
	})
}

func setImageRefMetadata(m withMetadata, opts ...RefOption) error {
	md := m.Metadata()
	for _, opt := range opts {
		if fn, ok := opt.(imageRefOption); ok {
			if err := fn(m); err != nil {
				return err
			}
		}
	}
	return md.Commit()
}

func initializeMetadata(m withMetadata, parent string, opts ...RefOption) error {
	md := m.Metadata()
	if tm := GetCreatedAt(md); !tm.IsZero() {
		return nil
	}

	if err := queueParent(md, parent); err != nil {
		return err
	}

	if err := queueCreatedAt(md, time.Now()); err != nil {
		return err
	}

	for _, opt := range opts {
		if fn, ok := opt.(func(withMetadata) error); ok {
			if err := fn(m); err != nil {
				return err
			}
		}
	}

	return md.Commit()
}

func adaptUsageInfo(info *client.UsageInfo) filters.Adaptor {
	return filters.AdapterFunc(func(fieldpath []string) (string, bool) {
		if len(fieldpath) == 0 {
			return "", false
		}

		switch fieldpath[0] {
		case "id":
			return info.ID, info.ID != ""
		case "parent":
			return info.Parent, info.Parent != ""
		case "description":
			return info.Description, info.Description != ""
		case "inuse":
			return "", info.InUse
		case "mutable":
			return "", info.Mutable
		case "immutable":
			return "", !info.Mutable
		case "type":
			return string(info.RecordType), info.RecordType != ""
		case "shared":
			return "", info.Shared
		case "private":
			return "", !info.Shared
		}

		// TODO: add int/datetime/bytes support for more fields

		return "", false
	})
}

type pruneOpt struct {
	filter       filters.Filter
	all          bool
	checkShared  ExternalRefChecker
	keepDuration time.Duration
	keepBytes    int64
	totalSize    int64
}

type deleteRecord struct {
	*cacheRecord
	lastUsedAt      *time.Time
	usageCount      int
	lastUsedAtIndex int
	usageCountIndex int
}

func sortDeleteRecords(toDelete []*deleteRecord) {
	sort.Slice(toDelete, func(i, j int) bool {
		if toDelete[i].lastUsedAt == nil {
			return true
		}
		if toDelete[j].lastUsedAt == nil {
			return false
		}
		return toDelete[i].lastUsedAt.Before(*toDelete[j].lastUsedAt)
	})

	maxLastUsedIndex := 0
	var val time.Time
	for _, v := range toDelete {
		if v.lastUsedAt != nil && v.lastUsedAt.After(val) {
			val = *v.lastUsedAt
			maxLastUsedIndex++
		}
		v.lastUsedAtIndex = maxLastUsedIndex
	}

	sort.Slice(toDelete, func(i, j int) bool {
		return toDelete[i].usageCount < toDelete[j].usageCount
	})

	maxUsageCountIndex := 0
	var count int
	for _, v := range toDelete {
		if v.usageCount != count {
			count = v.usageCount
			maxUsageCountIndex++
		}
		v.usageCountIndex = maxUsageCountIndex
	}

	sort.Slice(toDelete, func(i, j int) bool {
		return float64(toDelete[i].lastUsedAtIndex)/float64(maxLastUsedIndex)+
			float64(toDelete[i].usageCountIndex)/float64(maxUsageCountIndex) <
			float64(toDelete[j].lastUsedAtIndex)/float64(maxLastUsedIndex)+
				float64(toDelete[j].usageCountIndex)/float64(maxUsageCountIndex)
	})
}

func diffIDFromDescriptor(desc ocispec.Descriptor) (digest.Digest, error) {
	diffIDStr, ok := desc.Annotations["containerd.io/uncompressed"]
	if !ok {
		return "", errors.Errorf("missing uncompressed annotation for %s", desc.Digest)
	}
	diffID, err := digest.Parse(diffIDStr)
	if err != nil {
		return "", errors.Wrapf(err, "failed to parse diffID %q for %s", diffIDStr, desc.Digest)
	}
	return diffID, nil
}


text:
{-# LANGUAGE Haskell98 #-}
{-# LINE 1 "Data/Streaming/Filesystem.hs" #-}

{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Streaming functions for interacting with the filesystem.
module Data.Streaming.Filesystem
    ( DirStream
    , openDirStream
    , readDirStream
    , closeDirStream
    , FileType (..)
    , getFileType
    ) where

import Data.Typeable (Typeable)

import System.Posix.Directory (DirStream, openDirStream, closeDirStream)
import qualified System.Posix.Directory as Posix
import qualified System.Posix.Files as PosixF
import Control.Exception (try, IOException)

readDirStream :: DirStream -> IO (Maybe FilePath)
readDirStream ds = do
    fp <- Posix.readDirStream ds
    case fp of
        "" -> return Nothing
        "." -> readDirStream ds
        ".." -> readDirStream ds
        _ -> return $ Just fp

getFileType :: FilePath -> IO FileType
getFileType fp = do
    s <- PosixF.getSymbolicLinkStatus fp
    case () of
        ()
            | PosixF.isRegularFile s -> return FTFile
            | PosixF.isDirectory s -> return FTDirectory
            | PosixF.isSymbolicLink s -> do
                es' <- try $ PosixF.getFileStatus fp
                case es' of
                    Left (_ :: IOException) -> return FTOther
                    Right s'
                        | PosixF.isRegularFile s' -> return FTFileSym
                        | PosixF.isDirectory s' -> return FTDirectorySym
                        | otherwise -> return FTOther
            | otherwise -> return FTOther

data FileType
    = FTFile
    | FTFileSym -- ^ symlink to file
    | FTDirectory
    | FTDirectorySym -- ^ symlink to a directory
    | FTOther
    deriving (Show, Read, Eq, Ord, Typeable)


text:
/*	$NetBSD: drbbc.c,v 1.5 1999/03/14 22:42:12 is Exp $	*/

/*-
 * Copyright (c) 1997 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Ignatios Souvatzis.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *        This product includes software developed by the NetBSD
 *        Foundation, Inc. and its contributors.
 * 4. Neither the name of The NetBSD Foundation nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/systm.h>
#if 0
#include <machine/psl.h>
#endif
#include <machine/cpu.h>
#include <amiga/amiga/device.h>
#include <amiga/amiga/custom.h>
#include <amiga/amiga/cia.h>
#include <amiga/amiga/drcustom.h>
#include <amiga/dev/rtc.h>

#include <dev/ic/ds.h>

int draco_ds_read_bit __P((void *));
void draco_ds_write_bit __P((void *, int));
void draco_ds_reset __P((void *));

void drbbc_attach __P((struct device *, struct device *, void *));
int drbbc_match __P((struct device *, struct cfdata *, void *));

int dracougettod __P((struct timeval *));
#ifdef __NOTYET__
int dracousettod __P((struct timeval *));
#endif

struct drbbc_softc {
	struct device sc_dev;
	struct ds_handle sc_dsh;
};

struct cfattach drbbc_ca = {
	sizeof(struct drbbc_softc),
	drbbc_match,
	drbbc_attach
};

struct drbbc_softc *drbbc_sc;

int
drbbc_match(pdp, cfp, auxp)
	struct device *pdp;
	struct cfdata *cfp;
	void *auxp;
{
	if (is_draco() && matchname(auxp, "drbbc") && (cfp->cf_unit == 0))
		return (1);
	else
		return (0);
}

void
drbbc_attach(pdp, dp, auxp)
	struct device *pdp, *dp;
	void *auxp;
{
	int i;
	struct drbbc_softc *sc;
	u_int8_t rombuf[8];

	sc = (struct drbbc_softc *)dp;

	sc->sc_dsh.ds_read_bit = draco_ds_read_bit;
	sc->sc_dsh.ds_write_bit = draco_ds_write_bit;
	sc->sc_dsh.ds_reset = draco_ds_reset;
	sc->sc_dsh.ds_hw_handle = (void *)(DRCCADDR + DRIOCTLPG*NBPG);

	sc->sc_dsh.ds_reset(sc->sc_dsh.ds_hw_handle);

	ds_write_byte(&sc->sc_dsh, DS_ROM_READ);
	for (i=0; i<8; ++i)
		rombuf[i] = ds_read_byte(&sc->sc_dsh);

	hostid = (rombuf[3] << 24) + (rombuf[2] << 16) +
		(rombuf[1] << 8) + rombuf[7];

	printf(": ROM %02x %02x%02x%02x%02x%02x%02x %02x (DraCo sernum %ld)\n",
		rombuf[7], rombuf[6], rombuf[5], rombuf[4],
		rombuf[3], rombuf[2], rombuf[1], rombuf[0],
		hostid);

	ugettod = dracougettod;
	usettod = (void *)0;
	drbbc_sc = sc;
}

int
draco_ds_read_bit(p)
	void *p;
{
	struct drioct *draco_ioct;

	draco_ioct = p;

	while (draco_ioct->io_status & DRSTAT_CLKBUSY);

	draco_ioct->io_clockw1 = 0;

	while (draco_ioct->io_status & DRSTAT_CLKBUSY);

	return (draco_ioct->io_status & DRSTAT_CLKDAT);
}

void
draco_ds_write_bit(p, b)
	void *p;
	int b;
{
	struct drioct *draco_ioct;

	draco_ioct = p;

	while (draco_ioct->io_status & DRSTAT_CLKBUSY);

	if (b)
		draco_ioct->io_clockw1 = 0;
	else
		draco_ioct->io_clockw0 = 0;
}

void
draco_ds_reset(p)
	void *p;
{
	struct drioct *draco_ioct;

	draco_ioct = p;

	draco_ioct->io_clockrst = 0;
}

int
dracougettod(tvp)
	struct timeval *tvp;
{
	u_int32_t clkbuf;
	u_int32_t usecs;

	drbbc_sc->sc_dsh.ds_reset(drbbc_sc->sc_dsh.ds_hw_handle);

	ds_write_byte(&drbbc_sc->sc_dsh, DS_ROM_SKIP);

	ds_write_byte(&drbbc_sc->sc_dsh, DS_MEM_READ_MEMORY);
	/* address of seconds/256: */
	ds_write_byte(&drbbc_sc->sc_dsh, 0x02);
	ds_write_byte(&drbbc_sc->sc_dsh, 0x02);

	usecs = (ds_read_byte(&drbbc_sc->sc_dsh) * 1000000) / 256;
	clkbuf = ds_read_byte(&drbbc_sc->sc_dsh)
	    + (ds_read_byte(&drbbc_sc->sc_dsh)<<8)
	    + (ds_read_byte(&drbbc_sc->sc_dsh)<<16)
	    + (ds_read_byte(&drbbc_sc->sc_dsh)<<24);

	/* BSD time is wr. 1.1.1970; AmigaOS time wrt. 1.1.1978 */

	clkbuf += (8*365 + 2) * 86400;

	tvp->tv_sec = clkbuf;
	tvp->tv_usec = usecs;

	return (1);
}


text:
package com.evolveum.midpoint.model.common.expression;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.namespace.QName;

import com.evolveum.midpoint.model.common.expression.functions.BasicExpressionFunctions;
import com.evolveum.midpoint.model.common.expression.functions.BasicExpressionFunctionsXPath;
import com.evolveum.midpoint.model.common.expression.functions.FunctionLibrary;
import com.evolveum.midpoint.model.common.expression.functions.LogExpressionFunctions;
import com.evolveum.midpoint.prism.Item;
import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.Objectable;
import com.evolveum.midpoint.prism.PrismContainer;
import com.evolveum.midpoint.prism.PrismContainerDefinition;
import com.evolveum.midpoint.prism.PrismContainerValue;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismObjectDefinition;
import com.evolveum.midpoint.prism.PrismPropertyDefinition;
import com.evolveum.midpoint.prism.PrismPropertyValue;
import com.evolveum.midpoint.prism.PrismValue;
import com.evolveum.midpoint.prism.Structured;
import com.evolveum.midpoint.prism.Visitable;
import com.evolveum.midpoint.prism.Visitor;
import com.evolveum.midpoint.prism.crypto.EncryptionException;
import com.evolveum.midpoint.prism.crypto.Protector;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.PlusMinusZero;
import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.path.ItemPathSegment;
import com.evolveum.midpoint.prism.path.NameItemPathSegment;
import com.evolveum.midpoint.prism.query.AllFilter;
import com.evolveum.midpoint.prism.query.ExpressionWrapper;
import com.evolveum.midpoint.prism.query.InOidFilter;
import com.evolveum.midpoint.prism.query.LogicalFilter;
import com.evolveum.midpoint.prism.query.NoneFilter;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.prism.query.PropertyValueFilter;
import com.evolveum.midpoint.prism.query.UndefinedFilter;
import com.evolveum.midpoint.prism.query.ValueFilter;
import com.evolveum.midpoint.prism.util.JavaTypeConverter;
import com.evolveum.midpoint.prism.util.PrismUtil;
import com.evolveum.midpoint.prism.xml.XmlTypeConverter;
import com.evolveum.midpoint.prism.xml.XsdTypeMapper;
import com.evolveum.midpoint.schema.constants.ExpressionConstants;
import com.evolveum.midpoint.schema.constants.MidPointConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ObjectResolver;
import com.evolveum.midpoint.security.api.MidPointPrincipal;
import com.evolveum.midpoint.security.api.SecurityEnforcer;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.PrettyPrinter;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.QueryInterpretationOfNoValueType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType;

/**
 * @author semancik
 *
 */
public class ExpressionUtil {

	private static final Trace LOGGER = TraceManager.getTrace(ExpressionUtil.class);

	public static <V extends PrismValue> PrismValueDeltaSetTriple<V> toOutputTriple(
			PrismValueDeltaSetTriple<V> resultTriple, ItemDefinition outputDefinition,
			final ItemPath residualPath, final Protector protector, final PrismContext prismContext) {
		final Class<?> resultTripleValueClass = resultTriple.getRealValueClass();
		if (resultTripleValueClass == null) {
			// triple is empty. type does not matter.
			return resultTriple;
		}
		Class<?> expectedJavaType = XsdTypeMapper.toJavaType(outputDefinition.getTypeName());
		if (expectedJavaType == null) {
			expectedJavaType = prismContext.getSchemaRegistry()
					.getCompileTimeClass(outputDefinition.getTypeName());
		}
		if (resultTripleValueClass == expectedJavaType) {
			return resultTriple;
		}
		final Class<?> finalExpectedJavaType = expectedJavaType;

		PrismValueDeltaSetTriple<V> clonedTriple = resultTriple.clone();
		clonedTriple.accept(new Visitor() {
			@Override
			public void visit(Visitable visitable) {
				if (visitable instanceof PrismPropertyValue<?>) {
					PrismPropertyValue<Object> pval = (PrismPropertyValue<Object>) visitable;
					Object realVal = pval.getValue();
					if (realVal != null) {
						if (Structured.class.isAssignableFrom(resultTripleValueClass)) {
							if (residualPath != null && !residualPath.isEmpty()) {
								realVal = ((Structured) realVal).resolve(residualPath);
							}
						}
						if (finalExpectedJavaType != null) {
							Object convertedVal = convertValue(finalExpectedJavaType, realVal, protector,
									prismContext);
							pval.setValue(convertedVal);
						}
					}
				}
			}
		});
		return clonedTriple;
	}

	/**
	 * Slightly more powerful version of "convert" as compared to
	 * JavaTypeConverter. This version can also encrypt/decrypt and also handles
	 * polystrings.
	 */
	public static <I, O> O convertValue(Class<O> finalExpectedJavaType, I inputVal, Protector protector,
			PrismContext prismContext) {
		if (inputVal == null) {
			return null;
		}
		if (finalExpectedJavaType.isInstance(inputVal)) {
			return (O) inputVal;
		}

		Object intermediateVal = inputVal;
		if (finalExpectedJavaType == ProtectedStringType.class) {
			String valueToEncrypt;
			if (inputVal instanceof String) {
				valueToEncrypt = (String) inputVal;
			} else {
				valueToEncrypt = JavaTypeConverter.convert(String.class, inputVal);
			}
			try {
				intermediateVal = protector.encryptString(valueToEncrypt);
			} catch (EncryptionException e) {
				throw new SystemException(e.getMessage(), e);
			}
		} else {

			if (inputVal instanceof ProtectedStringType) {
				String decryptedString;
				try {
					intermediateVal = protector.decryptString((ProtectedStringType) inputVal);
				} catch (EncryptionException e) {
					throw new SystemException(e.getMessage(), e);
				}

			}
		}

		O convertedVal = JavaTypeConverter.convert(finalExpectedJavaType, intermediateVal);

		PrismUtil.recomputeRealValue(convertedVal, prismContext);

		return convertedVal;
	}

	public static Object resolvePath(ItemPath path, ExpressionVariables variables, Object defaultContext,
			ObjectResolver objectResolver, String shortDesc, Task task, OperationResult result)
					throws SchemaException, ObjectNotFoundException {

		Object root = defaultContext;
		ItemPath relativePath = path;
		ItemPathSegment first = path.first();
		String varDesc = "default context";
		if (first instanceof NameItemPathSegment && ((NameItemPathSegment) first).isVariable()) {
			QName varName = ((NameItemPathSegment) first).getName();
			varDesc = "variable " + PrettyPrinter.prettyPrint(varName);
			relativePath = path.rest();
			if (variables.containsKey(varName)) {
				root = variables.get(varName);
			} else {
				throw new SchemaException("No variable with name " + varName + " in " + shortDesc);
			}
		}
		if (root == null) {
			return null;
		}
		if (relativePath.isEmpty()) {
			return root;
		}

		if (root instanceof ObjectReferenceType) {
			root = resolveReference((ObjectReferenceType) root, objectResolver, varDesc, shortDesc, task,
					result);
		}

		if (root instanceof Objectable) {
			return (((Objectable) root).asPrismObject()).find(relativePath);
		}
		if (root instanceof PrismObject<?>) {
			return ((PrismObject<?>) root).find(relativePath);
		} else if (root instanceof PrismContainer<?>) {
			return ((PrismContainer<?>) root).find(relativePath);
		} else if (root instanceof PrismContainerValue<?>) {
			return ((PrismContainerValue<?>) root).find(relativePath);
		} else if (root instanceof Item<?, ?>) {
			// Except for container (which is handled above)
			throw new SchemaException(
					"Cannot apply path " + relativePath + " to " + root + " in " + shortDesc);
		} else if (root instanceof ObjectDeltaObject<?>) {
			return ((ObjectDeltaObject<?>) root).findIdi(relativePath);
		} else if (root instanceof ItemDeltaItem<?, ?>) {
			return ((ItemDeltaItem<?, ?>) root).findIdi(relativePath);
		} else {
			throw new IllegalArgumentException(
					"Unexpected root " + root + " (relative path:" + relativePath + ") in " + shortDesc);
		}
	}

	private static PrismObject<?> resolveReference(ObjectReferenceType ref, ObjectResolver objectResolver,
			String varDesc, String contextDescription, Task task, OperationResult result)
					throws SchemaException, ObjectNotFoundException {
		if (ref.getOid() == null) {
			throw new SchemaException(
					"Null OID in reference in variable " + varDesc + " in " + contextDescription);
		} else {
			try {

				ObjectType objectType = objectResolver.resolve(ref, ObjectType.class, null,
						contextDescription, task, result);
				if (objectType == null) {
					throw new IllegalArgumentException(
							"Resolve returned null for " + ref + " in " + contextDescription);
				}
				return objectType.asPrismObject();

			} catch (ObjectNotFoundException e) {
				throw new ObjectNotFoundException("Object not found during variable " + varDesc
						+ " resolution in " + contextDescription + ": " + e.getMessage(), e, ref.getOid());
			} catch (SchemaException e) {
				throw new SchemaException("Schema error during variable " + varDesc + " resolution in "
						+ contextDescription + ": " + e.getMessage(), e);
			}
		}
	}

	public static <ID extends ItemDefinition> ID resolveDefinitionPath(ItemPath path,
			ExpressionVariables variables, PrismObjectDefinition<?> defaultContext, String shortDesc)
					throws SchemaException {
		while (path != null && !path.isEmpty() && !(path.first() instanceof NameItemPathSegment)) {
			path = path.rest();
		}
		Object root = defaultContext;
		ItemPath relativePath = path;
		NameItemPathSegment first = (NameItemPathSegment) path.first();
		if (first.isVariable()) {
			relativePath = path.rest();
			QName varName = first.getName();
			if (variables.containsKey(varName)) {
				Object varValue = variables.get(varName);
				if (varValue instanceof ItemDeltaItem<?, ?>) {
					root = ((ItemDeltaItem<?, ?>) varValue).getDefinition();
				} else if (varValue instanceof Item<?, ?>) {
					root = ((Item<?, ?>) varValue).getDefinition();
				} else if (varValue instanceof Objectable) {
					root = ((Objectable) varValue).asPrismObject().getDefinition();
				} else if (varValue instanceof ItemDefinition) {
					root = varValue;
				} else {
					throw new IllegalStateException("Unexpected content of variable " + varName + ": "
							+ varValue + " (" + varValue.getClass() + ")");
				}
				if (root == null) {
					throw new IllegalStateException(
							"Null definition in content of variable " + varName + ": " + varValue);
				}
			} else {
				throw new SchemaException("No variable with name " + varName + " in " + shortDesc);
			}
		}
		if (root == null) {
			return null;
		}
		if (relativePath.isEmpty()) {
			return (ID) root;
		}
		ItemDefinition result = null;
		if (root instanceof PrismObjectDefinition<?>) {
			return ((PrismObjectDefinition<?>) root).findItemDefinition(relativePath);
		} else if (root instanceof PrismContainerDefinition<?>) {
			return ((PrismContainerDefinition<?>) root).findItemDefinition(relativePath);
		} else if (root instanceof ItemDefinition) {
			// Except for container (which is handled above)
			throw new SchemaException(
					"Cannot apply path " + relativePath + " to " + root + " in " + shortDesc);
		} else {
			throw new IllegalArgumentException("Unexpected root " + root + " in " + shortDesc);
		}
	}

	public static <IV extends PrismValue, ID extends ItemDefinition> ItemDeltaItem<IV, ID> toItemDeltaItem(
			Object object, ObjectResolver objectResolver, String string, OperationResult result) {
		if (object == null) {
			return null;
		}

		if (object instanceof ItemDeltaItem<?, ?>) {
			return (ItemDeltaItem<IV, ID>) object;
		}

		if (object instanceof PrismObject<?>) {
			return (ItemDeltaItem<IV, ID>) new ObjectDeltaObject((PrismObject<?>) object, null,
					(PrismObject<?>) object);
		} else if (object instanceof Item<?, ?>) {
			return new ItemDeltaItem<IV, ID>((Item<IV, ID>) object, null, (Item<IV, ID>) object);
		} else if (object instanceof ItemDelta<?, ?>) {
			return new ItemDeltaItem<IV, ID>(null, (ItemDelta<IV, ID>) object, null);
		} else {
			throw new IllegalArgumentException("Unexpected object " + object + " " + object.getClass());
		}

	}

	public static FunctionLibrary createBasicFunctionLibrary(PrismContext prismContext, Protector protector) {
		FunctionLibrary lib = new FunctionLibrary();
		lib.setVariableName(MidPointConstants.FUNCTION_LIBRARY_BASIC_VARIABLE_NAME);
		lib.setNamespace(MidPointConstants.NS_FUNC_BASIC);
		BasicExpressionFunctions func = new BasicExpressionFunctions(prismContext, protector);
		lib.setGenericFunctions(func);
		BasicExpressionFunctionsXPath funcXPath = new BasicExpressionFunctionsXPath(func);
		lib.setXmlFunctions(funcXPath);
		return lib;
	}

	public static FunctionLibrary createLogFunctionLibrary(PrismContext prismContext) {
		FunctionLibrary lib = new FunctionLibrary();
		lib.setVariableName(MidPointConstants.FUNCTION_LIBRARY_LOG_VARIABLE_NAME);
		lib.setNamespace(MidPointConstants.NS_FUNC_LOG);
		LogExpressionFunctions func = new LogExpressionFunctions(prismContext);
		lib.setGenericFunctions(func);
		return lib;
	}

	public static ObjectQuery evaluateQueryExpressions(ObjectQuery origQuery, ExpressionVariables variables,
			ExpressionFactory expressionFactory, PrismContext prismContext, String shortDesc, Task task,
			OperationResult result)
					throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException {
		if (origQuery == null) {
			return null;
		}
		ObjectQuery query = origQuery.clone();
		ObjectFilter evaluatedFilter = evaluateFilterExpressionsInternal(query.getFilter(), variables,
				expressionFactory, prismContext, shortDesc, task, result);
		query.setFilter(evaluatedFilter);
		return query;
	}

	public static ObjectFilter evaluateFilterExpressions(ObjectFilter origFilter,
			ExpressionVariables variables, ExpressionFactory expressionFactory, PrismContext prismContext,
			String shortDesc, Task task, OperationResult result)
					throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException {
		if (origFilter == null) {
			return null;
		}

		return evaluateFilterExpressionsInternal(origFilter, variables, expressionFactory, prismContext,
				shortDesc, task, result);
	}

	private static ObjectFilter evaluateFilterExpressionsInternal(ObjectFilter filter,
			ExpressionVariables variables, ExpressionFactory expressionFactory, PrismContext prismContext,
			String shortDesc, Task task, OperationResult result)
					throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException {
		if (filter == null) {
			return null;
		}

		if (filter instanceof InOidFilter) {

//			InOidFilter inOidFilter = (InOidFilter) filter;
//			if (inOidFilter.getOids() != null && !inOidFilter.getOids().isEmpty()) {
//				return inOidFilter.clone();
//			}

			ExpressionWrapper expressionWrapper = ((InOidFilter) filter).getExpression();
			if (expressionWrapper == null || expressionWrapper.getExpression() == null) {
				LOGGER.warn("No valueExpression in filter in {}. Returning original filter", shortDesc);
				InOidFilter inOidFilter = (InOidFilter) filter;
				if (inOidFilter.getOids() != null && !inOidFilter.getOids().isEmpty()){
					return filter.clone();
				}
				return NoneFilter.createNone();
			}

			if (!(expressionWrapper.getExpression() instanceof ExpressionType)) {
				throw new SchemaException("Unexpected expression type "
						+ expressionWrapper.getExpression().getClass() + " in filter in " + shortDesc);
			}

			ExpressionType valueExpression = (ExpressionType) expressionWrapper.getExpression();

			try {
				Collection<String> expressionResult = evaluateStringExpression(variables, prismContext,
						valueExpression, expressionFactory, shortDesc, task, result);

				if (expressionResult == null || expressionResult.isEmpty()) {
					LOGGER.debug("Result of search filter expression was null or empty. Expression: {}",
							valueExpression);
					return createFilterForNoValue(filter, valueExpression);
				}
				// TODO: log more context
				LOGGER.trace("Search filter expression in the rule for {} evaluated to {}.",
						new Object[] { shortDesc, expressionResult });

				InOidFilter evaluatedFilter = (InOidFilter) filter.clone();
				evaluatedFilter.setOids(expressionResult);
				evaluatedFilter.setExpression(null);
				if (LOGGER.isTraceEnabled()) {
					LOGGER.trace("Transformed filter to:\n{}", evaluatedFilter.debugDump());
				}
				return evaluatedFilter;
			} catch (Exception ex) {
				throw new ExpressionEvaluationException(ex);
			}
		} else if (filter instanceof LogicalFilter) {
			List<ObjectFilter> conditions = ((LogicalFilter) filter).getConditions();
			LogicalFilter evaluatedFilter = ((LogicalFilter) filter).cloneEmpty();
			for (ObjectFilter condition : conditions) {
				ObjectFilter evaluatedSubFilter = evaluateFilterExpressionsInternal(condition, variables,
						expressionFactory, prismContext, shortDesc, task, result);
				evaluatedFilter.addCondition(evaluatedSubFilter);
			}
			return evaluatedFilter;

		} else if (filter instanceof PropertyValueFilter) {
			PropertyValueFilter pvfilter = (PropertyValueFilter) filter;

			if (pvfilter.getValues() != null && !pvfilter.getValues().isEmpty()) {
				// We have value. Nothing to evaluate.
				return pvfilter.clone();
			}

			ExpressionWrapper expressionWrapper = pvfilter.getExpression();
			if (expressionWrapper == null || expressionWrapper.getExpression() == null) {
				LOGGER.warn("No valueExpression in filter in {}. Returning original filter", shortDesc);
				return pvfilter.clone();
			}
			if (!(expressionWrapper.getExpression() instanceof ExpressionType)) {
				throw new SchemaException("Unexpected expression type "
						+ expressionWrapper.getExpression().getClass() + " in filter in " + shortDesc);
			}

			ExpressionType valueExpression = (ExpressionType) expressionWrapper.getExpression();

			try {
				PrismValue expressionResult = evaluateExpression(variables, prismContext, valueExpression,
						filter, expressionFactory, shortDesc, task, result);

				if (expressionResult == null || expressionResult.isEmpty()) {
					LOGGER.debug("Result of search filter expression was null or empty. Expression: {}",
							valueExpression);

					return createFilterForNoValue(pvfilter, valueExpression);
				}
				// TODO: log more context
				LOGGER.trace("Search filter expression in the rule for {} evaluated to {}.",
						new Object[] { shortDesc, expressionResult });

				PropertyValueFilter evaluatedFilter = (PropertyValueFilter) pvfilter.clone();
				evaluatedFilter.setValue(expressionResult);
				evaluatedFilter.setExpression(null);
				// }
				if (LOGGER.isTraceEnabled()) {
					LOGGER.trace("Transformed filter to:\n{}", evaluatedFilter.debugDump());
				}
				return evaluatedFilter;

			} catch (RuntimeException ex) {
				LoggingUtils.logException(LOGGER, "Couldn't evaluate expression " + valueExpression + ".",
						ex);
				throw new SystemException(
						"Couldn't evaluate expression" + valueExpression + ": " + ex.getMessage(), ex);

			} catch (SchemaException ex) {
				LoggingUtils.logException(LOGGER, "Couldn't evaluate expression " + valueExpression + ".",
						ex);
				throw new SchemaException(
						"Couldn't evaluate expression" + valueExpression + ": " + ex.getMessage(), ex);
			} catch (ObjectNotFoundException ex) {
				LoggingUtils.logException(LOGGER, "Couldn't evaluate expression " + valueExpression + ".",
						ex);
				throw new ObjectNotFoundException(
						"Couldn't evaluate expression" + valueExpression + ": " + ex.getMessage(), ex);
			} catch (ExpressionEvaluationException ex) {
				LoggingUtils.logException(LOGGER, "Couldn't evaluate expression " + valueExpression + ".",
						ex);
				throw new ExpressionEvaluationException(
						"Couldn't evaluate expression" + valueExpression + ": " + ex.getMessage(), ex);
			}

		} else {
			throw new IllegalStateException("Unsupported filter type: " + filter.getClass());
		}

	}

	private static ObjectFilter createFilterForNoValue(ObjectFilter filter, ExpressionType valueExpression) throws ExpressionEvaluationException {
		QueryInterpretationOfNoValueType queryInterpretationOfNoValue = valueExpression.getQueryInterpretationOfNoValue();
		if (queryInterpretationOfNoValue == null) {
			queryInterpretationOfNoValue = QueryInterpretationOfNoValueType.FILTER_EQUAL_NULL;
		}

		switch (queryInterpretationOfNoValue) {

			case FILTER_UNDEFINED:
				return UndefinedFilter.createUndefined();

			case FILTER_NONE:
				return NoneFilter.createNone();

			case FILTER_ALL:
				return AllFilter.createAll();

			case FILTER_EQUAL_NULL:
				if (filter instanceof PropertyValueFilter) {
					PropertyValueFilter evaluatedFilter = (PropertyValueFilter) filter.clone();
					evaluatedFilter.setExpression(null);
					return evaluatedFilter;
				} else if (filter instanceof InOidFilter) {
					return NoneFilter.createNone();
				} else {
					throw new IllegalArgumentException("Unknow filter to evaluate: " + filter);
				}

			case ERROR:
				throw new ExpressionEvaluationException("Expression "+valueExpression+" evaluated to no value");

			default:
				throw new IllegalArgumentException("Unknown value "+queryInterpretationOfNoValue+" in queryInterpretationOfNoValue in "+valueExpression);

		}

	}

	private static <V extends PrismValue> V evaluateExpression(ExpressionVariables variables,
			PrismContext prismContext, ExpressionType expressionType, ObjectFilter filter,
			ExpressionFactory expressionFactory, String shortDesc, Task task, OperationResult parentResult)
					throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException {

		// TODO rafactor after new query engine is implemented
		ItemDefinition outputDefinition = null;
		if (filter instanceof ValueFilter) {
			outputDefinition = ((ValueFilter) filter).getDefinition();
		}

		if (outputDefinition == null) {
			outputDefinition = new PrismPropertyDefinition(ExpressionConstants.OUTPUT_ELMENT_NAME,
					DOMUtil.XSD_STRING, prismContext);
		}

		return evaluateExpression(variables, outputDefinition, expressionType, expressionFactory, shortDesc,
				task, parentResult);

		// String expressionResult =
		// expressionHandler.evaluateExpression(currentShadow, valueExpression,
		// shortDesc, result);
	}

	public static <V extends PrismValue, D extends ItemDefinition> V evaluateExpression(
			ExpressionVariables variables, D outputDefinition, ExpressionType expressionType,
			ExpressionFactory expressionFactory, String shortDesc, Task task, OperationResult parentResult)
					throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException {

		Expression<V, D> expression = expressionFactory.makeExpression(expressionType, outputDefinition,
				shortDesc, task, parentResult);

		ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, variables, shortDesc, task,
				parentResult);
		PrismValueDeltaSetTriple<V> outputTriple = expression.evaluate(params);

		LOGGER.trace("Result of the expression evaluation: {}", outputTriple);

		if (outputTriple == null) {
			return null;
		}
		Collection<V> nonNegativeValues = outputTriple.getNonNegativeValues();
		if (nonNegativeValues == null || nonNegativeValues.isEmpty()) {
			return null;
		}
		if (nonNegativeValues.size() > 1) {
			throw new ExpressionEvaluationException("Expression returned more than one value ("
					+ nonNegativeValues.size() + ") in " + shortDesc);
		}

		return nonNegativeValues.iterator().next();
	}

	private static Collection<String> evaluateStringExpression(ExpressionVariables variables,
			PrismContext prismContext, ExpressionType expressionType, ExpressionFactory expressionFactory,
			String shortDesc, Task task, OperationResult parentResult)
					throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException {

		PrismPropertyDefinition<String> outputDefinition = new PrismPropertyDefinition(
				ExpressionConstants.OUTPUT_ELMENT_NAME, DOMUtil.XSD_STRING, prismContext);
		outputDefinition.setMaxOccurs(-1);
		Expression<PrismPropertyValue<String>, PrismPropertyDefinition<String>> expression = expressionFactory
				.makeExpression(expressionType, outputDefinition, shortDesc, task, parentResult);

		ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, variables, shortDesc, task,
				parentResult);
		PrismValueDeltaSetTriple<PrismPropertyValue<String>> outputTriple = expression.evaluate(params);

		LOGGER.trace("Result of the expression evaluation: {}", outputTriple);

		if (outputTriple == null) {
			return null;
		}
		Collection<PrismPropertyValue<String>> nonNegativeValues = outputTriple.getNonNegativeValues();
		if (nonNegativeValues == null || nonNegativeValues.isEmpty()) {
			return null;
		}

		return PrismValue.getRealValuesOfCollection((Collection) nonNegativeValues);
		// return nonNegativeValues.iterator().next();
	}

	public static PrismPropertyValue<Boolean> evaluateCondition(ExpressionVariables variables,
			ExpressionType expressionType, ExpressionFactory expressionFactory, String shortDesc, Task task,
			OperationResult parentResult)
					throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException {
		ItemDefinition outputDefinition = new PrismPropertyDefinition<Boolean>(
				ExpressionConstants.OUTPUT_ELMENT_NAME, DOMUtil.XSD_BOOLEAN,
				expressionFactory.getPrismContext());
		return (PrismPropertyValue<Boolean>) evaluateExpression(variables, outputDefinition, expressionType,
				expressionFactory, shortDesc, task, parentResult);
	}

	public static Map<QName, Object> compileVariablesAndSources(ExpressionEvaluationContext params) {
		Map<QName, Object> variablesAndSources = new HashMap<QName, Object>();

		if (params.getVariables() != null) {
			for (Entry<QName, Object> entry : params.getVariables().entrySet()) {
				variablesAndSources.put(entry.getKey(), entry.getValue());
			}
		}

		if (params.getSources() != null) {
			for (Source<?, ?> source : params.getSources()) {
				variablesAndSources.put(source.getName(), source);
			}
		}

		return variablesAndSources;
	}

	public static boolean hasExplicitTarget(List<MappingType> mappingTypes) {
		for (MappingType mappingType : mappingTypes) {
			if (hasExplicitTarget(mappingType)) {
				return true;
			}
		}
		return false;
	}

	private static boolean hasExplicitTarget(MappingType mappingType) {
		return mappingType.getTarget() != null;
	}

	public static boolean computeConditionResult(
			Collection<PrismPropertyValue<Boolean>> booleanPropertyValues) {
		if (booleanPropertyValues == null || booleanPropertyValues.isEmpty()) {
			// No value means false
			return false;
		}
		boolean hasFalse = false;
		for (PrismPropertyValue<Boolean> pval : booleanPropertyValues) {
			Boolean value = pval.getValue();
			if (Boolean.TRUE.equals(value)) {
				return true;
			}
			if (Boolean.FALSE.equals(value)) {
				hasFalse = true;
			}
		}
		if (hasFalse) {
			return false;
		}
		// No value or all values null. Return default.
		return true;
	}

	public static PlusMinusZero computeConditionResultMode(boolean condOld, boolean condNew) {
		if (condOld && condNew) {
			return PlusMinusZero.ZERO;
		}
		if (!condOld && !condNew) {
			return null;
		}
		if (condOld && !condNew) {
			return PlusMinusZero.MINUS;
		}
		if (!condOld && condNew) {
			return PlusMinusZero.PLUS;
		}
		throw new IllegalStateException("notreached");
	}

	public static void addActorVariable(ExpressionVariables scriptVariables,
			SecurityEnforcer securityEnforcer) {
		// There can already be a value, because for mappings, we create the
		// variable before parsing sources.
		// For other scripts we do it just before the execution, to catch all
		// possible places where scripts can be executed.

		UserType oldActor = (UserType) scriptVariables.get(ExpressionConstants.VAR_ACTOR);
		if (oldActor != null) {
			return;
		}

		UserType actor = null;
		try {
			if (securityEnforcer != null) {
				if (!securityEnforcer.isAuthenticated()) {
					// Nothing to set up. This is most likely evaluation of role
					// condition before
					// the authentication is complete.
					return;
				}
				MidPointPrincipal principal = securityEnforcer.getPrincipal();
				if (principal != null) {
					actor = principal.getUser();
				}
			}
			if (actor == null) {
				LOGGER.error("Couldn't get principal information - the 'actor' variable is set to null");
			}
		} catch (SecurityViolationException e) {
			LoggingUtils.logUnexpectedException(LOGGER,
					"Couldn't get principal information - the 'actor' variable is set to null", e);
		}
		scriptVariables.addVariableDefinition(ExpressionConstants.VAR_ACTOR, actor);
	}

	public static <D extends ItemDefinition> Object convertToOutputValue(Long longValue, D outputDefinition,
			Protector protector) throws ExpressionEvaluationException, SchemaException {
		if (longValue == null) {
			return null;
		}
		QName outputType = outputDefinition.getTypeName();
		if (outputType.equals(DOMUtil.XSD_INT)) {
			return longValue.intValue();
		} else if (outputType.equals(DOMUtil.XSD_LONG)) {
			return longValue;
		} else {
			return convertToOutputValue(longValue.toString(), outputDefinition, protector);
		}
	}

	public static <D extends ItemDefinition> Object convertToOutputValue(String stringValue,
			D outputDefinition, Protector protector) throws ExpressionEvaluationException, SchemaException {
		if (stringValue == null) {
			return null;
		}
		QName outputType = outputDefinition.getTypeName();
		if (outputType.equals(DOMUtil.XSD_STRING)) {
			return stringValue;
		} else if (outputType.equals(ProtectedStringType.COMPLEX_TYPE)) {
			try {
				return protector.encryptString(stringValue);
			} catch (EncryptionException e) {
				throw new ExpressionEvaluationException("Crypto error: " + e.getMessage(), e);
			}
		} else if (XmlTypeConverter.canConvert(outputType)) {
			Class<?> outputJavaType = XsdTypeMapper.toJavaType(outputType);
			try {
				return XmlTypeConverter.toJavaValue(stringValue, outputJavaType, true);
			} catch (NumberFormatException e) {
				throw new SchemaException("Cannot convert string '" + stringValue + "' to data type "
						+ outputType + ": invalid number format", e);
			} catch (IllegalArgumentException e) {
				throw new SchemaException("Cannot convert string '" + stringValue + "' to data type "
						+ outputType + ": " + e.getMessage(), e);
			}
		} else {
			throw new IllegalArgumentException(
					"Expression cannot generate values for properties of type " + outputType);
		}
	}

}