Skip to content

Conversation

@rlaxowns7916
Copy link
Contributor

Summary

Abstract List<MatcherOperator> behind a small wrapper to make the matching layer extensible.
Introduces MatcherOperatorRegistry as the abstraction that manages registration and lookup of MatcherOperators.

(Related issue: N/A)

(Optional): Description
This PR refactors the internal storage and lookup of matchers:

  • New interface: MatcherOperatorRegistry
  • Default Implementation: DefaultMatcherOperatorRegistry
    • ExactTypeMatcher operators indexed by exact Class<?> in a Map.
    • AssignableTypeMatcher operators also stored in a Map, keyed by their anchor type (Class<?> used on the left side of isAssignableFrom).

How Has This Been Tested?

  • No new tests were required because this is an internal refactor.

Is the Document updated?

  • x

Comment on lines +40 to +43

public Class<?> getAnchorType() {
return this.type;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exposed a public getter for anchorType in AssignableTypeMatcher to make it accessible from DefaultImpl.

Comment on lines -62 to -66
private void checkPriority(int priority) {
if (priority < 0) {
throw new IllegalArgumentException("Priority must be greater than or equal to 0");
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In DefaultImpl, I used PriorityMatcherOperator to preserve the order. If negative conditions should not be removed, should I create an additional implementation for that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it would be helpful if you mentioned that the priority could be less than zero.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful to add the {@link DefaultMatcherOperatorRegistry} for an example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change was applied, but it does not appear in the git diff.

property.javaField?.annotations?.toList() ?: listOf()

override fun getType(): Class<*> = property.javaField!!.type
override fun getType(): Class<*> = property.javaField?.type ?: Object::class.java
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid NPEs when javaField is missing, I added a fallback to Object::class.java. Since this method returns Class<*> (Java reflection), Object is more explicit than Any even though both map to java.lang.Object at runtime.

If null is a valid return value, consider changing the return type to Class<*>? instead of falling back to Object::class.java.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please provide some examples of situations in which the javaField is missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i commented it below

@@ -0,0 +1,84 @@
package com.navercorp.fixturemonkey.api.matcher;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/*
 * Fixture Monkey
 *
 * Copyright (c) 2021-present NAVER Corp.
 *
 * 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
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

Please add the license

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change was applied, but it does not appear in the git diff.

import com.navercorp.fixturemonkey.api.property.Property;
import com.navercorp.fixturemonkey.api.type.Types;

public class DefaultMatcherOperatorRegistry<T> implements MatcherOperatorRegistry<T> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to inherit right now; please add final.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the MatcherOperatorRegistry splits two concerns: the registry and the retriever.

The MatcherOperatorRegistry should have the methods addFirst and addLast.

A new interface is needed to get the List.

The implementations of two interface should be DefaultMatcherOperatorRegistry

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with your idea of separating concerns into a Retriever and a Registry.
The original DefaultMatcherOperatorRegistry was responsible for both concerns,
so I thought it would be clearer to rename it to Container.

Comment on lines 21 to 52
public void addFirst(MatcherOperator<T> matcherOperator) {
addInternal(matcherOperator, true);
}

public void addLast(MatcherOperator<T> matcherOperator) {
addInternal(matcherOperator, false);
}

private void addInternal(MatcherOperator<T> matcherOperator, boolean prepend) {
int seq = sequenceIssuer.incrementAndGet();
int sequence = prepend ? -seq : seq;

Matcher matcher = matcherOperator.getMatcher();
T operator = matcherOperator.getOperator();

PriorityMatcherOperator<T> priorityMatcherOperator = new PriorityMatcherOperator<>(
matcher,
operator,
sequence
);

if (matcher instanceof ExactTypeMatcher) {
Class<?> type = ((ExactTypeMatcher)matcher).getType();
typeAwareIntrospectors.computeIfAbsent(type, k -> new ArrayList<>()).add(priorityMatcherOperator);
} else if (matcher instanceof AssignableTypeMatcher) {
Class<?> anchorType = ((AssignableTypeMatcher)matcher).getAnchorType();
typeAssignableIntrospectors.computeIfAbsent(anchorType, k -> new ArrayList<>())
.add(priorityMatcherOperator);
} else {
typeUnknownIntrospectors.add(priorityMatcherOperator);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better readiness would be achieved if the methods were placed behind the overridden methods.

@@ -0,0 +1,11 @@
package com.navercorp.fixturemonkey.api.matcher;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/*
 * Fixture Monkey
 *
 * Copyright (c) 2021-present NAVER Corp.
 *
 * 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
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

Please add the license

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change was applied, but it does not appear in the git diff.

public interface MatcherOperatorRegistry<T> {
List<MatcherOperator<T>> values();

List<MatcherOperator<T>> get(Property property);
Copy link
Contributor

@seongahjo seongahjo Sep 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'get' is ambiguous; it would be better to use getListByProperty.

More getXXX methods may be needed in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change was applied, but it does not appear in the git diff.


import com.navercorp.fixturemonkey.api.property.Property;

public interface MatcherOperatorRegistry<T> {
Copy link
Contributor

@seongahjo seongahjo Sep 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add @API(since = "1.1.16", status = EXPERIMENTAL)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change was applied, but it does not appear in the git diff.

@seongahjo seongahjo added this to the 1.1.16 milestone Sep 19, 2025
MatcherOperatorRetriever<PropertyGenerator> propertyGenerators,
PropertyGenerator defaultPropertyGenerator,
List<MatcherOperator<ObjectPropertyGenerator>> objectPropertyGenerators,
MatcherOperatorRetriever<ObjectPropertyGenerator> objectPropertyGenerators,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines +57 to +58
if (propertyType != UnidentifiableType.class) {
for (Map.Entry<Class<?>, List<PriorityMatcherOperator<T>>> e : typeAssignableIntrospectors.entrySet()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the type cannot be identified, it is skipped.

Comment on lines -62 to -66
private void checkPriority(int priority) {
if (priority < 0) {
throw new IllegalArgumentException("Priority must be greater than or equal to 0");
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change was applied, but it does not appear in the git diff.

Comment on lines +478 to +501
/**
* If backed only by a getter, {@code javaProperty} or {@code javaField} may be null and cause NPE.
* To avoid this, returns {@link Types.UnidentifiableType} when the type cannot be identified.
*
* @see com.navercorp.fixturemonkey.kotlin.expression.root
*/
override fun getType(): Class<*> = property.javaField?.type ?: Types.UnidentifiableType::class.java

/**
* If backed only by a getter, {@code javaProperty} or {@code javaField} may be null and cause NPE.
* To avoid this, returns {@link Types.UnidentifiableType} when the type cannot be identified.
*
* @see com.navercorp.fixturemonkey.kotlin.expression.root
*/
override fun getAnnotatedType(): AnnotatedType =
property.javaField!!.annotatedType
property.javaField?.annotatedType ?: object : AnnotatedType {
override fun getType(): Type = Types.UnidentifiableType::class.java

override fun getAnnotations(): Array<Annotation> = emptyArray()

override fun <T : Annotation?> getAnnotation(annotationClass: Class<T>): T? = null

override fun getDeclaredAnnotations(): Array<Annotation> = emptyArray()
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When extracting a JavaClass from a KProperty, the type can only be resolved if it is field-backed.
If the property is backed by a getter, the actual Java type cannot be determined, which may lead to a NullPointerException.

Previously, a generic Object type was returned in such cases, but this was ambiguous.

To make the intent explicit and allow the caller to handle branching logic more clearly, the return value was changed to the existing UnidentifiableType.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This triggered an NPE

@seongahjo seongahjo merged commit bb81f25 into naver:main Oct 10, 2025
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants