How to get Kotlin AST? - abstract-syntax-tree

I have a string with Kotlin source in it. How can I compile it at run-time and get abstract syntax tree and types info to analyze?

I have some investigation of Kotlin compiler. Some proof of concept to getting of AST can be seen on my GitHub repo.
It's a sketch only, but can be helpful:
class KotlinScriptParser {
companion object {
private val LOG = Logger.getLogger(KotlinScriptParser.javaClass.name)
private val messageCollector = object : MessageCollector {
override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation) {
val path = location.path
val position = if (path == null) "" else "$path: (${location.line}, ${location.column}) "
val text = position + message
if (CompilerMessageSeverity.VERBOSE.contains(severity)) {
LOG.finest(text)
} else if (CompilerMessageSeverity.ERRORS.contains(severity)) {
LOG.severe(text)
} else if (severity == CompilerMessageSeverity.INFO) {
LOG.info(text)
} else {
LOG.warning(text)
}
}
}
private val classPath: ArrayList<File> by lazy {
val classpath = arrayListOf<File>()
classpath += PathUtil.getResourcePathForClass(AnnotationTarget.CLASS.javaClass)
classpath
}
}
fun parse(vararg files: String): TopDownAnalysisContext {
// The Kotlin compiler configuration
val configuration = CompilerConfiguration()
val groupingCollector = GroupingMessageCollector(messageCollector)
val severityCollector = MessageSeverityCollector(groupingCollector)
configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, severityCollector)
configuration.addJvmClasspathRoots(PathUtil.getJdkClassesRoots())
// The path to .kt files sources
files.forEach { configuration.addKotlinSourceRoot(it) }
// Configuring Kotlin class path
configuration.addJvmClasspathRoots(classPath)
configuration.put(JVMConfigurationKeys.MODULE_NAME, JvmAbi.DEFAULT_MODULE_NAME)
configuration.put<List<AnalyzerScriptParameter>>(JVMConfigurationKeys.SCRIPT_PARAMETERS, CommandLineScriptUtils.scriptParameters())
val rootDisposable = Disposer.newDisposable()
try {
val environment = KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
val ktFiles = environment.getSourceFiles()
val sharedTrace = CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace()
val moduleContext = TopDownAnalyzerFacadeForJVM.createContextWithSealedModule(environment.project,
environment.getModuleName())
val project = moduleContext.project
val allFiles = JvmAnalyzerFacade.getAllFilesToAnalyze(project, null, ktFiles)
val providerFactory = FileBasedDeclarationProviderFactory(moduleContext.storageManager, allFiles)
val lookupTracker = LookupTracker.DO_NOTHING
val packagePartProvider = JvmPackagePartProvider(environment)
val container = createContainerForTopDownAnalyzerForJvm(
moduleContext,
sharedTrace,
providerFactory,
GlobalSearchScope.allScope(project),
lookupTracker,
packagePartProvider)
val additionalProviders = ArrayList<PackageFragmentProvider>()
additionalProviders.add(container.javaDescriptorResolver.packageFragmentProvider)
return container.lazyTopDownAnalyzerForTopLevel.analyzeFiles(TopDownAnalysisMode.LocalDeclarations, allFiles, additionalProviders)
} finally {
rootDisposable.dispose()
if (severityCollector.anyReported(CompilerMessageSeverity.ERROR)) {
throw RuntimeException("Compilation error")
}
}
}
}
fun main(args: Array<String>) {
val scriptFile = "/media/data/java/blackfern/kotlin-compile-test/test.kt"
val parser = KotlinScriptParser()
// Getting a root element of the AST
val analyzeContext = parser.parse(scriptFile)
// Sample AST investigation
val function = analyzeContext.functions.keys.first()
val body = function.bodyExpression as KtBlockExpression
}

There's no standard API to do this at the moment. You can play with the Kotlin compiler and REPL source code to try to achieve this.

Related

firebase Storage sync

I am trying to get the image_list in Uri format.
In the process of retrieving photos from firebase storage, a coroutine was used to solve the async.
private fun test() {
val storageReference =
FirebaseStorage.getInstance().reference.child("Ballad_images")
val temp_time = ArrayList<String>()
val temp_time_3 = ArrayList<String>()
val image_list = ArrayList<String>()
storageReference.listAll().addOnSuccessListener { listResult ->
GlobalScope.launch() {
val job1 = launch {
for (fileRef in listResult.items) {
fileRef.downloadUrl.addOnSuccessListener { Uri ->
image_list.add(Uri.toString())
image_list.sort()
}
}
}
val job2 = launch {
for (fileRef in listResult.items) {
temp_time.add(fileRef.name)
}//for
for (i in 0..temp_time.size - 1) {
var temp_time_2 = temp_time.get(i).split('.')//2021 05 29 -1 jpg
temp_time_3.add(temp_time_2[1])
//05
}//for
}
runBlocking {
job1.join()
println("image_list : " + image_list.size)
job2.join()
println("temp_time : " + temp_time_3)
//delay(4000L)
println("image_list : " + image_list.size)
}
}//GlobalScope
}
}
end job1.join()
After finishing job2.join()
If you give it a delay, the data will come out.
When I see it, in join1
I think this is happening because of fileRef.downloadUrl.addOnSuccessListener
Is there a way to populate the image_list from job1?

Paging3 - How to do Reverse Pagination in Chat App

I am recently migrate to Paging3. However, I noticed that most of the tutorials and guides are mainly on normal pagination (from top to bottom).
I need to implement the REVERSE pagination as user scroll to top boundary, will load for page 2,3,4..
Is there any tutorial/guide for this?
PS: Now the initial loading is working fine, but when I scroll to top-most, I have no idea how to load Page 2 data.
My current approach
PagingSource
class ChatPagingSource(
private val apiService: ApiService,
private val roomId: String
): PagingSource<Int, Message>() {
override fun getRefreshKey(state: PagingState<Int, Message>): Int? = null
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Message> {
return try{
val page = params.key?: 1
val pageSize = params.loadSize
val call = apiService.getMessageFeedPaging(
room_id = roomId,
page = page,
max = pageSize,
exclude = EXCLUDE_TYPE
)
val repoItems = call.messages
val prevKey = if(page > 1) page - 1 else null
val nextKey = if(repoItems.isNotEmpty()) page + 1 else null
if(repoItems.isNotEmpty()) {
val messageList = mutableListOf<Message>()
for (i in repoItems) {
val replyData = Converters.convertReplyDataAPItoReplyData(i.reply_data)
val msg = Converters.convertMessageAPItoMessage(replyData, i, hasError = false)
messageList.add(msg)
}
LoadResult.Page(messageList, prevKey, nextKey)
} else {
LoadResult.Page(listOf(), prevKey, nextKey)
}
}catch (e: Exception) {
LoadResult.Error(e)
}
}
}
Repository
fun loadRemoteMessageStream(roomId: String): LiveData<PagingData<Message>> {
return Pager(
config = PagingConfig(20),
pagingSourceFactory = { ChatPagingSource(apiService, roomId) }
).liveData
}
ViewModel
private val _remoteMessage = chatRepository.loadRemoteMessageStream(currentRoomId)
.cachedIn(viewModelScope)
.let { it as MutableLiveData<PagingData<Message>> }
val remoteMessage: LiveData<PagingData<Message>> = _remoteMessage
Fragment
chatViewModel.remoteMessage.observe(viewLifecycleOwner, {
chatAdapter.submitData(viewLifecycleOwner.lifecycle, it)
})
In case this helps anyone, I will post out my own answer.
The key is to reverse prevKey and nextKey and fixed the pageSize that your API required (in my case is 20).
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Message> {
val pageSize = 20
val prevKey = if(repoItems.isNotEmpty()) page + 1 else null
val nextKey = if(page > 1) page - 1 else null
}
Then, in the recyclerview, you should use stackFromEnd = true so that initially the recyclerview will auto-scroll to bottom.

How to sum of values from Firebase Database in Kotlin?

I have a Firebase Database with several purchases in Android app, points value is assigned for each purchase, then entries are showing by following way:
When userId is log in, a button appears and when user click on it the "showInfoClient" function is called and it must show user and the amount of points. Code is:
private val database = Firebase.database
private val myref = database.getReference("compras")
fun showInfoClient(view: View) {
val userlog = FirebaseAuth.getInstance().currentUser?.displayName
val alertDialogInfo = AlertDialog.Builder(this).create()
myref.orderByChild("userId").equalTo(userlog).addListenerForSingleValueEvent(object :
ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
var sum = 0
for (data in dataSnapshot.children) {
sum += data.child("puntos").getValue(Int::class.java)!!
Toast.makeText(this#Principal, sum, Toast.LENGTH_SHORT).show()
}
}
override fun onCancelled(databaseError: DatabaseError) {}
})
alertDialogInfo.setTitle(userlog)
alertDialogInfo.setMessage("Puntos: ") // + sum
alertDialogInfo.setButton(
AlertDialog.BUTTON_POSITIVE, "OK"
) { dialog, _ ->; dialog.dismiss() }
alertDialogInfo.show()
val btnPositive = alertDialogInfo.getButton(AlertDialog.BUTTON_POSITIVE)
val layoutParams = btnPositive.layoutParams as LinearLayout.LayoutParams
layoutParams.weight = 100f
btnPositive.layoutParams = layoutParams
}
I have tried to use different options but i´m not able to set "sum" value on
alertDialogInfo.setMessage("Puntos: $sum")
Thanks in advance
Any code that needs data from the database needs to be inside onDataChange or be called from there. Code outside of onDataChange will (most likely) run before the data is loaded.
So:
val userlog = FirebaseAuth.getInstance().currentUser?.displayName
val alertDialogInfo = AlertDialog.Builder(this).create()
myref.orderByChild("userId").equalTo(userlog).addListenerForSingleValueEvent(object :
ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
var sum = 0
for (data in dataSnapshot.children) {
sum += data.child("puntos").getValue(Int::class.java)!!
}
alertDialogInfo.setTitle(userlog)
alertDialogInfo.setMessage("Puntos: ") // + sum
alertDialogInfo.setButton(
AlertDialog.BUTTON_POSITIVE, "OK"
) { dialog, _ ->; dialog.dismiss() }
alertDialogInfo.show()
val btnPositive = alertDialogInfo.getButton(AlertDialog.BUTTON_POSITIVE)
val layoutParams = btnPositive.layoutParams as LinearLayout.LayoutParams
layoutParams.weight = 100f
btnPositive.layoutParams = layoutParams
}
override fun onCancelled(databaseError: DatabaseError) {
throw databaseError.toException(); // never ignore errors
}
})
For a longer explanation see:
getContactsFromFirebase() method return an empty list
Setting Singleton property value in Firebase Listener

Corda Transaction Time get from vault Query

Is it possible to getting transaction consumed time which can be seen in vault_state through vault query?
Yes. You can find the time the state was consumed using the statesMetadata field of the query result.
Within a flow:
#Suspendable
override fun call() {
val results = serviceHub.vaultService.queryBy(ContractState::class.java)
val statesAndMetadata = results.states.zip(results.statesMetadata)
statesAndMetadata.forEach { (stateAndRef, metadata) ->
val consumedTime = metadata.consumedTime
}
}
Within an RPC client:
fun main(args: Array<String>) {
require(args.size == 1) { "Usage: TemplateClient <node address>" }
val nodeAddress = parse(args[0])
val client = CordaRPCClient(nodeAddress)
// Can be amended in the com.template.MainKt file.
val cordaRPCOps = client.start("user1", "test").proxy
val results = cordaRPCOps.vaultQueryBy<ContractState>()
val statesAndMetadata = results.states.zip(results.statesMetadata)
statesAndMetadata.forEach { (stateAndRef, metadata) ->
val consumedTime = metadata.consumedTime
}
}

Play framework: How to process server errors in HTTP filters?

I am trying to create an HTTP filter that logs some info about the request, say headers, and a limited (so the memory doesn't explode) part of the request body in case of an error.
To do that I've followed the docs (https://www.playframework.com/documentation/2.6.x/ScalaHttpFilters) and came up with something like this:
class RequestErrorLogFilter #Inject()(actorSystem: ActorSystem)(implicit ec: ExecutionContext)
extends EssentialFilter {
private val logger = org.slf4j.LoggerFactory.getLogger("application.AccumulatorFlowFilter")
private implicit val logging = Logging(actorSystem.eventStream, logger.getName)
override def apply(next: EssentialAction): EssentialAction = new EssentialAction {
override def apply(request: RequestHeader): Accumulator[ByteString, Result] = {
val accumulator: Accumulator[ByteString, Result] = next(request)
val data = ArrayBuffer.empty[ByteString]
var totalSize = 0
val maxSize = 1024
val flow: Flow[ByteString, ByteString, NotUsed] = Flow[ByteString]
.map((in: ByteString) => {
val left = maxSize - totalSize
if (left > 0) {
val takeNow =
if (in.size > left) {
in.slice(0, left)
} else {
in
}
data.append(takeNow)
totalSize += takeNow.size
}
in
})
val accumulatorWithResult = accumulator.through(flow).map { result =>
// this code doesn't get executed in case of an exception in a controller
logger.info(s"The flow has completed and the result is $result")
if (result.header.status >= 400) {
val headerText = data.map(_.utf8String).mkString("")
logger.warn(s"There was an error. Request head: $headerText")
}
result
}
accumulatorWithResult
}
}
}
This works fine for client errors (like 400 - bad request), or for any error returned from a controller, but in case of an exception inside of a controller, filter's "callback" isn't executed, so there's no opportunity to log what happened.
And there's a same problem with a much simpler "AccessLogHttpFilter" which I think is a recommended solution to create an access log with play applications:
class LoggingFilter #Inject() (val mat: Materializer, implicit val ec: ExecutionContext)
extends Filter {
def apply(nextFilter: RequestHeader => Future[Result])
(requestHeader: RequestHeader): Future[Result] = {
val startTime = System.currentTimeMillis
nextFilter(requestHeader).map { result =>
val endTime = System.currentTimeMillis
val requestTime = endTime - startTime
Logger.info(s"${requestHeader.method} ${requestHeader.uri} took ${requestTime}ms and " +
s"returned ${result.header.status}")
result.withHeaders("Request-Time" -> requestTime.toString)
}
}
}
Is there a way to make play invoke http filter code even in case of exceptions?
Is there some other workaround?
Figured it out.
For an EssentialFilter to handle an error you need to add a .recover() call to an accumulator:
class RequestErrorLogFilter #Inject()(actorSystem: ActorSystem)(implicit ec: ExecutionContext)
extends EssentialFilter {
private val logger = org.slf4j.LoggerFactory.getLogger("application.AccumulatorFlowFilter")
private implicit val logging = Logging(actorSystem.eventStream, logger.getName)
override def apply(next: EssentialAction): EssentialAction = new EssentialAction {
override def apply(request: RequestHeader): Accumulator[ByteString, Result] = {
val accumulator: Accumulator[ByteString, Result] = next(request)
val data = ArrayBuffer.empty[ByteString]
var totalSize = 0
val maxSize = 1024
val flow: Flow[ByteString, ByteString, NotUsed] = Flow[ByteString]
.map((in: ByteString) => {
val left = maxSize - totalSize
if (left > 0) {
val takeNow =
if (in.size > left) {
in.slice(0, left)
} else {
in
}
data.append(takeNow)
totalSize += takeNow.size
}
in
})
val accumulatorWithResult: Accumulator[ByteString, Result] = accumulator.through(flow).map { result =>
logger.info(s"The flow has completed and the result is $result")
if (result.header.status >= 400) {
val headerText = data.map(_.utf8String).mkString("")
logger.warn(s"There was an error. Request head: $headerText")
}
result
}
accumulatorWithResult.recover {
case error =>
val headerText = data.map(_.utf8String).mkString("")
logger.warn(s"There was an error: $error. Request head: $headerText")
throw error
}
}
}
}
And for a simple Filter you need a .failed.foreach call on the result future:
class LoggingFilter #Inject() (val mat: Materializer, implicit val ec: ExecutionContext)
extends Filter {
def apply(nextFilter: RequestHeader => Future[Result])
(requestHeader: RequestHeader): Future[Result] = {
val startTime = System.currentTimeMillis
val eventualResult = nextFilter(requestHeader)
eventualResult.failed.foreach { error: Throwable =>
val endTime = System.currentTimeMillis
val requestTime = endTime - startTime
Logger.info(s"${requestHeader.method} ${requestHeader.uri} took ${requestTime}ms and " +
s"returned 500 $error")
}
eventualResult.map { result =>
val endTime = System.currentTimeMillis
val requestTime = endTime - startTime
Logger.info(s"${requestHeader.method} ${requestHeader.uri} took ${requestTime}ms and " +
s"returned ${result.header.status}")
result.withHeaders("Request-Time" -> requestTime.toString)
}
}
}

Resources