<commit_before>/**
 * Copyright 2011-2014 eBusiness Information, Groupe Excilys (www.ebusinessinformation.fr)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 		http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.gatling.core.session.el

import java.lang.{ StringBuilder => JStringBuilder }

import scala.concurrent.forkjoin.ThreadLocalRandom
import scala.reflect.ClassTag

import io.gatling.core.session.{ Expression, Session }
import io.gatling.core.util.NumberHelper.IntString
import io.gatling.core.util.TypeHelper.TypeCaster
import io.gatling.core.validation.{ FailureWrapper, SuccessWrapper, Validation, Success, Failure }

import scala.util.parsing.combinator.RegexParsers

object ELMessages {
  def undefinedSeqIndex(name: String, index: Int) = s"Seq named '$name' is undefined for index $index".failure
  def undefinedSessionAttribute(name: String) = s"No attribute named '$name' is defined".failure
  def undefinedMapKey(map: String, key: String) = s"Map named '$map' does not contain key '$key'".failure
  def sizeNotSupported(value: Any, name: String) = s"$value named '$name' does not support .size function".failure
  def accessByKeyNotSupported(value: Any, name: String) = s"$value named '$name' does not support access by key".failure
  def randomNotSupported(value: Any, name: String) = s"$value named '$name' does not support .random function".failure
  def indexAccessNotSupported(value: Any, name: String) = s"$value named '$name' does not support index access".failure
}

trait Part[+T] {
  def apply(session: Session): Validation[T]
}

case class StaticPart(string: String) extends Part[String] {
  def apply(session: Session): Validation[String] = string.success
}

case class AttributePart(name: String) extends Part[Any] {
  def apply(session: Session): Validation[Any] = session(name).validate[Any]
}

case class SizePart(seqPart: Part[Any], name: String) extends Part[Int] {
  def apply(session: Session): Validation[Int] =
    seqPart(session) match {
      case Success(t: Traversable[_])                   => t.size.success
      case Success(collection: java.util.Collection[_]) => collection.size.success
      case Success(map: java.util.Map[_, _])            => map.size.success
      case Success(arr: Array[_])                       => arr.length.success
      case Success(other)                               => ELMessages.sizeNotSupported(other, name)
      case f: Failure                                   => f
    }
}

case class RandomPart(seq: Part[Any], name: String) extends Part[Any] {
  def apply(session: Session): Validation[Any] = {
      def random(size: Int) = ThreadLocalRandom.current.nextInt(size)

    seq(session) match {
      case Success(s: Seq[_])               => s(random(s.size)).success
      case Success(list: java.util.List[_]) => list.get(random(list.size)).success
      case Success(arr: Array[_])           => arr(random(arr.length)).success
      case Success(other)                   => ELMessages.randomNotSupported(other, name)
      case f: Failure                       => f
    }
  }
}

case class SeqElementPart(seq: Part[Any], seqName: String, index: String) extends Part[Any] {
  def apply(session: Session): Validation[Any] = {

      def seqElementPart(index: Int): Validation[Any] = seq(session) match {
        case Success(s: Seq[_]) =>
          s.lift(index) match {
            case Some(e) => e.success
            case None    => ELMessages.undefinedSeqIndex(seqName, index)
          }

        case Success(arr: Array[_]) =>
          if (index < arr.length) arr(index).success
          else ELMessages.undefinedSeqIndex(seqName, index)

        case Success(list: java.util.List[_]) =>
          if (index < list.size) list.get(index).success
          else ELMessages.undefinedSeqIndex(seqName, index)

        case Success(other) => ELMessages.indexAccessNotSupported(other, seqName)

        case f: Failure     => f
      }

    index match {
      case IntString(i) => seqElementPart(i)
      case _            => session(index).validate[Int].flatMap(seqElementPart)
    }
  }
}

case class MapKeyPart(map: Part[Any], mapName: String, key: String) extends Part[Any] {

  def apply(session: Session): Validation[Any] = {
    map(session) match {
      case Success(m: Map[Any, Any]) => m.get(key) match {
        case Some(value) => value.success
        case None        => ELMessages.undefinedMapKey(mapName, key)
      }

      case Success(map: java.util.Map[Any, Any]) =>
        if (map.containsKey(key)) map.get(key).success
        else ELMessages.undefinedMapKey(mapName, key)

      case Success(other) => ELMessages.accessByKeyNotSupported(other, mapName)

      case f: Failure     => f
    }
  }
}

class ELParserException(string: String, msg: String) extends Exception(s"Failed to parse $string with error '$msg'")
object ELMissingAttributeName extends Exception("${} is not valid and must contain an attribute name")

object ELCompiler {

  val StaticPartPattern = "[^$]+".r
  val NamePattern = "[^.${}()]+".r

  val ElCompiler = new ThreadLocal[ELCompiler] { override def initialValue = new ELCompiler }

  def compile[T: ClassTag](string: String): Expression[T] = {
    val parts = ElCompiler.get.parseEl(string)

    parts match {
      case List(StaticPart(staticStr)) =>
        val stringV = staticStr.asValidation[T]
        _ => stringV

        case List(dynamicPart) => dynamicPart(_).flatMap(_.asValidation[T])

      case _ =><commit_msg>cleaner with flatMap<commit_after>/**
 * Copyright 2011-2014 eBusiness Information, Groupe Excilys (www.ebusinessinformation.fr)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 		http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.gatling.core.session.el

import java.lang.{ StringBuilder => JStringBuilder }
import java.util.{ Collection => JCollection, List => JList, Map => JMap }

import scala.concurrent.forkjoin.ThreadLocalRandom
import scala.reflect.ClassTag

import io.gatling.core.session.{ Expression, Session }
import io.gatling.core.util.NumberHelper.IntString
import io.gatling.core.util.TypeHelper.TypeCaster
import io.gatling.core.validation.{ FailureWrapper, SuccessWrapper, Validation }

import scala.util.parsing.combinator.RegexParsers

object ELMessages {
  def undefinedSeqIndex(name: String, index: Int) = s"Seq named '$name' is undefined for index $index".failure
  def undefinedSessionAttribute(name: String) = s"No attribute named '$name' is defined".failure
  def undefinedMapKey(map: String, key: String) = s"Map named '$map' does not contain key '$key'".failure
  def sizeNotSupported(value: Any, name: String) = s"$value named '$name' does not support .size function".failure
  def accessByKeyNotSupported(value: Any, name: String) = s"$value named '$name' does not support access by key".failure
  def randomNotSupported(value: Any, name: String) = s"$value named '$name' does not support .random function".failure
  def indexAccessNotSupported(value: Any, name: String) = s"$value named '$name' does not support index access".failure
}

trait Part[+T] {
  def apply(session: Session): Validation[T]
}

case class StaticPart(string: String) extends Part[String] {
  def apply(session: Session): Validation[String] = string.success
}

case class AttributePart(name: String) extends Part[Any] {
  def apply(session: Session): Validation[Any] = session(name).validate[Any]
}

case class SizePart(seqPart: Part[Any], name: String) extends Part[Int] {
  def apply(session: Session): Validation[Int] =
    seqPart(session).flatMap {
      _ match {
        case t: Traversable[_]          => t.size.success
        case collection: JCollection[_] => collection.size.success
        case map: JMap[_, _]            => map.size.success
        case arr: Array[_]              => arr.length.success
        case other                      => ELMessages.sizeNotSupported(other, name)
      }
    }
}

case class RandomPart(seq: Part[Any], name: String) extends Part[Any] {
  def apply(session: Session): Validation[Any] = {
      def random(size: Int) = ThreadLocalRandom.current.nextInt(size)

    seq(session).flatMap {
      _ match {
        case seq: Seq[_]    => seq(random(seq.size)).success
        case list: JList[_] => list.get(random(list.size)).success
        case arr: Array[_]  => arr(random(arr.length)).success
        case other          => ELMessages.randomNotSupported(other, name)
      }
    }
  }
}

case class SeqElementPart(seq: Part[Any], seqName: String, index: String) extends Part[Any] {
  def apply(session: Session): Validation[Any] = {

      def seqElementPart(index: Int): Validation[Any] = seq(session).flatMap {
        _ match {

          case seq: Seq[_] =>
            if (seq.isDefinedAt(index)) seq(index).success
            else ELMessages.undefinedSeqIndex(seqName, index)

          case arr: Array[_] =>
            if (index < arr.length) arr(index).success
            else ELMessages.undefinedSeqIndex(seqName, index)

          case list: JList[_] =>
            if (index < list.size) list.get(index).success
            else ELMessages.undefinedSeqIndex(seqName, index)

          case other => ELMessages.indexAccessNotSupported(other, seqName)
        }
      }

    index match {
      case IntString(i) => seqElementPart(i)
      case _            => session(index).validate[Int].flatMap(seqElementPart)
    }
  }
}

case class MapKeyPart(map: Part[Any], mapName: String, key: String) extends Part[Any] {

  def apply(session: Session): Validation[Any] = map(session).flatMap {
    _ match {
      case m: Map[Any, Any] => m.get(key) match {
        case Some(value) => value.success
        case None        => ELMessages.undefinedMapKey(mapName, key)
      }

      case map: JMap[Any, Any] =>
        if (map.containsKey(key)) map.get(key).success
        else ELMessages.undefinedMapKey(mapName, key)

      case other => ELMessages.accessByKeyNotSupported(other, mapName)
    }
  }
}

class ELParserException(string: String, msg: String) extends Exception(s"Failed to parse $string with error '$msg'")
object ELMissingAttributeName extends Exception("${} is not valid and must contain an attribute name")

object ELCompiler {

  val StaticPartPattern = "[^$]+".r
  val NamePattern = "[^.${}()]+".r

  val ElCompiler = new ThreadLocal[ELCompiler] { override def initialValue = new ELCompiler }

  def compile[T: ClassTag](string: String): Expression[T] = {
    val parts = ElCompiler.get.parseEl(string)

    parts match {
      case List(StaticPart(staticStr)) =>
        val stringV = staticStr.asValidation[T]
        _ => stringV

        case List(dynamicPart) => dynamicPart(_).flatMap(_.asValidation[T])

      case _ =>
{'fraction_non_alphanumeric': 0.11660351360661385, 'fraction_numerical': 0.0020668274199104374, 'mean_word_length': 3.3922087745839637, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 62, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5833114', 'n_tokens_mistral': 3386, 'n_tokens_neox': 3292, 'n_words': 957}