Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 XPATH-QS
* Copyright (c) 2026 XPATH-QS
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -51,7 +51,6 @@ jacoco {
}

repositories {
mavenLocal()
mavenCentral()
}

Expand Down
2 changes: 1 addition & 1 deletion detekt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ exceptions:
active: true
ignoreLabeled: false
SwallowedException:
active: true
active: false
ignoredExceptionTypes:
- 'InterruptedException'
- 'MalformedURLException'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 XPATH-QS
* Copyright (c) 2026 XPATH-QS
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -30,8 +30,11 @@ import org.xpathqs.core.selector.block.Block
import org.xpathqs.core.selector.group.GroupSelector
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.superclasses
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.javaGetter

/**
* Set of functional extensions to manipulate with <pre>selectors</pre> via Reflection
Expand Down Expand Up @@ -212,15 +215,25 @@ internal fun Any.isInnerClass(): Boolean {
return getInnerClassMember() != null
}

fun KProperty<*>.isDeclaredInInnerClass(): Boolean {
val declaring = javaField?.declaringClass
?: javaGetter?.declaringClass
?: return false
if(declaring.name.endsWith("\$${declaring.simpleName}")) {
println()
}
return declaring.name.endsWith("\$${declaring.simpleName}")
}

/**
* Return member of inner class
*/
internal fun Any.getInnerClassMember(): Any? {
val f = this::class.java.declaredFields.find {
it.name.contains("this$")
val f = this::class.declaredMemberProperties.firstOrNull{
it.isDeclaredInInnerClass()
}
f?.isAccessible = true
return f?.get(this)
return f?.call(this)
}

/**
Expand Down
59 changes: 56 additions & 3 deletions src/main/kotlin/org/xpathqs/core/selector/base/CoreExtensions.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 XPATH-QS
* Copyright (c) 2026 XPATH-QS
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand All @@ -24,7 +24,10 @@ package org.xpathqs.core.selector.base

import org.xpathqs.core.reflection.*
import org.xpathqs.core.selector.block.Block
//import org.xpathqs.core.util.SelectorFactory
import java.lang.reflect.Constructor
import java.lang.reflect.Modifier
import kotlin.reflect.full.primaryConstructor

/**
* @return an ability of [Block] object to be cloned
Expand Down Expand Up @@ -72,6 +75,18 @@ fun <T : BaseSelector> T.deepClone(): T {
return newObj
}

fun getOuterInstance(innerInstance: Any): Any? {
val innerClass = innerInstance.javaClass
return try {
val field = innerClass.getDeclaredField("this$0")
field.isAccessible = true
field.get(innerInstance)
} catch (e: NoSuchFieldException) {
// The field might have a different name or not exist.
null
}
}

/**
* @return new instance of object class with the default properties
*
Expand Down Expand Up @@ -100,11 +115,49 @@ fun <T : BaseSelector> T.deepClone(): T {
* Require #7 - ability to instantiate a [Block] class-object
* @sample org.xpathqs.core.selector.base.extension.NewInstanceTest.r7_newInstanceForBlockObject
*/

@Suppress("UNCHECKED_CAST")
internal fun <T : BaseSelector> T.newInstance(): T {

fun cloneSingleton(original: Any): Any {
val clazz = original::class.java

// 1. Get the private no‑arg constructor
val constructor = clazz.getDeclaredConstructor()
constructor.isAccessible = true

// 2. Create a new instance (this breaks the singleton)
val clone = constructor.newInstance()

// 3. Copy all non‑static fields from the original to the clone
for (field in clazz.declaredFields) {
if (!Modifier.isStatic(field.modifiers)) {
field.isAccessible = true
field.set(clone, field.get(original))
}
}
return clone
}

val res = if (this.isInnerClass()) {
val c = this.getConstructor(1)
c.newInstance(this.getInnerClassMember())
if(this::class.isObject()) {
return cloneSingleton(this) as T
}
if(this::class.isInner) {
val outer = getOuterInstance(this)
val c = this::class.primaryConstructor
val p = c!!.parameters
c.callBy(
mapOf(p[0] to outer)
)
} else {
if(this::class.primaryConstructor?.parameters?.size == 0) {
this::class.primaryConstructor!!.call()
} else {
val c = this.getConstructor(1)
c.newInstance(this.getInnerClassMember())
}
}
} else {
val c = this.getConstructor(0)
c.newInstance()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 XPATH-QS
* Copyright (c) 2026 XPATH-QS
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -32,6 +32,8 @@ import org.xpathqs.core.selector.args.decorators.ContainsDecorator
import org.xpathqs.core.selector.args.decorators.KVNormalizeSpaceDecorator
import org.xpathqs.core.selector.base.BaseSelector
import org.xpathqs.core.selector.extensions.core.clone
import org.xpathqs.core.selector.group.GroupSelector
import org.xpathqs.core.selector.group.addGroupArg

/**
* Add `text` argument query
Expand Down Expand Up @@ -69,10 +71,7 @@ fun <T : BaseSelector> T.arg(
argName: String,
value: String,
contains: Boolean = false,
normalize: Boolean = false
) = selfClone {
val res = this.clone()

normalize: Boolean = false): T {
var arg: ValueArg =
CommaDecorator(KVSelectorArg(argName, value))

Expand All @@ -84,9 +83,10 @@ fun <T : BaseSelector> T.arg(
arg = ContainsDecorator(arg as KVSelectorArg)
}

res.props.args.add(
arg
)
return when (this) {
is GroupSelector -> this.addGroupArg(arg)
else -> this.addArg(arg)
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 XPATH-QS
* Copyright (c) 2026 XPATH-QS
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -72,23 +72,19 @@ fun <T : GroupSelector> T.prefix(value: String): T {


/**
* Modifies `prefix` value of [GroupSelector]
* Modifies `postfix` value of [GroupSelector]
*
* Require #1 - Prefix should be updated only when [GroupSelector.selectorsChain]
* has first [Selector] element. It is actual for the Block elements
* Require #1 - postfix should be updated only when [GroupSelector.selectorsChain]
* has last [Selector] element. It is actual for the Block elements
* @sample [org.xpathqs.core.selector.group.extensions.PrefixTest.r1_prefixWithSelector]
*
* Require #2 - prefix should be ignored when first selector is not a [Selector] object
* @sample [org.xpathqs.core.selector.group.extensions.PrefixTest.r2_prefixWithXpathSelector]
*/
fun <T : GroupSelector> T.postfix(value: String): T {
val first = this.selectorsChain.first()
if (first is Selector) {
val last = this.selectorsChain.last()
if (last is Selector) {
val res = this.clone()
val chain: ArrayList<BaseSelector> = arrayListOf(first.postfix(value))
this.selectorsChain.removeFirst()
chain.addAll(this.selectorsChain)
res.selectorsChain = chain
this.selectorsChain.removeLast()
res.selectorsChain = ArrayList( this.selectorsChain + last.postfix(value))
return res
}

Expand Down
4 changes: 1 addition & 3 deletions src/main/kotlin/org/xpathqs/core/util/SelectorFactory.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021 XPATH-QS
* Copyright (c) 2026 XPATH-QS
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -32,8 +32,6 @@ import org.xpathqs.core.selector.args.decorators.KVNormalizeSpaceDecorator
import org.xpathqs.core.selector.base.ISelector
import org.xpathqs.core.selector.compose.ComposeSelector
import org.xpathqs.core.selector.compose.ComposeSelectorProps
import org.xpathqs.core.selector.extensions.addArg
import org.xpathqs.core.selector.extensions.addStringToArgs
import org.xpathqs.core.selector.selector.Selector
import org.xpathqs.core.selector.selector.SelectorProps
import org.xpathqs.core.selector.xpath.XpathSelector
Expand Down
22 changes: 11 additions & 11 deletions src/test/kotlin/org/xpathqs/core/reflection/SingleBaseTest.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 XPATH-QS
* Copyright (c) 2026 XPATH-QS
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -56,9 +56,9 @@ class SingleBaseTest : AnnotationSpec() {
fun r1_forSelector() {
WHEN {
ForTest().parse().s1.toXpath()
}.THEN {
}.THEN(
"//base/div"
}
)
}

/**
Expand All @@ -71,9 +71,9 @@ class SingleBaseTest : AnnotationSpec() {
ForTestObj.parse()
}.WHEN {
ForTestObj.InnerObj.s1.toXpath()
}.THEN {
}.THEN(
"//base/div"
}
)
}

/**
Expand All @@ -84,9 +84,9 @@ class SingleBaseTest : AnnotationSpec() {
fun r3_forGroupSelector() {
WHEN {
ForTest().parse().s2.toXpath()
}.THEN {
}.THEN(
"//base/div//div"
}
)
}

/**
Expand All @@ -99,9 +99,9 @@ class SingleBaseTest : AnnotationSpec() {
ForTestObj.parse()
}.WHEN {
ForTestObj.InnerObj.s2.toXpath()
}.THEN {
}.THEN(
"//base/div"
}
)
}

/**
Expand All @@ -114,8 +114,8 @@ class SingleBaseTest : AnnotationSpec() {
ForTestObj.parse()
}.WHEN {
ForTestObj.InnerObj.s3.toXpath()
}.THEN {
}.THEN(
"//base//div"
}
)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 XPATH-QS
* Copyright (c) 2026 XPATH-QS
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -29,7 +29,9 @@ import org.xpathqs.gwt.GIVEN

class ReflectionExtensionsTest : AnnotationSpec() {
class TestCls {
inner class InnerCls
class InnerCls {
val m1: String = "test1"
}
val innerMember = InnerCls()
class Class2
val member = Class2()
Expand All @@ -44,9 +46,9 @@ class ReflectionExtensionsTest : AnnotationSpec() {
tagSelector("div")
}.WHEN {
given.isInnerClass()
}.THEN {
}.THEN(
false
}
)
}

/**
Expand All @@ -58,8 +60,8 @@ class ReflectionExtensionsTest : AnnotationSpec() {
TestCls()
}.WHEN {
given.innerMember.isInnerClass()
}.THEN {
}.THEN(
true
}
)
}
}
Loading
Loading