Skip to content

Commit

Permalink
Merge pull request #112 from SpineEventEngine/new-extensions
Browse files Browse the repository at this point in the history
Move extensions from `validation` library
  • Loading branch information
yevhenii-nadtochii authored Jan 27, 2025
2 parents bea9d54 + d12c8cb commit 9cdbf48
Show file tree
Hide file tree
Showing 10 changed files with 453 additions and 16 deletions.
28 changes: 14 additions & 14 deletions dependencies.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@


# Dependencies of `io.spine.tools:intellij-platform:2.0.0-SNAPSHOT.241`
# Dependencies of `io.spine.tools:intellij-platform:2.0.0-SNAPSHOT.242`

## Runtime
1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3.
Expand Down Expand Up @@ -493,12 +493,12 @@

The dependencies distributed under several licenses, are used according their commercial-use-friendly license.

This report was generated on **Tue Jan 14 15:55:52 CET 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
This report was generated on **Mon Jan 27 16:11:27 CET 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).




# Dependencies of `io.spine.tools:intellij-platform-java:2.0.0-SNAPSHOT.241`
# Dependencies of `io.spine.tools:intellij-platform-java:2.0.0-SNAPSHOT.242`

## Runtime
1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3.
Expand Down Expand Up @@ -1752,12 +1752,12 @@ This report was generated on **Tue Jan 14 15:55:52 CET 2025** using [Gradle-Lice

The dependencies distributed under several licenses, are used according their commercial-use-friendly license.

This report was generated on **Tue Jan 14 15:55:52 CET 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
This report was generated on **Mon Jan 27 16:11:28 CET 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).




# Dependencies of `io.spine.tools:spine-plugin-base:2.0.0-SNAPSHOT.241`
# Dependencies of `io.spine.tools:spine-plugin-base:2.0.0-SNAPSHOT.242`

## Runtime
1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2.
Expand Down Expand Up @@ -2505,12 +2505,12 @@ This report was generated on **Tue Jan 14 15:55:52 CET 2025** using [Gradle-Lice

The dependencies distributed under several licenses, are used according their commercial-use-friendly license.

This report was generated on **Tue Jan 14 15:55:53 CET 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
This report was generated on **Mon Jan 27 16:11:28 CET 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).




# Dependencies of `io.spine.tools:spine-plugin-testlib:2.0.0-SNAPSHOT.241`
# Dependencies of `io.spine.tools:spine-plugin-testlib:2.0.0-SNAPSHOT.242`

## Runtime
1. **Group** : com.google.auto.value. **Name** : auto-value-annotations. **Version** : 1.10.2.
Expand Down Expand Up @@ -3373,12 +3373,12 @@ This report was generated on **Tue Jan 14 15:55:53 CET 2025** using [Gradle-Lice

The dependencies distributed under several licenses, are used according their commercial-use-friendly license.

This report was generated on **Tue Jan 14 15:55:53 CET 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
This report was generated on **Mon Jan 27 16:11:29 CET 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).




# Dependencies of `io.spine.tools:spine-psi:2.0.0-SNAPSHOT.241`
# Dependencies of `io.spine.tools:spine-psi:2.0.0-SNAPSHOT.242`

## Runtime
1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3.
Expand Down Expand Up @@ -4380,12 +4380,12 @@ This report was generated on **Tue Jan 14 15:55:53 CET 2025** using [Gradle-Lice

The dependencies distributed under several licenses, are used according their commercial-use-friendly license.

This report was generated on **Tue Jan 14 15:55:53 CET 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
This report was generated on **Mon Jan 27 16:11:29 CET 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).




# Dependencies of `io.spine.tools:spine-psi-java:2.0.0-SNAPSHOT.241`
# Dependencies of `io.spine.tools:spine-psi-java:2.0.0-SNAPSHOT.242`

## Runtime
1. **Group** : be.cyberelf.nanoxml. **Name** : nanoxml. **Version** : 2.2.3.
Expand Down Expand Up @@ -6065,12 +6065,12 @@ This report was generated on **Tue Jan 14 15:55:53 CET 2025** using [Gradle-Lice

The dependencies distributed under several licenses, are used according their commercial-use-friendly license.

This report was generated on **Tue Jan 14 15:55:54 CET 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
This report was generated on **Mon Jan 27 16:11:29 CET 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).




# Dependencies of `io.spine.tools:spine-tool-base:2.0.0-SNAPSHOT.241`
# Dependencies of `io.spine.tools:spine-tool-base:2.0.0-SNAPSHOT.242`

## Runtime
1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2.
Expand Down Expand Up @@ -6867,4 +6867,4 @@ This report was generated on **Tue Jan 14 15:55:54 CET 2025** using [Gradle-Lice

The dependencies distributed under several licenses, are used according their commercial-use-friendly license.

This report was generated on **Tue Jan 14 15:55:54 CET 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
This report was generated on **Mon Jan 27 16:11:30 CET 2025** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject.
-->
<groupId>io.spine.tools</groupId>
<artifactId>tool-base</artifactId>
<version>2.0.0-SNAPSHOT.241</version>
<version>2.0.0-SNAPSHOT.242</version>

<inceptionYear>2015</inceptionYear>

Expand Down
59 changes: 59 additions & 0 deletions psi-java/src/main/kotlin/io/spine/tools/psi/java/PsiClassExts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,62 @@ public fun PsiClass.implement(superInterface: PsiJavaCodeReferenceElement) {
implements.add(superInterface)
}
}

/**
* Looks for a nested class declared in this [PsiClass].
*
* @param simpleName The simple name of the class.
* @return The found class, or `null` if this [PsiClass] does not have such a class.
*/
public fun PsiClass.findNested(simpleName: String): PsiClass? =
innerClasses.firstOrNull { it.name == simpleName }

/**
* Returns a nested class declared in this [PsiClass].
*
* @param simpleName The simple name of the class.
* @throws IllegalStateException if this [PsiClass] does not have such a class.
*/
public fun PsiClass.nested(simpleName: String): PsiClass =
innerClasses.firstOrNull { it.name == simpleName }
?: error {
"The class `$qualifiedName` does not have a nested class named `$name`."
}

/**
* Looks for a method in this [PsiClass] matching the given signature
* specified as [text].
*
* An example usage:
*
* ```
* val method = psiClass.findMethodBySignature("public Builder setName(Name value)")
* ```
*
* @param text The method signature as text.
* @return The found [PsiMethod], or `null` if this class does not have such a method.
*/
public fun PsiClass.findMethodBySignature(text: String): PsiMethod? {
val reference = elementFactory.createMethodFromText(text, null)
return findMethodBySignature(reference, false)
}

/**
* Returns a method from this [PsiClass] matching the given signature
* specified as [text].
*
* An example usage:
*
* ```
* val method = psiClass.getMethodBySignature("public Builder setName(Name value)")
* ```
*
* @param text The method signature as text.
* @throws IllegalStateException if this class does not have such a method.
*/
public fun PsiClass.methodWithSignature(text: String): PsiMethod =
findMethodBySignature(text)
?: error(
"Could not find the method with the signature `$text` " +
"in the `$qualifiedName` class."
)
72 changes: 72 additions & 0 deletions psi-java/src/main/kotlin/io/spine/tools/psi/java/PsiElementExts.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2025, TeamDev. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Redistribution and use in source and/or binary forms, with or without
* modification, must retain the above copyright notice and the following
* disclaimer.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package io.spine.tools.psi.java

import com.intellij.psi.PsiElement

/**
* Looks for the first child of this [PsiElement], the text representation
* of which satisfies both [startsWith] and [contains] criteria.
*
* This method performs a depth-first search of the PSI hierarchy.
* So, the second direct child of this [PsiElement] is checked only
* when the first child and all its descendants are checked.
*
* @return the found element, or `null` if this [PsiElement] does not contain such an element.
*/
public fun PsiElement.findFirstByText(
startsWith: String,
contains: String = startsWith
): PsiElement? = children.firstNotNullOfOrNull { element ->
val text = element.text
when {
!text.contains(contains) -> null
text.startsWith(startsWith) -> element
else -> element.findFirstByText(startsWith, contains)
}
}

/**
* Returns the first child of this [PsiElement], the text representation
* of which satisfies both [startsWith] and [contains] criteria.
*
* This method performs a depth-first search of the PSI hierarchy.
* So, the second direct child of this [PsiElement] is checked only
* when the first child and all its descendants are checked.
*
* @throws [IllegalStateException] if this [PsiElement] does not contain such an element.
*/
public fun PsiElement.getFirstByText(
startsWith: String,
contains: String = startsWith
): PsiElement =
findFirstByText(startsWith, contains)
?: error {
"The element was not found." +
"Search criteria: [startsWith=`$startsWith`, contains=`$contains`]." +
"Searched element: `$this`."
}
100 changes: 100 additions & 0 deletions psi-java/src/main/kotlin/io/spine/tools/psi/java/PsiStatements.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright 2025, TeamDev. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Redistribution and use in source and/or binary forms, with or without
* modification, must retain the above copyright notice and the following
* disclaimer.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package io.spine.tools.psi.java

import com.intellij.psi.PsiCodeBlock
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementFactory

/**
* A list of statements extracted from the given [PsiCodeBlock],
* excluding the surrounding braces.
*
* This type addresses several challenges:
*
* 1. In PSI, a code block cannot be created without curly braces. As a result, even if
* you only need a list of statements, you must create a full block with braces.
* 2. Inserting such a block into another code block requires range copying
* to skip the braces.
* 3. While a list of statements can be [retrieved][PsiCodeBlock.getStatements] directly from
* the code block, formatting elements like whitespace and newlines are omitted, leaving
* only the raw statements. Adding these statements individually to a method body often
* results in invalid or non-compilable Java code.
*/
public class PsiStatements(codeBlock: PsiCodeBlock) {

/**
* All children of [PsiCodeBlock] without right and left braces.
*/
private val children = codeBlock.children
.copyOfRange(1, codeBlock.children.size - 1)

/**
* Returns the first child of this element.
*/
public val firstChild: PsiElement = children.first()

/**
* Returns the last child of this element.
*/
public val lastChild: PsiElement = children.last()
}

/**
* Creates a new [PsiStatements] from the given [text].
*
* @param text The text of the statements to create.
* @param context The PSI element used as context for resolving references.
*/
public fun PsiElementFactory.createStatementsFromText(
text: String,
context: PsiElement?
): PsiStatements {
val codeBlock = createCodeBlockFromText("{$text}", context)
return PsiStatements(codeBlock)
}

/**
* Adds the given [statements] to this [PsiElement].
*/
public fun PsiElement.add(statements: PsiStatements) {
addRange(statements.firstChild, statements.lastChild)
}

/**
* Adds the given [statements] to this [PsiElement] after the [anchor].
*/
public fun PsiElement.addAfter(statements: PsiStatements, anchor: PsiElement) {
addRangeAfter(statements.firstChild, statements.lastChild, anchor)
}

/**
* Adds the given [statements] to this [PsiElement] before the [anchor].
*/
public fun PsiElement.addBefore(statements: PsiStatements, anchor: PsiElement) {
addRangeBefore(statements.firstChild, statements.lastChild, anchor)
}
9 changes: 9 additions & 0 deletions psi-java/src/test/kotlin/io/spine/tools/psi/TestFixtures.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,12 @@ internal fun readResource(fileName: String): String {
val code = loaded.convertLineSeparators()
return code
}

/**
* A signature of [com.google.protobuf.Message.Builder.mergeFrom] method.
*/
internal val MERGE_FROM_SIGNATURE = """
public Builder mergeFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
""".trimIndent()
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ import com.intellij.psi.PsiFileFactory
import com.intellij.psi.PsiJavaCodeReferenceElement
import com.intellij.psi.PsiMethod
import io.kotest.matchers.ints.shouldBeGreaterThan
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.spine.testing.TestValues
import io.spine.tools.java.reference
import io.spine.tools.psi.MERGE_FROM_SIGNATURE
import io.spine.tools.psi.java.Environment.elementFactory
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
Expand Down Expand Up @@ -77,6 +79,20 @@ internal class PsiClassExtsSpec: PsiTest() {
method.isPublic shouldBe true
}

@Test
fun `find a method by its signature`() {
val psiFile = parse("FieldPath.java")
val psiClass = psiFile.topLevelClass.nested("Builder")
psiClass.findMethodBySignature(MERGE_FROM_SIGNATURE).shouldNotBeNull()
}

@Test
fun `return a nested class`() {
val psiFile = parse("FieldPath.java")
val psiClass = psiFile.topLevelClass.findNested("Builder")
psiClass.shouldNotBeNull()
}

@Test
fun `add an element first`() {
val methodName = "doFirst"
Expand Down
Loading

0 comments on commit 9cdbf48

Please sign in to comment.