├── .github └── workflows │ ├── ci.yml │ └── clean.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.sbt ├── project ├── build.properties └── plugins.sbt ├── scex-core └── src │ ├── main │ └── scala │ │ └── com │ │ └── avsystem │ │ └── scex │ │ ├── EvaluationException.scala │ │ ├── Expression.scala │ │ ├── ExpressionContext.scala │ │ ├── ExpressionDebugInfo.scala │ │ ├── ExpressionProfile.scala │ │ ├── NamedSource.scala │ │ ├── Setter.scala │ │ ├── Type.scala │ │ ├── compiler │ │ ├── CachingScexCompiler.scala │ │ ├── ClassfileReusingScexCompiler.scala │ │ ├── CodeGeneration.scala │ │ ├── ContextTypeInfo.scala │ │ ├── DefaultScexCompiler.scala │ │ ├── ExpressionDef.scala │ │ ├── ExpressionMacroProcessor.scala │ │ ├── ExpressionSourceFile.scala │ │ ├── ExpressionTreeAttachment.scala │ │ ├── JavaTypeParsing.scala │ │ ├── Markers.scala │ │ ├── ScexCompiler.scala │ │ ├── ScexGlobal.scala │ │ ├── ScexSettings.scala │ │ ├── ScexSourceFile.scala │ │ ├── SourceInfo.scala │ │ ├── SymbolErasures.scala │ │ ├── TemplateInterpolations.scala │ │ ├── TemplateOptimizingScexCompiler.scala │ │ ├── WeakReferenceWrappingScexCompiler.scala │ │ ├── annotation │ │ │ ├── Input.scala │ │ │ ├── NotValidated.scala │ │ │ ├── RootAdapter.scala │ │ │ └── RootValue.scala │ │ ├── presentation │ │ │ ├── CachingScexPresentationCompiler.scala │ │ │ ├── IGlobal.scala │ │ │ ├── ScexPresentationCompiler.scala │ │ │ └── ast │ │ │ │ ├── Attachments.scala │ │ │ │ ├── Flags.scala │ │ │ │ ├── Modifiers.scala │ │ │ │ ├── Name.scala │ │ │ │ ├── PrettyPrint.scala │ │ │ │ ├── Translator.scala │ │ │ │ └── Tree.scala │ │ └── xmlfriendly │ │ │ ├── XmlFriendlyScexCompiler.scala │ │ │ └── XmlFriendlyTranslator.scala │ │ ├── japi │ │ ├── DefaultJavaScexCompiler.scala │ │ ├── JavaExpressionContext.scala │ │ ├── JavaScexCompiler.scala │ │ ├── ScalaTypeTokens.scala │ │ └── XmlFriendlyJavaScexCompiler.scala │ │ ├── parsing │ │ ├── PString.scala │ │ ├── PositionMapping.scala │ │ ├── PositionTrackingParsers.scala │ │ ├── ScalaParsingCommons.scala │ │ └── TemplateParser.scala │ │ ├── presentation │ │ ├── Attributes.scala │ │ ├── MathAttributes.scala │ │ ├── SymbolAttributes.scala │ │ └── annotation │ │ │ ├── Documentation.java │ │ │ └── ParameterNames.java │ │ ├── symboldsl │ │ ├── SymbolDsl.scala │ │ ├── SymbolInfo.scala │ │ ├── SymbolInfoList.scala │ │ └── TypeInfo.scala │ │ ├── util │ │ ├── CacheImplicits.scala │ │ ├── CommonUtils.scala │ │ ├── DynamicAdapters.scala │ │ ├── DynamicVariableAccessor.scala │ │ ├── Fluent.scala │ │ ├── Literal.scala │ │ ├── LoggingUtils.scala │ │ ├── NamingThreadFactory.scala │ │ ├── PredefinedAccessSpecs.scala │ │ ├── SimpleContext.scala │ │ └── TypesafeEquals.scala │ │ └── validation │ │ ├── SymbolValidator.scala │ │ ├── SyntaxValidator.scala │ │ └── ValidationContext.scala │ └── test │ ├── java │ └── com │ │ └── avsystem │ │ └── scex │ │ └── compiler │ │ ├── BaseOne.java │ │ ├── BaseTwo.java │ │ ├── BoundedParameterizedClass.java │ │ ├── DerivedJavaRoot.java │ │ ├── EnumInside.java │ │ ├── JavaRoot.java │ │ ├── JavaRootWithGetter.java │ │ ├── JavaSetterTarget.java │ │ ├── JavaTypes.java │ │ ├── OuterClass.java │ │ ├── ParameterizedClass.java │ │ ├── RecursiveGenericClass.java │ │ ├── SubRoot.java │ │ └── overriding │ │ ├── Base.java │ │ ├── DynamicGetter.java │ │ ├── GenericBase.java │ │ ├── Klass.java │ │ └── Specialized.java │ ├── resources │ └── logback.xml │ └── scala │ └── com │ └── avsystem │ └── scex │ └── compiler │ ├── ArbitraryCompilationTest.scala │ ├── ClassTaggedContext.scala │ ├── ClassfileReusingTest.scala │ ├── CompilationTest.scala │ ├── ContextAccessingRoot.scala │ ├── DynamicVariables.scala │ ├── FancySplicedRoot.scala │ ├── InterceptingPluginScexCompiler.scala │ ├── JavaScexCompilerTest.scala │ ├── JavaTypeParsingTest.scala │ ├── LiteralExpressionsTest.scala │ ├── OufOfDateUnitScexCompilerTest.scala │ ├── ScalaParsingCommonsTest.scala │ ├── ScalaTypeTokensTest.scala │ ├── ScexCompilationCachingTest.scala │ ├── ScexCompilerTest.scala │ ├── SetterExpressionsTest.scala │ ├── ShiftInfoPositionMappingTest.scala │ ├── SomeDynamic.scala │ ├── TemplateExpressionsTest.scala │ ├── TemplateParserTest.scala │ ├── TestExtensions.scala │ ├── TestUtils.scala │ ├── TypesafeEqualsTest.scala │ ├── ValueRoot.scala │ ├── presentation │ ├── CompletionTest.scala │ ├── ScopeAndTypeCompletionTest.scala │ └── TypeCompletionPrefixTest.scala │ └── xmlfriendly │ ├── PStringTest.scala │ ├── XmlFriendlyCompilerTest.scala │ └── XmlFriendlyTranslatorTest.scala ├── scex-macros └── src │ └── main │ ├── scala-2.12 │ └── com │ │ └── avsystem │ │ └── scex │ │ └── util │ │ └── CrossMacroUtils.scala │ ├── scala-2.13 │ └── com │ │ └── avsystem │ │ └── scex │ │ └── util │ │ └── CrossMacroUtils.scala │ └── scala │ └── com │ └── avsystem │ └── scex │ ├── Macros.scala │ ├── symboldsl │ ├── SymbolDslMacros.scala │ └── SymbolInfoParser.scala │ └── util │ ├── MacroUtils.scala │ └── TypeWrapper.scala ├── scex-test └── src │ └── main │ ├── java │ ├── BaseBridge.java │ ├── BaseStatic.java │ ├── ConcreteBridge.java │ ├── JavaClass.java │ ├── JavaDynamic.java │ ├── JavaLol.java │ ├── Static.java │ └── TypedLol.java │ ├── resources │ └── logback.xml │ └── scala │ ├── AutoConvertTest.scala │ ├── ExistentialCase.scala │ ├── MemoryTest.scala │ ├── MethodTypes.scala │ ├── Playground.scala │ ├── ScalaClass.scala │ ├── SymbolValidatorTests.scala │ ├── Target.scala │ ├── TemplateOptimizingPerformanceTest.scala │ ├── TypeConvertersTest.scala │ ├── ValidationTest.scala │ ├── WildcardsTest.scala │ └── com │ └── avsystem │ └── scex │ └── example │ └── Quickstart.scala └── scex-util └── src ├── main └── scala │ └── com │ └── avsystem │ └── scex │ └── util │ ├── Bytes.scala │ ├── CommonDateFormat.scala │ ├── CommonExpressionUtils.scala │ ├── CommonSymbolValidators.scala │ ├── EnrichedArray.scala │ ├── EnrichedDate.scala │ ├── EscapedBytes.scala │ ├── ExpressionRecoverableException.scala │ ├── JavaCollectionExtensions.scala │ ├── StringMiscOps.scala │ ├── StringNetworkOps.scala │ └── function │ ├── DateUtil.java │ ├── DateUtilImpl.java │ ├── FormatUtil.java │ ├── FormatUtilImpl.java │ ├── NetFunctions.java │ ├── NetUtil.java │ ├── NetUtilImpl.java │ ├── StringFunctions.java │ ├── StringUtil.java │ └── StringUtilImpl.java └── test └── scala └── com └── avsystem └── scex └── util ├── EnrichedDateTest.scala └── QmarkTest.scala /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Continuous Integration 9 | 10 | on: 11 | pull_request: 12 | branches: ['**'] 13 | push: 14 | branches: ['**'] 15 | tags: [v*] 16 | 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | 20 | jobs: 21 | build: 22 | name: Build and Test 23 | strategy: 24 | matrix: 25 | os: [ubuntu-latest] 26 | scala: [2.13.16] 27 | java: [temurin@17, temurin@21] 28 | runs-on: ${{ matrix.os }} 29 | steps: 30 | - name: Checkout current branch (full) 31 | uses: actions/checkout@v4 32 | with: 33 | fetch-depth: 0 34 | 35 | - name: Setup Java (temurin@17) 36 | if: matrix.java == 'temurin@17' 37 | uses: actions/setup-java@v4 38 | with: 39 | distribution: temurin 40 | java-version: 17 41 | cache: sbt 42 | 43 | - name: Setup Java (temurin@21) 44 | if: matrix.java == 'temurin@21' 45 | uses: actions/setup-java@v4 46 | with: 47 | distribution: temurin 48 | java-version: 21 49 | cache: sbt 50 | 51 | - name: Setup sbt 52 | uses: sbt/setup-sbt@v1 53 | 54 | - name: Check that workflows are up to date 55 | run: sbt '++ ${{ matrix.scala }}' githubWorkflowCheck 56 | 57 | - name: Build project 58 | run: sbt '++ ${{ matrix.scala }}' test 59 | 60 | - name: Compress target directories 61 | run: tar cf targets.tar scex-macros/target scex-core/target scex-test/target scex-util/target target project/target 62 | 63 | - name: Upload target directories 64 | uses: actions/upload-artifact@v4 65 | with: 66 | name: target-${{ matrix.os }}-${{ matrix.scala }}-${{ matrix.java }} 67 | path: targets.tar 68 | 69 | publish: 70 | name: Publish Artifacts 71 | needs: [build] 72 | if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v')) 73 | strategy: 74 | matrix: 75 | os: [ubuntu-latest] 76 | scala: [2.13.16] 77 | java: [temurin@17] 78 | runs-on: ${{ matrix.os }} 79 | steps: 80 | - name: Checkout current branch (full) 81 | uses: actions/checkout@v4 82 | with: 83 | fetch-depth: 0 84 | 85 | - name: Setup Java (temurin@17) 86 | if: matrix.java == 'temurin@17' 87 | uses: actions/setup-java@v4 88 | with: 89 | distribution: temurin 90 | java-version: 17 91 | cache: sbt 92 | 93 | - name: Setup Java (temurin@21) 94 | if: matrix.java == 'temurin@21' 95 | uses: actions/setup-java@v4 96 | with: 97 | distribution: temurin 98 | java-version: 21 99 | cache: sbt 100 | 101 | - name: Setup sbt 102 | uses: sbt/setup-sbt@v1 103 | 104 | - name: Download target directories (2.13.16) 105 | uses: actions/download-artifact@v4 106 | with: 107 | name: target-${{ matrix.os }}-2.13.16-${{ matrix.java }} 108 | 109 | - name: Inflate target directories (2.13.16) 110 | run: | 111 | tar xf targets.tar 112 | rm targets.tar 113 | 114 | - env: 115 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 116 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 117 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 118 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 119 | run: sbt ci-release 120 | -------------------------------------------------------------------------------- /.github/workflows/clean.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Clean 9 | 10 | on: push 11 | 12 | jobs: 13 | delete-artifacts: 14 | name: Delete Artifacts 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | steps: 19 | - name: Delete artifacts 20 | shell: bash {0} 21 | run: | 22 | # Customize those three lines with your repository and credentials: 23 | REPO=${GITHUB_API_URL}/repos/${{ github.repository }} 24 | 25 | # A shortcut to call GitHub API. 26 | ghapi() { curl --silent --location --user _:$GITHUB_TOKEN "$@"; } 27 | 28 | # A temporary file which receives HTTP response headers. 29 | TMPFILE=$(mktemp) 30 | 31 | # An associative array, key: artifact name, value: number of artifacts of that name. 32 | declare -A ARTCOUNT 33 | 34 | # Process all artifacts on this repository, loop on returned "pages". 35 | URL=$REPO/actions/artifacts 36 | while [[ -n "$URL" ]]; do 37 | 38 | # Get current page, get response headers in a temporary file. 39 | JSON=$(ghapi --dump-header $TMPFILE "$URL") 40 | 41 | # Get URL of next page. Will be empty if we are at the last page. 42 | URL=$(grep '^Link:' "$TMPFILE" | tr ',' '\n' | grep 'rel="next"' | head -1 | sed -e 's/.*.*//') 43 | rm -f $TMPFILE 44 | 45 | # Number of artifacts on this page: 46 | COUNT=$(( $(jq <<<$JSON -r '.artifacts | length') )) 47 | 48 | # Loop on all artifacts on this page. 49 | for ((i=0; $i < $COUNT; i++)); do 50 | 51 | # Get name of artifact and count instances of this name. 52 | name=$(jq <<<$JSON -r ".artifacts[$i].name?") 53 | ARTCOUNT[$name]=$(( $(( ${ARTCOUNT[$name]} )) + 1)) 54 | 55 | id=$(jq <<<$JSON -r ".artifacts[$i].id?") 56 | size=$(( $(jq <<<$JSON -r ".artifacts[$i].size_in_bytes?") )) 57 | printf "Deleting '%s' #%d, %'d bytes\n" $name ${ARTCOUNT[$name]} $size 58 | ghapi -X DELETE $REPO/actions/artifacts/$id 59 | done 60 | done 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.ld 2 | *.log 3 | *.class 4 | .idea 5 | !.idea/vcs.xml 6 | !.idea/codeStyleSettings.xml 7 | 8 | *.iws 9 | *.iml 10 | *.ipr 11 | 12 | license.dat 13 | public.key 14 | target 15 | build 16 | out 17 | testClassfileCache 18 | classfileCache 19 | scex_classes 20 | .bsp/ 21 | 22 | *~ 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 AVSystem 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scex 2 | 3 | [![Continuous Integration](https://github.com/AVSystem/scex/actions/workflows/ci.yml/badge.svg)](https://github.com/AVSystem/scex/actions/workflows/ci.yml) 4 | 5 | Extensible, fast and secure Scala expression evaluation engine 6 | 7 | Created, developed and maintained at [AVSystem](http://www.avsystem.com/). 8 | 9 | ## Quickstart 10 | 11 | See [Quickstart Example](https://github.com/AVSystem/scex/blob/master/scex-test/src/main/scala/com/avsystem/scex/example/Quickstart.scala) 12 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | name := "scex" 2 | 3 | inThisBuild(Seq( 4 | organization := "com.avsystem.scex", 5 | scalaVersion := "2.13.16", 6 | githubWorkflowTargetTags ++= Seq("v*"), 7 | githubWorkflowJavaVersions := Seq(JavaSpec.temurin("17"), JavaSpec.temurin("21")), 8 | githubWorkflowPublishTargetBranches := Seq(RefPredicate.StartsWith(Ref.Tag("v"))), 9 | 10 | githubWorkflowPublish := Seq(WorkflowStep.Sbt( 11 | List("ci-release"), 12 | env = Map( 13 | "PGP_PASSPHRASE" -> "${{ secrets.PGP_PASSPHRASE }}", 14 | "PGP_SECRET" -> "${{ secrets.PGP_SECRET }}", 15 | "SONATYPE_PASSWORD" -> "${{ secrets.SONATYPE_PASSWORD }}", 16 | "SONATYPE_USERNAME" -> "${{ secrets.SONATYPE_USERNAME }}" 17 | ) 18 | )), 19 | )) 20 | 21 | val CompileAndTest = "compile->compile;test->test" 22 | 23 | val parserCombinatorsVersion = "2.4.0" 24 | val avsCommonsVersion = "2.22.0" 25 | val slf4jVersion = "2.0.17" 26 | val logbackVersion = "1.5.18" // Tests only 27 | val commonsLang3Version = "3.17.0" 28 | val commonsCodecVersion = "1.18.0" 29 | val guavaVersion = "33.4.8-jre" 30 | val commonsNetVersion = "3.11.1" 31 | val scalatestVersion = "3.2.19" 32 | 33 | val noPublishSettings = Seq( 34 | publish / skip := true 35 | ) 36 | 37 | lazy val subprojectSettings = Seq( 38 | crossVersion := CrossVersion.full, 39 | 40 | javacOptions ++= Seq( 41 | "--release", "17", 42 | "-parameters" 43 | ), 44 | 45 | scalacOptions ++= Seq( 46 | "-feature", 47 | "-deprecation", 48 | "-unchecked", 49 | "-language:implicitConversions", 50 | "-language:existentials", 51 | "-language:dynamics", 52 | "-language:experimental.macros", 53 | "-Xfatal-warnings", 54 | "-Xlint:-missing-interpolator,-adapted-args,-unused,_" 55 | ), 56 | 57 | scalacOptions ++= Seq( 58 | "-Xnon-strict-patmat-analysis", 59 | "-Xlint:-strict-unsealed-patmat" 60 | ), 61 | 62 | projectInfo := ModuleInfo( 63 | nameFormal = "SCEX", 64 | description = "Extensible, fast and secure Scala expression evaluation engine", 65 | homepage = Some(url("https://github.com/AVSystem/scex")), 66 | startYear = Some(2015), 67 | organizationName = "AVSystem", 68 | organizationHomepage = Some(url("http://www.avsystem.com/")), 69 | scmInfo = Some(ScmInfo( 70 | browseUrl = url("https://github.com/AVSystem/scex.git"), 71 | connection = "scm:git:git@github.com:AVSystem/scex.git", 72 | devConnection = Some("scm:git:git@github.com:AVSystem/scex.git") 73 | )), 74 | licenses = Vector(License.MIT), 75 | developers = Vector( 76 | Developer("ddworak", "Dawid Dworak", "d.dworak@avsystem.com", url("https://github.com/ddworak")), 77 | ), 78 | ), 79 | 80 | publishMavenStyle := true, 81 | pomIncludeRepository := { _ => false }, 82 | 83 | Test / fork := true, 84 | Test / javaOptions += "-Xmx1G", 85 | Test / outputStrategy := Some(LoggedOutput(new Logger { 86 | def log(level: Level.Value, message: => String): Unit = () 87 | def success(message: => String): Unit = () 88 | def trace(t: => Throwable): Unit = () 89 | })), 90 | libraryDependencies ++= Seq( 91 | compilerPlugin("com.avsystem.commons" %% "commons-analyzer" % avsCommonsVersion), 92 | "org.scalatest" %% "scalatest" % scalatestVersion % Test 93 | ), 94 | Compile / doc / sources := Seq.empty 95 | ) 96 | 97 | lazy val scex = project.in(file(".")) 98 | .aggregate(`scex-macros`, `scex-core`, `scex-util`, `scex-test`) 99 | .settings(noPublishSettings: _*) 100 | 101 | lazy val `scex-macros` = project 102 | .settings(subprojectSettings: _*) 103 | .settings( 104 | libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value, 105 | libraryDependencies += "com.avsystem.commons" %% "commons-macros" % avsCommonsVersion 106 | ) 107 | 108 | lazy val `scex-core` = project.dependsOn(`scex-macros` % CompileAndTest) 109 | .settings(subprojectSettings: _*) 110 | .settings( 111 | libraryDependencies ++= Seq( 112 | "org.scala-lang" % "scala-reflect" % scalaVersion.value, 113 | "org.scala-lang" % "scala-compiler" % scalaVersion.value, 114 | "org.scala-lang.modules" %% "scala-parser-combinators" % parserCombinatorsVersion, 115 | "com.avsystem.commons" %% "commons-core" % avsCommonsVersion, 116 | "org.slf4j" % "slf4j-api" % slf4jVersion, 117 | "commons-codec" % "commons-codec" % commonsCodecVersion, 118 | "com.google.guava" % "guava" % guavaVersion, 119 | "ch.qos.logback" % "logback-classic" % logbackVersion % Test, 120 | ) 121 | ) 122 | 123 | lazy val `scex-util` = project.dependsOn(`scex-core` % CompileAndTest) 124 | .settings(subprojectSettings: _*) 125 | .settings( 126 | libraryDependencies ++= Seq( 127 | "org.apache.commons" % "commons-lang3" % commonsLang3Version, 128 | "commons-net" % "commons-net" % commonsNetVersion, 129 | ) 130 | ) 131 | 132 | lazy val `scex-test` = project.dependsOn(`scex-core` % CompileAndTest, `scex-util`) 133 | .settings(subprojectSettings: _*) 134 | .settings(noPublishSettings: _*) -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | # suppress inspection "UnusedProperty" 2 | sbt.version=1.11.1 3 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn 2 | 3 | addSbtPlugin("org.jetbrains.scala" % "sbt-ide-settings" % "1.1.2") 4 | addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.11.1") 5 | addSbtPlugin("com.github.sbt" % "sbt-github-actions" % "0.25.0") 6 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/EvaluationException.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | 3 | import scala.util.control.NoStackTrace 4 | 5 | /** 6 | * Created: 16-06-2014 7 | * Author: ghik 8 | */ 9 | case class EvaluationException(cause: Throwable) extends RuntimeException(cause) with NoStackTrace 10 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/Expression.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | 3 | import com.avsystem.scex.compiler.SourceInfo 4 | 5 | import scala.runtime.AbstractFunction1 6 | import scala.util.control.NonFatal 7 | 8 | trait Expression[-C <: ExpressionContext[_, _], +T] extends (C => T) { 9 | @throws(classOf[EvaluationException]) 10 | def apply(c: C): T 11 | 12 | def debugInfo: ExpressionDebugInfo 13 | } 14 | 15 | abstract class AbstractExpression[-C <: ExpressionContext[_, _], +T] 16 | extends AbstractFunction1[C, T] with Expression[C, T] { 17 | 18 | def sourceInfo: SourceInfo 19 | 20 | def eval(context: C): T 21 | 22 | final def apply(context: C): T = try eval(context) catch { 23 | case NonFatal(cause) => throw new EvaluationException(cause) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/ExpressionContext.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | 3 | import com.avsystem.scex.compiler.annotation.NotValidated 4 | 5 | import scala.language.higherKinds 6 | 7 | /** 8 | * Created: 23-09-2013 9 | * Author: ghik 10 | */ 11 | trait ExpressionContext[R, V] { 12 | type Root = R 13 | type Var = V 14 | type VarTag[T] 15 | 16 | @NotValidated def root: R 17 | 18 | def setVariable(name: String, value: V): Unit 19 | @NotValidated def getVariable(name: String): V 20 | 21 | def setTypedVariable[T](name: String, value: T)(implicit tag: VarTag[T]): Unit 22 | @NotValidated def getTypedVariable[T](name: String)(implicit tag: VarTag[T]): T 23 | } 24 | 25 | sealed trait NoTag 26 | object NoTag extends NoTag { 27 | @NotValidated implicit val noTag: NoTag = this 28 | } 29 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/ExpressionDebugInfo.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | 3 | import com.avsystem.scex.compiler.ExpressionDef 4 | 5 | /** 6 | * Created: 16-06-2014 7 | * Author: ghik 8 | */ 9 | class ExpressionDebugInfo( 10 | val definition: ExpressionDef) { 11 | 12 | lazy val originalLines = { 13 | val mapping = definition.positionMapping.reverse 14 | val lineLengths = definition.expression.split('\n').iterator.map(l => l.length + 1) 15 | val originalLineStarts = lineLengths.scanLeft(0)(_ + _).map(mapping.apply) 16 | 17 | originalLineStarts.sliding(2).map { 18 | case Seq(start, end) => definition.originalExpression.substring(start, end - 1) 19 | }.toVector 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/ExpressionProfile.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | 3 | import com.avsystem.commons.annotation.bincompat 4 | import com.avsystem.scex.presentation.SymbolAttributes 5 | import com.avsystem.scex.validation.{SymbolValidator, SyntaxValidator} 6 | 7 | class ExpressionProfile( 8 | val name: String, 9 | val syntaxValidator: SyntaxValidator, 10 | val symbolValidator: SymbolValidator, 11 | val symbolAttributes: SymbolAttributes, 12 | val expressionHeader: String, 13 | val expressionUtils: NamedSource, 14 | val dynamicVariablesEnabled: Boolean) { 15 | 16 | @bincompat 17 | def this( 18 | name: String, 19 | syntaxValidator: SyntaxValidator, 20 | symbolValidator: SymbolValidator, 21 | symbolAttributes: SymbolAttributes, 22 | expressionHeader: String, 23 | expressionUtils: NamedSource 24 | ) = this(name, syntaxValidator, symbolValidator, symbolAttributes, expressionHeader, expressionUtils, dynamicVariablesEnabled = true) 25 | 26 | override def toString = s"ExpressionProfile[$name]" 27 | } 28 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/NamedSource.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | 3 | /** 4 | * Represents named source "file". Used to pass around compilable code fragments in runtime. 5 | * 6 | * Created: 29-10-2014 7 | * Author: ghik 8 | */ 9 | case class NamedSource(name: String, code: String) 10 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/Setter.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | 3 | import scala.annotation.implicitNotFound 4 | 5 | /** 6 | * Created: 28-11-2013 7 | * Author: ghik 8 | */ 9 | trait Setter[-T] extends (T => Unit) { 10 | def acceptedType: Type 11 | } 12 | 13 | // just to look nicer in Java 14 | abstract class AbstractSetter[-T] extends Setter[T] 15 | 16 | @implicitNotFound("Property being set has type ${B}, but value has type ${A} and no conversion is available") 17 | case class SetterConversion[-A, +B](fun: A => B) extends AnyVal 18 | object SetterConversion { 19 | def convert[A, B](a: A)(implicit sc: SetterConversion[A, B]): B = 20 | sc.fun(a) 21 | 22 | implicit def fallbackConversion[A]: SetterConversion[A, A] = 23 | SetterConversion(identity) 24 | } 25 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/Type.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | 3 | /** 4 | * Author: ghik 5 | * Created: 12/10/14. 6 | */ 7 | case class Type(fullRepr: String, erasure: Class[_]) { 8 | override def toString = fullRepr 9 | } 10 | 11 | object Type { 12 | final val NoType = Type("NoType", null) 13 | } 14 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/CachingScexCompiler.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler 3 | 4 | import com.avsystem.scex.compiler.ScexCompiler.CompilationFailedException 5 | import com.avsystem.scex.parsing.PositionMapping 6 | import com.avsystem.scex.validation.{SymbolValidator, SyntaxValidator} 7 | import com.google.common.cache.CacheBuilder 8 | import com.google.common.util.concurrent.ExecutionError 9 | 10 | import java.util.concurrent.{ExecutionException, TimeUnit} 11 | import scala.util.{Failure, Success, Try} 12 | 13 | trait CachingScexCompiler extends ScexCompiler { 14 | 15 | import com.avsystem.scex.util.CommonUtils._ 16 | 17 | private val preprocessingCache = CacheBuilder.newBuilder 18 | .expireAfterAccess(settings.expressionExpirationTime.value, TimeUnit.SECONDS) 19 | .maximumSize(settings.expressionCacheSize.value) 20 | .build[(String, Boolean), (String, PositionMapping)] 21 | 22 | private val expressionCache = CacheBuilder.newBuilder 23 | .expireAfterAccess(settings.expressionExpirationTime.value, TimeUnit.SECONDS) 24 | .maximumSize(settings.expressionCacheSize.value) 25 | .build[ExpressionDef, Try[RawExpression]] 26 | 27 | // holds names of packages to which profiles are compiled 28 | private val profileCompilationResultsCache = 29 | CacheBuilder.newBuilder.build[ExpressionProfile, Try[Option[String]]] 30 | 31 | // holds names of packages to which utils are compiled 32 | private val utilsCompilationResultsCache = 33 | CacheBuilder.newBuilder.build[String, Try[Option[String]]] 34 | 35 | // holds code of implicit adapters over Java classes that add Scala-style getters to Java bean getters 36 | private val javaGetterAdaptersCache = 37 | CacheBuilder.newBuilder.build[(String, String, Seq[Class[_]], Boolean), Try[Seq[Option[String]]]] 38 | 39 | private val syntaxValidatorsCache = 40 | CacheBuilder.newBuilder.build[String, SyntaxValidator] 41 | 42 | private val symbolValidatorsCache = 43 | CacheBuilder.newBuilder.build[String, SymbolValidator] 44 | 45 | // used to avoid unexpected exceptions caching, such as a random NPE thrown during a machine I/O error 46 | private def invalidateCacheEntry(result: Try[_], invalidate: () => Unit): Unit = 47 | if (!settings.cacheUnexpectedCompilationExceptions.value) 48 | result match { 49 | case Failure(_: CompilationFailedException) | Success(_) => 50 | case Failure(_) => invalidate() 51 | } 52 | 53 | override protected def preprocess(expression: String, template: Boolean) = 54 | unwrapExecutionException( 55 | preprocessingCache.get((expression, template), callable(super.preprocess(expression, template)))) 56 | 57 | override protected def compileExpression(exprDef: ExpressionDef) = { 58 | val result = unwrapExecutionException(expressionCache.get(exprDef, callable(super.compileExpression(exprDef)))) 59 | invalidateCacheEntry(result, () => expressionCache.invalidate(exprDef)) 60 | 61 | result 62 | } 63 | 64 | override protected def compileProfileObject(profile: ExpressionProfile) = { 65 | val result = unwrapExecutionException(underLock( 66 | profileCompilationResultsCache.get(profile, callable(super.compileProfileObject(profile))))) 67 | invalidateCacheEntry(result, () => profileCompilationResultsCache.invalidate(profile)) 68 | 69 | result 70 | } 71 | 72 | override protected def compileExpressionUtils(source: NamedSource) = { 73 | val result = unwrapExecutionException(underLock( 74 | utilsCompilationResultsCache.get(source.name, callable(super.compileExpressionUtils(source))))) 75 | invalidateCacheEntry(result, () => utilsCompilationResultsCache.invalidate(source.name)) 76 | 77 | result 78 | } 79 | 80 | override protected def compileJavaGetterAdapters(profile: ExpressionProfile, name: String, classes: Seq[Class[_]], full: Boolean) = 81 | unwrapExecutionException(underLock( 82 | javaGetterAdaptersCache.get((profile.name, name, classes, full), callable(super.compileJavaGetterAdapters(profile, name, classes, full))))) 83 | 84 | override def compileSyntaxValidator(source: NamedSource) = 85 | unwrapExecutionException( 86 | syntaxValidatorsCache.get(source.name, callable(super.compileSyntaxValidator(source)))) 87 | 88 | override def compileSymbolValidator(source: NamedSource) = 89 | unwrapExecutionException( 90 | symbolValidatorsCache.get(source.name, callable(super.compileSymbolValidator(source)))) 91 | 92 | override def reset(): Unit = underLock { 93 | super.reset() 94 | expressionCache.invalidateAll() 95 | profileCompilationResultsCache.invalidateAll() 96 | utilsCompilationResultsCache.invalidateAll() 97 | javaGetterAdaptersCache.invalidateAll() 98 | syntaxValidatorsCache.invalidateAll() 99 | symbolValidatorsCache.invalidateAll() 100 | } 101 | 102 | private def unwrapExecutionException[T](code: => T) = 103 | try code catch { 104 | case e: ExecutionException => throw e.getCause 105 | case e: ExecutionError => throw e.getCause 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/ContextTypeInfo.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler 2 | 3 | import com.avsystem.commons.annotation.bincompat 4 | import com.avsystem.scex.{ExpressionContext, Macros} 5 | 6 | case class ContextTypeInfo[C](fullTypeString: String, rootObjectClass: Class[_]) { 7 | @bincompat 8 | private[compiler] def this(fullTypeString: String, rootObjectClassName: String) = 9 | this(fullTypeString, Class.forName(rootObjectClassName)) 10 | } 11 | object ContextTypeInfo { 12 | // Scala, seriously, why do I have to do this with a macro? 13 | implicit def typeInfo[C <: ExpressionContext[_, _]]: ContextTypeInfo[C] = macro Macros.mkContextTypeInfo[C] 14 | 15 | @bincompat 16 | private[compiler] def apply[C](fullTypeString: String, rootObjectClassName: String): ContextTypeInfo[C] = 17 | new ContextTypeInfo(fullTypeString, Class.forName(rootObjectClassName)) 18 | } 19 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/DefaultScexCompiler.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler 3 | 4 | import com.avsystem.scex.compiler.presentation.{CachingScexPresentationCompiler, ScexPresentationCompiler} 5 | 6 | /** 7 | * Created: 17-10-2013 8 | * Author: ghik 9 | */ 10 | class DefaultScexCompiler(val settings: ScexSettings) 11 | extends ScexCompiler 12 | with ScexPresentationCompiler 13 | with ClassfileReusingScexCompiler 14 | with TemplateOptimizingScexCompiler 15 | with CachingScexCompiler 16 | with CachingScexPresentationCompiler 17 | with WeakReferenceWrappingScexCompiler 18 | 19 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/ExpressionDef.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler 3 | 4 | import com.avsystem.scex.parsing.PositionMapping 5 | 6 | /** 7 | * Created: 14-11-2013 8 | * Author: ghik 9 | */ 10 | case class ExpressionDef( 11 | profile: ExpressionProfile, 12 | template: Boolean, 13 | setter: Boolean, 14 | expression: String, 15 | header: String, 16 | contextType: String, 17 | resultType: String, 18 | variableTypes: Map[String, String])( 19 | 20 | val originalExpression: String, 21 | val positionMapping: PositionMapping, 22 | val rootObjectClass: Class[_]) { 23 | } 24 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/ExpressionSourceFile.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler 3 | 4 | import scala.reflect.internal.util.Position 5 | 6 | /** 7 | * Created: 13-12-2013 8 | * Author: ghik 9 | */ 10 | class ExpressionSourceFile( 11 | val exprDef: ExpressionDef, 12 | sourceName: String, 13 | val code: String, 14 | startOffset: Int) extends ScexSourceFile(sourceName, code, shared = false) { 15 | 16 | require(exprDef != null, "Expression definition cannot be null") 17 | 18 | val expressionPos = Position.range(this, startOffset, startOffset, startOffset + exprDef.expression.length) 19 | lazy val bareSource = new ScexSourceFile(sourceName, exprDef.originalExpression, shared = false) 20 | } 21 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/ExpressionTreeAttachment.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler 2 | 3 | /** 4 | * Created: 04-11-2014 5 | * Author: ghik 6 | */ 7 | object ExpressionTreeAttachment 8 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/Markers.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler 3 | 4 | /** 5 | * Created: 12-12-2013 6 | * Author: ghik 7 | */ 8 | object Markers { 9 | 10 | trait Synthetic extends Any 11 | 12 | trait ExpressionUtil extends Any with Synthetic 13 | 14 | trait JavaGetterAdapter extends Any with Synthetic 15 | 16 | trait ProfileObject extends Any with Synthetic 17 | 18 | } 19 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/ScexGlobal.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler 3 | 4 | import com.avsystem.scex.util.MacroUtils 5 | 6 | import scala.collection.mutable 7 | import scala.reflect.internal.util._ 8 | import scala.reflect.io.AbstractFile 9 | import scala.tools.nsc.Global 10 | import scala.tools.nsc.plugins.Plugin 11 | 12 | /** 13 | * Created: 01-04-2014 14 | * Author: ghik 15 | */ 16 | trait ScexGlobal extends Global with MacroUtils with SymbolErasures { 17 | lazy val universe: this.type = this 18 | 19 | def loadAdditionalPlugins(): List[Plugin] = Nil 20 | 21 | def parseExpression(code: String, template: Boolean): Tree = { 22 | val (wrappedCode, offset) = CodeGeneration.wrapForParsing(code, template) 23 | val sourceFile = new BatchSourceFile("(for_parsing)", wrappedCode) 24 | val unit = new CompilationUnit(sourceFile) 25 | val PackageDef(_, List(ModuleDef(_, _, Template(_, _, List(_, expressionTree))))) = new syntaxAnalyzer.UnitParser(unit).parse() 26 | moveTree(expressionTree, -offset) 27 | } 28 | 29 | def movePosition(pos: Position, offset: Int): Position = pos match { 30 | case tp: TransparentPosition => new TransparentPosition(tp.source, tp.start + offset, tp.point + offset, tp.end + offset) 31 | case rp: RangePosition => new RangePosition(rp.source, rp.start + offset, rp.point + offset, rp.end + offset) 32 | case op: OffsetPosition => new OffsetPosition(op.source, op.point + offset) 33 | case _ => pos 34 | } 35 | 36 | def moveTree(tree: Tree, offset: Int): Tree = { 37 | tree.foreach { t => 38 | t.setPos(movePosition(t.pos, offset)) 39 | } 40 | tree 41 | } 42 | 43 | /** 44 | * Locator with slightly modified inclusion check. 45 | */ 46 | class ScexLocator(pos: Position) extends Traverser { 47 | var last: Tree = _ 48 | 49 | def locateIn(root: Tree): Tree = { 50 | this.last = EmptyTree 51 | traverse(root) 52 | this.last 53 | } 54 | 55 | override def traverse(t: Tree): Unit = { 56 | t match { 57 | case tt: TypeTree if tt.original != null && includes(tt.pos, tt.original.pos) => 58 | traverse(tt.original) 59 | case _ => 60 | if (includes(t.pos, pos)) { 61 | if (!t.pos.isTransparent) last = t 62 | super.traverse(t) 63 | } else t match { 64 | case mdef: MemberDef => 65 | traverseTrees(mdef.mods.annotations) 66 | case _ => 67 | } 68 | } 69 | } 70 | 71 | private def includes(pos1: Position, pos2: Position): Boolean = 72 | (pos1 includes pos2) && pos1.end > pos2.start 73 | } 74 | 75 | override protected def loadRoughPluginsList(): List[Plugin] = 76 | loadAdditionalPlugins() ::: super.loadRoughPluginsList() 77 | 78 | // toplevel symbol dropping is implemented based on how it's done in the Scala Presentation Compiler 79 | // (which happens e.g. when a source file is deleted) 80 | private val toplevelSymbolsMap = new mutable.WeakHashMap[AbstractFile, mutable.Set[Symbol]] 81 | 82 | override def registerTopLevelSym(sym: Symbol): Unit = { 83 | super.registerTopLevelSym(sym) 84 | toplevelSymbolsMap.getOrElseUpdate(sym.sourceFile, new mutable.HashSet) += sym 85 | } 86 | 87 | def forgetSymbolsFromSource(file: AbstractFile): Unit = { 88 | val symbols = toplevelSymbolsMap.get(file).map(_.toSet).getOrElse(Set.empty) 89 | symbols.foreach { s => 90 | //like in: scala.tools.nsc.interactive.Global.filesDeleted 91 | s.owner.info.decls unlink s 92 | } 93 | toplevelSymbolsMap.remove(file) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/ScexSettings.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler 2 | 3 | import java.io.File 4 | 5 | import scala.reflect.io.{Directory, PlainDirectory} 6 | import scala.tools.nsc.Settings 7 | 8 | /** 9 | * Created: 21-10-2014 10 | * Author: ghik 11 | */ 12 | class ScexSettings extends Settings { 13 | 14 | classpath.value = System.getProperty("java.class.path") 15 | exposeEmptyPackage.value = true 16 | // preserving 2.10 behaviour of macro expansion in presentation compiler 17 | // https://github.com/scala/scala/commit/6e4c926b4a4c5e8dd350ae3a150490a794b139ca 18 | // TODO: maybe try to make it work with MacroExpand.Discard ? 19 | Ymacroexpand.value = MacroExpand.Normal 20 | nopredef.value = true 21 | 22 | private val Positive = Some((1, Int.MaxValue)) 23 | 24 | final val expressionExpirationTime = IntSetting("-SCEXexpression-expiration-time", 25 | "Expiration time for expression cache, in seconds", 3600, Positive, _ => None) 26 | 27 | final val expressionCacheSize = IntSetting("-SCEXexpression-cache-size", 28 | "Maximum size of expression cache", 5000, Positive, _ => None) 29 | 30 | final val completionExpirationTime = IntSetting("-SCEXerrors-expiration-time", 31 | "Expiration time for completion caches, in seconds", 600, Positive, _ => None) 32 | 33 | final val errorsCacheSize = IntSetting("-SCEXerrors-cache-size", 34 | "Maximum size of errors cache", 10000, Positive, _ => None) 35 | 36 | final val scopeCompletionCacheSize = IntSetting("-SCEXscope-completion-cache-size", 37 | "Maximum size of scope completion cache", 3000, Positive, _ => None) 38 | 39 | final val typeMembersCacheSize = IntSetting("-SCEXtype-members-cache-size", 40 | "Maximum size of type members cache", 10000, Positive, _ => None) 41 | 42 | final val resetAfterCount = IntSetting("-SCEXreset-after-count", 43 | "Number of compilations after which the compiler will be reset", 2000, Positive, _ => None) 44 | 45 | final val classfileDirectory = StringSetting("-SCEXclassfile-directory", "directory", 46 | "Directory for classfile cache", "") 47 | 48 | final val noPresentation = BooleanSetting("-SCEXno-presentation", 49 | "Turns of the 'presentation' part of the compiler") 50 | 51 | final val noGetterAdapters = BooleanSetting("-SCEXno-getter-adapters", 52 | "Disables generation of Java getter adapter methods") 53 | 54 | final val backwardsCompatCacheVersion = StringSetting("-SCEXbackwards-compat-cache-version", "versionString", 55 | "Additional version string for controlling invalidation of classfile cache", "0") 56 | 57 | final val cacheUnexpectedCompilationExceptions = BooleanSetting("-SCEXcache-unexpected-compilation-exceptions", 58 | "Enables the caching of unexpected exceptions (such as NPE when accessing scex_classes) thrown during the expression compilation. " + 59 | "CompilationFailedExceptions are always cached, regardless of this setting. They indicate e.g. syntax errors, which should always be cached to avoid redundant compilations.", default = false) 60 | 61 | def resolvedClassfileDir: Option[PlainDirectory] = Option(classfileDirectory.value) 62 | .filter(_.trim.nonEmpty).map(path => new PlainDirectory(new Directory(new File(path)))) 63 | } 64 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/ScexSourceFile.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler 2 | 3 | import scala.reflect.internal.util.BatchSourceFile 4 | 5 | /** 6 | * Created: 28-10-2014 7 | * Author: ghik 8 | */ 9 | class ScexSourceFile(name: String, contents: String, val shared: Boolean) 10 | extends BatchSourceFile(name, contents) { 11 | 12 | override def equals(that: Any): Boolean = that match { 13 | case that: ScexSourceFile => file.path == that.file.path && start == that.start 14 | case _ => super.equals(that) 15 | } 16 | 17 | override def hashCode: Int = file.path.## + start.## 18 | 19 | } 20 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/SourceInfo.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler 2 | 3 | /** 4 | * Created: 03-11-2014 5 | * Author: ghik 6 | */ 7 | class SourceInfo( 8 | val sourceName: String, 9 | val fullCode: String, 10 | val startOffset: Int, 11 | val endOffset: Int, 12 | val firstLine: Int, 13 | val lastLine: Int) -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/TemplateInterpolations.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler 3 | 4 | import com.avsystem.scex.compiler.annotation.NotValidated 5 | 6 | import scala.annotation.compileTimeOnly 7 | import scala.language.experimental.macros 8 | 9 | /** 10 | * Created: 18-11-2013 11 | * Author: ghik 12 | */ 13 | trait TemplateInterpolations[T] { 14 | 15 | implicit class TemplateInterpolation(sc: StringContext) { 16 | /** 17 | * String interpolation used to compile template expressions. 18 | * Template expressions have form `part0${arg1}part1${arg2}part2...` where "parts" are plaintext 19 | * fragments (unquoted string literals) and "args" are arbitrary subexpressions. 20 | *

21 | * Template interpolation tries to concatenate `parts` and `args` according to following rules: 22 | *

46 | * 47 | * @return 48 | */ 49 | def t[A](args: A*): T = macro Macros.templateInterpolation_impl[T, A] 50 | 51 | // non-macro version of `t` used only in presentation compiler so that completer doesn't get confused 52 | @compileTimeOnly("this can only be used in scex presentation compiler") 53 | def p[A](args: A*): T = sys.error("stub") 54 | } 55 | 56 | } 57 | 58 | object TemplateInterpolations { 59 | 60 | def safeToString[T](t: T): String = 61 | if (t == null) null else t.toString 62 | 63 | trait Splicer[T] { 64 | def toString(t: T): String 65 | } 66 | 67 | @NotValidated 68 | def concat(parts: String*)(args: Any*): String = { 69 | require(parts.size == args.size + 1) 70 | concatIterator(parts: _*)(args.iterator) 71 | } 72 | 73 | def concatIterator(parts: String*)(args: Iterator[Any]): String = { 74 | val sb = new StringBuilder(parts.head) 75 | (args zip parts.tail.iterator).foreach { 76 | case (arg, part) => sb.append(String.valueOf(arg)).append(part) 77 | } 78 | sb.result() 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/WeakReferenceWrappingScexCompiler.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler 3 | 4 | import scala.ref.WeakReference 5 | import scala.util.Try 6 | 7 | /** 8 | * Wraps compiled expression into a wrapper that only holds weak reference to underlying expression. 9 | * This is to allow actually compiled expression classes to be GCed after the compiler is reset. 10 | * This trait should be used together with CachingScexCompiler to avoid recompilation of expressions 11 | * every time GC wipes out the weak reference. 12 | * 13 | * Created: 02-04-2014 14 | * Author: ghik 15 | */ 16 | trait WeakReferenceWrappingScexCompiler extends ScexCompiler { 17 | 18 | /** 19 | * Wrapper that avoids holding strong reference to actual compiled expression. 20 | */ 21 | private class WeakExpressionWrapper(exprDef: ExpressionDef, initiallyWrapped: RawExpression) extends RawExpression { 22 | var expressionRef = new WeakReference(initiallyWrapped) 23 | 24 | private def wrappedExpression: RawExpression = 25 | expressionRef.get match { 26 | case Some(expr) => expr 27 | case None => 28 | expressionRef = new WeakReference(actuallyCompileExpression(exprDef).get) 29 | wrappedExpression 30 | } 31 | 32 | def apply(context: ExpressionContext[_, _]) = 33 | wrappedExpression.apply(context) 34 | 35 | def debugInfo = 36 | wrappedExpression.debugInfo 37 | } 38 | 39 | private def actuallyCompileExpression(exprDef: ExpressionDef) = 40 | super.compileExpression(exprDef) 41 | 42 | override protected def compileExpression(exprDef: ExpressionDef): Try[RawExpression] = 43 | actuallyCompileExpression(exprDef).map(new WeakExpressionWrapper(exprDef, _)) 44 | } 45 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/annotation/Input.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler.annotation 2 | 3 | import scala.annotation.StaticAnnotation 4 | 5 | /** 6 | * Created: 04-11-2014 7 | * Author: ghik 8 | */ 9 | class Input extends StaticAnnotation -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/annotation/NotValidated.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler.annotation 3 | 4 | import scala.annotation.StaticAnnotation 5 | 6 | /** 7 | * Ident and Select trees whose symbol is annotated with this annotation will not be validated, along with the 8 | * qualifier of Select. This is to mark symbols that serve as always-available API inside expressions, like root object 9 | * or variable getters/setters. 10 | * 11 | * Created: 23-09-2013 12 | * Author: ghik 13 | */ 14 | class NotValidated extends StaticAnnotation 15 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/annotation/RootAdapter.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler.annotation 3 | 4 | class RootAdapter extends RootValue 5 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/annotation/RootValue.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler.annotation 2 | 3 | /** 4 | * Author: ghik 5 | * Created: 13/01/15. 6 | */ 7 | class RootValue extends Input 8 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/presentation/CachingScexPresentationCompiler.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler.presentation 3 | 4 | import java.util.concurrent.{ExecutionException, TimeUnit} 5 | import java.{lang => jl, util => ju} 6 | 7 | import com.avsystem.scex.compiler.ExpressionDef 8 | import com.avsystem.scex.compiler.ScexCompiler.{CompilationFailedException, CompileError} 9 | import com.avsystem.scex.compiler.presentation.ScexPresentationCompiler.{Member, Completion} 10 | import com.avsystem.scex.presentation.SymbolAttributes 11 | import com.avsystem.scex.util.TypeWrapper 12 | import com.google.common.cache.CacheBuilder 13 | import com.google.common.util.concurrent.ExecutionError 14 | 15 | /** 16 | * Created: 12-12-2013 17 | * Author: ghik 18 | */ 19 | trait CachingScexPresentationCompiler extends ScexPresentationCompiler { 20 | 21 | import com.avsystem.scex.util.CommonUtils._ 22 | 23 | private val errorsCache = CacheBuilder.newBuilder 24 | .expireAfterAccess(settings.completionExpirationTime.value, TimeUnit.SECONDS) 25 | .maximumSize(settings.errorsCacheSize.value) 26 | .build[ExpressionDef, List[CompileError]] 27 | 28 | private val scopeCompletionCache = CacheBuilder.newBuilder 29 | .expireAfterAccess(settings.completionExpirationTime.value, TimeUnit.SECONDS) 30 | .maximumSize(settings.scopeCompletionCacheSize.value) 31 | .build[ExpressionDef, Completion] 32 | 33 | case class TypeMembersCacheKey(profile: ExpressionProfile, contextType: String, ownerType: TypeWrapper) 34 | 35 | private val typeMembersCache = CacheBuilder.newBuilder 36 | .expireAfterAccess(settings.completionExpirationTime.value, TimeUnit.SECONDS) 37 | .maximumSize(settings.typeMembersCacheSize.value) 38 | .build[TypeMembersCacheKey, Vector[Member]] 39 | 40 | private val symbolAttributesCache = CacheBuilder.newBuilder 41 | .build[String, SymbolAttributes] 42 | 43 | override protected def getErrors(exprDef: ExpressionDef) = 44 | unwrapExecutionException(errorsCache.get(exprDef, callable(super.getErrors(exprDef)))) 45 | 46 | override protected def getScopeCompletion(exprDef: ExpressionDef) = 47 | unwrapExecutionException(scopeCompletionCache.get(exprDef, callable(super.getScopeCompletion(exprDef)))) 48 | 49 | override protected def getTypeMembers(global: IGlobal)(exprDef: ExpressionDef, ownerTpe: global.Type) 50 | (computeMembers: => Vector[Member]): Vector[Member] = { 51 | 52 | val key = TypeMembersCacheKey(exprDef.profile, exprDef.contextType, TypeWrapper(global)(ownerTpe.map(_.widen))) 53 | unwrapExecutionException(typeMembersCache.get(key, callable(computeMembers))) 54 | } 55 | 56 | override def compileSymbolAttributes(source: NamedSource) = 57 | unwrapExecutionException(symbolAttributesCache.get(source.name, callable(super.compileSymbolAttributes(source)))) 58 | 59 | override def reset() = synchronized { 60 | super.reset() 61 | errorsCache.invalidateAll() 62 | scopeCompletionCache.invalidateAll() 63 | typeMembersCache.invalidateAll() 64 | symbolAttributesCache.invalidateAll() 65 | } 66 | 67 | private def unwrapExecutionException[T](code: => T) = 68 | try code catch { 69 | case e: ExecutionException => throw e.getCause 70 | case e: ExecutionError => throw e.getCause 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/presentation/ast/Attachments.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler.presentation.ast 3 | 4 | import com.avsystem.commons.jiop.JavaInterop._ 5 | 6 | /** 7 | * Created: 12-03-2014 8 | * Author: ghik 9 | */ 10 | case class Attachments(tpe: Type, position: Position) 11 | 12 | object Attachments { 13 | val empty = new Attachments(Type.NoType, null) 14 | } 15 | 16 | case class Symbol(names: List[Name]) { 17 | def name = names.head.name 18 | 19 | def isTerm = names.head.isTerm 20 | 21 | def isType = names.head.isType 22 | 23 | lazy val fullName = names.reverseIterator.map(_.name).mkString(".") 24 | 25 | def namesAsJava = names.asJava 26 | } 27 | 28 | case class Position(start: Int, end: Int, transparent: Boolean) { 29 | def includes(other: Position) = 30 | other != null && start <= other.start && end >= other.end && end > other.start 31 | 32 | override def toString = { 33 | val repr = if (start != end) s"$start:$end" else start.toString 34 | if (transparent) s"<$repr>" else s"[$repr]" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/presentation/ast/Flags.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler.presentation.ast 3 | 4 | import scala.reflect.internal.ModifierFlags 5 | 6 | /** 7 | * Created: 12-03-2014 8 | * Author: ghik 9 | */ 10 | case class Flags(flags: Long)(flagString: String) { 11 | private def hasFlag(flag: Long) = (flags & flag) != 0 12 | 13 | override def toString = 14 | flagString 15 | 16 | def isDefaultInit = hasFlag(ModifierFlags.DEFAULTINIT) 17 | 18 | def isPreSuper = hasFlag(ModifierFlags.PRESUPER) 19 | 20 | def isDefaultParam = hasFlag(ModifierFlags.DEFAULTPARAM) 21 | 22 | def isContravariant = hasFlag(ModifierFlags.CONTRAVARIANT) 23 | 24 | def isCovariant = hasFlag(ModifierFlags.COVARIANT) 25 | 26 | def isParam = hasFlag(ModifierFlags.PARAM) 27 | 28 | def isByNameParam = hasFlag(ModifierFlags.BYNAMEPARAM) 29 | 30 | def isAbstractOverride = hasFlag(ModifierFlags.ABSOVERRIDE) 31 | 32 | def isCase = hasFlag(ModifierFlags.CASE) 33 | 34 | def isLocal = hasFlag(ModifierFlags.LOCAL) 35 | 36 | def isProtected = hasFlag(ModifierFlags.PROTECTED) 37 | 38 | def isPrivate = hasFlag(ModifierFlags.PRIVATE) 39 | 40 | def isOverride = hasFlag(ModifierFlags.OVERRIDE) 41 | 42 | def isLazy = hasFlag(ModifierFlags.LAZY) 43 | 44 | def isImplicit = hasFlag(ModifierFlags.IMPLICIT) 45 | 46 | def isSealed = hasFlag(ModifierFlags.SEALED) 47 | 48 | def isFinal = hasFlag(ModifierFlags.FINAL) 49 | 50 | def isAbstract = hasFlag(ModifierFlags.ABSTRACT) 51 | 52 | def isDeferred = hasFlag(ModifierFlags.DEFERRED) 53 | 54 | def isMacro = hasFlag(ModifierFlags.MACRO) 55 | 56 | def isMutable = hasFlag(ModifierFlags.MUTABLE) 57 | 58 | def isInterface = hasFlag(ModifierFlags.INTERFACE) 59 | 60 | def isTrait = hasFlag(ModifierFlags.TRAIT) 61 | } 62 | 63 | object Flags { 64 | final val empty = Flags(0)("") 65 | } -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/presentation/ast/Modifiers.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler.presentation.ast 3 | 4 | import com.avsystem.commons.jiop.JavaInterop._ 5 | 6 | /** 7 | * Created: 12-03-2014 8 | * Author: ghik 9 | */ 10 | case class Modifiers(flags: Flags, privateWithin: Name, annotations: List[Tree]) extends PrettyPrint { 11 | def annotationsAsJava = annotations.asJava 12 | } 13 | 14 | object Modifiers { 15 | final val empty = Modifiers(Flags.empty, TypeName.EMPTY, Nil) 16 | } -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/presentation/ast/Name.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler.presentation.ast 3 | 4 | sealed trait Name { 5 | val name: String 6 | 7 | def isTerm = false 8 | 9 | def isType = false 10 | } 11 | 12 | case class TermName(name: String) extends Name { 13 | override def isTerm = true 14 | } 15 | 16 | object TermName { 17 | final val WILDCARD = TermName("_") 18 | final val ERROR = TermName("") 19 | final val EMPTY = TermName("") 20 | final val PACKAGE = TermName("") 21 | final val CONSTRUCTOR = TermName("") 22 | final val ROOTPKG = TermName("_root_") 23 | } 24 | 25 | case class TypeName(name: String) extends Name { 26 | override def isType = true 27 | } 28 | 29 | object TypeName { 30 | final val WILDCARD = TypeName("_") 31 | final val ERROR = TypeName("") 32 | final val EMPTY = TypeName("") 33 | final val PACKAGE = TypeName("") 34 | final val WILDCARD_STAR = TypeName("_*") 35 | } 36 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/presentation/ast/PrettyPrint.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler.presentation.ast 3 | 4 | /** 5 | * Created: 13-03-2014 6 | * Author: ghik 7 | */ 8 | trait PrettyPrint extends Product 9 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/xmlfriendly/XmlFriendlyScexCompiler.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler.xmlfriendly 3 | 4 | import com.avsystem.scex.compiler.ScexCompiler 5 | import com.avsystem.scex.parsing.PositionMapping 6 | 7 | /** 8 | * 9 | * Scex compiler that accepts modified, XML-friendly syntax: 10 | *
    11 | *
  • string literals can be enclosed in both single and double quotes
  • 12 | *
  • identifiers 'lt', 'gt', 'lte', 'gte', 'and' and 'or' are 13 | * aliases of '<', '>', '<=', '>=', '&&' and '||'
  • 14 | *
  • interpolation arguments must always be blocks, i.e ${ident}, not $ident 15 | *
  • dollars need not be escaped using two dollars
  • 16 | *
17 | * 18 | * Created: 16-08-2013 19 | * Author: ghik 20 | */ 21 | trait XmlFriendlyScexCompiler extends ScexCompiler { 22 | override protected def preprocess(expression: String, template: Boolean): (String, PositionMapping) = { 23 | val (superExpression, superMapping) = super.preprocess(expression, template) 24 | val ps = XmlFriendlyTranslator.translate(superExpression, template) 25 | (ps.result, ps.positionMapping andThen superMapping) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/compiler/xmlfriendly/XmlFriendlyTranslator.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler.xmlfriendly 3 | 4 | import com.avsystem.scex.compiler.CodeGeneration 5 | import com.avsystem.scex.parsing.{Binding, PString, PositionTrackingParsers} 6 | import com.avsystem.scex.util.CommonUtils._ 7 | 8 | /** 9 | * Parser that translates XML-friendly expressions into correct scala code. 10 | * Implemented using Scala parser combinators (recursive descent parser). 11 | * 12 | * Created: 17-09-2013 13 | * Author: ghik 14 | */ 15 | object XmlFriendlyTranslator extends PositionTrackingParsers { 16 | final val AllowedKeywords = Set( 17 | "case", "else", "false", "if", "match", "new", "null", "true" 18 | ) 19 | 20 | final val XmlFriendlyOperators = Map( 21 | "lt" -> "< ", 22 | "gt" -> "> ", 23 | "lte" -> "<= ", 24 | "gte" -> ">= ", 25 | "and" -> "&& ", 26 | "or" -> "||" 27 | ).withDefault(identity) 28 | 29 | def escapeIdent(pstr: PString): PString = 30 | if (!AllowedKeywords.contains(pstr.result) && ScalaKeywords.contains(pstr.result)) 31 | "`".bind(Binding.Right) + pstr + "`".bind(Binding.Left) 32 | else 33 | pstr.withResult(XmlFriendlyOperators(pstr.result)) 34 | 35 | def translate(expr: String, template: Boolean = false): PString = 36 | parse(if (template) templateParser else expressionParser, expr).getOrElse(PString(expr, 0, expr.length, Vector.empty)) 37 | 38 | val expressionParser: Parser[PString] = standardExpression ~ arbitraryEnding ^^ concat 39 | val templateParser: Parser[PString] = stringExpression ~ arbitraryEnding ^^ concat 40 | 41 | def literalPart: Parser[PString] = 42 | rep1("[^$]+".rp) ^^ join 43 | 44 | def literalDollar: Parser[PString] = 45 | "\\$(?!\\{)".rp ^^ (_ + "$".bind(Binding.Left)) 46 | 47 | def stringExpression: Parser[PString] = 48 | rep(literalPart | literalDollar | interpolatedParam) ^^ join 49 | 50 | def interpolatedParam: Parser[PString] = 51 | "$".p ~ block ^^ concat 52 | 53 | def standardExpression: Parser[PString] = 54 | rep(ident | btident | variable | stringlit | number | block | delim | operator | bracket | whitespace) ^^ join 55 | 56 | def ident: Parser[PString] = 57 | "[A-Za-z_][A-Za-z_0-9]*".rp ^^ escapeIdent 58 | 59 | def btident: Parser[PString] = 60 | "`[^`]*`?".rp 61 | 62 | def stringlit: Parser[PString] = 63 | (("\'".p ~ stringlitContents ~ opt("\'".p)) | ("\"".p ~ stringlitContents ~ opt("\"".p))) ^^ { 64 | case beg ~ contents ~ endOpt => 65 | beg.withResult("\"") + contents + endOpt.map(_.withResult("\"")) 66 | } 67 | 68 | def stringlitContents: Parser[PString] = 69 | """([^"'\p{Cntrl}\\]|\\[\\'"bfnrt]|\\u[a-fA-F0-9]{4})*""".rp 70 | 71 | def number: Parser[PString] = 72 | "[0-9]+".rp 73 | 74 | def block: Parser[PString] = "{".p ~ standardExpression ~ "}".p ^^ { 75 | case lb ~ contents ~ rb => lb + contents + rb 76 | } 77 | 78 | def operator: Parser[PString] = 79 | "[\\^\\-\\\\~!@#$%&*=+<>/?|:]".rp 80 | 81 | def delim: Parser[PString] = "[,;.]".rp 82 | 83 | def variable: Parser[PString] = "#".p ~ (ident | btident) ^^ { case hash ~ id => 84 | " ".bind(Binding.Left) + hash.replaceWith(CodeGeneration.VariablesSymbol + ".", Binding.Right) + id 85 | } 86 | 87 | def bracket: Parser[PString] = 88 | "[()\\[\\]]".rp 89 | 90 | def whitespace: Parser[PString] = 91 | "\\s+".rp 92 | 93 | def arbitraryEnding: Parser[PString] = 94 | ".*$".rp 95 | 96 | override def skipWhitespace = false 97 | 98 | def concat(result: PString ~ PString): PString = result match { 99 | case first ~ second => first + second 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/japi/DefaultJavaScexCompiler.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package japi 3 | 4 | import com.avsystem.scex.compiler._ 5 | import com.avsystem.scex.compiler.presentation.{CachingScexPresentationCompiler, ScexPresentationCompiler} 6 | 7 | /** 8 | * Created: 17-09-2013 9 | * Author: ghik 10 | */ 11 | class DefaultJavaScexCompiler(val settings: ScexSettings) 12 | extends ScexCompiler 13 | with ScexPresentationCompiler 14 | with ClassfileReusingScexCompiler 15 | with TemplateOptimizingScexCompiler 16 | with CachingScexCompiler 17 | with CachingScexPresentationCompiler 18 | with WeakReferenceWrappingScexCompiler 19 | with JavaScexCompiler 20 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/japi/JavaExpressionContext.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.japi 2 | 3 | import com.avsystem.scex.{ExpressionContext, NoTag} 4 | 5 | abstract class JavaExpressionContext[R, V] extends ExpressionContext[R, V] { 6 | type VarTag[T] = NoTag 7 | 8 | private type SuperSelf = ExpressionContext[R, V] {type VarTag[T] = NoTag} 9 | 10 | def setTypedVariable[T](name: String, value: T): Unit = (this: SuperSelf).setTypedVariable(name, value)(NoTag) 11 | def getTypedVariable[T](name: String): T = (this: SuperSelf).getTypedVariable(name)(NoTag) 12 | } 13 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/japi/ScalaTypeTokens.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package japi 3 | 4 | import com.avsystem.scex.compiler.JavaTypeParsing 5 | import com.google.common.reflect.TypeToken 6 | 7 | /** 8 | * Created: 18-11-2013 9 | * Author: ghik 10 | */ 11 | object ScalaTypeTokens { 12 | def any = TypeToken.of(JavaTypeParsing.TypeAny).asInstanceOf[TypeToken[Any]] 13 | 14 | def anyVal = TypeToken.of(JavaTypeParsing.TypeAnyVal).asInstanceOf[TypeToken[AnyVal]] 15 | 16 | def anyRef = TypeToken.of(JavaTypeParsing.TypeAnyRef).asInstanceOf[TypeToken[AnyRef]] 17 | 18 | def scalaNull = TypeToken.of(JavaTypeParsing.TypeNull).asInstanceOf[TypeToken[Null]] 19 | 20 | def nothing = TypeToken.of(JavaTypeParsing.TypeNothing).asInstanceOf[TypeToken[Nothing]] 21 | 22 | def create[T]: TypeToken[T] = macro Macros.materializeTypeToken[T] 23 | } 24 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/japi/XmlFriendlyJavaScexCompiler.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package japi 3 | 4 | import com.avsystem.scex.compiler._ 5 | import com.avsystem.scex.compiler.presentation.{CachingScexPresentationCompiler, ScexPresentationCompiler} 6 | import com.avsystem.scex.compiler.xmlfriendly.XmlFriendlyScexCompiler 7 | 8 | /** 9 | * Created: 17-09-2013 10 | * Author: ghik 11 | */ 12 | class XmlFriendlyJavaScexCompiler(val settings: ScexSettings) 13 | extends ScexCompiler 14 | with ScexPresentationCompiler 15 | with ClassfileReusingScexCompiler 16 | with TemplateOptimizingScexCompiler 17 | with XmlFriendlyScexCompiler 18 | with CachingScexCompiler 19 | with CachingScexPresentationCompiler 20 | with WeakReferenceWrappingScexCompiler 21 | with JavaScexCompiler 22 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/parsing/PString.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.parsing 2 | 3 | import com.avsystem.commons.misc.{AbstractValueEnum, AbstractValueEnumCompanion, EnumCtx} 4 | import scala.annotation.tailrec 5 | 6 | import scala.collection.immutable.SortedMap 7 | 8 | final class Binding(implicit enumCtx: EnumCtx) extends AbstractValueEnum 9 | object Binding extends AbstractValueEnumCompanion[Binding] { 10 | final val Left, Right: Value = new Binding 11 | } 12 | 13 | /** 14 | * Created: 24-10-2013 15 | * Author: ghik 16 | */ 17 | case class Modification(offset: Int, amount: Int, binding: Binding) 18 | 19 | case class Bound(str: String, binding: Binding) { 20 | def +(pstr: PString): PString = 21 | PString(str, pstr.beg, pstr.beg, Vector(Modification(pstr.beg, str.length, binding))) + pstr 22 | } 23 | 24 | case class PString(result: String, beg: Int, end: Int, mods: Vector[Modification]) { 25 | lazy val positionMapping: PositionMapping = { 26 | val normalizedMods = if (beg > 0) Modification(0, -beg, Binding.Right) :: mods.toList else mods.toList 27 | val (shiftMapping, reverseShiftMapping) = PString.computeMapping(normalizedMods, Nil, Nil) 28 | new ShiftInfoPositionMapping(shiftMapping, reverseShiftMapping) 29 | } 30 | 31 | def +(other: PString): PString = other match { 32 | case PString("", _, _, Vector()) => 33 | this 34 | 35 | case PString(otherResult, otherBeg, otherEnd, otherMods) => 36 | require(end <= otherBeg) 37 | 38 | val newMods = 39 | if (end == otherBeg) 40 | mods ++ otherMods 41 | else 42 | (mods :+ Modification(end, end - otherBeg, Binding.Right)) ++ otherMods 43 | 44 | PString(result + otherResult, beg, otherEnd, newMods) 45 | } 46 | 47 | def +(other: Bound): PString = 48 | PString(result + other.str, beg, end, mods :+ Modification(end, other.str.length, other.binding)) 49 | 50 | def +(otherOpt: Option[PString]): PString = 51 | otherOpt match { 52 | case Some(pstr) => this + pstr 53 | case None => this 54 | } 55 | 56 | def replaceWith(replacement: String, binding: Binding): PString = 57 | copy(result = replacement, mods = 58 | Modification(beg, -result.length, binding) +: Modification(beg, replacement.length, binding) +: mods) 59 | 60 | def withResult(result: String): PString = 61 | copy(result = result) 62 | } 63 | 64 | object PString { 65 | @tailrec private[scex] def computeMapping( 66 | mods: List[Modification], 67 | acc: List[(Int, ShiftInfo)], 68 | racc: List[(Int, ShiftInfo)] 69 | ): (SortedMap[Int, ShiftInfo], SortedMap[Int, ShiftInfo]) = 70 | (mods, acc, racc) match { 71 | case (Modification(offset, amount, binding) :: tail, (prevOffset, prevInfo) :: accTail, (rprevOffset, rprevInfo) :: raccTail) => 72 | val newAcc = if (offset == prevOffset) 73 | (prevOffset, prevInfo.update(amount, binding)) :: accTail 74 | else 75 | (offset, ShiftInfo(prevInfo.totalShift, amount, binding)) :: acc 76 | 77 | val roffset = offset + (if (offset == prevOffset) prevInfo.totalPrevShift else prevInfo.totalShift) 78 | val newRacc = if (roffset == rprevOffset) 79 | (rprevOffset, rprevInfo.update(-amount, binding)) :: raccTail 80 | else 81 | (roffset, ShiftInfo(rprevInfo.totalShift, -amount, binding)) :: racc 82 | 83 | computeMapping(tail, newAcc, newRacc) 84 | 85 | case (Modification(offset, amount, binding) :: tail, Nil, Nil) => 86 | computeMapping(tail, List((offset, ShiftInfo(0, amount, binding))), List((offset, ShiftInfo(0, -amount, binding)))) 87 | 88 | case (Nil, _, _) => 89 | (SortedMap(acc: _*), SortedMap(racc: _*)) 90 | 91 | case tuple => 92 | throw new IllegalArgumentException(tuple.toString()) 93 | } 94 | } -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/parsing/PositionMapping.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.parsing 2 | 3 | import scala.annotation.nowarn 4 | import scala.collection.immutable.SortedMap 5 | 6 | /** 7 | * Created: 24-10-2013 8 | * Author: ghik 9 | */ 10 | trait PositionMapping { 11 | def apply(pos: Int): Int 12 | 13 | def reverse: PositionMapping 14 | 15 | def compose(other: PositionMapping): PositionMapping = 16 | if (this eq EmptyPositionMapping) other 17 | else if (other eq EmptyPositionMapping) this 18 | else ComposedPositionMapping(this, other) 19 | 20 | def andThen(other: PositionMapping): PositionMapping = 21 | other compose this 22 | } 23 | 24 | case class ShiftInfo(totalPrevShift: Int, addedLeft: Int, removedLeft: Int, addedRight: Int, removedRight: Int) { 25 | def update(amount: Int, binding: Binding): ShiftInfo = 26 | if (amount > 0 && binding == Binding.Left) 27 | copy(addedLeft = addedLeft + amount) 28 | else if (amount < 0 && binding == Binding.Left) 29 | copy(removedLeft = removedLeft - amount) 30 | else if (amount > 0 && binding == Binding.Right) 31 | copy(addedRight = addedRight + amount) 32 | else if (amount < 0 && binding == Binding.Right) 33 | copy(removedRight = removedRight - amount) 34 | else this 35 | 36 | def totalShift: Int = 37 | totalPrevShift + addedLeft - removedLeft + addedRight - removedRight 38 | } 39 | 40 | object ShiftInfo { 41 | def empty(totalPrevShift: Int): ShiftInfo = 42 | new ShiftInfo(totalPrevShift, 0, 0, 0, 0) 43 | 44 | def apply(totalPrevShift: Int, amount: Int, binding: Binding): ShiftInfo = 45 | empty(totalPrevShift).update(amount, binding) 46 | 47 | def apply(totalPrevShift: Int, added: Int, removed: Int, binding: Binding): ShiftInfo = 48 | empty(totalPrevShift).update(added, binding).update(-removed, binding) 49 | } 50 | 51 | class ShiftInfoPositionMapping( 52 | private val shiftMapping: SortedMap[Int, ShiftInfo], 53 | private val reverseShiftMapping: SortedMap[Int, ShiftInfo] 54 | ) extends PositionMapping { 55 | 56 | @nowarn("msg=deprecated") 57 | def apply(pos: Int): Int = shiftMapping.to(pos).lastOption match { 58 | case Some((offset, si)) => 59 | // removedleft|removedright 60 | // addedleft|addedright 61 | // 62 | // All 'removedleft' positions map to the first position of 'addedleft' or last position before it if empty. 63 | // All 'removedright' positions map to the first position of 'addedright' or first position after it if empty. 64 | val relpos = pos - offset 65 | val reloffset = offset + si.totalPrevShift 66 | if (relpos < si.removedLeft) 67 | reloffset - (if (si.addedLeft == 0 && reloffset > 0) 1 else 0) 68 | else if (relpos < si.removedLeft + si.removedRight) 69 | reloffset + si.addedLeft 70 | else 71 | pos + si.totalShift 72 | 73 | case None => 74 | pos 75 | } 76 | 77 | def reverse: PositionMapping = 78 | new ShiftInfoPositionMapping(reverseShiftMapping, shiftMapping) 79 | 80 | override def equals(other: Any): Boolean = other match { 81 | case op: ShiftInfoPositionMapping => shiftMapping == op.shiftMapping 82 | case _ => false 83 | } 84 | 85 | override lazy val hashCode: Int = 86 | shiftMapping.hashCode() 87 | 88 | override def toString: String = 89 | s"PositionMapping($shiftMapping)" 90 | } 91 | 92 | case class SingleShiftPositionMapping(amount: Int) extends PositionMapping { 93 | def apply(pos: Int): Int = pos + amount 94 | 95 | def reverse: PositionMapping = SingleShiftPositionMapping(-amount) 96 | } 97 | 98 | case class ComposedPositionMapping(left: PositionMapping, right: PositionMapping) extends PositionMapping { 99 | def apply(pos: Int): Int = left(right(pos)) 100 | 101 | def reverse: PositionMapping = ComposedPositionMapping(right.reverse, left.reverse) 102 | } 103 | 104 | object EmptyPositionMapping extends PositionMapping { 105 | def apply(pos: Int): Int = pos 106 | 107 | def reverse: PositionMapping = this 108 | } 109 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/parsing/PositionTrackingParsers.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.parsing 2 | 3 | import scala.util.parsing.combinator.RegexParsers 4 | 5 | /** 6 | * Extensions for Scala parser combinators that allow to turn Parser[String] instances into Parser[PString] instances 7 | * that hold information about differences between original and transformed string that later allow to map cursor 8 | * positions between the two. 9 | * 10 | * Created: 21-10-2013 11 | * Author: ghik 12 | */ 13 | trait PositionTrackingParsers extends RegexParsers { 14 | 15 | class ParserWithPos(parser: Parser[String]) extends Parser[PString] { 16 | def apply(in: Input): ParseResult[PString] = parser(in).map { str => 17 | PString(str, in.offset, in.offset + str.length, Vector.empty) 18 | } 19 | } 20 | 21 | class ReplacingParser(parser: Parser[String], replacement: String, binding: Binding) extends Parser[PString] { 22 | def apply(in: Input): ParseResult[PString] = parser(in).map { str => 23 | val mods = Vector(Modification(in.offset, -str.length, binding), Modification(in.offset, replacement.length, binding)) 24 | PString(replacement, in.offset, in.offset + str.length, mods) 25 | } 26 | } 27 | 28 | implicit class stringExtensions(str: String) { 29 | def replaceWith(replacement: String, binding: Binding): Parser[PString] = 30 | new ReplacingParser(str, replacement, binding) 31 | 32 | def p: Parser[PString] = 33 | new ParserWithPos(str) 34 | 35 | def rp: Parser[PString] = 36 | new ParserWithPos(str.r) 37 | 38 | def bind(binding: Binding): Bound = 39 | new Bound(str, binding) 40 | } 41 | 42 | def join(pstrs: Iterable[PString]): PString = 43 | if (pstrs.nonEmpty) pstrs.reduce(_ + _) else PString("", 0, 0, Vector.empty) 44 | 45 | def withOffset[T](parser: Parser[T]): Parser[(T, Int)] = new Parser[(T, Int)] { 46 | override def apply(in: Input): ParseResult[(T, Int)] = 47 | parser.apply(in).map(r => (r, in.offset)) 48 | } 49 | 50 | } 51 | 52 | object PositionTrackingParsers extends PositionTrackingParsers 53 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/parsing/TemplateParser.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.parsing 2 | 3 | /** 4 | * Created: 03-11-2014 5 | * Author: ghik 6 | */ 7 | object TemplateParser extends ScalaParsingCommons with PositionTrackingParsers { 8 | 9 | private val part = multilineInterpolationChars ^^ (_.replace("$$", "$")) 10 | private val arg = new ParserWithPos(interpolationArg) 11 | 12 | private val templateParser = rep(part ~ arg) ~ part ^^ { 13 | case pairs ~ lastPart => (pairs.map(_._1) :+ lastPart, pairs.map(_._2)) 14 | } 15 | 16 | def parseTemplate(expr: String): ParseResult[(List[String], List[PString])] = 17 | parseAll(templateParser, expr) 18 | } 19 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/presentation/Attributes.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.presentation 2 | 3 | /** 4 | * Author: ghik 5 | * Created: 11/17/14. 6 | */ 7 | final class Attributes( 8 | val paramNames: Option[List[String]], 9 | val documentation: Option[String]) { 10 | 11 | def orElse(attrs: => Attributes): Attributes = { 12 | lazy val otherAttrs = attrs 13 | new Attributes( 14 | paramNames orElse otherAttrs.paramNames, 15 | documentation orElse otherAttrs.documentation 16 | ) 17 | } 18 | 19 | } 20 | 21 | object Attributes { 22 | def apply(paramNames: List[String] = null, documentation: String = null) = 23 | new Attributes(Option(paramNames), Option(documentation)) 24 | 25 | val empty = Attributes() 26 | } 27 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/presentation/SymbolAttributes.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.presentation 2 | 3 | import com.avsystem.scex.symboldsl.{SymbolDsl, SymbolDslMacros, SymbolInfo, SymbolInfoList} 4 | 5 | import scala.language.experimental.macros 6 | 7 | /** 8 | * Author: ghik 9 | * Created: 11/17/14. 10 | */ 11 | class SymbolAttributes(val infoList: List[SymbolInfo[Attributes]]) extends SymbolInfoList[Attributes] { 12 | def combine(other: SymbolAttributes) = 13 | SymbolAttributes(infoList ++ other.infoList) 14 | } 15 | 16 | object SymbolAttributes extends SymbolDsl { 17 | type Payload = Attributes 18 | 19 | def apply(attrList: List[SymbolInfo[Attributes]]): SymbolAttributes = 20 | new SymbolAttributes(attrList) 21 | 22 | val empty: SymbolAttributes = apply(Nil) 23 | 24 | def attributes(any: Any): List[SymbolInfo[Attributes]] = macro SymbolDslMacros.attributes_impl 25 | } 26 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/presentation/annotation/Documentation.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.presentation.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Author: ghik 10 | * Created: 11/17/14. 11 | */ 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD}) 14 | public @interface Documentation { 15 | String value(); 16 | } 17 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/presentation/annotation/ParameterNames.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.presentation.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Adds information about parameter names of Java methods. 10 | * 11 | * @deprecated you should use -parameters compiler option for Java instead 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) 15 | @Deprecated 16 | public @interface ParameterNames { 17 | String[] value(); 18 | } 19 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/symboldsl/SymbolInfo.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package symboldsl 3 | 4 | /** 5 | * Author: ghik 6 | * Created: 11/14/14. 7 | */ 8 | case class SymbolInfo[T](typeInfo: TypeInfo, memberSignature: String, implicitConv: Option[String], payload: T) 9 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/symboldsl/SymbolInfoList.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.symboldsl 2 | 3 | import com.avsystem.scex.util.MacroUtils 4 | 5 | import scala.collection.immutable.{SortedSet, TreeSet} 6 | import scala.reflect.api.Universe 7 | 8 | /** 9 | * Author: ghik 10 | * Created: 11/17/14. 11 | */ 12 | trait SymbolInfoList[T] { 13 | case class InfoWithIndex(info: SymbolInfo[T], index: Int) 14 | 15 | val infoList: List[SymbolInfo[T]] 16 | 17 | lazy val size = infoList.length 18 | 19 | lazy val bySignaturesMap: Map[String, List[InfoWithIndex]] = 20 | infoList.zipWithIndex.map { 21 | case (info, index) => InfoWithIndex(info, index) 22 | }.groupBy(_.info.memberSignature).map { 23 | case (signature, infos) => (signature, infos.sortBy(_.index)) 24 | }.withDefaultValue(Nil) 25 | 26 | def matchingInfos(u: Universe)(prefixTpe: u.Type, symbol: u.Symbol, implicitConv: Option[u.Tree]): List[InfoWithIndex] = { 27 | val macroUtils = MacroUtils(u) 28 | import macroUtils._ 29 | 30 | val signatures: List[String] = 31 | withOverrides(symbol).map(memberSignature) 32 | 33 | val implicitConvPath = implicitConv.map(path) 34 | 35 | signatures.flatMap { signature => 36 | bySignaturesMap(signature).filter { case InfoWithIndex(symbolInfo, _) => 37 | signature == symbolInfo.memberSignature && 38 | prefixTpe <:< symbolInfo.typeInfo.typeIn(u) && 39 | implicitConvPath == symbolInfo.implicitConv 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/symboldsl/TypeInfo.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package symboldsl 3 | 4 | import scala.reflect.api.{TypeCreator, Universe} 5 | 6 | class TypeInfo(typeCreator: TypeCreator, val clazz: Option[Class[_]], val isJava: Boolean, typeRepr: String) { 7 | 8 | def typeIn(u: Universe): u.Type = 9 | u.TypeTag[Any](u.rootMirror, typeCreator).tpe 10 | 11 | override def toString = typeRepr 12 | } 13 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/util/CacheImplicits.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package util 3 | 4 | import com.google.common.cache.{CacheLoader, RemovalListener, RemovalNotification} 5 | 6 | import scala.language.implicitConversions 7 | 8 | 9 | object CacheImplicits { 10 | implicit def funToCacheLoader[K, V](fun: K => V): CacheLoader[K, V] = 11 | new CacheLoader[K, V] { 12 | def load(key: K): V = fun(key) 13 | } 14 | 15 | implicit def funToRemovalListener[K, V](fun: RemovalNotification[K, V] => Unit): RemovalListener[K, V] = 16 | new RemovalListener[K, V] { 17 | def onRemoval(notification: RemovalNotification[K, V]): Unit = { 18 | fun(notification) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/util/CommonUtils.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package util 3 | 4 | import java.lang.reflect.{Method, Modifier} 5 | import java.util.concurrent.Callable 6 | import com.google.common.base.Predicate 7 | 8 | import scala.annotation.nowarn 9 | import scala.collection.mutable 10 | import scala.reflect.ClassTag 11 | 12 | 13 | /** 14 | * Created with IntelliJ IDEA. 15 | * User: ghik 16 | * Date: 08.01.13 17 | * Time: 21:03 18 | */ 19 | object CommonUtils { 20 | val ScalaKeywords = Set( 21 | "abstract", "case", "catch", "class", "def", "do", "else", "extends", "false", "final", "finally", "for", "forSome", 22 | "if", "implicit", "import", "lazy", "match", "new", "null", "object", "override", "package", "private", "protected", 23 | "return", "sealed", "super", "this", "throw", "trait", "try", "true", "type", "val", "var", "while", "with", "yield" 24 | ) 25 | 26 | val BeanGetterNamePattern = "get(([A-Z][a-z0-9_]*)+)".r 27 | val BooleanBeanGetterNamePattern = "is(([A-Z][a-z0-9_]*)+)".r 28 | val BeanSetterNamePattern = "set(([A-Z][a-z0-9_]*)+)".r 29 | 30 | object JavaGetterName { 31 | def unapply(getterName: String) = getterName match { 32 | case BeanGetterNamePattern(capitalizedProperty, _) => 33 | Some((capitalizedProperty.head.toLower.toString + capitalizedProperty.tail, false)) 34 | case BooleanBeanGetterNamePattern(capitalizedProperty, _) => 35 | Some((capitalizedProperty.head.toLower.toString + capitalizedProperty.tail, true)) 36 | case _ => None 37 | } 38 | } 39 | 40 | implicit class EnhancedInt(val i: Int) extends AnyVal { 41 | def times(expr: => Any): Unit = { 42 | var c = 0 43 | while (c < i) { 44 | expr 45 | c += 1 46 | } 47 | } 48 | } 49 | 50 | implicit class EnhancedString(val str: String) extends AnyVal { 51 | def leftPad(w: Int) = 52 | if (str.length >= w) 53 | str.substring(0, w) 54 | else { 55 | str + " " * (str.length - w) 56 | } 57 | 58 | def isAlphaNumeric = str.forall(_.isLetterOrDigit) 59 | } 60 | 61 | def benchmark(expr: => Any): Double = { 62 | val start = System.nanoTime() 63 | expr 64 | (System.nanoTime() - start) / 1000000000.0 65 | } 66 | 67 | @nowarn("msg=deprecated") 68 | def directSuperclasses(clazz: Class[_]) = { 69 | val resultBuilder = new mutable.HashSet[Class[_]] 70 | if (clazz.getSuperclass != null) { 71 | resultBuilder += clazz.getSuperclass 72 | } 73 | clazz.getInterfaces.foreach { iface => 74 | if (!resultBuilder.exists(iface.isAssignableFrom)) { 75 | resultBuilder.retain(c => !c.isAssignableFrom(iface)) 76 | resultBuilder += iface 77 | } 78 | } 79 | resultBuilder.toSet 80 | } 81 | 82 | def isMultipleInherited(clazz: Class[_], method: Method) = 83 | directSuperclasses(clazz).flatMap { superClass => 84 | try { 85 | Some(superClass.getMethod(method.getName, method.getParameterTypes: _*)) 86 | .filter(m => Modifier.isPublic(m.getModifiers)).map(_.getDeclaringClass) 87 | } catch { 88 | case _: NoSuchMethodException => None 89 | } 90 | }.size > 1 91 | 92 | def hierarchy(clazz: Class[_]): Set[Class[_]] = { 93 | val resultBuilder = Set.newBuilder[Class[_]] 94 | 95 | def fill(clazz: Class[_]): Unit = { 96 | if (clazz != null) { 97 | resultBuilder += clazz 98 | fill(clazz.getSuperclass) 99 | clazz.getInterfaces.foreach(fill) 100 | } 101 | } 102 | 103 | fill(clazz) 104 | resultBuilder.result() 105 | } 106 | 107 | def pluralize(count: Int, noun: String) = 108 | s"$count $noun" + (if (count != 1) "s" else "") 109 | 110 | def callable[T](expr: => T) = 111 | new Callable[T] { 112 | def call() = expr 113 | } 114 | 115 | type GFunction[F, T] = com.google.common.base.Function[F, T] 116 | 117 | def guavaFun[A, B](f: A => B): GFunction[A, B] = 118 | new GFunction[A, B] { 119 | def apply(input: A): B = f(input) 120 | } 121 | 122 | def guavaPred[A](p: A => Boolean): Predicate[A] = 123 | new Predicate[A] { 124 | def apply(input: A): Boolean = p(input) 125 | } 126 | 127 | implicit class universalOps[A](val a: A) { 128 | def toOpt = Option(a) 129 | 130 | def passTo[B](f: A => B): B = f(a) 131 | } 132 | 133 | implicit class optionOps[A](private val opt: Option[A]) extends AnyVal { 134 | def filterByClass[T: ClassTag]: Option[T] = 135 | opt match { 136 | case Some(t: T) => Some(t) 137 | case _ => None 138 | } 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/util/DynamicAdapters.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util 2 | 3 | import java.util.Collections 4 | import java.{lang => jl, util => ju} 5 | 6 | import scala.collection.mutable 7 | import com.avsystem.commons.jiop.JavaInterop._ 8 | import scala.language.dynamics 9 | 10 | 11 | /** 12 | * Interfaces and utilities to allow Java implementations of `scala.Dynamic` 13 | */ 14 | object DynamicAdapters { 15 | 16 | trait SelectAdapter[+R] extends Dynamic { 17 | def selectDynamic(member: String): R 18 | } 19 | 20 | trait UpdateAdapter[-A] extends Dynamic { 21 | def updateDynamic(member: String)(value: A): Unit 22 | } 23 | 24 | trait Apply0Adapter[+R] extends Dynamic { 25 | def applyDynamic(method: String): R 26 | } 27 | 28 | trait Apply1Adapter[-A1, +R] extends Dynamic { 29 | def applyDynamic(method: String)(arg1: A1): R 30 | } 31 | 32 | trait Apply2Adapter[-A1, -A2, +R] extends Dynamic { 33 | def applyDynamic(method: String)(arg1: A1, arg2: A2): R 34 | } 35 | 36 | trait Apply3Adapter[-A1, -A2, -A3, +R] extends Dynamic { 37 | def applyDynamic(method: String)(arg1: A1, arg2: A2, arg3: A3): R 38 | } 39 | 40 | trait Apply4Adapter[-A1, -A2, -A3, -A4, +R] extends Dynamic { 41 | def applyDynamic(method: String)(arg1: A1, arg2: A2, arg3: A3, arg4: A4): R 42 | } 43 | 44 | trait Apply5Adapter[-A1, -A2, -A3, -A4, -A5, +R] extends Dynamic { 45 | def applyDynamic(method: String)(arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5): R 46 | } 47 | 48 | trait ApplyVarargsAdapter[-AV, +R] extends Dynamic { 49 | /** 50 | * Note: you can use `DynamicAdapters#varargsAsJavaList` to convert 51 | * args to `java.util.List`. 52 | */ 53 | def applyDynamic(method: String)(args: AV*): R 54 | } 55 | 56 | /** 57 | * Converts Scala-style varargs (a `scala.collection.Seq`) into an unmodifiable `java.util.List`. 58 | */ 59 | def varargsAsJavaList[AV](args: AV*) = args.asJava 60 | 61 | trait ApplyNamedAdapter[-AV, +R] extends Dynamic { 62 | /** 63 | * Note: you can use `DynamicAdapters#namedArgsAsJavaMap` and 64 | * `DynamicAdapters#unnamedArgsAsJavaList` to extract named and unnamed 65 | * parameters from args as `java.util.Map` and `java.util.List` 66 | */ 67 | def applyDynamicNamed(method: String)(args: (String, AV)*): R 68 | } 69 | 70 | /** 71 | * Extracts named arguments from named argument list and returns it 72 | * as unmodifiable `java.util.Map` with preserved order. 73 | * 74 | * E.g. for invocation obj.someDynamicMethod(a = 1, 2, b = 3, 4), returned map will be 75 | * {a=1,b=3} 76 | */ 77 | def namedArgsAsJavaMap[AV](args: (String, AV)*) = Collections.unmodifiableMap[String, AV]( 78 | mutable.LinkedHashMap(args.filter(_._1.nonEmpty): _*).asJava) 79 | 80 | /** 81 | * Extracts unnamed argument values from named argument list and returns it as unmodifiable 82 | * `java.util.List`. 83 | * 84 | * E.g. for invocation obj.someDynamicMethod(a = 1, 2, b = 3, 4), returned list will be 85 | * [2,4] 86 | */ 87 | def unnamedArgsAsJavaList[AV](args: (String, AV)*) = 88 | args.collect({ case ("", arg) => arg}).asJava 89 | 90 | /** 91 | * Convenience trait that can be implemented by contexts passed as expression inputs to provide 92 | * dynamic variable support. 93 | * 94 | * @tparam T 95 | */ 96 | trait DynamicVariableSupport[T] extends Dynamic { 97 | def selectDynamic(name: String): T 98 | 99 | def updateDynamic(name: String)(value: T): Unit 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/util/DynamicVariableAccessor.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package util 3 | 4 | import com.avsystem.scex.compiler.annotation.NotValidated 5 | 6 | import scala.language.dynamics 7 | 8 | /** 9 | * Created: 23-09-2013 10 | * Author: ghik 11 | */ 12 | class DynamicVariableAccessor[C <: ExpressionContext[_, V], V](ctx: C) extends TypedVariableAccessor(ctx) with Dynamic { 13 | @NotValidated def selectDynamic(name: String): V = 14 | ctx.getVariable(name) 15 | 16 | @NotValidated def updateDynamic(name: String)(value: V): Unit = 17 | ctx.setVariable(name, value) 18 | } 19 | 20 | class TypedVariableAccessor[C <: ExpressionContext[_, _]](val ctx: C) { 21 | @NotValidated protected def inferVarTag[T](implicit vt: ctx.VarTag[T]): ctx.VarTag[T] = vt 22 | } 23 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/util/Fluent.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package util 3 | 4 | /** 5 | * Created: 14-11-2013 6 | * Author: ghik 7 | */ 8 | trait Fluent { 9 | @inline 10 | protected final def fluent(code: Unit): this.type = this 11 | } 12 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/util/Literal.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package util 3 | 4 | import java.{lang => jl} 5 | 6 | /** 7 | * Created: 18-11-2013 8 | * Author: ghik 9 | */ 10 | case class Literal(literalString: String) extends AnyVal { 11 | override def toString = literalString 12 | 13 | def toBoolean = literalString.toBoolean 14 | 15 | def toChar = 16 | if (literalString.length == 1) literalString.charAt(0) 17 | else throw new IllegalArgumentException(s"Expected string with exactly one character, got ${'"'}$literalString${'"'}") 18 | 19 | def toByte = literalString.toByte 20 | 21 | def toShort = literalString.toShort 22 | 23 | def toInt = literalString.toInt 24 | 25 | def toLong = literalString.toLong 26 | 27 | def toFloat = literalString.toFloat 28 | 29 | def toDouble = literalString.toDouble 30 | } 31 | 32 | object Literal { 33 | 34 | implicit def literalToString(lit: Literal): String = 35 | lit.literalString 36 | 37 | implicit def literalToBoolean(lit: Literal): Boolean = 38 | lit.toBoolean 39 | 40 | implicit def literalToJBoolean(lit: Literal): jl.Boolean = 41 | lit.toBoolean 42 | 43 | implicit def literalToChar(lit: Literal): Char = 44 | lit.toChar 45 | 46 | implicit def literalToJCharacter(lit: Literal): jl.Character = 47 | lit.toChar 48 | 49 | implicit def literalToByte(lit: Literal): Byte = 50 | lit.toByte 51 | 52 | implicit def literalToJByte(lit: Literal): jl.Byte = 53 | lit.toByte 54 | 55 | implicit def literalToShort(lit: Literal): Short = 56 | lit.toShort 57 | 58 | implicit def literalToJShort(lit: Literal): jl.Short = 59 | lit.toShort 60 | 61 | implicit def literalToInt(lit: Literal): Int = 62 | lit.toInt 63 | 64 | implicit def literalToJInteger(lit: Literal): jl.Integer = 65 | lit.toInt 66 | 67 | implicit def literalToLong(lit: Literal): Long = 68 | lit.toLong 69 | 70 | implicit def literalToJLong(lit: Literal): jl.Long = 71 | lit.toLong 72 | 73 | implicit def literalToFloat(lit: Literal): Float = 74 | lit.toFloat 75 | 76 | implicit def literalToJFloat(lit: Literal): jl.Float = 77 | lit.toFloat 78 | 79 | implicit def literalToDouble(lit: Literal): Double = 80 | lit.toDouble 81 | 82 | implicit def literalToJDouble(lit: Literal): jl.Double = 83 | lit.toDouble 84 | 85 | } 86 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/util/LoggingUtils.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package util 3 | 4 | import org.slf4j.{Logger, LoggerFactory} 5 | 6 | import scala.reflect.{ClassTag, classTag} 7 | 8 | /** 9 | * Created: 06-12-2013 10 | * Author: ghik 11 | */ 12 | trait LoggingUtils { 13 | 14 | protected case class LazyLogger(underlying: Logger) { 15 | def trace(msg: => String, cause: Throwable = null): Unit = { 16 | if (underlying.isTraceEnabled) { 17 | underlying.trace(msg, cause) 18 | } 19 | } 20 | 21 | def debug(msg: => String, cause: Throwable = null): Unit = { 22 | if (underlying.isDebugEnabled) { 23 | underlying.debug(msg, cause) 24 | } 25 | } 26 | 27 | def info(msg: => String, cause: Throwable = null): Unit = { 28 | if (underlying.isInfoEnabled) { 29 | underlying.info(msg, cause) 30 | } 31 | } 32 | 33 | def warn(msg: => String, cause: Throwable = null): Unit = { 34 | if (underlying.isWarnEnabled) { 35 | underlying.warn(msg, cause) 36 | } 37 | } 38 | 39 | def error(msg: => String, cause: Throwable = null): Unit = { 40 | if (underlying.isErrorEnabled) { 41 | underlying.error(msg, cause) 42 | } 43 | } 44 | } 45 | 46 | protected def createLogger[T: ClassTag] = 47 | LazyLogger(LoggerFactory.getLogger(classTag[T].runtimeClass)) 48 | } 49 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/util/NamingThreadFactory.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util 2 | 3 | import java.util.concurrent.ThreadFactory 4 | import java.util.concurrent.atomic.AtomicInteger 5 | import scala.annotation.nowarn 6 | 7 | /** 8 | * Author: ghik 9 | * Created: 25/01/16. 10 | */ 11 | class NamingThreadFactory(prefix: String) extends ThreadFactory { 12 | private val group: ThreadGroup = { 13 | @nowarn("msg=deprecated") 14 | val s: SecurityManager = System.getSecurityManager 15 | if (s != null) s.getThreadGroup else Thread.currentThread.getThreadGroup 16 | } 17 | 18 | private val threadNo: AtomicInteger = new AtomicInteger(1) 19 | 20 | def newThread(r: Runnable) = { 21 | val t = new Thread(group, r, prefix + "-" + threadNo.getAndIncrement(), 0) 22 | if (t.isDaemon) t.setDaemon(false) 23 | if (t.getPriority != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY) 24 | t 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/util/SimpleContext.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util 2 | 3 | import com.avsystem.scex.NoTag 4 | import com.avsystem.scex.japi.JavaExpressionContext 5 | 6 | import scala.collection.mutable 7 | 8 | /** 9 | * Created: 23-09-2013 10 | * Author: ghik 11 | */ 12 | case class SimpleContext[R](root: R) extends JavaExpressionContext[R, String] { 13 | private val variables = new mutable.HashMap[String, String] 14 | private val typedVariables = new mutable.HashMap[String, Any] 15 | 16 | def setVariable(name: String, value: String): Unit = 17 | variables(name) = value 18 | 19 | def getVariable(name: String): String = 20 | variables(name) 21 | 22 | def getTypedVariable[T](name: String)(implicit tag: NoTag): T = 23 | typedVariables(name).asInstanceOf[T] 24 | 25 | def setTypedVariable[T](name: String, value: T)(implicit tag: NoTag): Unit = 26 | typedVariables(name) = value 27 | } 28 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/util/TypesafeEquals.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package util 3 | 4 | /** 5 | * Created: 20-11-2013 6 | * Author: ghik 7 | */ 8 | object TypesafeEquals { 9 | implicit def typesafeEqualsEnabled: TypesafeEqualsEnabled = null 10 | } 11 | 12 | trait TypesafeEqualsEnabled 13 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/validation/SymbolValidator.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package validation 3 | 4 | import com.avsystem.scex.symboldsl.{SymbolDslMacros, SymbolDsl, SymbolInfo, SymbolInfoList} 5 | import com.avsystem.scex.util.CommonUtils._ 6 | import com.avsystem.scex.util.LoggingUtils 7 | 8 | import scala.language.experimental.macros 9 | import scala.language.{dynamics, implicitConversions} 10 | 11 | trait SymbolValidator extends SymbolInfoList[Boolean] with LoggingUtils { 12 | private val logger = createLogger[SymbolValidator] 13 | 14 | def combine(otherValidator: SymbolValidator) = 15 | SymbolValidator(infoList ++ otherValidator.infoList) 16 | 17 | lazy val referencedJavaClasses = infoList.iterator.flatMap({ 18 | case SymbolInfo(typeInfo, _, _, true) if typeInfo.isJava => 19 | typeInfo.clazz.toList.flatMap(hierarchy) 20 | case _ => Nil 21 | }).toSet 22 | 23 | private lazy val specsLength = infoList.length 24 | 25 | private def lowestPriority(allowedByDefault: Boolean) = 26 | if (allowedByDefault) specsLength else specsLength + 1 27 | 28 | def validateMemberAccess(vc: ValidationContext)(access: vc.MemberAccess): vc.ValidationResult = { 29 | import vc._ 30 | 31 | access match { 32 | case access@SimpleMemberAccess(tpe, symbol, implicitConv, allowedByDefault, position) => 33 | logger.trace(s"Validating access: $access") 34 | 35 | // SymbolInfos that match this invocation 36 | val matchingSpecs = matchingInfos(vc.universe)(tpe, symbol, implicitConv) 37 | 38 | def specsRepr = matchingSpecs.map({ case InfoWithIndex(spec, idx) => s"$idx: $spec" }).mkString("\n") 39 | logger.trace(s"Matching signatures:\n$specsRepr") 40 | 41 | // get 'allow' field from matching spec that appeared first in ACL or false if there was no matching spec 42 | val (allow, index) = matchingSpecs.headOption.map { 43 | case InfoWithIndex(info, idx) => (info.payload, idx) 44 | } getOrElse(allowedByDefault, lowestPriority(allowedByDefault)) 45 | 46 | ValidationResult(index, if (allow) Nil else List(access)) 47 | 48 | case MultipleMemberAccesses(accesses) => 49 | // all must be allowed 50 | val (allowed, denied) = accesses.map(a => validateMemberAccess(vc)(a)).partition(_.deniedAccesses.isEmpty) 51 | if (denied.nonEmpty) 52 | ValidationResult(denied.minBy(_.priority).priority, denied.flatMap(_.deniedAccesses)) 53 | else if (allowed.nonEmpty) 54 | ValidationResult(allowed.maxBy(_.priority).priority, Nil) 55 | else 56 | ValidationResult(lowestPriority(allowedByDefault = true), Nil) 57 | 58 | case AlternativeMemberAccess(accesses) => 59 | // take the soonest-validated alternative 60 | if (accesses.nonEmpty) 61 | accesses.map(a => validateMemberAccess(vc)(a)).minBy(_.priority) 62 | else 63 | ValidationResult(lowestPriority(allowedByDefault = true), Nil) 64 | 65 | case NoMemberAccess => 66 | ValidationResult(lowestPriority(allowedByDefault = true), Nil) 67 | } 68 | } 69 | } 70 | 71 | object SymbolValidator extends SymbolDsl { 72 | type Payload = Boolean 73 | type MemberAccessSpec = SymbolInfo[Boolean] 74 | 75 | def apply(acl: List[MemberAccessSpec]): SymbolValidator = 76 | new SymbolValidator { 77 | val infoList = acl 78 | } 79 | 80 | val empty: SymbolValidator = apply(Nil) 81 | 82 | /** 83 | * Encloses block of statements that specify methods that are allowed to be called in expressions. 84 | * Code inside allow block is virtualized - it's not actually compiled to bytecode. 85 | * Multiple allow/deny blocks joined with ++ operator form an ACL-like structure. 86 | * @param expr 87 | * @return 88 | */ 89 | def allow(expr: Any): List[MemberAccessSpec] = macro SymbolDslMacros.allow_impl 90 | 91 | /** 92 | * Encloses block of statements that specify methods that are not allowed to be called in expressions. 93 | * Code inside deny block is virtualized - it's not actually compiled to bytecode. 94 | * Multiple allow/deny blocks joined with ++ operator form an ACL-like structure. 95 | * @param expr 96 | * @return 97 | */ 98 | def deny(expr: Any): List[MemberAccessSpec] = macro SymbolDslMacros.deny_impl 99 | 100 | } 101 | -------------------------------------------------------------------------------- /scex-core/src/main/scala/com/avsystem/scex/validation/SyntaxValidator.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package validation 3 | 4 | import scala.reflect.macros.Universe 5 | 6 | 7 | /** 8 | * Trait for expression syntax validator. This validator validates only language constructs, invocation validation 9 | * is performed by SymbolValidator. 10 | */ 11 | trait SyntaxValidator { 12 | def validateSyntax(u: Universe)(tree: u.Tree): (Boolean, List[u.Tree]) 13 | } 14 | 15 | object SyntaxValidator { 16 | val SimpleExpressions: SyntaxValidator = new SyntaxValidator { 17 | def validateSyntax(u: Universe)(tree: u.Tree): (Boolean, List[u.Tree]) = { 18 | import u._ 19 | 20 | def isLambdaParamDef(valDef: ValDef) = 21 | valDef.mods.hasFlag(Flag.PARAM) && valDef.rhs == EmptyTree 22 | 23 | tree match { 24 | case _: Block | _: Select | _: Apply | _: TypeApply | _: Ident | 25 | _: If | _: Literal | _: New | _: This | _: Typed | _: TypTree => 26 | (true, tree.children) 27 | case Function(valDefs, body) if valDefs.forall(isLambdaParamDef) => 28 | (true, body :: valDefs.map(_.tpt)) 29 | case valDef: ValDef if valDef.mods.hasFlag(Flag.ARTIFACT) || valDef.mods.hasFlag(Flag.SYNTHETIC) => 30 | (true, tree.children) 31 | case _ => (false, tree.children) 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /scex-core/src/test/java/com/avsystem/scex/compiler/BaseOne.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler; 2 | 3 | /** 4 | * Created: 09-04-2014 5 | * Author: ghik 6 | */ 7 | public class BaseOne { 8 | public String getId() { 9 | return "tehId"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /scex-core/src/test/java/com/avsystem/scex/compiler/BaseTwo.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler; 2 | 3 | /** 4 | * Created: 09-04-2014 5 | * Author: ghik 6 | */ 7 | public interface BaseTwo { 8 | public String getId(); 9 | } 10 | -------------------------------------------------------------------------------- /scex-core/src/test/java/com/avsystem/scex/compiler/BoundedParameterizedClass.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler; 2 | 3 | import java.io.Closeable; 4 | import java.io.InputStream; 5 | 6 | public class BoundedParameterizedClass { 7 | } 8 | -------------------------------------------------------------------------------- /scex-core/src/test/java/com/avsystem/scex/compiler/DerivedJavaRoot.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler; 2 | 3 | public class DerivedJavaRoot extends JavaRoot { 4 | @Override 5 | public void overriddenMethod() { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /scex-core/src/test/java/com/avsystem/scex/compiler/EnumInside.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler; 2 | 3 | /** 4 | * Author: ghik 5 | * Created: 22/01/15. 6 | */ 7 | public class EnumInside { 8 | public enum TheEnum { 9 | THIS, THAT 10 | } 11 | 12 | public TheEnum getLol() { 13 | return null; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /scex-core/src/test/java/com/avsystem/scex/compiler/JavaRoot.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler; 2 | 3 | public class JavaRoot { 4 | public String getProperty() { 5 | return "property"; 6 | } 7 | 8 | public boolean isExtraordinary() { 9 | return true; 10 | } 11 | 12 | public Boolean isExtraordinarilyBoxed() { 13 | return false; 14 | } 15 | 16 | public double field = 42.42; 17 | 18 | public int twice(int i) { 19 | return i * 2; 20 | } 21 | 22 | public void overriddenMethod() { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /scex-core/src/test/java/com/avsystem/scex/compiler/JavaRootWithGetter.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler; 2 | 3 | /** 4 | * Author: ghik 5 | * Created: 09/04/15. 6 | */ 7 | public interface JavaRootWithGetter { 8 | public String getName(); 9 | } 10 | -------------------------------------------------------------------------------- /scex-core/src/test/java/com/avsystem/scex/compiler/JavaSetterTarget.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler; 2 | 3 | /** 4 | * Created: 28-11-2013 5 | * Author: ghik 6 | */ 7 | public class JavaSetterTarget { 8 | public int field = 0; 9 | 10 | private int beanprop; 11 | private Boolean awesome; 12 | 13 | public int getBeanprop() { 14 | return beanprop; 15 | } 16 | 17 | public void setBeanprop(int beanprop) { 18 | this.beanprop = beanprop; 19 | } 20 | 21 | public Boolean isAwesome() { 22 | return awesome; 23 | } 24 | 25 | public void setAwesome(Boolean awesome) { 26 | this.awesome = awesome; 27 | } 28 | 29 | public JavaSetterTarget self() { 30 | return this; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /scex-core/src/test/java/com/avsystem/scex/compiler/JavaTypes.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler; 2 | 3 | import com.google.common.reflect.TypeToken; 4 | 5 | import java.lang.reflect.Type; 6 | 7 | public class JavaTypes { 8 | public static Type comparableOfString() { 9 | return new TypeToken>() { 10 | }.getType(); 11 | } 12 | 13 | public static Type comparableOfWildcard() { 14 | return new TypeToken>() { 15 | }.getType(); 16 | } 17 | 18 | public static Type complexParameterizedType() { 19 | return new TypeToken.DeeplyInnerGeneric>() { 20 | }.getType(); 21 | } 22 | 23 | public static Type partiallyWildcardedParameterizedType() { 24 | return new TypeToken.DeeplyInnerGeneric>() { 25 | }.getType(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /scex-core/src/test/java/com/avsystem/scex/compiler/OuterClass.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler; 2 | 3 | public class OuterClass { 4 | public static class InnerStaticClass { 5 | } 6 | 7 | public class InnerClass { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /scex-core/src/test/java/com/avsystem/scex/compiler/ParameterizedClass.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class ParameterizedClass { 7 | public static class StaticInnerGeneric { 8 | public class DeeplyInnerGeneric { 9 | public String fjeld = "[fjeld]"; 10 | 11 | public Map getSampleMap() { 12 | return new HashMap<>(); 13 | } 14 | 15 | public boolean isAwesome() { 16 | return true; 17 | } 18 | 19 | public boolean getAwesomeness() { 20 | return true; 21 | } 22 | 23 | public String handleStuff(String stuff) { 24 | return "[" + stuff + " handled]"; 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /scex-core/src/test/java/com/avsystem/scex/compiler/RecursiveGenericClass.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler; 2 | 3 | public class RecursiveGenericClass> { 4 | } 5 | -------------------------------------------------------------------------------- /scex-core/src/test/java/com/avsystem/scex/compiler/SubRoot.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler; 2 | 3 | /** 4 | * Created: 09-04-2014 5 | * Author: ghik 6 | */ 7 | public class SubRoot extends BaseOne implements BaseTwo { 8 | public SubRoot self() { 9 | return this; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /scex-core/src/test/java/com/avsystem/scex/compiler/overriding/Base.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler.overriding; 2 | 3 | /** 4 | * Author: ghik 5 | * Created: 22/01/15. 6 | */ 7 | public interface Base extends DynamicGetter { 8 | @Override 9 | public Base get(String property); 10 | } 11 | -------------------------------------------------------------------------------- /scex-core/src/test/java/com/avsystem/scex/compiler/overriding/DynamicGetter.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler.overriding; 2 | 3 | /** 4 | * Author: ghik 5 | * Created: 22/01/15. 6 | */ 7 | public interface DynamicGetter { 8 | public Object get(String property); 9 | } 10 | -------------------------------------------------------------------------------- /scex-core/src/test/java/com/avsystem/scex/compiler/overriding/GenericBase.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler.overriding; 2 | 3 | /** 4 | * Author: ghik 5 | * Created: 29/10/15. 6 | */ 7 | public abstract class GenericBase { 8 | public abstract T getThat(); 9 | } 10 | -------------------------------------------------------------------------------- /scex-core/src/test/java/com/avsystem/scex/compiler/overriding/Klass.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler.overriding; 2 | 3 | /** 4 | * Author: ghik 5 | * Created: 22/01/15. 6 | */ 7 | public class Klass implements Base { 8 | @Override 9 | public Klass get(String property) { 10 | return this; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /scex-core/src/test/java/com/avsystem/scex/compiler/overriding/Specialized.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler.overriding; 2 | 3 | /** 4 | * Author: ghik 5 | * Created: 29/10/15. 6 | */ 7 | public class Specialized extends GenericBase { 8 | @Override 9 | public String getThat() { 10 | return "that"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /scex-core/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ${PATTERN} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/ArbitraryCompilationTest.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler 3 | 4 | import java.util.concurrent.Callable 5 | import java.{lang => jl, util => ju} 6 | import org.scalatest.funsuite.AnyFunSuite 7 | 8 | /** 9 | * Created: 17-10-2013 10 | * Author: ghik 11 | */ 12 | class ArbitraryCompilationTest extends AnyFunSuite { 13 | val compiler = new DefaultScexCompiler(new ScexSettings) 14 | 15 | test("arbitrary source code compilation test") { 16 | val code = 17 | """ 18 | |package com.avsystem.scex 19 | |package test 20 | | 21 | |class Stuff extends java.util.concurrent.Callable[String] { 22 | | def call = "stuff" 23 | |} 24 | | 25 | """.stripMargin 26 | 27 | val clazz = compiler.compileClass(code, "com.avsystem.scex.test.Stuff") 28 | val callable = clazz.getConstructor().newInstance().asInstanceOf[Callable[String]] 29 | 30 | assert("stuff" == callable.call()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/ClassTaggedContext.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler 2 | 3 | import com.avsystem.scex.ExpressionContext 4 | 5 | import scala.collection.mutable 6 | import scala.reflect.ClassTag 7 | 8 | /** 9 | * Author: ghik 10 | * Created: 22/10/15. 11 | */ 12 | class ClassTaggedContext extends ExpressionContext[Unit, Unit] { 13 | type VarTag[T] = ClassTag[T] 14 | 15 | val typedVars = new mutable.HashMap[(String, Class[_]), Any] 16 | 17 | def root: Unit = () 18 | 19 | def getTypedVariable[T](name: String)(implicit tag: ClassTag[T]): T = 20 | typedVars((name, tag.runtimeClass)).asInstanceOf[T] 21 | 22 | def setTypedVariable[T](name: String, value: T)(implicit tag: ClassTag[T]): Unit = 23 | typedVars((name, tag.runtimeClass)) = value 24 | 25 | def getVariable(name: String): Unit = () 26 | def setVariable(name: String, value: Unit): Unit = () 27 | } 28 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/CompilationTest.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler 3 | 4 | import com.avsystem.commons.misc.TypeString 5 | import com.avsystem.scex.compiler.ScexCompiler.CompilationFailedException 6 | import com.avsystem.scex.japi.{DefaultJavaScexCompiler, JavaScexCompiler} 7 | import com.avsystem.scex.presentation.{Attributes, SymbolAttributes} 8 | import com.avsystem.scex.symboldsl.SymbolInfo 9 | import com.avsystem.scex.util.{PredefinedAccessSpecs, SimpleContext} 10 | import com.avsystem.scex.validation.SymbolValidator.MemberAccessSpec 11 | import com.avsystem.scex.validation.{SymbolValidator, SyntaxValidator} 12 | import org.scalatest.{BeforeAndAfterAll, Suite} 13 | 14 | import scala.reflect.io.AbstractFile 15 | 16 | /** 17 | * Created: 18-11-2013 18 | * Author: ghik 19 | */ 20 | trait CompilationTest extends BeforeAndAfterAll { this: Suite => 21 | 22 | val compiler = createCompiler 23 | 24 | protected def createCompiler: JavaScexCompiler = { 25 | val settings = new ScexSettings 26 | settings.classfileDirectory.value = "testClassfileCache" 27 | new DefaultJavaScexCompiler(settings) 28 | } 29 | 30 | override protected def beforeAll() = { 31 | val classfileDir = AbstractFile.getDirectory(compiler.settings.classfileDirectory.value) 32 | if (classfileDir != null) { 33 | classfileDir.delete() 34 | } 35 | } 36 | 37 | def catchAndPrint(code: => Any): Unit = { 38 | try code catch { 39 | case t: Throwable => t.printStackTrace(System.out) 40 | } 41 | } 42 | 43 | private var profileId = 0 44 | 45 | def newProfileName() = { 46 | profileId += 1 47 | "test" + profileId 48 | } 49 | 50 | def createProfile(acl: List[MemberAccessSpec] = Nil, attributes: List[SymbolInfo[Attributes]] = Nil, 51 | header: String = "import com.avsystem.scex.compiler._", utils: String = "", dynamicVariablesEnabled: Boolean = true) = { 52 | 53 | val profileName = newProfileName() 54 | val expressionUtils = NamedSource(profileName, utils) 55 | new ExpressionProfile(profileName, SyntaxValidator.SimpleExpressions, SymbolValidator(acl), 56 | SymbolAttributes(attributes), header, expressionUtils, dynamicVariablesEnabled) 57 | } 58 | 59 | def assertMemberAccessForbidden(expr: => Any): Unit = { 60 | val exception = intercept[CompilationFailedException](expr) 61 | assert(exception.errors.forall(_.msg.startsWith("Member access forbidden"))) 62 | } 63 | 64 | def evaluateTemplate[T: TypeString](expr: String, acl: List[MemberAccessSpec] = defaultAcl, header: String = "") = 65 | compiler.getCompiledExpression[SimpleContext[Unit], T]( 66 | createProfile(acl), expr, template = true, header = header).apply(SimpleContext(())) 67 | 68 | def evaluate[T: TypeString](expr: String, acl: List[MemberAccessSpec] = defaultAcl) = { 69 | compiler.getCompiledExpression[SimpleContext[Unit], T](createProfile(acl), expr, template = false).apply(SimpleContext(())) 70 | } 71 | 72 | def defaultAcl = PredefinedAccessSpecs.basicOperations 73 | } 74 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/ContextAccessingRoot.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler 2 | 3 | import com.avsystem.scex.ExpressionContext 4 | 5 | class ContextAccessingRoot { 6 | def gimmeVar(name: String)(implicit ctx: ExpressionContext[ContextAccessingRoot, String]): String = 7 | ctx.getVariable(name) 8 | } 9 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/DynamicVariables.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler 3 | 4 | import com.avsystem.scex.util.DynamicAdapters.DynamicVariableSupport 5 | 6 | import scala.collection.mutable 7 | 8 | /** 9 | * Created: 18-09-2013 10 | * Author: ghik 11 | */ 12 | class DynamicVariables extends DynamicVariableSupport[String] { 13 | private val map = new mutable.HashMap[String, String] 14 | 15 | def set(name: String, value: String) = updateDynamic(name)(value) 16 | 17 | def get(name: String) = selectDynamic(name) 18 | 19 | def selectDynamic(name: String) = map(name) 20 | 21 | def updateDynamic(name: String)(value: String): Unit = { 22 | map(name) = value 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/FancySplicedRoot.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler 3 | 4 | import com.avsystem.scex.compiler.TemplateInterpolations.Splicer 5 | 6 | /** 7 | * Created: 31-03-2014 8 | * Author: ghik 9 | */ 10 | class FancySplicedRoot { 11 | def self = this 12 | } 13 | 14 | object FancySplicedRoot { 15 | 16 | implicit object fancySplicer extends Splicer[FancySplicedRoot] { 17 | def toString(t: FancySplicedRoot) = "FANCY" 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/InterceptingPluginScexCompiler.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler 2 | 3 | import scala.tools.nsc._ 4 | import scala.tools.nsc.plugins.{Plugin, PluginComponent} 5 | 6 | /** 7 | * Created: 27-10-2014 8 | * Author: ghik 9 | */ 10 | trait InterceptingPluginScexCompiler extends ScexCompiler { 11 | 12 | protected def runsAfter: List[String] 13 | 14 | protected def intercept(global: Global)(unit: global.CompilationUnit): Unit 15 | 16 | private class Interceptor(val global: ScexGlobal) extends Plugin { 17 | plugin => 18 | 19 | import global._ 20 | 21 | val name = "interceptingPlugin" 22 | val components: List[PluginComponent] = List(component) 23 | val description = "SCEX generic interceptor" 24 | 25 | private object component extends PluginComponent { 26 | val global: plugin.global.type = plugin.global 27 | val runsAfter = InterceptingPluginScexCompiler.this.runsAfter 28 | val phaseName = "scexIntercept" 29 | 30 | def newPhase(prev: Phase) = new StdPhase(prev) { 31 | override def apply(unit: CompilationUnit): Unit = { 32 | intercept(global)(unit) 33 | } 34 | } 35 | } 36 | 37 | } 38 | 39 | override protected def loadCompilerPlugins(global: ScexGlobal) = 40 | new Interceptor(global) :: super.loadCompilerPlugins(global) 41 | } 42 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/JavaScexCompilerTest.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler 2 | 3 | import com.avsystem.scex.Expression 4 | import com.avsystem.scex.compiler.CodeGeneration.{TypedVariables, escapedChar} 5 | import com.avsystem.scex.compiler.JavaScexCompilerTest.SuspiciousCharsAllowedInVariableNames 6 | import com.avsystem.scex.compiler.ScexCompiler.CompilationFailedException 7 | import com.avsystem.scex.japi.{ScalaTypeTokens, XmlFriendlyJavaScexCompiler} 8 | import com.avsystem.scex.util.{PredefinedAccessSpecs, SimpleContext} 9 | import com.avsystem.scex.validation.SymbolValidator.MemberAccessSpec 10 | import org.scalatest.funsuite.AnyFunSuite 11 | 12 | /** 13 | * Author: ghik 14 | * Created: 26/10/15. 15 | */ 16 | class JavaScexCompilerTest extends AnyFunSuite with CompilationTest { 17 | override protected def createCompiler = new XmlFriendlyJavaScexCompiler(new ScexSettings) 18 | 19 | test("typed variables test") { 20 | // given a valid type variable & an expression that uses the typed variable 21 | val variableName = "someDouble" 22 | val expr = s"#$variableName.toDegrees" 23 | 24 | // then expression is compiled without errors 25 | val compiledExpr = compileExpression(expr, variableName) 26 | 27 | // when expression is evaluated with valid value in context 28 | val context = initContextWithTypedVariable(variableName, math.Pi) 29 | val result = compiledExpr(context) 30 | 31 | // then the result is correct 32 | assert(math.Pi.toDegrees == result) 33 | } 34 | 35 | TypedVariables.IllegalCharsInVarName.foreach { char => 36 | test(s"typed variables fail if char [code=${char.toInt}, escaped=${escapedChar(char)}] is in var name test") { 37 | // given a typed variable with illegal char in name & an expression that uses the typed variable 38 | val variableName = s"2varName$char" 39 | val expr = s"#`$variableName`.toDegrees" 40 | 41 | // then compilation should fail 42 | withClue(s"[code=${char.toInt}] character in variable name [$variableName] should not be allowed") { 43 | assertThrows[CompilationFailedException] { 44 | compileExpression(expr, variableName) 45 | } 46 | } 47 | } 48 | } 49 | 50 | SuspiciousCharsAllowedInVariableNames.foreach { char => 51 | test(s"typed variables work if char [code=$char.toInt] is in var name test") { 52 | // given a typed variable with valid name & an expression that uses the typed variable 53 | val variableName = s"""2varName$char""" 54 | val expr = s"#`$variableName`.toDegrees" 55 | 56 | // then expression is compiled without errors 57 | val compiledExpr = compileExpression(expr, variableName) 58 | 59 | // when expression is evaluated with valid value in context 60 | val context = initContextWithTypedVariable(variableName, math.Pi) 61 | val result = compiledExpr(context) 62 | 63 | // then the result is correct 64 | assert(math.Pi.toDegrees == result) 65 | } 66 | } 67 | 68 | private def initContextWithTypedVariable(variableName: String, value: Double): SimpleContext[Unit] = { 69 | val ctx = SimpleContext(()) 70 | ctx.setTypedVariable(variableName, value) 71 | ctx 72 | } 73 | 74 | private def compileExpression( 75 | expr: String, 76 | variableName: String, 77 | accessControlList: List[MemberAccessSpec] = PredefinedAccessSpecs.basicOperations 78 | ): Expression[SimpleContext[Unit], Double] = 79 | compiler.buildExpression 80 | .contextType(ScalaTypeTokens.create[SimpleContext[Unit]]) 81 | .resultType(classOf[Double]) 82 | .expression(expr) 83 | .template(false) 84 | .variableClass(variableName, classOf[Double]) 85 | .profile(createProfile(accessControlList)) 86 | .get 87 | } 88 | 89 | object JavaScexCompilerTest { 90 | // These characters caused compilation errors if they were put in typed variable names until v1.35 release 91 | private val SuspiciousCharsAllowedInVariableNames: Seq[Char] = Seq(' ', '\t', '@', '/') 92 | } 93 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/JavaTypeParsingTest.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler 3 | 4 | import org.scalatest.funsuite.AnyFunSuite 5 | 6 | class JavaTypeParsingTest extends AnyFunSuite { 7 | 8 | import com.avsystem.scex.compiler.JavaTypeParsing._ 9 | 10 | test("toplevel non-parameterized classes") { 11 | assertResult("java.lang.Object") { 12 | javaTypeAsScalaType(classOf[Object]) 13 | } 14 | assertResult("com.avsystem.scex.compiler.OuterClass") { 15 | javaTypeAsScalaType(classOf[OuterClass]) 16 | } 17 | } 18 | 19 | test("static inner classes") { 20 | assertResult("com.avsystem.scex.compiler.OuterClass.InnerStaticClass") { 21 | javaTypeAsScalaType(classOf[OuterClass.InnerStaticClass]) 22 | } 23 | } 24 | 25 | test("non-static inner classes") { 26 | assertResult("com.avsystem.scex.compiler.OuterClass#InnerClass") { 27 | javaTypeAsScalaType(classOf[OuterClass#InnerClass]) 28 | } 29 | } 30 | 31 | test("raw parameterized classes") { 32 | assertResult("com.avsystem.scex.compiler.ParameterizedClass[T] forSome {type T}") { 33 | javaTypeAsScalaType(classOf[ParameterizedClass[_]]) 34 | } 35 | } 36 | 37 | test("raw parameterized classes with bounds") { 38 | assertResult("com.avsystem.scex.compiler.BoundedParameterizedClass[T] forSome {type T <: java.io.InputStream with java.io.Closeable}") { 39 | javaTypeAsScalaType(classOf[BoundedParameterizedClass[_]]) 40 | } 41 | } 42 | 43 | test("recursively generic classes") { 44 | assertResult("com.avsystem.scex.compiler.RecursiveGenericClass[T] forSome {type T <: java.lang.Comparable[T]}") { 45 | javaTypeAsScalaType(classOf[RecursiveGenericClass[_]]) 46 | } 47 | } 48 | 49 | test("deeply nested parameterized classes") { 50 | assertResult("com.avsystem.scex.compiler.ParameterizedClass.StaticInnerGeneric[A]#DeeplyInnerGeneric[B] forSome {type A <: java.lang.Cloneable; type B}") { 51 | javaTypeAsScalaType(classOf[ParameterizedClass.StaticInnerGeneric[A]#DeeplyInnerGeneric[B] forSome {type A; type B}]) 52 | } 53 | } 54 | 55 | test("simple parameterized types") { 56 | assertResult("java.lang.Comparable[java.lang.String]") { 57 | javaTypeAsScalaType(JavaTypes.comparableOfString()) 58 | } 59 | } 60 | 61 | test("simple wildcard types") { 62 | assertResult("java.lang.Comparable[T1] forSome {type T1}") { 63 | javaTypeAsScalaType(JavaTypes.comparableOfWildcard()) 64 | } 65 | } 66 | 67 | test("deeply nested parameterized types") { 68 | assertResult("com.avsystem.scex.compiler.ParameterizedClass.StaticInnerGeneric[T1]#DeeplyInnerGeneric[T2] forSome {type T1; type T2}") { 69 | javaTypeAsScalaType(JavaTypes.complexParameterizedType()) 70 | } 71 | } 72 | 73 | test("deeply nested partially wildcarded parameterized types") { 74 | assertResult("com.avsystem.scex.compiler.ParameterizedClass.StaticInnerGeneric[java.lang.Cloneable]#DeeplyInnerGeneric[T1] forSome {type T1}") { 75 | javaTypeAsScalaType(JavaTypes.partiallyWildcardedParameterizedType()) 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/LiteralExpressionsTest.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler 3 | 4 | import java.lang.annotation.RetentionPolicy 5 | import com.avsystem.scex.compiler.ScexCompiler.CompilationFailedException 6 | import com.avsystem.scex.compiler.TestUtils.CustomBooleanConversionRoot 7 | import com.avsystem.scex.util.{PredefinedAccessSpecs, SimpleContext} 8 | import org.scalatest.funsuite.AnyFunSuite 9 | 10 | /** 11 | * Created: 04-04-2014 12 | * Author: ghik 13 | */ 14 | class LiteralExpressionsTest extends AnyFunSuite with CompilationTest { 15 | 16 | import com.avsystem.scex.validation.SymbolValidator._ 17 | 18 | test("string literal test") { 19 | assert("trololo dafuq" == evaluateTemplate[String]("trololo dafuq")) 20 | } 21 | 22 | test("string literal with money test") { 23 | assert("hajs$hajs" == evaluateTemplate[String]("hajs$$hajs")) 24 | } 25 | 26 | test("string literal with fancy characters test") { 27 | assert(raw"""trololo "" \" \\ '' dafuq\n""" == evaluateTemplate[String](raw"""trololo "" \" \\ '' dafuq\n""")) 28 | } 29 | 30 | test("boolean literal test") { 31 | assert(evaluateTemplate[Boolean]("true")) 32 | } 33 | 34 | test("boolean literal test with surrounding whitespaces") { 35 | intercept[CompilationFailedException] { 36 | evaluateTemplate[Boolean](" true ") 37 | } 38 | } 39 | 40 | test("invalid boolean literal test") { 41 | intercept[CompilationFailedException] { 42 | evaluateTemplate[Boolean]("hueheuahueh") 43 | } 44 | } 45 | 46 | test("invalid boolean literal test - expression as literal") { 47 | intercept[CompilationFailedException] { 48 | evaluateTemplate[Boolean]("true && false") 49 | } 50 | } 51 | 52 | test("enum literal test") { 53 | assert(RetentionPolicy.RUNTIME == evaluateTemplate[RetentionPolicy]("RUNTIME")) 54 | } 55 | 56 | test("bad enum literal test 1") { 57 | intercept[CompilationFailedException] { 58 | evaluateTemplate[RetentionPolicy](" RUNTIME ") 59 | } 60 | } 61 | 62 | test("bad enum literal test 2") { 63 | intercept[CompilationFailedException] { 64 | evaluateTemplate[RetentionPolicy]("HUEHUE") 65 | } 66 | } 67 | 68 | test("no literal conversion found test") { 69 | intercept[CompilationFailedException] { 70 | evaluateTemplate[List[String]]("hueheuehuaheh") 71 | } 72 | } 73 | 74 | test("custom literal conversion test") { 75 | val acl = PredefinedAccessSpecs.basicOperations ++ allow { 76 | TestUtils.all.introduced.members 77 | } 78 | 79 | val header = "import com.avsystem.scex.compiler.TestUtils.zeroOneLiteralToBoolean" 80 | assert(true == evaluateTemplate[Boolean]("1", acl, header)) 81 | } 82 | 83 | test("disallowed custom literal conversion test") { 84 | intercept[CompilationFailedException] { 85 | val header = "import com.avsystem.scex.compiler.TestUtils.zeroOneLiteralToBoolean" 86 | evaluateTemplate[Boolean]("1", header = header) 87 | } 88 | } 89 | 90 | test("input dependent custom literal conversion test") { 91 | val acl = PredefinedAccessSpecs.basicOperations ++ allow { 92 | on { r: CustomBooleanConversionRoot => 93 | r.all.introduced.members 94 | } 95 | } 96 | 97 | val cexpr = compiler.getCompiledExpression[SimpleContext[CustomBooleanConversionRoot], Boolean]( 98 | createProfile(acl), "TRÓ", template = true, header = "") 99 | 100 | assert(cexpr(SimpleContext(new CustomBooleanConversionRoot("ZUO", "TRÓ")))) 101 | } 102 | 103 | test("java inner enum test") { 104 | val acl = allow { 105 | on { ei: EnumInside => 106 | ei.all.members 107 | } 108 | } 109 | assert(EnumInside.TheEnum.THIS == evaluateTemplate[EnumInside.TheEnum]("THIS", acl)) 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/OufOfDateUnitScexCompilerTest.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler 2 | 3 | import com.avsystem.scex.compiler.presentation.ScexPresentationCompiler 4 | import com.avsystem.scex.japi.{JavaScexCompiler, ScalaTypeTokens} 5 | import com.avsystem.scex.util.{PredefinedAccessSpecs, SimpleContext} 6 | import org.scalatest.funsuite.AnyFunSuite 7 | 8 | class OufOfDateUnitScexCompilerTest extends AnyFunSuite with CompilationTest { 9 | 10 | override protected def createCompiler: noCacheCompiler.type = noCacheCompiler 11 | 12 | object noCacheCompiler 13 | extends ScexCompiler 14 | with ScexPresentationCompiler 15 | with JavaScexCompiler 16 | with ClassfileReusingScexCompiler { 17 | 18 | val settings = new ScexSettings 19 | settings.classfileDirectory.value = "testClassfileCache" 20 | } 21 | 22 | /** * 23 | * Purpose of this test it to compile same expression using same profile multiple times with disabled caching to force execution of backgroundCompile method 24 | * backgroundCompile was infinitely executed with changes introduced by 2.13.13 25 | */ 26 | test("out of date compilation") { 27 | val acl = PredefinedAccessSpecs.basicOperations 28 | val profile = createProfile(acl) 29 | 30 | def compileExpression(): Unit = { 31 | compiler.buildExpression 32 | .contextType(ScalaTypeTokens.create[SimpleContext[Unit]]) 33 | .resultType(classOf[String]) 34 | .expression(s""""value"""") 35 | .template(false) 36 | .profile(profile) 37 | .get 38 | } 39 | 40 | compileExpression() 41 | compileExpression() 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/ScalaParsingCommonsTest.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler 2 | 3 | import com.avsystem.scex.parsing.ScalaParsingCommons 4 | import org.scalatest.funsuite.AnyFunSuite 5 | 6 | /** 7 | * Created: 31-10-2014 8 | * Author: ghik 9 | */ 10 | class ScalaParsingCommonsTest extends AnyFunSuite { 11 | 12 | import com.avsystem.scex.parsing.ScalaParsingCommons._ 13 | 14 | def success(parser: Parser[String], input: String) = 15 | parseAll(parser, input) match { 16 | case Success(`input`, _) => true 17 | case _ => false 18 | } 19 | 20 | def failure(parser: Parser[String], input: String) = 21 | !success(parser, input) 22 | 23 | test("ident test") { 24 | assert(success(ident, "srsly")) 25 | assert(success(ident, "srsly_")) 26 | assert(success(ident, "srsly__")) 27 | assert(success(ident, "srsly_dafuq")) 28 | assert(success(ident, "srsly_+=:")) 29 | assert(success(ident, "srsly_dafuq_+=:")) 30 | assert(failure(ident, "+=:")) 31 | assert(failure(ident, "srsly+=:")) 32 | assert(failure(ident, "0srsly")) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/ScalaTypeTokensTest.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler 2 | 3 | import com.avsystem.commons.jiop.JavaInterop._ 4 | import com.avsystem.scex.japi.ScalaTypeTokens.create 5 | 6 | object ScalaTypeTokensTest { 7 | create[String] 8 | create[JList[String]] 9 | create[JList[_]] 10 | } 11 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/ScexCompilationCachingTest.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler 2 | 3 | import com.avsystem.scex.ExpressionProfile 4 | import com.avsystem.scex.compiler.ScexCompiler.CompileError 5 | import com.avsystem.scex.japi.{DefaultJavaScexCompiler, JavaScexCompiler, ScalaTypeTokens} 6 | import com.avsystem.scex.util.{PredefinedAccessSpecs, SimpleContext} 7 | import com.google.common.util.concurrent.UncheckedExecutionException 8 | import org.scalatest.funsuite.AnyFunSuite 9 | 10 | final class ScexCompilationCachingTest extends AnyFunSuite with CompilationTest { 11 | 12 | private var compilationCount = 0 13 | 14 | private val settings = new ScexSettings 15 | settings.classfileDirectory.value = "testClassfileCache" 16 | settings.noGetterAdapters.value = true // to reduce number of compilations in tests 17 | 18 | private val acl = PredefinedAccessSpecs.basicOperations 19 | private val defaultProfile = createProfile(acl, utils = "val utilValue = 42") 20 | 21 | private def createFailingCompiler: JavaScexCompiler = 22 | new DefaultJavaScexCompiler(settings) { 23 | override protected def compile(sourceFile: ScexSourceFile): Either[ScexClassLoader, List[CompileError]] = { 24 | compilationCount += 1 25 | if (compilationCount == 1) throw new NullPointerException() 26 | else super.compile(sourceFile) 27 | } 28 | } 29 | 30 | private def createCountingCompiler: JavaScexCompiler = 31 | new DefaultJavaScexCompiler(settings) { 32 | override protected def compile(sourceFile: ScexSourceFile): Either[ScexClassLoader, List[CompileError]] = { 33 | compilationCount += 1 34 | super.compile(sourceFile) 35 | } 36 | } 37 | 38 | override def newProfileName(): String = "constant_name" 39 | 40 | private def compileExpression( 41 | compiler: JavaScexCompiler, 42 | expression: String = s""""value"""", 43 | profile: ExpressionProfile = defaultProfile, 44 | ): Unit = { 45 | compiler.buildExpression 46 | .contextType(ScalaTypeTokens.create[SimpleContext[Unit]]) 47 | .resultType(classOf[String]) 48 | .expression(expression) 49 | .template(false) 50 | .profile(profile) 51 | .get 52 | } 53 | 54 | test("Unexpected exceptions shouldn't be cached by default") { 55 | compilationCount = 0 56 | val compiler = createFailingCompiler 57 | 58 | assertThrows[UncheckedExecutionException](compileExpression(compiler)) 59 | assert(compilationCount == 1) // utils compilation ended with NPE 60 | compileExpression(compiler) 61 | assert(compilationCount == 3) // 2x utils compilation + 1x final expression compilation 62 | } 63 | 64 | test("Unexpected exceptions should be cached when enabled using ScexSetting") { 65 | compilationCount = 0 66 | val compiler = createFailingCompiler 67 | compiler.settings.cacheUnexpectedCompilationExceptions.value = true 68 | 69 | assertThrows[UncheckedExecutionException](compileExpression(compiler)) 70 | assert(compilationCount == 1) 71 | assertThrows[UncheckedExecutionException](compileExpression(compiler)) 72 | assert(compilationCount == 1) // result fetched from cache 73 | } 74 | 75 | test("CompilationFailedExceptions should always be cached") { 76 | compilationCount = 0 77 | val compiler = createCountingCompiler 78 | val profile = createProfile(acl, utils = """invalidValue""") 79 | 80 | compiler.settings.cacheUnexpectedCompilationExceptions.value = true 81 | assertThrows[UncheckedExecutionException](compileExpression(compiler, profile = profile)) 82 | assert(compilationCount == 1) 83 | assertThrows[UncheckedExecutionException](compileExpression(compiler, profile = profile)) 84 | assert(compilationCount == 1) 85 | 86 | compiler.settings.cacheUnexpectedCompilationExceptions.value = false 87 | assertThrows[UncheckedExecutionException](compileExpression(compiler, profile = profile)) 88 | assert(compilationCount == 1) 89 | assertThrows[UncheckedExecutionException](compileExpression(compiler, profile = profile)) 90 | assert(compilationCount == 1) 91 | } 92 | 93 | test("Successful compilation should always be cached") { 94 | compilationCount = 0 95 | val compiler = createCountingCompiler 96 | 97 | compiler.settings.cacheUnexpectedCompilationExceptions.value = true 98 | compileExpression(compiler) 99 | assert(compilationCount == 2) // utils + expression value 100 | compileExpression(compiler) 101 | assert(compilationCount == 2) 102 | 103 | compiler.settings.cacheUnexpectedCompilationExceptions.value = false 104 | compileExpression(compiler) 105 | assert(compilationCount == 2) 106 | compileExpression(compiler) 107 | assert(compilationCount == 2) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/ShiftInfoPositionMappingTest.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler 3 | 4 | import com.avsystem.scex.parsing.{Binding, ShiftInfo, ShiftInfoPositionMapping} 5 | import org.scalatest.funsuite.AnyFunSuite 6 | 7 | import scala.collection.immutable.SortedMap 8 | 9 | /** 10 | * Created: 24-10-2013 11 | * Author: ghik 12 | */ 13 | class ShiftInfoPositionMappingTest extends AnyFunSuite { 14 | test("empty mapping test") { 15 | val mapping = new ShiftInfoPositionMapping(SortedMap.empty, SortedMap.empty) 16 | val reverse = mapping.reverse 17 | 18 | for (i <- -5 to 5) { 19 | assert(mapping(i) == i) 20 | } 21 | for (i <- -5 to 5) { 22 | assert(reverse(i) == i) 23 | } 24 | } 25 | 26 | test("something was added at the beginning") { 27 | val added = 5 28 | val mapping = new ShiftInfoPositionMapping(SortedMap( 29 | 0 -> ShiftInfo(0, added, Binding.Left) 30 | ), null) 31 | 32 | for (i <- -5 until 0) { 33 | assert(mapping(i) == i) 34 | } 35 | for (i <- 0 to 10) { 36 | assert(mapping(i) == i + added) 37 | } 38 | } 39 | 40 | test("something was removed at the beginning") { 41 | val removed = 5 42 | val mapping = new ShiftInfoPositionMapping(SortedMap( 43 | 0 -> ShiftInfo(0, -removed, Binding.Right) 44 | ), null) 45 | 46 | for (i <- -5 until 0) { 47 | assert(mapping(i) == i) 48 | } 49 | for (i <- 0 to removed) { 50 | assert(mapping(i) == 0) 51 | } 52 | for (i <- removed to 10) { 53 | assert(mapping(i) == i - removed) 54 | } 55 | } 56 | 57 | test("something was added and removed at the beginning") { 58 | val added = 3 59 | val removed = 5 60 | val mapping = new ShiftInfoPositionMapping(SortedMap( 61 | 0 -> ShiftInfo(0, added, removed, Binding.Right) 62 | ), null) 63 | 64 | for (i <- -5 until 0) { 65 | assert(mapping(i) == i) 66 | } 67 | for (i <- 0 until removed) { 68 | assert(mapping(i) == 0) 69 | } 70 | for (i <- removed to 10) { 71 | assert(mapping(i) == i - removed + added) 72 | } 73 | } 74 | 75 | test("more complex test") { 76 | /* 77 | 0123 45678 901234567 78 | oooraaaaaoorrraorroooooo 79 | 012334567890000122234567 80 | */ 81 | 82 | val mapping = new ShiftInfoPositionMapping(SortedMap( 83 | 3 -> ShiftInfo(0, 5, 1, Binding.Left), 84 | 6 -> ShiftInfo(4, 1, 3, Binding.Left), 85 | 10 -> ShiftInfo(2, 0, 2, Binding.Right) 86 | ), null) 87 | 88 | val results = Array(0, 1, 2, 3, 8, 9, 10, 10, 10, 11, 12, 12, 12, 13, 14, 15, 16, 17, 18) 89 | for (i <- -5 until 0) { 90 | assert(mapping(i) == i) 91 | } 92 | for (i <- results.indices) { 93 | assert(mapping(i) == results(i)) 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/SomeDynamic.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler 3 | 4 | import scala.language.dynamics 5 | 6 | /** 7 | * Created: 8/8/13 8 | * Author: ghik 9 | */ 10 | object SomeDynamic extends Dynamic { 11 | def selectDynamic(attr: String) = attr 12 | } 13 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/TemplateParserTest.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler 2 | 3 | import com.avsystem.scex.compiler.xmlfriendly.XmlFriendlyTranslator 4 | import com.avsystem.scex.parsing.TemplateParser 5 | import com.google.common.io.ByteStreams 6 | import org.scalatest.funsuite.AnyFunSuite 7 | 8 | /** 9 | * Created: 03-11-2014 10 | * Author: ghik 11 | */ 12 | class TemplateParserTest extends AnyFunSuite { 13 | def parse(expr: String): (List[String], List[String]) = { 14 | val (parts, args) = TemplateParser.parseTemplate(expr).get 15 | (parts, args.map(_.result)) 16 | } 17 | 18 | test("literal test") { 19 | assert(parse("") == (List(""), Nil)) 20 | assert(parse("stuff") == (List("stuff"), Nil)) 21 | } 22 | 23 | test("dollar test") { 24 | assert(parse("$$") == (List("$"), Nil)) 25 | assert(parse("abc$$def") == (List("abc$def"), Nil)) 26 | } 27 | 28 | test("single arg test") { 29 | assert(parse("$ident") == (List("", ""), List("$ident"))) 30 | assert(parse("${ident}") == (List("", ""), List("${ident}"))) 31 | assert(parse("abc${ident}") == (List("abc", ""), List("${ident}"))) 32 | assert(parse("${ident}def") == (List("", "def"), List("${ident}"))) 33 | assert(parse("abc${ident}def") == (List("abc", "def"), List("${ident}"))) 34 | } 35 | 36 | test("escaping test") { 37 | assert(parse("${\"}\"}") == (List("", ""), List("${\"}\"}"))) 38 | } 39 | 40 | test("nested blocks test") { 41 | assert(parse("${{{}{{}}{}}}}") == (List("", "}"), List("${{{}{{}}{}}}"))) 42 | } 43 | 44 | test("dollars and newlines") { 45 | val tpl = 46 | """${""}a 47 | |a$$b 48 | |b${""}""".stripMargin 49 | 50 | assert(parse(tpl) == (List("", "a\na$b\nb", ""), List("""${""}""", """${""}"""))) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/TestExtensions.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler 2 | 3 | /** 4 | * Author: ghik 5 | * Created: 19/10/15. 6 | */ 7 | object TestExtensions { 8 | implicit class any2qmark[A](a: => A) { 9 | def ?[B >: A](default: => B) = { 10 | val ar = a 11 | if (ar == null) default else ar 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/TestUtils.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler 3 | 4 | import com.avsystem.scex.util.Literal 5 | 6 | /** 7 | * Created: 04-04-2014 8 | * Author: ghik 9 | */ 10 | object TestUtils { 11 | implicit def zeroOneLiteralToBoolean(lit: Literal): Boolean = lit.literalString match { 12 | case "0" => false 13 | case "1" => true 14 | case _ => throw new IllegalArgumentException(s"Must be 0 or 1, found ${lit.literalString}") 15 | } 16 | 17 | class CustomBooleanConversionRoot(falseVal: String, trueVal: String) { 18 | implicit def customLiteralToBoolean(lit: Literal): Boolean = lit.literalString match { 19 | case `falseVal` => false 20 | case `trueVal` => true 21 | case _ => throw new IllegalArgumentException(s"Must be $falseVal or $trueVal, found ${lit.literalString}") 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/TypesafeEqualsTest.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler 3 | 4 | import com.avsystem.commons.misc.TypeString 5 | import com.avsystem.scex.compiler.ScexCompiler.CompilationFailedException 6 | import com.avsystem.scex.presentation.SymbolAttributes 7 | import com.avsystem.scex.util.{PredefinedAccessSpecs, SimpleContext} 8 | import com.avsystem.scex.validation.{SymbolValidator, SyntaxValidator} 9 | import org.scalatest.funsuite.AnyFunSuite 10 | 11 | /** 12 | * Created: 20-11-2013 13 | * Author: ghik 14 | */ 15 | class TypesafeEqualsTest extends AnyFunSuite with CompilationTest { 16 | 17 | import com.avsystem.scex.validation.SymbolValidator._ 18 | 19 | override def evaluate[T: TypeString](expr: String, acl: List[MemberAccessSpec] = PredefinedAccessSpecs.basicOperations) = { 20 | val profile = new ExpressionProfile(newProfileName(), SyntaxValidator.SimpleExpressions, SymbolValidator(acl), 21 | SymbolAttributes(Nil), "import com.avsystem.scex.util.TypesafeEquals._", NamedSource("test", "")) 22 | 23 | compiler.getCompiledExpression[SimpleContext[Unit], T](profile, expr, template = false).apply(SimpleContext(())) 24 | } 25 | 26 | test("same type equality test") { 27 | assert(true == evaluate[Boolean]("\"lol\" == \"lol\"")) 28 | assert(false == evaluate[Boolean]("\"lol\" == \"lol2\"")) 29 | } 30 | 31 | test("same type inequality test") { 32 | assert(false == evaluate[Boolean]("\"lol\" != \"lol\"")) 33 | assert(true == evaluate[Boolean]("\"lol\" != \"lol2\"")) 34 | } 35 | 36 | test("same hierarchy equality test") { 37 | val acl = PredefinedAccessSpecs.basicOperations ++ allow(new Object) 38 | 39 | assert(false == evaluate[Boolean]("\"somestring\" == new Object", acl)) 40 | assert(false == evaluate[Boolean]("new Object == \"somestring\"", acl)) 41 | } 42 | 43 | test("implicit-conversion based equality test") { 44 | val acl = PredefinedAccessSpecs.basicOperations ++ allow(intWrapper _) 45 | 46 | assert(true == evaluate[Boolean]("1 == (1: scala.runtime.RichInt)", acl)) 47 | assert(true == evaluate[Boolean]("(1: scala.runtime.RichInt) == 1", acl)) 48 | } 49 | 50 | test("unrelated types test") { 51 | val exception = intercept[CompilationFailedException] { 52 | evaluate[Boolean]("1 == \"somestring\"") 53 | } 54 | assert(exception.errors.head.msg == "Values of types Int and String cannot be compared for equality") 55 | } 56 | 57 | test("literals test") { 58 | assert(true == evaluate[Boolean]("com.avsystem.scex.util.Literal(\"42\") == 42")) 59 | assert(true == evaluate[Boolean]("42 == com.avsystem.scex.util.Literal(\"42\")")) 60 | } 61 | 62 | test("complex expression test") { 63 | assert(true == evaluate[Boolean]( """("fuu" == "lol") || ("dafuq" != "srsly") """)) 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/ValueRoot.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler 3 | 4 | /** 5 | * Created: 20-10-2014 6 | * Author: ghik 7 | */ 8 | class ValueRoot[V](val value: V) 9 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/presentation/CompletionTest.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.compiler.presentation 2 | 3 | import com.avsystem.commons.misc.TypeString 4 | import com.avsystem.scex.Type 5 | import com.avsystem.scex.compiler.presentation.ScexPresentationCompiler.{Member, Param} 6 | 7 | import scala.reflect.{ClassTag, classTag} 8 | 9 | /** 10 | * Author: ghik 11 | * Created: 11/18/14. 12 | */ 13 | trait CompletionTest { 14 | protected def scexType[T: TypeString : ClassTag]: Type = 15 | Type(TypeString.of[T], classTag[T].runtimeClass) 16 | 17 | case class PartialMember( 18 | name: String, 19 | returnType: Type, 20 | params: List[List[Param]] = Nil, 21 | implicitParams: List[Param] = Nil, 22 | doc: String = null) 23 | 24 | def asPartial(member: Member) = PartialMember( 25 | member.name, 26 | member.returnType, 27 | member.params, 28 | member.implicitParams, 29 | member.documentation.orNull) 30 | 31 | } 32 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/xmlfriendly/PStringTest.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler.xmlfriendly 3 | 4 | import com.avsystem.scex.parsing.{Binding, Modification, PString, ShiftInfo} 5 | import org.scalatest.funsuite.AnyFunSuite 6 | 7 | import scala.collection.immutable.SortedMap 8 | 9 | /** 10 | * Created: 25-10-2013 11 | * Author: ghik 12 | */ 13 | class PStringTest extends AnyFunSuite { 14 | def test(name: String)(modifications: Modification*) 15 | (shiftMapping: (Int, ShiftInfo)*)(reverseShiftMapping: (Int, ShiftInfo)*): Unit = super.test(name) { 16 | val (actualShiftMapping, actualReverseShiftMapping) = 17 | PString.computeMapping(modifications.toList, Nil, Nil) 18 | 19 | assert(actualShiftMapping == SortedMap(shiftMapping: _*), "actual mapping") 20 | assert(actualReverseShiftMapping == SortedMap(reverseShiftMapping: _*), "reverse mapping") 21 | } 22 | 23 | test("no modifications test")()()() 24 | 25 | test("single add test")( 26 | Modification(0, 5, Binding.Left))( 27 | 0 -> ShiftInfo(0, 5, 0, Binding.Left))( 28 | 0 -> ShiftInfo(0, 0, 5, Binding.Left)) 29 | 30 | test("single remove test")( 31 | Modification(0, -5, Binding.Left))( 32 | 0 -> ShiftInfo(0, 0, 5, Binding.Left))( 33 | 0 -> ShiftInfo(0, 5, 0, Binding.Left)) 34 | 35 | test("remove+add replace test")( 36 | Modification(0, -5, Binding.Left), Modification(0, 3, Binding.Left))( 37 | 0 -> ShiftInfo(0, 3, 5, Binding.Left))( 38 | 0 -> ShiftInfo(0, 5, 3, Binding.Left)) 39 | 40 | test("add+remove replace test")( 41 | Modification(0, 3, Binding.Left), Modification(0, -5, Binding.Left))( 42 | 0 -> ShiftInfo(0, 3, 5, Binding.Left))( 43 | 0 -> ShiftInfo(0, 5, 3, Binding.Left)) 44 | 45 | test("consecutive remove and add test")( 46 | Modification(0, -5, Binding.Left), Modification(5, 3, Binding.Left))( 47 | 0 -> ShiftInfo(0, 0, 5, Binding.Left), 5 -> ShiftInfo(-5, 3, 0, Binding.Left))( 48 | 0 -> ShiftInfo(0, 5, 3, Binding.Left)) 49 | 50 | test("complex test")( 51 | Modification(2, 5, Binding.Left), 52 | Modification(3, -4, Binding.Left), 53 | Modification(10, -2, Binding.Left), 54 | Modification(12, 4, Binding.Left) 55 | )( 56 | 2 -> ShiftInfo(0, 5, 0, Binding.Left), 57 | 3 -> ShiftInfo(5, 0, 4, Binding.Left), 58 | 10 -> ShiftInfo(1, 0, 2, Binding.Left), 59 | 12 -> ShiftInfo(-1, 4, 0, Binding.Left) 60 | )( 61 | 2 -> ShiftInfo(0, 0, 5, Binding.Left), 62 | 8 -> ShiftInfo(-5, 4, 0, Binding.Left), 63 | 11 -> ShiftInfo(-1, 2, 4, Binding.Left) 64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /scex-core/src/test/scala/com/avsystem/scex/compiler/xmlfriendly/XmlFriendlyTranslatorTest.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex 2 | package compiler.xmlfriendly 3 | 4 | import com.google.common.io.ByteStreams 5 | import org.scalatest.funsuite.AnyFunSuite 6 | 7 | /** 8 | * Created: 23-04-2014 9 | * Author: ghik 10 | */ 11 | class XmlFriendlyTranslatorTest extends AnyFunSuite { 12 | 13 | import com.avsystem.scex.compiler.xmlfriendly.XmlFriendlyTranslator.translate 14 | 15 | test("variables test") { 16 | val original = " #lol" 17 | val translated = " _vars.lol" 18 | val pstr = translate(original) 19 | assert(translated == pstr.result) 20 | assert(original.indices.map(pstr.positionMapping.apply) == 21 | Seq(0, 2, 8, 9, 10)) 22 | assert(translated.indices.map(pstr.positionMapping.reverse.apply) == 23 | Seq(0, 0, 1, 1, 1, 1, 1, 1, 2, 3, 4)) 24 | } 25 | 26 | test("variables in template test") { 27 | assert("fuu #lol $$lol ${fuu} haha ${ _vars.dafuq + 5} ss" == 28 | translate("fuu #lol $lol ${fuu} haha ${#dafuq + 5} ss", template = true).result) 29 | } 30 | 31 | test("negated variable test") { 32 | assert("! _vars.costam" == translate("!#costam").result) 33 | } 34 | 35 | test("operator translation test") { 36 | assert("a || b && c" == translate("a or b and c").result) 37 | } 38 | 39 | test("operator translation in template test") { 40 | assert("a or ${b && c} or d" == translate("a or ${b and c} or d", template = true).result) 41 | } 42 | 43 | test("large template test") { 44 | val str = "lol" * 10000 45 | assert(str == translate(str, template = true).result) 46 | } 47 | 48 | test("large template test 2") { 49 | val str = "lol${}" * 10000 50 | assert(str == translate(str, template = true).result) 51 | } 52 | 53 | test("backticked variable test") { 54 | assert("${ _vars.`type`}" == translate("${#`type`}").result) 55 | } 56 | 57 | test("keyword variable test") { 58 | assert("${ _vars.`type`}" == translate("${#type}").result) 59 | } 60 | 61 | test("unused scala keyword test") { 62 | val original = "${type}" 63 | val translated = "${`type`}" 64 | val pstr = translate(original) 65 | assert(translated == pstr.result) 66 | assert(original.indices.map(pstr.positionMapping.apply) == 67 | Seq(0, 1, 3, 4, 5, 6, 8)) 68 | assert(translated.indices.map(pstr.positionMapping.reverse.apply) == 69 | Seq(0, 1, 2, 2, 3, 4, 5, 5, 6)) 70 | } 71 | 72 | test("unused scala keyword selection test") { 73 | assert("${costam.`type`}" == translate("${costam.type}").result) 74 | } 75 | 76 | test("literal dollars and newlines test") { 77 | val res = 78 | """${""}a 79 | |a$$b 80 | |b$$$$cc""".stripMargin 81 | assert(res == translate("${''}a\na$b\nb$$cc", template = true).result) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /scex-macros/src/main/scala-2.12/com/avsystem/scex/util/CrossMacroUtils.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util 2 | 3 | trait CrossMacroUtils { this: MacroUtils => 4 | 5 | import universe._ 6 | 7 | type CrossNamedArg = AssignOrNamedArg 8 | object CrossNamedArg { 9 | def apply(lhs: Tree, rhs: Tree): CrossNamedArg = AssignOrNamedArg(lhs, rhs) 10 | def unapply(tree: CrossNamedArg): Option[(Tree, Tree)] = AssignOrNamedArg.unapply(tree) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /scex-macros/src/main/scala-2.13/com/avsystem/scex/util/CrossMacroUtils.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util 2 | 3 | trait CrossMacroUtils { this: MacroUtils => 4 | type CrossNamedArg = universe.NamedArg 5 | final lazy val CrossNamedArg = universe.NamedArg 6 | } 7 | -------------------------------------------------------------------------------- /scex-macros/src/main/scala/com/avsystem/scex/symboldsl/SymbolDslMacros.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.symboldsl 2 | 3 | import com.avsystem.scex.util.MacroUtils 4 | 5 | import scala.reflect.macros.blackbox 6 | 7 | class SymbolDslMacros(val c: blackbox.Context) extends MacroUtils { self => 8 | 9 | import c.universe._ 10 | 11 | lazy val universe: c.universe.type = c.universe 12 | 13 | lazy val AttributesObj = q"$ScexPkg.presentation.Attributes" 14 | lazy val SymbolValidatorObj = q"$ScexPkg.validation.SymbolValidator" 15 | lazy val SymbolAttributesObj = q"$ScexPkg.presentation.SymbolAttributes" 16 | 17 | def allow_impl(expr: c.Expr[Any]): c.Tree = 18 | extractMemberAccessSpecs(expr, allow = true) 19 | 20 | def deny_impl(expr: c.Expr[Any]): c.Tree = 21 | extractMemberAccessSpecs(expr, allow = false) 22 | 23 | private def extractMemberAccessSpecs(expr: c.Expr[Any], allow: Boolean): c.Tree = 24 | new SymbolInfoParser[self.c.type](self.c) { 25 | def defaultPayload: Tree = q"$allow" 26 | def dslObject: Tree = SymbolValidatorObj 27 | }.extractSymbolInfos(expr.tree) 28 | 29 | def attributes_impl(any: c.Expr[Any]): c.Tree = 30 | new SymbolInfoParser[self.c.type](self.c) { 31 | def defaultPayload: Tree = q"$AttributesObj.empty" 32 | def dslObject: Tree = SymbolAttributesObj 33 | }.extractSymbolInfos(any.tree) 34 | } 35 | -------------------------------------------------------------------------------- /scex-macros/src/main/scala/com/avsystem/scex/util/TypeWrapper.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util 2 | 3 | import java.util.Objects 4 | import java.{lang => jl, util => ju} 5 | 6 | import scala.collection.mutable.ListBuffer 7 | import scala.reflect.api.Universe 8 | 9 | /** 10 | * The purpose of this class is to have meaningful equals/hashCode semantics for Type objects 11 | */ 12 | class TypeWrapper(universeWithTpe: (u.type, u.Type) forSome {val u: Universe}) { 13 | private val u = universeWithTpe._1 14 | private val tpe = universeWithTpe._2.asInstanceOf[u.Type] 15 | 16 | import u._ 17 | 18 | private def getSymbols(tpe: Type): List[Symbol] = { 19 | val b = new ListBuffer[Symbol] 20 | 21 | def symbolsIn(tpe: Type): Unit = tpe match { 22 | case SingleType(pre, sym) => 23 | b += sym 24 | symbolsIn(pre) 25 | case TypeRef(pre, sym, args) => 26 | b += sym 27 | symbolsIn(pre) 28 | args.foreach(symbolsIn) 29 | case _ => 30 | b += tpe.typeSymbol 31 | } 32 | symbolsIn(tpe) 33 | b.result() 34 | } 35 | 36 | private val symbols = getSymbols(tpe) 37 | 38 | override def equals(other: Any) = other match { 39 | case other: TypeWrapper => 40 | (u eq other.u) && symbols == other.symbols && tpe =:= other.tpe.asInstanceOf[Type] 41 | case _ => false 42 | } 43 | 44 | override val hashCode = Objects.hash(u, symbols) 45 | } 46 | 47 | object TypeWrapper { 48 | def apply(u: Universe)(tpe: u.Type) = new TypeWrapper((u, tpe): (u.type, u.Type)) 49 | } -------------------------------------------------------------------------------- /scex-test/src/main/java/BaseBridge.java: -------------------------------------------------------------------------------- 1 | public class BaseBridge { 2 | public T identity(T t) { 3 | return t; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /scex-test/src/main/java/BaseStatic.java: -------------------------------------------------------------------------------- 1 | public class BaseStatic { 2 | public static void fuu() { 3 | } 4 | } 5 | -------------------------------------------------------------------------------- /scex-test/src/main/java/ConcreteBridge.java: -------------------------------------------------------------------------------- 1 | public class ConcreteBridge extends BaseBridge { 2 | } 3 | -------------------------------------------------------------------------------- /scex-test/src/main/java/JavaClass.java: -------------------------------------------------------------------------------- 1 | public class JavaClass { 2 | public JavaClass(int whatever) { 3 | } 4 | 5 | public String fyield = "fyield"; 6 | 7 | public String getSomeProperty() { 8 | return "someProperty"; 9 | } 10 | 11 | public boolean isSomeBooleanProperty() { 12 | return true; 13 | } 14 | 15 | public void setSomeProperty(String someProperty) { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /scex-test/src/main/java/JavaDynamic.java: -------------------------------------------------------------------------------- 1 | import scala.Dynamic; 2 | 3 | /** 4 | * Created: 19-08-2013 5 | * Author: ghik 6 | */ 7 | public class JavaDynamic implements Dynamic { 8 | public String selectDynamic(String name) { 9 | return name; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /scex-test/src/main/java/JavaLol.java: -------------------------------------------------------------------------------- 1 | public class JavaLol { 2 | public static class StaticLol { 3 | 4 | } 5 | 6 | public class InnerLol { 7 | 8 | } 9 | 10 | public int fuu = 5; 11 | 12 | private int lol; 13 | 14 | public int getLol() { 15 | return lol; 16 | } 17 | 18 | public void setLol(int lol) { 19 | this.lol = lol; 20 | } 21 | 22 | public boolean isFoo() { 23 | return true; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /scex-test/src/main/java/Static.java: -------------------------------------------------------------------------------- 1 | public class Static extends BaseStatic { 2 | public static void bur() { 3 | } 4 | } 5 | -------------------------------------------------------------------------------- /scex-test/src/main/java/TypedLol.java: -------------------------------------------------------------------------------- 1 | import java.util.List; 2 | import java.util.Map; 3 | 4 | public class TypedLol> { 5 | public static class Stuff { 6 | 7 | } 8 | 9 | public class Dafuq> { 10 | public Map getStuff() { 11 | return null; 12 | } 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /scex-test/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ${PATTERN} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /scex-test/src/main/scala/AutoConvertTest.scala: -------------------------------------------------------------------------------- 1 | import java.{lang => jl, util => ju} 2 | 3 | import com.avsystem.scex.util.Literal 4 | 5 | /** 6 | * Created: 30-05-2014 7 | * Author: ghik 8 | */ 9 | object AutoConvertTest { 10 | 11 | implicit class stringAutoConvert(val str: String) extends AnyVal { 12 | def autoConvert[T](implicit conv: Literal => T): T = 13 | conv(Literal(str)) 14 | } 15 | 16 | def main(args: Array[String]): Unit = { 17 | val int: Int = "123".autoConvert 18 | val double: Double = "123.5".autoConvert 19 | println(int) 20 | println(123 + "22".toByte) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /scex-test/src/main/scala/ExistentialCase.scala: -------------------------------------------------------------------------------- 1 | import com.avsystem.scex.compiler.ScexSettings 2 | import com.avsystem.scex.japi.XmlFriendlyJavaScexCompiler 3 | import com.avsystem.scex.presentation.SymbolAttributes 4 | import com.avsystem.scex.util.{PredefinedAccessSpecs, SimpleContext} 5 | import com.avsystem.scex.validation.{SymbolValidator, SyntaxValidator} 6 | import com.avsystem.scex.{ExpressionProfile, NamedSource} 7 | 8 | /** 9 | * Created: 04-12-2013 10 | * Author: ghik 11 | */ 12 | object ExistentialCase { 13 | def main(args: Array[String]): Unit = { 14 | val compiler = new XmlFriendlyJavaScexCompiler(new ScexSettings) 15 | 16 | val symbolValidator = SymbolValidator(PredefinedAccessSpecs.basicOperations) 17 | val syntaxValidator = SyntaxValidator.SimpleExpressions 18 | val symbolAttributes = SymbolAttributes(Nil) 19 | 20 | val profile = new ExpressionProfile("test", syntaxValidator, symbolValidator, symbolAttributes, "", NamedSource("test", "")) 21 | 22 | val completion = compiler.getCompleter[SimpleContext[Unit], Int](profile, template = true) 23 | .getTypeCompletion("${'dafuq'.toInt}", 14) 24 | 25 | completion.members foreach println 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /scex-test/src/main/scala/MemoryTest.scala: -------------------------------------------------------------------------------- 1 | import com.avsystem.scex.compiler.ScexSettings 2 | import com.avsystem.scex.japi.DefaultJavaScexCompiler 3 | import com.avsystem.scex.presentation.SymbolAttributes 4 | import com.avsystem.scex.util.{PredefinedAccessSpecs, SimpleContext} 5 | import com.avsystem.scex.validation.{SymbolValidator, SyntaxValidator} 6 | import com.avsystem.scex.{ExpressionProfile, NamedSource} 7 | 8 | import scala.annotation.nowarn 9 | 10 | @nowarn("msg=a pure expression does nothing in statement position") 11 | object MemoryTest { 12 | 13 | case class Dummy(costam: Int) 14 | 15 | def main(args: Array[String]): Unit = { 16 | val compiler = new DefaultJavaScexCompiler(new ScexSettings) 17 | compiler.settings.expressionExpirationTime.value = 500 18 | compiler.settings.resetAfterCount.value = 10 19 | 20 | import com.avsystem.scex.validation.SymbolValidator._ 21 | val symbolValidator = SymbolValidator( 22 | PredefinedAccessSpecs.basicOperations ++ allow { 23 | on { dummy: Dummy => 24 | new Dummy(_) 25 | dummy.toString 26 | } 27 | } 28 | ) 29 | val symbolAttributes = SymbolAttributes(Nil) 30 | 31 | val profile = new ExpressionProfile("test", SyntaxValidator.SimpleExpressions, symbolValidator, 32 | symbolAttributes, "", NamedSource("test", "")) 33 | 34 | var i = 0 35 | while (true) { 36 | i += 1 37 | compiler.getCompiledExpression[SimpleContext[Any], String](profile, s"new MemoryTest.Dummy($i).toString") 38 | if (i % 10 == 0) { 39 | println(i) 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /scex-test/src/main/scala/MethodTypes.scala: -------------------------------------------------------------------------------- 1 | import java.{lang => jl, util => ju} 2 | 3 | import scala.reflect.runtime.universe._ 4 | 5 | object MethodTypes { 6 | 7 | class A { 8 | def a(cos: Any)(cosinnego: Int): Int = 5 9 | 10 | def b(cos: Int)(cosinnego: Int): String = "" 11 | 12 | def c = 5 13 | } 14 | 15 | class G[T] { 16 | def id(t: T): T = t 17 | } 18 | 19 | class C extends G[Int] 20 | 21 | implicit class IA(a: A) { 22 | def a(cos: String): String = "jfkdjdfk" 23 | } 24 | 25 | def methodTypesMatch(originalTpe: Type, implicitTpe: Type): Boolean = { 26 | def paramsMatch(origParams: List[Symbol], implParams: List[Symbol]): Boolean = 27 | (origParams, implParams) match { 28 | case (origHead :: origTail, implHead :: implTail) => 29 | implHead.typeSignature <:< origHead.typeSignature && paramsMatch(origTail, implTail) 30 | case (Nil, Nil) => true 31 | case _ => false 32 | } 33 | 34 | (originalTpe, implicitTpe) match { 35 | case (MethodType(origParams, origResultType), MethodType(implParams, implResultType)) => 36 | paramsMatch(origParams, implParams) && methodTypesMatch(origResultType, implResultType) 37 | case (MethodType(_, _), _) | (_, MethodType(_, _)) => false 38 | case (_, _) => true 39 | } 40 | } 41 | 42 | def typeSignatureOf[T: TypeTag](member: String) = 43 | typeOf[T].member(TermName(member)).typeSignature 44 | 45 | def main(args: Array[String]): Unit = { 46 | println(methodTypesMatch(typeSignatureOf[A]("a"), typeSignatureOf[A]("b"))) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /scex-test/src/main/scala/Playground.scala: -------------------------------------------------------------------------------- 1 | 2 | import java.{lang => jl, util => ju} 3 | 4 | import scala.language.experimental.macros 5 | 6 | object Playground { 7 | def main(args: Array[String]): Unit = { 8 | 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /scex-test/src/main/scala/ScalaClass.scala: -------------------------------------------------------------------------------- 1 | import java.{lang => jl, util => ju} 2 | 3 | class ScalaClass(costam: Int) extends JavaClass(costam) { 4 | def this(s: String) = this(s.length) 5 | 6 | def this() = this(0) 7 | 8 | val stuff = 5 9 | var otherStuff = 10 10 | 11 | def stuff(lol: Int) = 42 12 | 13 | override def getSomeProperty = "scalaProperty" 14 | } 15 | -------------------------------------------------------------------------------- /scex-test/src/main/scala/SymbolValidatorTests.scala: -------------------------------------------------------------------------------- 1 | import java.{lang => jl, util => ju} 2 | 3 | import com.avsystem.scex.validation.SymbolValidator._ 4 | 5 | 6 | object SymbolValidatorTests { 7 | 8 | def main(args: Array[String]): Unit = { 9 | var specs: List[MemberAccessSpec] = null 10 | def printSpecs(): Unit = { 11 | specs foreach println 12 | println() 13 | } 14 | 15 | specs = allow { 16 | on { t: Target => 17 | t.all.members 18 | } 19 | } 20 | printSpecs() 21 | 22 | specs = allow { 23 | on { t: Target => 24 | t.all.declared.members 25 | } 26 | } 27 | printSpecs() 28 | 29 | specs = allow { 30 | on { t: Target => 31 | t.all.constructors 32 | } 33 | } 34 | printSpecs() 35 | 36 | specs = allow { 37 | on { t: Target => 38 | t.all.scalaGetters 39 | } 40 | } 41 | printSpecs() 42 | 43 | specs = allow { 44 | on { t: Target => 45 | t.all.scalaSetters 46 | } 47 | } 48 | printSpecs() 49 | 50 | specs = allow { 51 | on { t: Target => 52 | t.all.beanGetters 53 | } 54 | } 55 | printSpecs() 56 | 57 | specs = allow { 58 | on { t: Target => 59 | t.all.beanSetters 60 | } 61 | } 62 | printSpecs() 63 | 64 | specs = allow { 65 | on { t: Target => 66 | t.all.membersNamed.a 67 | } 68 | } 69 | printSpecs() 70 | 71 | specs = allow { 72 | on { t: Target => 73 | t.all.membersNamed.c 74 | } 75 | } 76 | printSpecs() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /scex-test/src/main/scala/Target.scala: -------------------------------------------------------------------------------- 1 | import scala.beans.BeanProperty 2 | 3 | class Target { 4 | var a = 5 5 | val b = 10 6 | 7 | def c = 20 8 | 9 | def c_=(_c: Int): Unit = () 10 | 11 | def c(dafuq: Int) = "trololo" 12 | 13 | def a(aa: String): Unit = () 14 | 15 | @BeanProperty var d = 30 16 | 17 | def getE = 40 18 | 19 | def getF() = 50 20 | 21 | def isG = 60 22 | 23 | def isH() = 70 24 | 25 | def isI = true 26 | 27 | def isJ() = true 28 | } 29 | 30 | object OTarget extends Target { 31 | 32 | object Inner { 33 | def lol = 5 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /scex-test/src/main/scala/TemplateOptimizingPerformanceTest.scala: -------------------------------------------------------------------------------- 1 | import com.avsystem.scex.compiler.ScexSettings 2 | import com.avsystem.scex.japi.XmlFriendlyJavaScexCompiler 3 | import com.avsystem.scex.presentation.SymbolAttributes 4 | import com.avsystem.scex.util.{SimpleContext, PredefinedAccessSpecs} 5 | import com.avsystem.scex.validation.{SymbolValidator, SyntaxValidator} 6 | import com.avsystem.scex.{ExpressionProfile, NamedSource} 7 | 8 | /** 9 | * Created: 04-11-2014 10 | * Author: ghik 11 | */ 12 | object TemplateOptimizingPerformanceTest { 13 | def main(args: Array[String]): Unit = { 14 | val settings = new ScexSettings 15 | settings.classfileDirectory.value = "scex_classes" 16 | val compiler = new XmlFriendlyJavaScexCompiler(settings) 17 | 18 | val profile = new ExpressionProfile("test", 19 | SyntaxValidator.SimpleExpressions, 20 | SymbolValidator(PredefinedAccessSpecs.basicOperations), 21 | SymbolAttributes(Nil), 22 | "", 23 | NamedSource("empty", "")) 24 | 25 | val ctx = SimpleContext(()) 26 | compiler.getCompiledExpression[SimpleContext[Unit], String](profile, "${1+2}abc").apply(ctx) 27 | 28 | Thread.sleep(5000) 29 | 30 | val start = System.nanoTime() 31 | var i = 0 32 | while (i < 200000) { 33 | val expr = "${1+2+3+4}" + i 34 | compiler.getCompiledExpression[SimpleContext[Unit], String](profile, expr).apply(ctx) 35 | i += 1 36 | } 37 | val duration = System.nanoTime() - start 38 | println(duration / 1000000) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /scex-test/src/main/scala/TypeConvertersTest.scala: -------------------------------------------------------------------------------- 1 | import java.{lang => jl, util => ju} 2 | 3 | 4 | object TypeConvertersTest { 5 | 6 | import scala.language.existentials 7 | 8 | def main(args: Array[String]): Unit = { 9 | val clazz = classOf[TypedLol[T]#Dafuq[F] forSome {type T; type F}] 10 | 11 | import com.avsystem.scex.compiler.JavaTypeParsing._ 12 | 13 | println(javaTypeAsScalaType(clazz)) 14 | println(javaTypeAsScalaType(classOf[JavaLol#InnerLol])) 15 | println(typeVariableDeclarations(classToExistential(clazz).typeVars)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /scex-test/src/main/scala/WildcardsTest.scala: -------------------------------------------------------------------------------- 1 | 2 | object WildcardsTest { 3 | def main(args: Array[String]): Unit = { 4 | import com.avsystem.scex.validation.SymbolValidator._ 5 | 6 | allow { 7 | on { sc: ScalaClass => 8 | sc.all.members 9 | } 10 | } foreach println 11 | println() 12 | 13 | allow { 14 | on { sc: ScalaClass => 15 | sc.all.constructors 16 | } 17 | } foreach println 18 | println() 19 | 20 | allow { 21 | on { sc: ScalaClass => 22 | sc.all.declared.members 23 | } 24 | } foreach println 25 | println() 26 | 27 | allow { 28 | on { sc: ScalaClass => 29 | sc.all.introduced.members 30 | } 31 | } foreach println 32 | println() 33 | 34 | allow { 35 | on { sc: ScalaClass => 36 | sc.all.beanGetters 37 | } 38 | } foreach println 39 | println() 40 | 41 | allow { 42 | on { sc: ScalaClass => 43 | sc.all.beanSetters 44 | } 45 | } foreach println 46 | println() 47 | 48 | allow { 49 | on { sc: ScalaClass => 50 | sc.all.scalaGetters 51 | } 52 | } foreach println 53 | println() 54 | 55 | allow { 56 | on { sc: ScalaClass => 57 | sc.all.scalaSetters 58 | } 59 | } foreach println 60 | println() 61 | 62 | allow { 63 | on { sc: ScalaClass => 64 | sc.all.membersNamed.stuff 65 | } 66 | } foreach println 67 | println() 68 | 69 | allow { 70 | allStatic[String].members 71 | } foreach println 72 | println() 73 | 74 | allow { 75 | allStatic[String].membersNamed.valueOf 76 | } foreach println 77 | println() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /scex-util/src/main/scala/com/avsystem/scex/util/Bytes.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util 2 | 3 | import java.io.UnsupportedEncodingException 4 | import java.nio.charset.StandardCharsets 5 | import com.avsystem.commons.jiop.JavaInterop._ 6 | import com.avsystem.scex.presentation.annotation.Documentation 7 | import org.apache.commons.codec.binary.{Base64, Hex} 8 | 9 | import scala.collection.immutable.ArraySeq 10 | import scala.util.hashing.MurmurHash3 11 | 12 | final class Bytes(val bytes: Array[Byte]) extends Comparable[Bytes] { 13 | override def hashCode(): Int = MurmurHash3.bytesHash(bytes) 14 | override def equals(other: Any): Boolean = other match { 15 | case b: Bytes => java.util.Arrays.equals(bytes, b.bytes) 16 | case _ => false 17 | } 18 | override def toString: String = s"Bytes($escaped)" 19 | override def compareTo(o: Bytes): Int = { 20 | def loop(i: Int): Int = 21 | if (i == bytes.length && i == o.bytes.length) 0 22 | else if (i == bytes.length) -1 23 | else if (i == o.bytes.length) 1 24 | else { 25 | val b1 = bytes(i) 26 | val b2 = o.bytes(i) 27 | if (b1 == b2) loop(i + 1) else b1 - b2 28 | } 29 | loop(0) 30 | } 31 | 32 | @Documentation("Encodes this sequence of bytes as string with non-ASCII bytes and backslash escaped, e.g. 'hsg\\x7c\\x0dfoo\\\\bar'.") 33 | def escaped: String = EscapedBytes.render(bytes) 34 | 35 | @Documentation("Encodes this sequence of bytes as hexadecimal string.") 36 | def hex: String = Hex.encodeHexString(bytes) 37 | 38 | @Documentation("Encodes this sequence of bytes as BASE64 string.") 39 | def base64: String = Base64.encodeBase64String(bytes) 40 | 41 | @Documentation("Decodes this sequence of bytes as UTF-8 string.") 42 | def decodeUTF8: String = new String(bytes, StandardCharsets.UTF_8) 43 | 44 | @Documentation("Decodes this sequence of bytes as string using given charset.") 45 | def decode(charset: String): String = 46 | try new String(bytes, charset) catch { 47 | case e: UnsupportedEncodingException => throw new IllegalArgumentException(e) 48 | } 49 | 50 | def asList: JList[Byte] = ArraySeq.unsafeWrapArray(bytes).asJava 51 | } 52 | -------------------------------------------------------------------------------- /scex-util/src/main/scala/com/avsystem/scex/util/CommonDateFormat.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util 2 | 3 | import java.text.SimpleDateFormat 4 | import java.time.format.DateTimeFormatter 5 | 6 | // performance optimization, see https://github.com/AVSystem/scex/pull/38#discussion_r1724465654 7 | object CommonDateFormat { 8 | final val DefaultPattern = "yyyy.MM.dd HH:mm:ss" 9 | 10 | final val Formatter: DateTimeFormatter = DateTimeFormatter.ofPattern(DefaultPattern) 11 | 12 | // thread-local instance of SimpleDateFormat because it's not thread-safe 13 | private val dateFormatTL = new ThreadLocal[SimpleDateFormat] { 14 | override def initialValue(): SimpleDateFormat = { 15 | val df = new SimpleDateFormat(DefaultPattern) 16 | df.setLenient(false) 17 | df 18 | } 19 | } 20 | 21 | def get: SimpleDateFormat = dateFormatTL.get 22 | } 23 | -------------------------------------------------------------------------------- /scex-util/src/main/scala/com/avsystem/scex/util/CommonExpressionUtils.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util 2 | 3 | import com.avsystem.commons._ 4 | import com.avsystem.scex.SetterConversion 5 | import com.avsystem.scex.compiler.TemplateInterpolations.Splicer 6 | import com.avsystem.scex.presentation.annotation.Documentation 7 | import com.avsystem.scex.util.function._ 8 | 9 | import java.text.ParseException 10 | import java.util.Date 11 | 12 | /** 13 | * Author: ghik 14 | * Created: 16/10/15. 15 | */ 16 | object CommonExpressionUtils { 17 | // a replacement for safe dereference operator (?.) and elvis operator (?:) 18 | implicit class any2qmark[A](value: => A) { 19 | def ?[B >: A](default: => B): B = { 20 | val result = try value.opt catch { 21 | case _: NullPointerException | 22 | _: NoSuchElementException | 23 | _: UnsupportedOperationException | 24 | _: IndexOutOfBoundsException | 25 | _: NumberFormatException | 26 | _: IllegalArgumentException | 27 | _: ParseException | 28 | _: ExpressionRecoverableException => Opt.Empty 29 | } 30 | result getOrElse default 31 | } 32 | } 33 | 34 | implicit class exprUniversalOps[A](private val a: A) extends AnyVal { 35 | def useAs[B](f: A => B): B = f(a) 36 | } 37 | 38 | implicit class charOps(private val c: Char) extends AnyVal { 39 | def isHexDigit: Boolean = 40 | c.isDigit || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') 41 | } 42 | 43 | implicit def enrichArray[T <: AnyRef](array: Array[T]): EnrichedArray[T] = 44 | new EnrichedArray(array) 45 | 46 | implicit def enrichDate(date: JDate): EnrichedDate = 47 | new EnrichedDate(date) 48 | 49 | implicit def enrichDateTime(date: JDate): EnrichedZonedDate = 50 | EnrichedZonedDate.fromDate(date) 51 | 52 | implicit def stringNetworkOps(str: String): StringNetworkOps = 53 | new StringNetworkOps(str) 54 | 55 | implicit def stringMiscOps(str: String): StringMiscOps = 56 | new StringMiscOps(str) 57 | 58 | implicit def literalToStringList(literal: Literal): JList[String] = 59 | literal.literalString.splitBy(",") 60 | 61 | implicit def dateToOrdered(date: Date): Ordered[Date] = 62 | Ordered.orderingToOrdered(date) 63 | 64 | implicit def literalToDate(literal: Literal): Date = 65 | literal.literalString.toDate 66 | 67 | implicit def byteArrayToString(byteArray: Array[Byte]): String = 68 | new String(byteArray) 69 | 70 | implicit object dateSplicer extends Splicer[JDate] { 71 | def toString(date: Date): String = 72 | if (date != null) date.format else null 73 | } 74 | 75 | implicit object collectionSplicer extends Splicer[JSet[String]] { 76 | override def toString(col: JSet[String]): String = 77 | col.asScala.mkString(",") 78 | } 79 | 80 | // deprecated, exist for backwards compatibility of expressions 81 | @Documentation("String utility functions") 82 | val string: StringUtil = StringUtilImpl.INSTANCE 83 | 84 | @Documentation("Network utility functions") 85 | val net: NetUtil = NetUtilImpl.INSTANCE 86 | 87 | @Documentation("Formatting utility functions") 88 | val format: FormatUtil = FormatUtilImpl.INSTANCE 89 | 90 | @Documentation("Date utility functions") 91 | val date: DateUtil = DateUtilImpl.INSTANCE 92 | 93 | @Documentation("Mathematical functions") 94 | val math = scala.math.`package` 95 | 96 | @Documentation("Access context variables") 97 | def getVariable(name: String)(implicit ctx: SimpleContext[_]): String = { 98 | ctx.getVariable(name) 99 | } 100 | 101 | def bytes(bs: Byte*): Bytes = new Bytes(bs.toArray) 102 | 103 | implicit class byteListOps(private val l: JList[Byte]) extends AnyVal { 104 | def toBytes: Bytes = new Bytes(l.asScala.toArray) 105 | } 106 | 107 | implicit val stringToBooleanOnSetter: SetterConversion[String, JBoolean] = 108 | SetterConversion(_.toBoolean) 109 | 110 | implicit val stringToDateOnSetter: SetterConversion[String, JDate] = 111 | SetterConversion(CommonDateFormat.get.parse(_)) 112 | } 113 | -------------------------------------------------------------------------------- /scex-util/src/main/scala/com/avsystem/scex/util/CommonSymbolValidators.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util 2 | 3 | import com.avsystem.commons.jiop.JavaInterop._ 4 | import com.avsystem.scex.util.function._ 5 | 6 | import scala.annotation.nowarn 7 | import scala.util.matching.Regex.Match 8 | 9 | @nowarn("msg=a pure expression does nothing in statement position") 10 | object CommonSymbolValidators { 11 | 12 | import com.avsystem.scex.validation.SymbolValidator._ 13 | 14 | val javaCollectionExtensions = { 15 | import JavaCollectionExtensions._ 16 | 17 | allow { 18 | on { c: JCollection[Any@plus] => 19 | c.size 20 | c.contains _ 21 | c.containsAll _ 22 | c.isEmpty 23 | c.implicitlyAs[CollectionOps[Any]].all.members 24 | } 25 | on { sc: JCollection[String] => 26 | sc.implicitlyAs[StringCollectionOps].all.members 27 | } 28 | list _ 29 | range(_: Int, _: Int, _: Int) 30 | range(_: Int, _: Int) 31 | on { l: JList[Any@plus] => 32 | l.get _ 33 | l.implicitlyAs[ListOps[Any]].all.members 34 | } 35 | set _ 36 | on { s: JSet[Any@plus] => 37 | s.implicitlyAs[SetOps[Any]].all.members 38 | } 39 | map _ 40 | on { m: JMap[Any@plus, Any@plus] => 41 | m.isEmpty 42 | m.size 43 | m.get _ 44 | m.keySet 45 | m.values 46 | m.containsKey _ 47 | m.containsValue _ 48 | m.implicitlyAs[MapOps[Any, Any]].all.introduced.members 49 | } 50 | entry _ 51 | on { e: Entry[Any@plus, Any@plus] => 52 | e.key 53 | e.value 54 | e.withKey _ 55 | e.withValue _ 56 | e.toString 57 | } 58 | on { c: JCollection[Entry[Any@plus, Any@plus]@plus] => 59 | c.implicitlyAs[EntryCollectionOps[Any, Any]].all.members 60 | } 61 | on { c: JCollection[(Any, Any)@plus] => 62 | c.implicitlyAs[PairCollectionOps[Any, Any]].all.members 63 | } 64 | 65 | on { o: Ordering.type => 66 | o.BigDecimal 67 | o.BigInt 68 | o.Boolean 69 | o.Byte 70 | o.Char 71 | o.Double 72 | o.Float 73 | o.Int 74 | o.Long 75 | o.Short 76 | o.String 77 | o.Unit 78 | o.ordered(_: Nothing) 79 | } 80 | 81 | on { n: Numeric.type => 82 | n.ByteIsIntegral 83 | n.CharIsIntegral 84 | n.ShortIsIntegral 85 | n.IntIsIntegral 86 | n.LongIsIntegral 87 | n.FloatIsFractional 88 | n.DoubleIsFractional 89 | } 90 | } 91 | } 92 | 93 | val commonExpressionApi = { 94 | import CommonExpressionUtils._ 95 | 96 | deny { 97 | charOps 98 | exprUniversalOps 99 | 100 | on { b: Bytes => 101 | b.bytes 102 | } 103 | } ++ allow { 104 | CommonExpressionUtils.all.members 105 | 106 | // Common types 107 | on { any: Any => 108 | any ? (_: Any) 109 | any.useAs(_: Any => Any) 110 | } 111 | on { ch: Char => 112 | ch.implicitlyAs[charOps].all.members 113 | } 114 | on { str: String => 115 | str.getBytes 116 | } 117 | on { bl: JList[Byte] => 118 | bl.implicitlyAs[byteListOps].all.members 119 | } 120 | on { b: Bytes => 121 | b.all.introduced.members 122 | } 123 | on { d: JDate => 124 | d.after _ 125 | d.before _ 126 | d.compareTo _ 127 | } 128 | on { od: Ordered[JDate] => 129 | od.all.members 130 | } 131 | dateSplicer.toString(_: JDate) 132 | collectionSplicer.toString(_: JSet[String]) 133 | 134 | on { so: StringNetworkOps => so.all.introduced.members } 135 | on { so: StringMiscOps => so.all.introduced.members } 136 | on { ed: EnrichedDate => ed.all.introduced.members } 137 | on { ed: EnrichedZonedDate => ed.all.introduced.members } 138 | on { ea: EnrichedArray[_] => ea.all.introduced.members } 139 | 140 | on { su: StringUtil => su.all.members } 141 | on { nu: NetUtil => nu.all.members } 142 | on { fu: FormatUtil => fu.all.members } 143 | on { du: DateUtil => du.all.members } 144 | 145 | on { m: CommonExpressionUtils.math.type => 146 | m.random() 147 | } 148 | on { m: Match => 149 | m.group(_: Int) 150 | m.group(_: String) 151 | m.groupCount 152 | m.matched 153 | } 154 | } 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /scex-util/src/main/scala/com/avsystem/scex/util/EnrichedArray.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util 2 | 3 | import com.avsystem.commons.jiop.JavaInterop._ 4 | 5 | final class EnrichedArray[T](private val wrapped: Array[T]) extends AnyVal { 6 | def asList: JList[T] = java.util.Arrays.asList(wrapped: _*) 7 | def get(index: Int): T = wrapped(index) 8 | } 9 | -------------------------------------------------------------------------------- /scex-util/src/main/scala/com/avsystem/scex/util/EnrichedDate.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util 2 | 3 | import com.avsystem.scex.presentation.annotation.Documentation 4 | import org.apache.commons.lang3.time.DateUtils 5 | 6 | import java.time.format.DateTimeFormatter 7 | import java.time.temporal.ChronoField 8 | import java.time.{Clock, ZoneId, ZonedDateTime} 9 | import java.util.{Calendar, Date} 10 | 11 | final class EnrichedDate(private val wrapped: Date) extends AnyVal { 12 | @Documentation("Adds the provided amount of milliseconds to the date.") 13 | def addMilliseconds(amount: Int): Date = DateUtils.addMilliseconds(wrapped, amount) 14 | @Documentation("Adds the provided amount of seconds to the date.") 15 | def addSeconds(amount: Int): Date = DateUtils.addSeconds(wrapped, amount) 16 | @Documentation("Adds the provided amount of minutes to the date.") 17 | def addMinutes(amount: Int): Date = DateUtils.addMinutes(wrapped, amount) 18 | @Documentation("Adds the provided amount of hours to the date.") 19 | def addHours(amount: Int): Date = DateUtils.addHours(wrapped, amount) 20 | @Documentation("Adds the provided amount of days to the date.") 21 | def addDays(amount: Int): Date = DateUtils.addDays(wrapped, amount) 22 | @Documentation("Adds the provided amount of weeks to the date.") 23 | def addWeeks(amount: Int): Date = DateUtils.addWeeks(wrapped, amount) 24 | @Documentation("Adds the provided amount of months to the date.") 25 | def addMonths(amount: Int): Date = DateUtils.addMonths(wrapped, amount) 26 | @Documentation("Adds the provided amount of years to the date.") 27 | def addYears(amount: Int): Date = DateUtils.addYears(wrapped, amount) 28 | @Documentation("Truncates the date to seconds.") 29 | def truncateToSeconds: Date = DateUtils.truncate(wrapped, Calendar.SECOND) 30 | @Documentation("Truncates the date to minutes.") 31 | def truncateToMinutes: Date = DateUtils.truncate(wrapped, Calendar.MINUTE) 32 | @Documentation("Truncates the date to hours.") 33 | def truncateToHours: Date = DateUtils.truncate(wrapped, Calendar.HOUR) 34 | @Documentation("Truncates the date to days.") 35 | def truncateToDays: Date = DateUtils.truncate(wrapped, Calendar.DAY_OF_MONTH) 36 | @Documentation("Truncates the date to months.") 37 | def truncateToMonths: Date = DateUtils.truncate(wrapped, Calendar.MONTH) 38 | @Documentation("Truncates the date to years.") 39 | def truncateToYears: Date = DateUtils.truncate(wrapped, Calendar.YEAR) 40 | 41 | @Documentation("Returns the date as a number of milliseconds since 1970.01.01 00:00:00.") 42 | def millis: Long = wrapped.getTime 43 | } 44 | 45 | final class EnrichedZonedDate(private val zonedDateTime: ZonedDateTime) extends AnyVal { 46 | 47 | @Documentation("Formats the date using the default date format: yyyy.MM.dd HH:mm:ss.") 48 | def format: String = CommonDateFormat.Formatter.format(zonedDateTime) 49 | 50 | @Documentation("Formats the date according to provided date format. An example of correct date format is yyyy.MM.dd HH:mm:ss.") 51 | def format(dateFormat: String): String = DateTimeFormatter.ofPattern(dateFormat).format(zonedDateTime) 52 | 53 | @Documentation("Returns the seconds field of the date expressed in milliseconds.") 54 | def millisOfSecond: Int = zonedDateTime.get(ChronoField.MILLI_OF_SECOND) 55 | 56 | @Documentation("Returns the seconds field of the date.") 57 | def secondOfMinute: Int = zonedDateTime.get(ChronoField.SECOND_OF_MINUTE) 58 | 59 | @Documentation("Returns the number of seconds which passed since 00:00 for the date.") 60 | def secondOfDay: Int = zonedDateTime.get(ChronoField.SECOND_OF_DAY) 61 | 62 | @Documentation("Returns the minutes field of the date.") 63 | def minuteOfHour: Int = zonedDateTime.get(ChronoField.MINUTE_OF_HOUR) 64 | 65 | @Documentation("Returns the number of minutes which passed since 00:00 for the date.") 66 | def minuteOfDay: Int = zonedDateTime.get(ChronoField.MINUTE_OF_DAY) 67 | 68 | @Documentation("Returns the number of hours which passed since 00:00 for the date.") 69 | def hourOfDay: Int = zonedDateTime.get(ChronoField.HOUR_OF_DAY) 70 | 71 | @Documentation("Returns the day field of the date.") 72 | def dayOfMonth: Int = zonedDateTime.get(ChronoField.DAY_OF_MONTH) 73 | 74 | @Documentation("Returns a numeric value for day of the week of the date, where 1 - Monday, 7 - Sunday.") 75 | def dayOfWeek: Int = zonedDateTime.get(ChronoField.DAY_OF_WEEK) 76 | 77 | @Documentation("Returns a numeric value for the day of the year.") 78 | def dayOfYear: Int = zonedDateTime.get(ChronoField.DAY_OF_YEAR) 79 | 80 | @Documentation("Returns a numeric value for the month of the year, where 1 - January, 12 - December.") 81 | def monthOfYear: Int = zonedDateTime.get(ChronoField.MONTH_OF_YEAR) 82 | } 83 | 84 | object EnrichedZonedDate { 85 | def fromDate(date: Date, zone: ZoneId = Clock.systemDefaultZone().getZone) = 86 | new EnrichedZonedDate(ZonedDateTime.ofInstant(date.toInstant, zone)) 87 | } 88 | 89 | -------------------------------------------------------------------------------- /scex-util/src/main/scala/com/avsystem/scex/util/EscapedBytes.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util 2 | 3 | import java.io.ByteArrayOutputStream 4 | 5 | object EscapedBytes { 6 | def render(bytes: Array[Byte]): String = { 7 | val sb = new StringBuilder 8 | bytes.foreach { 9 | case '\\' => sb ++= "\\\\" 10 | case b if b > 0x1F && b < 0x7F => sb += b.toChar 11 | case b => sb ++= f"\\x$b%02x" 12 | } 13 | sb.result() 14 | } 15 | 16 | def parse(repr: String): Array[Byte] = { 17 | val baos = new ByteArrayOutputStream 18 | def loop(it: Iterator[Char]): Unit = if (it.hasNext) { 19 | it.next() match { 20 | case '\\' => it.next() match { 21 | case 'x' => 22 | def readDigit(): Int = if (it.hasNext) it.next() match { 23 | case d if d >= '0' && d <= '9' => d - '0' 24 | case d if d >= 'A' && d <= 'F' => d - 'A' + 10 25 | case d if d >= 'a' && d <= 'f' => d - 'a' + 10 26 | case c => throw new IllegalArgumentException(s"Expected hex digit, got character: $c") 27 | } else throw new IllegalArgumentException(s"Expected hex digit, got end of string") 28 | var byte = readDigit() * 16 29 | byte += readDigit() 30 | baos.write(byte) 31 | case '\\' => baos.write('\\') 32 | case c => 33 | throw new IllegalArgumentException(s"Invalid escape character: $c, only \\ and hex escapes are allowed") 34 | } 35 | case c if c > 0x1F && c < 0x7F => 36 | baos.write(c) 37 | case c => 38 | throw new IllegalArgumentException(s"Invalid character in binary representation: $c") 39 | } 40 | loop(it) 41 | } 42 | loop(repr.iterator) 43 | baos.toByteArray 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /scex-util/src/main/scala/com/avsystem/scex/util/ExpressionRecoverableException.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util 2 | 3 | /** 4 | * If some exception extends this trait, it may be caught and recovered from using `?` operator in expressions. 5 | * See [[CommonExpressionUtils.any2qmark.?]] 6 | */ 7 | trait ExpressionRecoverableException extends Throwable 8 | -------------------------------------------------------------------------------- /scex-util/src/main/scala/com/avsystem/scex/util/StringNetworkOps.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util 2 | 3 | import java.net.{InetAddress, UnknownHostException} 4 | 5 | import com.avsystem.scex.util.function.NetFunctions 6 | import com.avsystem.scex.presentation.annotation.Documentation 7 | 8 | final class StringNetworkOps(private val wrapped: String) extends AnyVal { 9 | 10 | @Documentation("Returns true if the string is an IPv4 address which belongs to the subnet provided as the " + 11 | "`subnetWithMask` argument. Use CIDR notation for the argument, e.g. `10.8.0.0/16`.") 12 | def isIpInSubnet(subnetWithMask: String): Boolean = 13 | NetFunctions.isIpInSubnet(wrapped, subnetWithMask) 14 | 15 | @Documentation("Returns true if the string is an IPv4 address which belongs to the subnet defined by the `subnet` and 'mask' arguments." + 16 | "Use dot-decimal notation for the arguments.") 17 | def isIpInSubnetWithMask(subnet: String, mask: String): Boolean = 18 | NetFunctions.isIpInSubnetWithMask(wrapped, subnet, mask) 19 | 20 | @Documentation("Returns the MAC address with any separating characters removed and letters changed to uppercase.") 21 | def stripMac: String = NetFunctions.stripMac(wrapped) 22 | @Documentation("Returns true if the string is a valid IP address.") 23 | def isIp: Boolean = NetFunctions.isIp(wrapped) 24 | @Documentation("Returns true if the string is a comma-separated list of valid IP addresses.") 25 | def isIps: Boolean = NetFunctions.isIps(wrapped) 26 | @Documentation("Returns true if the string is a valid hardware address.") 27 | def isMac: Boolean = NetFunctions.isMac(wrapped) 28 | 29 | @Documentation("Returns 0 if this address is equal to the one provided as the argument, -1 if less than, 1 if greater than.") 30 | def compareAsIpTo(ip: String): Integer = try { 31 | val adr1 = InetAddress.getByName(wrapped) 32 | val adr2 = InetAddress.getByName(ip) 33 | val inetAddressComparator = new NetFunctions.InetAddressComparator 34 | inetAddressComparator.compare(adr1, adr2) 35 | } catch { 36 | case _: UnknownHostException => null 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /scex-util/src/main/scala/com/avsystem/scex/util/function/FormatUtil.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util.function; 2 | 3 | import java.util.Collection; 4 | import com.avsystem.scex.presentation.annotation.Documentation; 5 | 6 | public interface FormatUtil { 7 | Number asNumber(String value); 8 | 9 | @Documentation("Returns true if the provided `value` contains only numeric values.") 10 | boolean isNumeric(String value); 11 | 12 | @Documentation("Transforms the provided `value` into a common scale. `siCompliance` set to `true` means a decimal " + 13 | "base is used (1000), otherwise binary (1024). `unitName` is appended to the output.") 14 | String normalizeValue(long value, boolean siCompliance, String unitName); 15 | 16 | @Documentation("Transforms the provided `value` into a common scale. `siCompliance` set to `true` means a decimal " + 17 | "base is used (1000), otherwise binary (1024). Select unit via `inputUnitIndex` integer value corresponding" + 18 | "to k, M, G, T, P, E. `unitName` is appended to the output.") 19 | String normalizeValue(long value, int inputUnitIndex, boolean siCompliance, String unitName); 20 | 21 | @Documentation("Returns normalized value for the provided number of `bytes`, e.g. `format.normalizeBytes(16777216)` returns 16.8 MB") 22 | String normalizeBytes(long bytes); 23 | 24 | @Documentation("Returns the numeric value of `seconds` as a string representation, e.g. `1000` as `16m 40s`.") 25 | String secondsToPeriod(long seconds); 26 | 27 | @Documentation("Returns the provided list of strings joined.") 28 | String join(Collection collection); 29 | 30 | @Documentation("Returns the provided list of strings joined and using the selected `delimiter`.") 31 | String join(Collection collection, String delimiter); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /scex-util/src/main/scala/com/avsystem/scex/util/function/FormatUtilImpl.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util.function; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.apache.commons.lang3.time.DurationFormatUtils; 5 | 6 | import java.math.BigDecimal; 7 | import java.math.MathContext; 8 | import java.time.Duration; 9 | import java.util.Collection; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | 13 | public class FormatUtilImpl implements FormatUtil { 14 | public static final FormatUtilImpl INSTANCE = new FormatUtilImpl(); 15 | 16 | private static final String NUMERIC_PATTERN = "((-|\\+)?[0-9]+(\\.[0-9]+)?)+"; 17 | 18 | private static final Pattern numericPattern = Pattern.compile(NUMERIC_PATTERN); 19 | 20 | private FormatUtilImpl() { 21 | } 22 | 23 | private static final String[] Q = new String[] { "", "k", "M", "G", "T", "P", "E" }; 24 | private Long SI = 1000L; 25 | private Long BINARY = 1024L; 26 | private static final BigDecimal ZERO = new BigDecimal(0); 27 | 28 | @Override 29 | public Number asNumber(String value) { 30 | if (StringUtils.isBlank(value)) { 31 | return ZERO; 32 | } 33 | return new BigDecimal(value, MathContext.UNLIMITED); 34 | } 35 | 36 | @Override 37 | public String normalizeValue(long value, int inputUnitIndex, boolean siCompliance, String unitName) { 38 | long unit = siCompliance ? SI : BINARY; 39 | for (int i = 6 + inputUnitIndex; i > 0; i--) { 40 | double step = Math.pow(unit, i); 41 | if (value > step) 42 | return String.format("%3.1f %s%s%s", value / step, Q[i], siCompliance ? "" : "i", unitName); 43 | } 44 | 45 | return Long.toString(value) + " " + unitName; 46 | } 47 | 48 | @Override 49 | public String normalizeValue(long value, boolean siCompliance, String unitName) { 50 | return normalizeValue(value, 0, siCompliance, unitName); 51 | } 52 | 53 | @Override 54 | public String normalizeBytes(long bytes) { 55 | return normalizeValue(bytes, true, "B"); 56 | } 57 | 58 | public String normalizeBytesWithDot(long bytes) { 59 | return normalizeBytes(bytes).replace(",", "."); 60 | } 61 | 62 | @Override 63 | public String secondsToPeriod(long seconds) { 64 | return formatDuration(Duration.ofSeconds(seconds)); 65 | } 66 | 67 | public static String formatDuration(Duration duration) { 68 | return DurationFormatUtils.formatDuration(duration.toMillis(), "[d'd 'H'h 'm'm ']s's'"); 69 | } 70 | 71 | /** 72 | * Check if given value is numeric 73 | * 74 | * @param value value to check 75 | * @return true valid value is numeric, false otherwise 76 | */ 77 | @Override 78 | public boolean isNumeric(String value) { 79 | if (value != null) { 80 | Matcher matcher = numericPattern.matcher(value); 81 | return matcher.matches(); 82 | } 83 | return false; 84 | } 85 | 86 | @Override 87 | public String join(Collection collection) { 88 | return join(collection, ","); 89 | } 90 | 91 | @Override 92 | public String join(Collection collection, String delimiter) { 93 | return StringUtils.join(collection.iterator(), delimiter); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /scex-util/src/main/scala/com/avsystem/scex/util/function/NetFunctions.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util.function; 2 | 3 | import com.google.common.net.InetAddresses; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.apache.commons.net.util.SubnetUtils; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.net.InetAddress; 10 | import java.util.Comparator; 11 | import java.util.Locale; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | public class NetFunctions { 16 | private static final Logger LOGGER = LoggerFactory.getLogger(NetFunctions.class); 17 | 18 | private static final String MACADDRESS_PATTERN = 19 | "^(([0-9A-Fa-f][0-9A-Fa-f][-:]?){5}[0-9A-Fa-f]" + 20 | "[0-9A-Fa-f])|(([0-9A-Fa-f][0-9A-Fa-f]" + 21 | "[0-9A-Fa-f][0-9A-Fa-f].){2}[0-9A-Fa-f]" + 22 | "[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f])$"; 23 | 24 | private static final Pattern macPattern = Pattern.compile(MACADDRESS_PATTERN); 25 | 26 | public static class InetAddressComparator implements Comparator { 27 | @Override 28 | public int compare(InetAddress adr1, InetAddress adr2) { 29 | byte[] ba1 = adr1.getAddress(); 30 | byte[] ba2 = adr2.getAddress(); 31 | 32 | // general ordering: ipv4 before ipv6 33 | if (ba1.length < ba2.length) return -1; 34 | if (ba1.length > ba2.length) return 1; 35 | 36 | // we have 2 ips of the same type, so we have to compare each byte 37 | for (int i = 0; i < ba1.length; i++) { 38 | int b1 = unsignedByteToInt(ba1[i]); 39 | int b2 = unsignedByteToInt(ba2[i]); 40 | if (b1 == b2) 41 | continue; 42 | if (b1 < b2) 43 | return -1; 44 | else 45 | return 1; 46 | } 47 | return 0; 48 | } 49 | 50 | private int unsignedByteToInt(byte b) { 51 | return (int) b & 0xFF; 52 | } 53 | } 54 | 55 | /** 56 | * @param ip 57 | * @param subnetWithMask CIDR signature (e.g. 192.168.0.57/27) 58 | * @return 59 | */ 60 | public static boolean isIpInSubnet(String ip, String subnetWithMask) { 61 | if (ip == null || subnetWithMask == null) { 62 | return false; 63 | } 64 | try { 65 | SubnetUtils.SubnetInfo subnetInfo = (new SubnetUtils(subnetWithMask)).getInfo(); 66 | return subnetInfo.isInRange(ip); 67 | } catch (Exception ex) { 68 | LOGGER.info("Ip not in subnet", ex); 69 | return false; 70 | } 71 | } 72 | 73 | public static boolean isIpInSubnetWithMask(String ip, String subnet, String mask) { 74 | if (ip == null || subnet == null || mask == null) { 75 | return false; 76 | } 77 | try { 78 | SubnetUtils.SubnetInfo subnetInfo = (new SubnetUtils(subnet, mask)).getInfo(); 79 | return subnetInfo.isInRange(ip); 80 | } catch (Exception ex) { 81 | LOGGER.info("Ip not in subnet", ex); 82 | return false; 83 | } 84 | } 85 | 86 | public static String stripMac(String mac) { 87 | if (mac == null) { 88 | return ""; 89 | } 90 | mac = StringUtils.stripToEmpty(mac).toUpperCase(Locale.ENGLISH); 91 | return mac.replaceAll("[^0-9A-F]+", ""); 92 | } 93 | 94 | /** 95 | * Validate ip address with regular expression 96 | * 97 | * @param ip ip address for validation 98 | * @return true valid ip address, false invalid ip address 99 | */ 100 | public static boolean isIp(final String ip) { 101 | if (ip == null) { 102 | return false; 103 | } 104 | return InetAddresses.isInetAddress(ip); 105 | } 106 | 107 | /** 108 | * Validate comma-separated ip addresses with regular expression 109 | * 110 | * @param ip ip address for validation 111 | * @return true valid ip address, false invalid ip address 112 | */ 113 | public static boolean isIps(final String ips) { 114 | if (ips == null) { 115 | return false; 116 | } 117 | 118 | if (ips != null) { 119 | for (String ip : ips.split(",")) { 120 | if (!isIp(ip.trim())) { 121 | return false; 122 | } 123 | } 124 | } 125 | return true; 126 | } 127 | 128 | /** 129 | * Validate mac address with regular expression 130 | * 131 | * @param mac mac address for validation 132 | * @return true valid mac address, false invalid mac address 133 | */ 134 | public static boolean isMac(final String mac) { 135 | if (mac == null) { 136 | return false; 137 | } 138 | Matcher matcher = macPattern.matcher(mac.replaceAll("\\s", "")); 139 | return matcher.matches(); 140 | } 141 | } -------------------------------------------------------------------------------- /scex-util/src/main/scala/com/avsystem/scex/util/function/NetUtil.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util.function; 2 | 3 | import com.avsystem.scex.presentation.annotation.Documentation; 4 | 5 | public interface NetUtil { 6 | @Documentation("Returns true if the value of `ip` argument belongs to the subnet provided as the `subnetWithMask` " + 7 | "argument. Use CIDR notation for the `subnetWithMask` argument, e.g. `10.8.0.0/16`.") 8 | boolean isIpInSubnet(String ip, String subnetWithMask); 9 | 10 | @Documentation("Returns true if the value of 'ip' argument belongs to the subnet defined by the `subnet` and 'mask' arguments. Use dot-decimal notation for the arguments.") 11 | boolean isIpInSubnetWithMask(String ip, String subnet, String mask); 12 | 13 | @Documentation("Returns network address (host identifier) for the provided `ip` and `mask`. Use dot-decimal notation for the arguments.") 14 | String networkAddress(String ip, String mask); 15 | 16 | @Documentation("Returns network address (host identifier) for the provided `subnetWithMask`. Use CIDR notation for the argument, e.g. `192.168.10.5/24`.") 17 | String networkAddress(String subnetWithMask); 18 | 19 | @Documentation("Converts subnet mask in CIDR notation to dot-decimal notation.") 20 | String getMask(String cidr); 21 | 22 | @Documentation("Returns minimal host address based on the provided `ip` and `mask`. Use dot-decimal notation for the arguments.") 23 | String getMinAddress(String ip, String mask); 24 | 25 | @Documentation("Returns maximal host address based on the provided `ip` and `mask`. Use dot-decimal notation for the arguments.") 26 | String getMaxAddress(String ip, String mask); 27 | 28 | @Documentation("Returns the `mac` address provided as the argument with any separating characters removed and letters changed to uppercase.") 29 | String stripMac(String mac); 30 | 31 | @Documentation("Returns 0 if this address is equal to the one provided as the argument, -1 if less than, 1 if greater than.") 32 | Integer compareIp(String ip1, String ip2); 33 | 34 | @Documentation("Returns true if the provided string is a valid IP address.") 35 | boolean isIp(String ip); 36 | 37 | @Documentation("Returns true if the provided object is a valid IP address.") 38 | public boolean isIp(Object ip); 39 | 40 | @Documentation("Returns true if the all the provided IP addresses in a comma-separated list are valid.") 41 | boolean isIps(String ips); 42 | 43 | @Documentation("Returns true if the provided `mac` argument is valid hardware address.") 44 | boolean isMac(String mac); 45 | 46 | @Documentation("Converts network mask in CIDR notation to dot-decimal notation, e.g. `/24` to `255.255.255.0`.") 47 | String maskFromCidr(String mask); 48 | 49 | @Documentation("Converts network mask in dot-decimal notation to CIDR notation, e.g. `255.255.255.0` to `/24`.") 50 | String maskToCidr(String mask); 51 | 52 | @Documentation("Returns true if the provided hostname is valid against RFC 1035 and RFC 1123.") 53 | boolean isValidHostname(String host); 54 | 55 | } -------------------------------------------------------------------------------- /scex-util/src/main/scala/com/avsystem/scex/util/function/NetUtilImpl.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util.function; 2 | 3 | import com.google.common.net.InternetDomainName; 4 | import org.apache.commons.net.util.SubnetUtils; 5 | 6 | import java.net.InetAddress; 7 | import java.net.UnknownHostException; 8 | 9 | public class NetUtilImpl implements NetUtil { 10 | public static final NetUtilImpl INSTANCE = new NetUtilImpl(); 11 | 12 | private NetUtilImpl() { 13 | } 14 | 15 | protected boolean isIpAndMask(String ip, String mask) { 16 | return isIp(ip) && isIp(mask); 17 | } 18 | 19 | @Override 20 | public boolean isIpInSubnet(String ip, String subnetWithMask) { 21 | return NetFunctions.isIpInSubnet(ip, subnetWithMask); 22 | } 23 | 24 | @Override 25 | public boolean isIpInSubnetWithMask(String ip, String subnet, String mask) { 26 | return NetFunctions.isIpInSubnetWithMask(ip, subnet, mask); 27 | } 28 | 29 | @Override 30 | public String stripMac(String mac) { 31 | return NetFunctions.stripMac(mac); 32 | } 33 | 34 | @Override 35 | public boolean isIp(String ip) { 36 | return NetFunctions.isIp(ip); 37 | } 38 | 39 | @Override 40 | public boolean isIp(Object ip) { 41 | return isIp(String.valueOf(ip)); 42 | } 43 | 44 | @Override 45 | public boolean isIps(String ips) { 46 | return NetFunctions.isIps(ips); 47 | } 48 | 49 | @Override 50 | public boolean isMac(String mac) { 51 | return NetFunctions.isMac(mac); 52 | } 53 | 54 | @Override 55 | public String maskFromCidr(String mask) { 56 | return getMask(mask); 57 | } 58 | 59 | @Override 60 | public String maskToCidr(String mask) { 61 | if (mask != null && mask.matches("\\d+\\.\\d+\\.\\d+\\.\\d+")) 62 | return "/" + new SubnetUtils("0.0.0.0", mask).getInfo().getCidrSignature().split("/")[1]; 63 | return null; 64 | } 65 | 66 | @Override 67 | public boolean isValidHostname(String host) { 68 | return InternetDomainName.isValid(host); 69 | } 70 | 71 | @Override 72 | public String networkAddress(String ip, String mask) { 73 | if (isIpAndMask(ip, mask)) { 74 | return new SubnetUtils(ip, mask).getInfo().getNetworkAddress(); 75 | } 76 | return null; 77 | } 78 | 79 | @Override 80 | public String getMask(String cidr) { 81 | if(cidr == null) 82 | return null; 83 | if (!cidr.startsWith("/")) 84 | cidr = "/" + cidr; 85 | if (cidr.matches("\\/\\d{1,2}")) { 86 | return new SubnetUtils("0.0.0.0" + cidr).getInfo().getNetmask(); 87 | } 88 | return null; 89 | } 90 | 91 | @Override 92 | public String getMinAddress(String ip, String mask) { 93 | if (isIpAndMask(ip, mask)) { 94 | return new SubnetUtils(ip, mask).getInfo().getLowAddress(); 95 | } 96 | return null; 97 | } 98 | 99 | @Override 100 | public String getMaxAddress(String ip, String mask) { 101 | if (isIpAndMask(ip, mask)) { 102 | return new SubnetUtils(ip, mask).getInfo().getHighAddress(); 103 | } 104 | return null; 105 | } 106 | 107 | @Override 108 | public String networkAddress(String subnetWithMask) { 109 | return new SubnetUtils(subnetWithMask).getInfo().getNetworkAddress(); 110 | } 111 | 112 | @Override 113 | public Integer compareIp(String ip1, String ip2) { 114 | try { 115 | InetAddress adr1 = InetAddress.getByName(ip1); 116 | InetAddress adr2 = InetAddress.getByName(ip2); 117 | NetFunctions.InetAddressComparator inetAddressComparator = new NetFunctions.InetAddressComparator(); 118 | return inetAddressComparator.compare(adr1, adr2); 119 | } catch (UnknownHostException e) { 120 | return null; 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /scex-util/src/main/scala/com/avsystem/scex/util/function/StringUtil.java: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util.function; 2 | 3 | import java.util.Collection; 4 | import com.avsystem.scex.presentation.annotation.Documentation; 5 | 6 | public interface StringUtil { 7 | @Documentation("Returns concatenation of the provided comma-separated parts into a new string.") 8 | String concat(String... parts); 9 | 10 | @Documentation("Returns true if the `list` of comma-separated values contains the string provided as the `item` argument.") 11 | boolean contains(String list, String item); 12 | 13 | @Documentation("Returns true if the `source` string contains the value provided as `item`.") 14 | boolean stringContains(String source, String item); 15 | 16 | @Documentation("Returns the first number in the provided string as a double value type. Returns 0 if string is null or does not include a number.") 17 | double extract(String string); 18 | 19 | @Documentation("Returns a string which matches the regular expression `pattern`. Returns null if there is no match.") 20 | String regexFind(String value, String pattern); 21 | 22 | @Documentation("Returns a group of characters which matches the regular expression `pattern`. Returns null if there is no match.") 23 | String regexFindGroup(String value, String pattern); 24 | 25 | @Documentation("Returns true if the string `value` matches the regular expression `pattern`.") 26 | boolean regexMatches(String value, String pattern); 27 | 28 | @Documentation("Returns a string with first part of the `value` string matching the regular expression `pattern` replaced with the `replacement` argument.") 29 | String regexReplace(String value, String pattern, String replacement); 30 | 31 | @Documentation("Returns a string with all parts of the `value` string matching the regular expression `pattern` replaced with the `replacement` argument.") 32 | String regexReplaceAll(String value, String pattern, String replacement); 33 | 34 | @Documentation("Returns all parts of a dot-separated string following the part indicated by `from` value, e.g. string.slice('a.b.c.d.', 2) returns 'c.d.'") 35 | String slice(String item, int from); 36 | 37 | @Documentation("Returns all parts of a dot-separated string following the part indicated by `from` and `to` values. " + 38 | "`Dot` boolean value determines if a dot is appended to the output, e.g. string.slice('a.b.c.d.', 3, 4, false) returns 'd'") 39 | String slice(String item, int from, int to, boolean dot); 40 | 41 | @Documentation("Returns the `source` string with all non-alphanumeric characters replaced with the `replacement` string.") 42 | String stripToAlphanumeric(String source, String replacement); 43 | 44 | @Documentation("Returns the `source` string with it last characters indicated by the `end` string removed.") 45 | String removeEnd(String source, String end); 46 | 47 | @Documentation("Returns the `source` string with it first characters indicated by the `end` string removed.") 48 | String removeStart(String source, String start); 49 | 50 | @Documentation("Returns the `source` string with `InternetGatewayDevice.` or `Device.` parts removed from its beginning.") 51 | String removeTRRoot(String source); 52 | 53 | @Documentation("Returns a random alphanumeric string. The `length` argument indicates the number of characters.") 54 | String random(int length); 55 | 56 | @Documentation("Prepends the `str` string with `padStr`, repeated if needed. Number of characters in the output corresponds to `size`.") 57 | String leftPad(String str, int size, String padStr); 58 | 59 | @Documentation("Appends `padStr` to the `str` string, repeated if needed. Number of characters in the output corresponds to `size`.") 60 | String rightPad(String str, int size, String padStr); 61 | 62 | @Documentation("Returns a substring of `str`. The substring begins at the specified `from` index and extends to the character at the `to` index.") 63 | String subString(String str, int from, int to); 64 | 65 | @Documentation("Returns an array of strings split from `str` with the given `separator`.") 66 | String[] split(String str, String separator); 67 | 68 | @Documentation("Returns the `str` string with whitespaces removed from both the beginning and end of the string.") 69 | String trimToEmpty(String str); 70 | 71 | @Documentation("Replaces all occurrences of `find` with `replacement` in the `string`.") 72 | String replace(String str, String find, String replacement); 73 | 74 | @Documentation("Returns a string which is a concatenation of the `list` elements using the provided `separator`.") 75 | String join(Collection list, String separator); 76 | 77 | @Documentation("Calculates the HMAC MD5 digest from the `str` string, using the provided `key`. The output is a 32 character hexadecimal string.") 78 | String hmacMD5(String str, String key); 79 | } 80 | -------------------------------------------------------------------------------- /scex-util/src/test/scala/com/avsystem/scex/util/EnrichedDateTest.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util 2 | 3 | import org.scalatest.funsuite.AnyFunSuite 4 | 5 | import java.time.ZoneId 6 | import java.util.{Calendar, Date} 7 | 8 | class EnrichedDateTest extends AnyFunSuite { 9 | 10 | import CommonExpressionUtils.enrichDate 11 | 12 | // 2019-04-17 12:55:14.456 UTC 13 | val testDate = new Date(1555505714456L) 14 | 15 | // enrichZonedDate tested explicitly to provide stable zone for tests 16 | val testZonedDate: EnrichedZonedDate = EnrichedZonedDate.fromDate(testDate, ZoneId.of("Europe/Warsaw")) 17 | 18 | def localMidnight: Calendar = { 19 | val time = Calendar.getInstance() 20 | time.set(Calendar.HOUR_OF_DAY, 0) 21 | time.set(Calendar.MINUTE, 0) 22 | time.set(Calendar.SECOND, 0) 23 | time.set(Calendar.MILLISECOND, 0) 24 | time 25 | } 26 | 27 | test("addMilliseconds") { 28 | assert(testDate.addMilliseconds(1000) == new Date(1555505715456L)) 29 | } 30 | 31 | test("addSeconds") { 32 | assert(testDate.addSeconds(1) == new Date(1555505715456L)) 33 | } 34 | 35 | test("addMinutes") { 36 | assert(testDate.addMinutes(1) == new Date(1555505774456L)) 37 | } 38 | 39 | test("addHours") { 40 | assert(testDate.addHours(1) == new Date(1555509314456L)) 41 | } 42 | 43 | test("addDays") { 44 | assert(testDate.addDays(1) == new Date(1555592114456L)) 45 | } 46 | 47 | test("addWeeks") { 48 | assert(testDate.addWeeks(1) == new Date(1556110514456L)) 49 | } 50 | 51 | test("addMonths") { 52 | assert(testDate.addMonths(1) == new Date(1558097714456L)) 53 | } 54 | 55 | // 2019-04-17 12:55:14.000 UTC 56 | test("truncate to seconds") { 57 | assert(testDate.truncateToSeconds == new Date(1555505714000L)) 58 | } 59 | // 2019-04-17 12:55:00.000 UTC 60 | test("truncate to minutes") { 61 | assert(testDate.truncateToMinutes == new Date(1555505700000L)) 62 | } 63 | // 2019-04-17 12:00:00.000 UTC 64 | test("truncate to hours") { 65 | assert(testDate.truncateToHours == new Date(1555502400000L)) 66 | } 67 | // 2019-04-17 00:00:00.000 default time zone 68 | 69 | val truncatedToDays: Calendar = localMidnight 70 | truncatedToDays.set(Calendar.YEAR, 2019) 71 | truncatedToDays.set(Calendar.MONTH, Calendar.APRIL) 72 | truncatedToDays.set(Calendar.DAY_OF_MONTH, 17) 73 | test("truncate to day") { 74 | assert(testDate.truncateToDays == truncatedToDays.getTime) 75 | } 76 | // 2019-04-01 00:00:00.000 default time zone 77 | val truncatedToMonths: Calendar = localMidnight 78 | truncatedToMonths.set(Calendar.YEAR, 2019) 79 | truncatedToMonths.set(Calendar.MONTH, Calendar.APRIL) 80 | truncatedToMonths.set(Calendar.DAY_OF_MONTH, 1) 81 | test("truncate to month") { 82 | assert(testDate.truncateToMonths == truncatedToMonths.getTime) 83 | } 84 | // 2019-01-01 00:00:00.000 default time zone 85 | val truncatedToYears: Calendar = localMidnight 86 | truncatedToYears.set(Calendar.YEAR, 2019) 87 | truncatedToYears.set(Calendar.MONTH, Calendar.JANUARY) 88 | truncatedToYears.set(Calendar.DAY_OF_MONTH, 1) 89 | test("truncate to year") { 90 | assert(testDate.truncateToYears == truncatedToYears.getTime) 91 | } 92 | 93 | test("millis") { 94 | assert(testDate.millis == 1555505714456L) 95 | } 96 | 97 | test("format with default format") { 98 | assert(testZonedDate.format == "2019.04.17 14:55:14") 99 | } 100 | 101 | test("format with custom format") { 102 | assert(testZonedDate.format("yyyy-MM-dd") == "2019-04-17") 103 | } 104 | 105 | test("millisOfSecond") { 106 | assert(testZonedDate.millisOfSecond == 456) 107 | } 108 | 109 | test("secondOfMinute") { 110 | assert(testZonedDate.secondOfMinute == 14) 111 | } 112 | 113 | test("secondOfDay") { 114 | assert(testZonedDate.secondOfDay == 53714) 115 | } 116 | 117 | test("minuteOfHour") { 118 | assert(testZonedDate.minuteOfHour == 55) 119 | } 120 | 121 | test("minuteOfDay") { 122 | assert(testZonedDate.minuteOfDay == 895) 123 | } 124 | 125 | test("hourOfDay") { 126 | assert(testZonedDate.hourOfDay == 14) 127 | } 128 | 129 | test("dayOfMonth") { 130 | assert(testZonedDate.dayOfMonth == 17) 131 | } 132 | 133 | test("dayOfWeek") { 134 | assert(testZonedDate.dayOfWeek == 3) 135 | } 136 | 137 | test("dayOfYear") { 138 | assert(testZonedDate.dayOfYear == 107) 139 | } 140 | 141 | test("monthOfYear") { 142 | assert(testZonedDate.monthOfYear == 4) 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /scex-util/src/test/scala/com/avsystem/scex/util/QmarkTest.scala: -------------------------------------------------------------------------------- 1 | package com.avsystem.scex.util 2 | 3 | import com.avsystem.commons.JDate 4 | import com.avsystem.scex.util.CommonExpressionUtils._ 5 | import com.avsystem.scex.util.JavaCollectionExtensions._ 6 | import org.scalatest.funsuite.AnyFunSuite 7 | 8 | import java.text.ParseException 9 | import java.{util => ju} 10 | 11 | class QmarkTest extends AnyFunSuite { 12 | test("recover from NullPointerException") { 13 | def expression = (null: String).length 14 | 15 | assertThrows[NullPointerException](expression) 16 | assert(0 == expression ? 0) 17 | } 18 | 19 | test("recover from NoSuchElementException") { 20 | def expression = ju.Arrays.asList[String]().anyElement 21 | 22 | assertThrows[NoSuchElementException](expression) 23 | assert("empty" == expression ? "empty") 24 | } 25 | 26 | test("recover from UnsupportedOperationException") { 27 | def expression = ju.Arrays.asList[Int]().min 28 | 29 | assertThrows[UnsupportedOperationException](expression) 30 | assert(0 == expression ? 0) 31 | } 32 | 33 | test("recover from IndexOutOfBoundsException") { 34 | def expression = ju.Arrays.asList[String]()(0) 35 | 36 | assertThrows[IndexOutOfBoundsException](expression) 37 | assert("empty" == expression ? "empty") 38 | } 39 | 40 | test("recover from NumberFormatException") { 41 | def expression = "zero".toInt 42 | 43 | assertThrows[NumberFormatException]("zero".toInt) 44 | assert(0 == expression ? 0) 45 | } 46 | 47 | test("recover from IllegalArgumentException") { 48 | def expression = ju.Arrays.asList[Int]().mean 49 | 50 | assertThrows[IllegalArgumentException](ju.Arrays.asList[Int]().mean) 51 | assert(0.0 == expression ? 0.0) 52 | } 53 | 54 | test("recover from ParseException") { 55 | // it looks like java.text.ParseException is not thrown by native scex utils API 56 | def expression: JDate = throw new ParseException("parsing failed", 0) 57 | 58 | assertThrows[ParseException](expression) 59 | assert(new JDate(0) == expression ? new JDate(0)) 60 | } 61 | 62 | test("recover from ExpressionRecoverableException") { 63 | class TestRecoverableException extends ExpressionRecoverableException 64 | 65 | def expression: String = throw new TestRecoverableException 66 | 67 | assertThrows[TestRecoverableException](expression) 68 | assert("recovered" == expression ? "recovered") 69 | } 70 | 71 | test("don't recover from generic RuntimeException") { 72 | def expression: String = throw new RuntimeException 73 | 74 | assertThrows[RuntimeException](expression) 75 | assertThrows[RuntimeException](expression ? "recovered") 76 | } 77 | } 78 | --------------------------------------------------------------------------------