├── src └── main │ ├── res │ ├── values │ │ ├── strings.xml │ │ ├── colors.xml │ │ └── themes.xml │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ ├── values-night │ │ └── themes.xml │ ├── layout │ │ └── activity_main.xml │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ └── drawable │ │ └── ic_launcher_background.xml │ ├── AndroidManifest.xml │ ├── scala │ └── com │ │ └── example │ │ └── androidscala213 │ │ └── MainActivity.scala │ └── java │ └── java │ └── lang │ └── ClassValue.java ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitmodules ├── .gitignore ├── proguard-rules.pro ├── gradle.properties ├── scala-library-hack ├── build.gradle └── src │ └── main │ └── java │ └── scala │ └── runtime │ └── Statics.java ├── gradlew.bat └── gradlew /src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AndroidScala213 3 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | includeBuild 'scala-android-plugin' 2 | include ':scala-library-hack' 3 | rootProject.name = "AndroidScala213" -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidscala213/AndroidScala213/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidscala213/AndroidScala213/HEAD/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidscala213/AndroidScala213/HEAD/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidscala213/AndroidScala213/HEAD/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidscala213/AndroidScala213/HEAD/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidscala213/AndroidScala213/HEAD/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidscala213/AndroidScala213/HEAD/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidscala213/AndroidScala213/HEAD/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidscala213/AndroidScala213/HEAD/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidscala213/AndroidScala213/HEAD/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidscala213/AndroidScala213/HEAD/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "scala-android-plugin"] 2 | path = scala-android-plugin 3 | url = https://github.com/androidscala213/scala-android-plugin.git 4 | branch = Java8 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | /scala-library-hack/build/ 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists -------------------------------------------------------------------------------- /src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /src/main/scala/com/example/androidscala213/MainActivity.scala: -------------------------------------------------------------------------------- 1 | package com.example.androidscala213 2 | 3 | import android.os.Bundle 4 | import android.widget.TextView 5 | import androidx.appcompat.app.AppCompatActivity 6 | 7 | import scala.reflect._ 8 | 9 | object MainActivity { 10 | val ownTag: ClassTag[MainActivity] = classTag[MainActivity] 11 | } 12 | 13 | class MainActivity extends AppCompatActivity { 14 | 15 | val helloWorld = List("H", "e", "l", "l", "o", " ", "W", "o", "r", "l", "d", "!") 16 | 17 | lazy val textView = findViewById(R.id.textView).asInstanceOf[TextView] 18 | 19 | override def onCreate(savedInstanceState: Bundle): Unit = { 20 | super.onCreate(savedInstanceState) 21 | setContentView(R.layout.activity_main) 22 | helloWorld match { 23 | case s: List[String] => 24 | textView.setText(s"${s.mkString("")} from \n ${MainActivity.ownTag}") 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 19 | 20 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true -------------------------------------------------------------------------------- /scala-library-hack/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { 4 | url "https://plugins.gradle.org/m2/" 5 | } 6 | } 7 | dependencies { 8 | classpath "com.github.jengelman.gradle.plugins:shadow:6.1.0" 9 | } 10 | } 11 | 12 | plugins { 13 | id 'java' 14 | id 'scala' 15 | id 'maven-publish' 16 | } 17 | 18 | apply plugin: "com.github.johnrengelman.shadow" 19 | 20 | tasks.withType(ScalaCompile) { 21 | scalaCompileOptions.additionalParameters = ["-feature", "-language:implicitConversions"] 22 | } 23 | 24 | artifacts { 25 | archives jar 26 | } 27 | 28 | repositories { 29 | mavenLocal() 30 | mavenCentral() 31 | } 32 | 33 | group 'com.scalapatch' 34 | version '2.13.5' 35 | 36 | repositories { 37 | mavenCentral() 38 | } 39 | 40 | sourceSets { 41 | main { 42 | scala { 43 | srcDirs = ['src/main/scala', 'src/main/java'] 44 | } 45 | java { 46 | srcDirs = [] 47 | } 48 | } 49 | } 50 | 51 | dependencies { 52 | implementation "org.scala-lang:scala-library:$version" 53 | } 54 | 55 | shadowJar { 56 | archiveClassifier.set('') 57 | } 58 | 59 | publishing { 60 | publications { 61 | shadow(MavenPublication) { publication -> 62 | project.shadow.component(publication) 63 | 64 | groupId = "org.scala-lang" 65 | artifactId = 'scala-library' 66 | 67 | pom { 68 | name = "Modified Scala library" 69 | description = "Scala library modified to drop some Java 8 depencency" 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /scala-library-hack/src/main/java/scala/runtime/Statics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala.runtime; 14 | 15 | import java.lang.invoke.MethodHandle; 16 | import java.lang.invoke.MethodHandles; 17 | import java.lang.invoke.MethodType; 18 | import java.lang.reflect.Field; 19 | 20 | /** Not for public consumption. Usage by the runtime only. 21 | */ 22 | 23 | public final class Statics { 24 | public static int mix(int hash, int data) { 25 | int h = mixLast(hash, data); 26 | h = Integer.rotateLeft(h, 13); 27 | return h * 5 + 0xe6546b64; 28 | } 29 | 30 | public static int mixLast(int hash, int data) { 31 | int k = data; 32 | 33 | k *= 0xcc9e2d51; 34 | k = Integer.rotateLeft(k, 15); 35 | k *= 0x1b873593; 36 | 37 | return hash ^ k; 38 | } 39 | 40 | public static int finalizeHash(int hash, int length) { 41 | return avalanche(hash ^ length); 42 | } 43 | 44 | /** Force all bits of the hash to avalanche. Used for finalizing the hash. */ 45 | public static int avalanche(int h) { 46 | h ^= h >>> 16; 47 | h *= 0x85ebca6b; 48 | h ^= h >>> 13; 49 | h *= 0xc2b2ae35; 50 | h ^= h >>> 16; 51 | 52 | return h; 53 | } 54 | 55 | public static int longHash(long lv) { 56 | int iv = (int)lv; 57 | if (iv == lv) 58 | return iv; 59 | 60 | return java.lang.Long.hashCode(lv); 61 | } 62 | 63 | public static int doubleHash(double dv) { 64 | int iv = (int)dv; 65 | if (iv == dv) 66 | return iv; 67 | 68 | long lv = (long)dv; 69 | if (lv == dv) 70 | return java.lang.Long.hashCode(lv); 71 | 72 | float fv = (float)dv; 73 | if (fv == dv) 74 | return java.lang.Float.hashCode(fv); 75 | 76 | return java.lang.Double.hashCode(dv); 77 | } 78 | 79 | public static int floatHash(float fv) { 80 | int iv = (int)fv; 81 | if (iv == fv) 82 | return iv; 83 | 84 | long lv = (long)fv; 85 | if (lv == fv) 86 | return java.lang.Long.hashCode(lv); 87 | 88 | return java.lang.Float.hashCode(fv); 89 | } 90 | 91 | /** 92 | * Hashcode algorithm is driven by the requirements imposed 93 | * by primitive equality semantics, namely that equal objects 94 | * have equal hashCodes. The first priority are the integral/char 95 | * types, which already have the same hashCodes for the same 96 | * values except for Long. So Long's hashCode is altered to 97 | * conform to Int's for all values in Int's range. 98 | * 99 | * Float is problematic because it's far too small to hold 100 | * all the Ints, so for instance Int.MaxValue.toFloat claims 101 | * to be == to each of the largest 64 Ints. There is no way 102 | * to preserve equals/hashCode alignment without compromising 103 | * the hashCode distribution, so Floats are only guaranteed 104 | * to have the same hashCode for whole Floats in the range 105 | * Short.MinValue to Short.MaxValue (2^16 total.) 106 | * 107 | * Double has its hashCode altered to match the entire Int range, 108 | * but is not guaranteed beyond that. (But could/should it be? 109 | * The hashCode is only 32 bits so this is a more tractable 110 | * issue than Float's, but it might be better simply to exclude it.) 111 | * 112 | * Note: BigInt and BigDecimal, being arbitrary precision, could 113 | * be made consistent with all other types for the Int range, but 114 | * as yet have not. 115 | * 116 | * Note: Among primitives, Float.NaN != Float.NaN, but the boxed 117 | * versions are equal. This still needs reconciliation. 118 | */ 119 | public static int anyHash(Object x) { 120 | if (x == null) 121 | return 0; 122 | 123 | if (x instanceof java.lang.Number) { 124 | return anyHashNumber((java.lang.Number) x); 125 | } 126 | 127 | return x.hashCode(); 128 | } 129 | 130 | private static int anyHashNumber(Number x) { 131 | if (x instanceof java.lang.Long) 132 | return longHash(((java.lang.Long)x).longValue()); 133 | 134 | if (x instanceof java.lang.Double) 135 | return doubleHash(((java.lang.Double)x).doubleValue()); 136 | 137 | if (x instanceof java.lang.Float) 138 | return floatHash(((java.lang.Float)x).floatValue()); 139 | 140 | return x.hashCode(); 141 | } 142 | 143 | /** Used as a marker object to return from PartialFunctions */ 144 | public static final Object pfMarker = new Object(); 145 | 146 | // @ForceInline would be nice here. 147 | public static void releaseFence() throws Throwable { 148 | VM.RELEASE_FENCE.invoke(); 149 | } 150 | 151 | final static class VM { 152 | static final MethodHandle RELEASE_FENCE; 153 | 154 | static { 155 | RELEASE_FENCE = mkHandle(); 156 | } 157 | 158 | private static MethodHandle mkHandle() { 159 | MethodHandles.Lookup lookup = MethodHandles.lookup(); 160 | try { 161 | return lookup.findStatic(Class.forName("java.lang.invoke.VarHandle"), "releaseFence", MethodType.methodType(Void.TYPE)); 162 | } catch (NoSuchMethodException | ClassNotFoundException e) { 163 | try { 164 | Class unsafeClass = Class.forName("sun.misc.Unsafe"); 165 | return lookup.findVirtual(unsafeClass, "storeFence", MethodType.methodType(void.class)).bindTo(findUnsafe(unsafeClass)); 166 | } catch (NoSuchMethodException | ClassNotFoundException | IllegalAccessException e1) { 167 | ExceptionInInitializerError error = new ExceptionInInitializerError(e1); 168 | error.addSuppressed(e); 169 | throw error; 170 | } 171 | } catch (IllegalAccessException e) { 172 | throw new ExceptionInInitializerError(e); 173 | } 174 | } 175 | 176 | private static Object findUnsafe(Class unsafeClass) throws IllegalAccessException { 177 | Object found = null; 178 | for (Field field : unsafeClass.getDeclaredFields()) { 179 | if (field.getType() == unsafeClass) { 180 | field.setAccessible(true); 181 | found = field.get(null); 182 | break; 183 | } 184 | } 185 | if (found == null) throw new IllegalStateException("No instance of Unsafe found"); 186 | return found; 187 | } 188 | } 189 | 190 | /** 191 | * Just throws an exception. 192 | * Used by the synthetic `productElement` and `productElementName` methods in case classes. 193 | * Delegating the exception-throwing to this function reduces the bytecode size of the case class. 194 | */ 195 | public static final T ioobe(int n) throws IndexOutOfBoundsException { 196 | throw new IndexOutOfBoundsException(String.valueOf(n)); 197 | } 198 | 199 | } 200 | -------------------------------------------------------------------------------- /src/main/java/java/lang/ClassValue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved. 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 | * 5 | * This code is free software; you can redistribute it and/or modify it 6 | * under the terms of the GNU General Public License version 2 only, as 7 | * published by the Free Software Foundation. Oracle designates this 8 | * particular file as subject to the "Classpath" exception as provided 9 | * by Oracle in the LICENSE file that accompanied this code. 10 | * 11 | * This code is distributed in the hope that it will be useful, but WITHOUT 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 | * version 2 for more details (a copy is included in the LICENSE file that 15 | * accompanied this code). 16 | * 17 | * You should have received a copy of the GNU General Public License version 18 | * 2 along with this work; if not, write to the Free Software Foundation, 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 | * 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 | * or visit www.oracle.com if you need additional information or have any 23 | * questions. 24 | */ 25 | 26 | package java.lang; 27 | 28 | import java.lang.ClassValue.ClassValueMap; 29 | import java.util.HashMap; 30 | import java.util.Map; 31 | import java.util.WeakHashMap; 32 | import java.lang.ref.WeakReference; 33 | import java.util.concurrent.atomic.AtomicInteger; 34 | 35 | import static java.lang.ClassValue.ClassValueMap.probeHomeLocation; 36 | import static java.lang.ClassValue.ClassValueMap.probeBackupLocations; 37 | 38 | /** 39 | * Lazily associate a computed value with (potentially) every type. 40 | * For example, if a dynamic language needs to construct a message dispatch 41 | * table for each class encountered at a message send call site, 42 | * it can use a {@code ClassValue} to cache information needed to 43 | * perform the message send quickly, for each class encountered. 44 | * @author John Rose, JSR 292 EG 45 | * @since 1.7 46 | */ 47 | public abstract class ClassValue { 48 | /** 49 | * Sole constructor. (For invocation by subclass constructors, typically 50 | * implicit.) 51 | */ 52 | protected ClassValue() { 53 | } 54 | 55 | /** 56 | * Computes the given class's derived value for this {@code ClassValue}. 57 | *

58 | * This method will be invoked within the first thread that accesses 59 | * the value with the {@link #get get} method. 60 | *

61 | * Normally, this method is invoked at most once per class, 62 | * but it may be invoked again if there has been a call to 63 | * {@link #remove remove}. 64 | *

65 | * If this method throws an exception, the corresponding call to {@code get} 66 | * will terminate abnormally with that exception, and no class value will be recorded. 67 | * 68 | * @param type the type whose class value must be computed 69 | * @return the newly computed value associated with this {@code ClassValue}, for the given class or interface 70 | * @see #get 71 | * @see #remove 72 | */ 73 | protected abstract T computeValue(Class type); 74 | 75 | /** 76 | * Returns the value for the given class. 77 | * If no value has yet been computed, it is obtained by 78 | * an invocation of the {@link #computeValue computeValue} method. 79 | *

80 | * The actual installation of the value on the class 81 | * is performed atomically. 82 | * At that point, if several racing threads have 83 | * computed values, one is chosen, and returned to 84 | * all the racing threads. 85 | *

86 | * The {@code type} parameter is typically a class, but it may be any type, 87 | * such as an interface, a primitive type (like {@code int.class}), or {@code void.class}. 88 | *

89 | * In the absence of {@code remove} calls, a class value has a simple 90 | * state diagram: uninitialized and initialized. 91 | * When {@code remove} calls are made, 92 | * the rules for value observation are more complex. 93 | * See the documentation for {@link #remove remove} for more information. 94 | * 95 | * @param type the type whose class value must be computed or retrieved 96 | * @return the current value associated with this {@code ClassValue}, for the given class or interface 97 | * @throws NullPointerException if the argument is null 98 | * @see #remove 99 | * @see #computeValue 100 | */ 101 | public T get(Class type) { 102 | // non-racing this.hashCodeForCache : final int 103 | ClassValue.Entry[] cache; 104 | ClassValue.Entry e = probeHomeLocation(cache = getCacheCarefully(type), this); 105 | // racing e : current value <=> stale value from current cache or from stale cache 106 | // invariant: e is null or an ClassValue.Entry with readable ClassValue.Entry.version and ClassValue.Entry.value 107 | if (match(e)) 108 | // invariant: No false positive matches. False negatives are OK if rare. 109 | // The key fact that makes this work: if this.version == e.version, 110 | // then this thread has a right to observe (final) e.value. 111 | return e.value(); 112 | // The fast path can fail for any of these reasons: 113 | // 1. no entry has been computed yet 114 | // 2. hash code collision (before or after reduction mod cache.length) 115 | // 3. an entry has been removed (either on this type or another) 116 | // 4. the GC has somehow managed to delete e.version and clear the reference 117 | return getFromBackup(cache, type); 118 | } 119 | 120 | /** 121 | * Removes the associated value for the given class. 122 | * If this value is subsequently {@linkplain #get read} for the same class, 123 | * its value will be reinitialized by invoking its {@link #computeValue computeValue} method. 124 | * This may result in an additional invocation of the 125 | * {@code computeValue} method for the given class. 126 | *

127 | * In order to explain the interaction between {@code get} and {@code remove} calls, 128 | * we must model the state transitions of a class value to take into account 129 | * the alternation between uninitialized and initialized states. 130 | * To do this, number these states sequentially from zero, and note that 131 | * uninitialized (or removed) states are numbered with even numbers, 132 | * while initialized (or re-initialized) states have odd numbers. 133 | *

134 | * When a thread {@code T} removes a class value in state {@code 2N}, 135 | * nothing happens, since the class value is already uninitialized. 136 | * Otherwise, the state is advanced atomically to {@code 2N+1}. 137 | *

138 | * When a thread {@code T} queries a class value in state {@code 2N}, 139 | * the thread first attempts to initialize the class value to state {@code 2N+1} 140 | * by invoking {@code computeValue} and installing the resulting value. 141 | *

142 | * When {@code T} attempts to install the newly computed value, 143 | * if the state is still at {@code 2N}, the class value will be initialized 144 | * with the computed value, advancing it to state {@code 2N+1}. 145 | *

146 | * Otherwise, whether the new state is even or odd, 147 | * {@code T} will discard the newly computed value 148 | * and retry the {@code get} operation. 149 | *

150 | * Discarding and retrying is an important proviso, 151 | * since otherwise {@code T} could potentially install 152 | * a disastrously stale value. For example: 153 | *

164 | * We can assume in the above scenario that {@code CV.computeValue} uses locks to properly 165 | * observe the time-dependent states as it computes {@code V1}, etc. 166 | * This does not remove the threat of a stale value, since there is a window of time 167 | * between the return of {@code computeValue} in {@code T} and the installation 168 | * of the the new value. No user synchronization is possible during this time. 169 | * 170 | * @param type the type whose class value must be removed 171 | * @throws NullPointerException if the argument is null 172 | */ 173 | public void remove(Class type) { 174 | ClassValueMap map = getMap(type); 175 | map.removeEntry(this); 176 | } 177 | 178 | // Possible functionality for JSR 292 MR 1 179 | /*public*/ void put(Class type, T value) { 180 | ClassValueMap map = getMap(type); 181 | map.changeEntry(this, value); 182 | } 183 | 184 | /// -------- 185 | /// Implementation... 186 | /// -------- 187 | 188 | /** Return the cache, if it exists, else a dummy empty cache. */ 189 | private static ClassValue.Entry[] getCacheCarefully(Class type) { 190 | // racing type.classValueMap{.cacheArray} : null => new ClassValue.Entry[X] <=> new ClassValue.Entry[Y] 191 | ClassValueMap map = classValueMap.get(type); 192 | if (map == null) return EMPTY_CACHE; 193 | ClassValue.Entry[] cache = map.getCache(); 194 | return cache; 195 | // invariant: returned value is safe to dereference and check for an ClassValue.Entry 196 | } 197 | 198 | /** Initial, one-element, empty cache used by all Class instances. Must never be filled. */ 199 | private static final ClassValue.Entry[] EMPTY_CACHE = { null }; 200 | 201 | /** 202 | * Slow tail of ClassValue.get to retry at nearby locations in the cache, 203 | * or take a slow lock and check the hash table. 204 | * Called only if the first probe was empty or a collision. 205 | * This is a separate method, so compilers can process it independently. 206 | */ 207 | private T getFromBackup(ClassValue.Entry[] cache, Class type) { 208 | ClassValue.Entry e = probeBackupLocations(cache, this); 209 | if (e != null) 210 | return e.value(); 211 | return getFromHashMap(type); 212 | } 213 | 214 | // Hack to suppress warnings on the (T) cast, which is a no-op. 215 | @SuppressWarnings("unchecked") 216 | ClassValue.Entry castEntry(ClassValue.Entry e) { return (ClassValue.Entry) e; } 217 | 218 | /** Called when the fast path of get fails, and cache reprobe also fails. 219 | */ 220 | private T getFromHashMap(Class type) { 221 | // The fail-safe recovery is to fall back to the underlying classValueMap. 222 | ClassValueMap map = getMap(type); 223 | for (;;) { 224 | ClassValue.Entry e = map.startEntry(this); 225 | if (!e.isPromise()) 226 | return e.value(); 227 | try { 228 | // Try to make a real entry for the promised version. 229 | e = makeEntry(e.version(), computeValue(type)); 230 | } finally { 231 | // Whether computeValue throws or returns normally, 232 | // be sure to remove the empty entry. 233 | e = map.finishEntry(this, e); 234 | } 235 | if (e != null) 236 | return e.value(); 237 | // else try again, in case a racing thread called remove (so e == null) 238 | } 239 | } 240 | 241 | /** Check that e is non-null, matches this ClassValue, and is live. */ 242 | boolean match(ClassValue.Entry e) { 243 | // racing e.version : null (blank) => unique Version token => null (GC-ed version) 244 | // non-racing this.version : v1 => v2 => ... (updates are read faithfully from volatile) 245 | return (e != null && e.get() == this.version); 246 | // invariant: No false positives on version match. Null is OK for false negative. 247 | // invariant: If version matches, then e.value is readable (final set in ClassValue.Entry.) 248 | } 249 | 250 | /** Internal hash code for accessing Class.classValueMap.cacheArray. */ 251 | final int hashCodeForCache = nextHashCode.getAndAdd(HASH_INCREMENT) & HASH_MASK; 252 | 253 | /** Value stream for hashCodeForCache. See similar structure in ThreadLocal. */ 254 | private static final AtomicInteger nextHashCode = new AtomicInteger(); 255 | 256 | /** Good for power-of-two tables. See similar structure in ThreadLocal. */ 257 | private static final int HASH_INCREMENT = 0x61c88647; 258 | 259 | /** Mask a hash code to be positive but not too large, to prevent wraparound. */ 260 | static final int HASH_MASK = (-1 >>> 2); 261 | 262 | /** 263 | * Private key for retrieval of this object from ClassValueMap. 264 | */ 265 | static class Identity { 266 | } 267 | /** 268 | * This ClassValue's identity, expressed as an opaque object. 269 | * The main object {@code ClassValue.this} is incorrect since 270 | * subclasses may override {@code ClassValue.equals}, which 271 | * could confuse keys in the ClassValueMap. 272 | */ 273 | final Identity identity = new Identity(); 274 | 275 | /** 276 | * Current version for retrieving this class value from the cache. 277 | * Any number of computeValue calls can be cached in association with one version. 278 | * But the version changes when a remove (on any type) is executed. 279 | * A version change invalidates all cache entries for the affected ClassValue, 280 | * by marking them as stale. Stale cache entries do not force another call 281 | * to computeValue, but they do require a synchronized visit to a backing map. 282 | *

283 | * All user-visible state changes on the ClassValue take place under 284 | * a lock inside the synchronized methods of ClassValueMap. 285 | * Readers (of ClassValue.get) are notified of such state changes 286 | * when this.version is bumped to a new token. 287 | * This variable must be volatile so that an unsynchronized reader 288 | * will receive the notification without delay. 289 | *

290 | * If version were not volatile, one thread T1 could persistently hold onto 291 | * a stale value this.value == V1, while while another thread T2 advances 292 | * (under a lock) to this.value == V2. This will typically be harmless, 293 | * but if T1 and T2 interact causally via some other channel, such that 294 | * T1's further actions are constrained (in the JMM) to happen after 295 | * the V2 event, then T1's observation of V1 will be an error. 296 | *

297 | * The practical effect of making this.version be volatile is that it cannot 298 | * be hoisted out of a loop (by an optimizing JIT) or otherwise cached. 299 | * Some machines may also require a barrier instruction to execute 300 | * before this.version. 301 | */ 302 | private volatile Version version = new Version<>(this); 303 | Version version() { return version; } 304 | void bumpVersion() { version = new Version<>(this); } 305 | static class Version { 306 | private final ClassValue classValue; 307 | private final ClassValue.Entry promise = new ClassValue.Entry<>(this); 308 | Version(ClassValue classValue) { this.classValue = classValue; } 309 | ClassValue classValue() { return classValue; } 310 | ClassValue.Entry promise() { return promise; } 311 | boolean isLive() { return classValue.version() == this; } 312 | } 313 | 314 | /** One binding of a value to a class via a ClassValue. 315 | * States are:

    316 | *
  • promise if value == ClassValue.Entry.this 317 | *
  • else dead if version == null 318 | *
  • else stale if version != classValue.version 319 | *
  • else live
320 | * Promises are never put into the cache; they only live in the 321 | * backing map while a computeValue call is in flight. 322 | * Once an entry goes stale, it can be reset at any time 323 | * into the dead state. 324 | */ 325 | static class Entry extends WeakReference> { 326 | final Object value; // usually of type T, but sometimes (ClassValue.Entry)this 327 | Entry(Version version, T value) { 328 | super(version); 329 | this.value = value; // for a regular entry, value is of type T 330 | } 331 | private void assertNotPromise() { assert(!isPromise()); } 332 | /** For creating a promise. */ 333 | Entry(Version version) { 334 | super(version); 335 | this.value = this; // for a promise, value is not of type T, but ClassValue.Entry! 336 | } 337 | /** Fetch the value. This entry must not be a promise. */ 338 | @SuppressWarnings("unchecked") // if !isPromise, type is T 339 | T value() { assertNotPromise(); return (T) value; } 340 | boolean isPromise() { return value == this; } 341 | Version version() { return get(); } 342 | ClassValue classValueOrNull() { 343 | Version v = version(); 344 | return (v == null) ? null : v.classValue(); 345 | } 346 | boolean isLive() { 347 | Version v = version(); 348 | if (v == null) return false; 349 | if (v.isLive()) return true; 350 | clear(); 351 | return false; 352 | } 353 | ClassValue.Entry refreshVersion(Version v2) { 354 | assertNotPromise(); 355 | @SuppressWarnings("unchecked") // if !isPromise, type is T 356 | ClassValue.Entry e2 = new ClassValue.Entry<>(v2, (T) value); 357 | clear(); 358 | // value = null -- caller must drop 359 | return e2; 360 | } 361 | static final ClassValue.Entry DEAD_ENTRY = new ClassValue.Entry<>(null, null); 362 | } 363 | 364 | 365 | private static volatile HashMap, ClassValueMap> classValueMap = new HashMap, ClassValueMap>(); 366 | 367 | /** Return the backing map associated with this type. */ 368 | private static ClassValueMap getMap(Class type) { 369 | // racing type.classValueMap : null (blank) => unique ClassValueMap 370 | // if a null is observed, a map is created (lazily, synchronously, uniquely) 371 | // all further access to that map is synchronized 372 | ClassValueMap map = classValueMap.get(type); 373 | if (map != null) return map; 374 | return initializeMap(type); 375 | } 376 | 377 | private static final Object CRITICAL_SECTION = new Object(); 378 | private static ClassValueMap initializeMap(Class type) { 379 | ClassValueMap map; 380 | synchronized (CRITICAL_SECTION) { // private object to avoid deadlocks 381 | // happens about once per type 382 | if ((map = classValueMap.get(type)) == null) 383 | classValueMap.put(type, map = new ClassValueMap(type)); 384 | } 385 | return map; 386 | } 387 | 388 | static ClassValue.Entry makeEntry(Version explicitVersion, T value) { 389 | // Note that explicitVersion might be different from this.version. 390 | return new ClassValue.Entry<>(explicitVersion, value); 391 | 392 | // As soon as the ClassValue.Entry is put into the cache, the value will be 393 | // reachable via a data race (as defined by the Java Memory Model). 394 | // This race is benign, assuming the value object itself can be 395 | // read safely by multiple threads. This is up to the user. 396 | // 397 | // The entry and version fields themselves can be safely read via 398 | // a race because they are either final or have controlled states. 399 | // If the pointer from the entry to the version is still null, 400 | // or if the version goes immediately dead and is nulled out, 401 | // the reader will take the slow path and retry under a lock. 402 | } 403 | 404 | // The following class could also be top level and non-public: 405 | 406 | /** A backing map for all ClassValues, relative a single given type. 407 | * Gives a fully serialized "true state" for each pair (ClassValue cv, Class type). 408 | * Also manages an unserialized fast-path cache. 409 | */ 410 | static class ClassValueMap extends WeakHashMap> { 411 | private final Class type; 412 | private ClassValue.Entry[] cacheArray; 413 | private int cacheLoad, cacheLoadLimit; 414 | 415 | /** Number of entries initially allocated to each type when first used with any ClassValue. 416 | * It would be pointless to make this much smaller than the Class and ClassValueMap objects themselves. 417 | * Must be a power of 2. 418 | */ 419 | private static final int INITIAL_ENTRIES = 32; 420 | 421 | /** Build a backing map for ClassValues, relative the given type. 422 | * Also, create an empty cache array and install it on the class. 423 | */ 424 | ClassValueMap(Class type) { 425 | this.type = type; 426 | sizeCache(INITIAL_ENTRIES); 427 | } 428 | 429 | ClassValue.Entry[] getCache() { return cacheArray; } 430 | 431 | /** Initiate a query. Store a promise (placeholder) if there is no value yet. */ 432 | synchronized 433 | ClassValue.Entry startEntry(ClassValue classValue) { 434 | @SuppressWarnings("unchecked") // one map has entries for all value types 435 | ClassValue.Entry e = (ClassValue.Entry) get(classValue.identity); 436 | Version v = classValue.version(); 437 | if (e == null) { 438 | e = v.promise(); 439 | // The presence of a promise means that a value is pending for v. 440 | // Eventually, finishEntry will overwrite the promise. 441 | put(classValue.identity, e); 442 | // Note that the promise is never entered into the cache! 443 | return e; 444 | } else if (e.isPromise()) { 445 | // Somebody else has asked the same question. 446 | // Let the races begin! 447 | if (e.version() != v) { 448 | e = v.promise(); 449 | put(classValue.identity, e); 450 | } 451 | return e; 452 | } else { 453 | // there is already a completed entry here; report it 454 | if (e.version() != v) { 455 | // There is a stale but valid entry here; make it fresh again. 456 | // Once an entry is in the hash table, we don't care what its version is. 457 | e = e.refreshVersion(v); 458 | put(classValue.identity, e); 459 | } 460 | // Add to the cache, to enable the fast path, next time. 461 | checkCacheLoad(); 462 | addToCache(classValue, e); 463 | return e; 464 | } 465 | } 466 | 467 | /** Finish a query. Overwrite a matching placeholder. Drop stale incoming values. */ 468 | synchronized 469 | ClassValue.Entry finishEntry(ClassValue classValue, ClassValue.Entry e) { 470 | @SuppressWarnings("unchecked") // one map has entries for all value types 471 | ClassValue.Entry e0 = (ClassValue.Entry) get(classValue.identity); 472 | if (e == e0) { 473 | // We can get here during exception processing, unwinding from computeValue. 474 | assert(e.isPromise()); 475 | remove(classValue.identity); 476 | return null; 477 | } else if (e0 != null && e0.isPromise() && e0.version() == e.version()) { 478 | // If e0 matches the intended entry, there has not been a remove call 479 | // between the previous startEntry and now. So now overwrite e0. 480 | Version v = classValue.version(); 481 | if (e.version() != v) 482 | e = e.refreshVersion(v); 483 | put(classValue.identity, e); 484 | // Add to the cache, to enable the fast path, next time. 485 | checkCacheLoad(); 486 | addToCache(classValue, e); 487 | return e; 488 | } else { 489 | // Some sort of mismatch; caller must try again. 490 | return null; 491 | } 492 | } 493 | 494 | /** Remove an entry. */ 495 | synchronized 496 | void removeEntry(ClassValue classValue) { 497 | // make all cache elements for this guy go stale: 498 | if (remove(classValue.identity) != null) { 499 | classValue.bumpVersion(); 500 | removeStaleEntries(classValue); 501 | } 502 | } 503 | 504 | /** Change the value for an entry. */ 505 | synchronized 506 | void changeEntry(ClassValue classValue, T value) { 507 | @SuppressWarnings("unchecked") // one map has entries for all value types 508 | ClassValue.Entry e0 = (ClassValue.Entry) get(classValue.identity); 509 | Version version = classValue.version(); 510 | if (e0 != null) { 511 | if (e0.version() == version && e0.value() == value) 512 | // no value change => no version change needed 513 | return; 514 | classValue.bumpVersion(); 515 | removeStaleEntries(classValue); 516 | } 517 | ClassValue.Entry e = makeEntry(version, value); 518 | put(classValue.identity, e); 519 | // Add to the cache, to enable the fast path, next time. 520 | checkCacheLoad(); 521 | addToCache(classValue, e); 522 | } 523 | 524 | /// -------- 525 | /// Cache management. 526 | /// -------- 527 | 528 | // Statics do not need synchronization. 529 | 530 | /** Load the cache entry at the given (hashed) location. */ 531 | static ClassValue.Entry loadFromCache(ClassValue.Entry[] cache, int i) { 532 | // non-racing cache.length : constant 533 | // racing cache[i & (mask)] : null <=> ClassValue.Entry 534 | return cache[i & (cache.length-1)]; 535 | // invariant: returned value is null or well-constructed (ready to match) 536 | } 537 | 538 | /** Look in the cache, at the home location for the given ClassValue. */ 539 | static ClassValue.Entry probeHomeLocation(ClassValue.Entry[] cache, ClassValue classValue) { 540 | return classValue.castEntry(loadFromCache(cache, classValue.hashCodeForCache)); 541 | } 542 | 543 | /** Given that first probe was a collision, retry at nearby locations. */ 544 | static ClassValue.Entry probeBackupLocations(ClassValue.Entry[] cache, ClassValue classValue) { 545 | if (PROBE_LIMIT <= 0) return null; 546 | // Probe the cache carefully, in a range of slots. 547 | int mask = (cache.length-1); 548 | int home = (classValue.hashCodeForCache & mask); 549 | ClassValue.Entry e2 = cache[home]; // victim, if we find the real guy 550 | if (e2 == null) { 551 | return null; // if nobody is at home, no need to search nearby 552 | } 553 | // assume !classValue.match(e2), but do not assert, because of races 554 | int pos2 = -1; 555 | for (int i = home + 1; i < home + PROBE_LIMIT; i++) { 556 | ClassValue.Entry e = cache[i & mask]; 557 | if (e == null) { 558 | break; // only search within non-null runs 559 | } 560 | if (classValue.match(e)) { 561 | // relocate colliding entry e2 (from cache[home]) to first empty slot 562 | cache[home] = e; 563 | if (pos2 >= 0) { 564 | cache[i & mask] = ClassValue.Entry.DEAD_ENTRY; 565 | } else { 566 | pos2 = i; 567 | } 568 | cache[pos2 & mask] = ((entryDislocation(cache, pos2, e2) < PROBE_LIMIT) 569 | ? e2 // put e2 here if it fits 570 | : ClassValue.Entry.DEAD_ENTRY); 571 | return classValue.castEntry(e); 572 | } 573 | // Remember first empty slot, if any: 574 | if (!e.isLive() && pos2 < 0) pos2 = i; 575 | } 576 | return null; 577 | } 578 | 579 | /** How far out of place is e? */ 580 | private static int entryDislocation(ClassValue.Entry[] cache, int pos, ClassValue.Entry e) { 581 | ClassValue cv = e.classValueOrNull(); 582 | if (cv == null) return 0; // entry is not live! 583 | int mask = (cache.length-1); 584 | return (pos - cv.hashCodeForCache) & mask; 585 | } 586 | 587 | /// -------- 588 | /// Below this line all functions are private, and assume synchronized access. 589 | /// -------- 590 | 591 | private void sizeCache(int length) { 592 | assert((length & (length-1)) == 0); // must be power of 2 593 | cacheLoad = 0; 594 | cacheLoadLimit = (int) ((double) length * CACHE_LOAD_LIMIT / 100); 595 | cacheArray = new ClassValue.Entry[length]; 596 | } 597 | 598 | /** Make sure the cache load stays below its limit, if possible. */ 599 | private void checkCacheLoad() { 600 | if (cacheLoad >= cacheLoadLimit) { 601 | reduceCacheLoad(); 602 | } 603 | } 604 | private void reduceCacheLoad() { 605 | removeStaleEntries(); 606 | if (cacheLoad < cacheLoadLimit) 607 | return; // win 608 | ClassValue.Entry[] oldCache = getCache(); 609 | if (oldCache.length > HASH_MASK) 610 | return; // lose 611 | sizeCache(oldCache.length * 2); 612 | for (ClassValue.Entry e : oldCache) { 613 | if (e != null && e.isLive()) { 614 | addToCache(e); 615 | } 616 | } 617 | } 618 | 619 | /** Remove stale entries in the given range. 620 | * Should be executed under a Map lock. 621 | */ 622 | private void removeStaleEntries(ClassValue.Entry[] cache, int begin, int count) { 623 | if (PROBE_LIMIT <= 0) return; 624 | int mask = (cache.length-1); 625 | int removed = 0; 626 | for (int i = begin; i < begin + count; i++) { 627 | ClassValue.Entry e = cache[i & mask]; 628 | if (e == null || e.isLive()) 629 | continue; // skip null and live entries 630 | ClassValue.Entry replacement = null; 631 | if (PROBE_LIMIT > 1) { 632 | // avoid breaking up a non-null run 633 | replacement = findReplacement(cache, i); 634 | } 635 | cache[i & mask] = replacement; 636 | if (replacement == null) removed += 1; 637 | } 638 | cacheLoad = Math.max(0, cacheLoad - removed); 639 | } 640 | 641 | /** Clearing a cache slot risks disconnecting following entries 642 | * from the head of a non-null run, which would allow them 643 | * to be found via reprobes. Find an entry after cache[begin] 644 | * to plug into the hole, or return null if none is needed. 645 | */ 646 | private ClassValue.Entry findReplacement(ClassValue.Entry[] cache, int home1) { 647 | ClassValue.Entry replacement = null; 648 | int haveReplacement = -1, replacementPos = 0; 649 | int mask = (cache.length-1); 650 | for (int i2 = home1 + 1; i2 < home1 + PROBE_LIMIT; i2++) { 651 | ClassValue.Entry e2 = cache[i2 & mask]; 652 | if (e2 == null) break; // End of non-null run. 653 | if (!e2.isLive()) continue; // Doomed anyway. 654 | int dis2 = entryDislocation(cache, i2, e2); 655 | if (dis2 == 0) continue; // e2 already optimally placed 656 | int home2 = i2 - dis2; 657 | if (home2 <= home1) { 658 | // e2 can replace entry at cache[home1] 659 | if (home2 == home1) { 660 | // Put e2 exactly where he belongs. 661 | haveReplacement = 1; 662 | replacementPos = i2; 663 | replacement = e2; 664 | } else if (haveReplacement <= 0) { 665 | haveReplacement = 0; 666 | replacementPos = i2; 667 | replacement = e2; 668 | } 669 | // And keep going, so we can favor larger dislocations. 670 | } 671 | } 672 | if (haveReplacement >= 0) { 673 | if (cache[(replacementPos+1) & mask] != null) { 674 | // Be conservative, to avoid breaking up a non-null run. 675 | cache[replacementPos & mask] = (ClassValue.Entry) ClassValue.Entry.DEAD_ENTRY; 676 | } else { 677 | cache[replacementPos & mask] = null; 678 | cacheLoad -= 1; 679 | } 680 | } 681 | return replacement; 682 | } 683 | 684 | /** Remove stale entries in the range near classValue. */ 685 | private void removeStaleEntries(ClassValue classValue) { 686 | removeStaleEntries(getCache(), classValue.hashCodeForCache, PROBE_LIMIT); 687 | } 688 | 689 | /** Remove all stale entries, everywhere. */ 690 | private void removeStaleEntries() { 691 | ClassValue.Entry[] cache = getCache(); 692 | removeStaleEntries(cache, 0, cache.length + PROBE_LIMIT - 1); 693 | } 694 | 695 | /** Add the given entry to the cache, in its home location, unless it is out of date. */ 696 | private void addToCache(ClassValue.Entry e) { 697 | ClassValue classValue = e.classValueOrNull(); 698 | if (classValue != null) 699 | addToCache(classValue, e); 700 | } 701 | 702 | /** Add the given entry to the cache, in its home location. */ 703 | private void addToCache(ClassValue classValue, ClassValue.Entry e) { 704 | if (PROBE_LIMIT <= 0) return; // do not fill cache 705 | // Add e to the cache. 706 | ClassValue.Entry[] cache = getCache(); 707 | int mask = (cache.length-1); 708 | int home = classValue.hashCodeForCache & mask; 709 | ClassValue.Entry e2 = placeInCache(cache, home, e, false); 710 | if (e2 == null) return; // done 711 | if (PROBE_LIMIT > 1) { 712 | // try to move e2 somewhere else in his probe range 713 | int dis2 = entryDislocation(cache, home, e2); 714 | int home2 = home - dis2; 715 | for (int i2 = home2; i2 < home2 + PROBE_LIMIT; i2++) { 716 | if (placeInCache(cache, i2 & mask, e2, true) == null) { 717 | return; 718 | } 719 | } 720 | } 721 | // Note: At this point, e2 is just dropped from the cache. 722 | } 723 | 724 | /** Store the given entry. Update cacheLoad, and return any live victim. 725 | * 'Gently' means return self rather than dislocating a live victim. 726 | */ 727 | private ClassValue.Entry placeInCache(ClassValue.Entry[] cache, int pos, ClassValue.Entry e, boolean gently) { 728 | ClassValue.Entry e2 = overwrittenEntry(cache[pos]); 729 | if (gently && e2 != null) { 730 | // do not overwrite a live entry 731 | return e; 732 | } else { 733 | cache[pos] = e; 734 | return e2; 735 | } 736 | } 737 | 738 | /** Note an entry that is about to be overwritten. 739 | * If it is not live, quietly replace it by null. 740 | * If it is an actual null, increment cacheLoad, 741 | * because the caller is going to store something 742 | * in its place. 743 | */ 744 | private ClassValue.Entry overwrittenEntry(ClassValue.Entry e2) { 745 | if (e2 == null) cacheLoad += 1; 746 | else if (e2.isLive()) return e2; 747 | return null; 748 | } 749 | 750 | /** Percent loading of cache before resize. */ 751 | private static final int CACHE_LOAD_LIMIT = 67; // 0..100 752 | /** Maximum number of probes to attempt. */ 753 | private static final int PROBE_LIMIT = 6; // 1.. 754 | // N.B. Set PROBE_LIMIT=0 to disable all fast paths. 755 | } 756 | } 757 | --------------------------------------------------------------------------------