Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add @NullUnmarked on classes #134

Merged
merged 47 commits into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
d465eda
init
nimakarimipour Feb 10, 2023
2e8e65f
update
nimakarimipour Feb 10, 2023
46d84ac
undo unwanted change
nimakarimipour Feb 11, 2023
1fba197
update
nimakarimipour Feb 11, 2023
3e2e8b4
update
nimakarimipour Feb 11, 2023
2bf6459
update
nimakarimipour Feb 11, 2023
70bf1ef
move to proper position
nimakarimipour Feb 11, 2023
6046ebf
update
nimakarimipour Feb 11, 2023
f98fc55
update
nimakarimipour Feb 11, 2023
fc41c67
undo deleted comment
nimakarimipour Feb 11, 2023
c201245
Update annotator-core/src/main/java/edu/ucr/cs/riple/core/Annotator.java
nimakarimipour Feb 12, 2023
da8c98a
reformat
nimakarimipour Feb 12, 2023
64d8c57
keep file path info for class with no fields as well
nimakarimipour Feb 12, 2023
47998c0
update unit test
nimakarimipour Feb 12, 2023
01c679a
remove print statement
nimakarimipour Feb 13, 2023
8a517d0
prevent constructor @NullUnmarked for constructors
nimakarimipour Feb 13, 2023
8c2c3ae
Merge branch 'nimak/resolve-remaining' into nimak/nullunmarked-class
nimakarimipour Feb 13, 2023
6b49f66
merge with nimak/force-resolve
nimakarimipour Feb 13, 2023
fcd2fe6
add comment
nimakarimipour Feb 13, 2023
ab8d201
Merge branch 'nimak/resolve-remaining' into nimak/nullunmarked-class
nimakarimipour Feb 13, 2023
5f25f1b
update unit test
nimakarimipour Feb 13, 2023
27a2535
update nullaway test version
nimakarimipour Feb 14, 2023
0d630c6
Merge branch 'nimak/resolve-remaining' into nimak/update-nullaway
nimakarimipour Feb 14, 2023
b27c14a
Merge branch 'master' into nimak/resolve-remaining
nimakarimipour Feb 14, 2023
79b11b1
Merge branch 'nimak/resolve-remaining' into nimak/update-nullaway
nimakarimipour Feb 14, 2023
2b13142
Merge branch 'master' into nimak/update-nullaway
nimakarimipour Feb 15, 2023
4f18e80
fix bug
nimakarimipour Feb 15, 2023
9b729e8
bug fix
nimakarimipour Feb 15, 2023
dcf81b4
reformat
nimakarimipour Feb 15, 2023
0550bb5
update unit test expected values
nimakarimipour Feb 15, 2023
15fbbd4
Merge branch 'master' into nimak/nullunmarked-class
nimakarimipour Feb 15, 2023
9798ca4
Merge branch 'nimak/update-nullaway' into nimak/nullunmarked-class
nimakarimipour Feb 15, 2023
084e9e6
update
nimakarimipour Feb 15, 2023
cafef18
rename to init block
nimakarimipour Feb 15, 2023
139c19a
remove import from unit test input
nimakarimipour Feb 15, 2023
1f39e96
Merge branch 'master' into nimak/nullunmarked-class
nimakarimipour Feb 15, 2023
a811889
remove import from unit test input
nimakarimipour Feb 15, 2023
71b998b
add unit test for named inner classes
nimakarimipour Feb 15, 2023
4fb60f1
add unit test for named inner classes
nimakarimipour Feb 15, 2023
344007b
rename test
nimakarimipour Feb 15, 2023
45374b8
Merge branch 'master' into nimak/update-nullaway
nimakarimipour Feb 15, 2023
3e1bb7c
Update annotator-core/src/main/java/edu/ucr/cs/riple/core/metadata/tr…
nimakarimipour Feb 15, 2023
3a43655
Update annotator-core/src/main/java/edu/ucr/cs/riple/core/metadata/tr…
nimakarimipour Feb 15, 2023
ca510cc
Merge branch 'nimak/update-nullaway' into nimak/nullunmarked-class
nimakarimipour Feb 15, 2023
95429c8
merge master
nimakarimipour Feb 16, 2023
4eb1bbe
Merge branch 'master' into nimak/nullunmarked-class
nimakarimipour Feb 16, 2023
74e44b9
update comment
nimakarimipour Feb 17, 2023
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
2 changes: 1 addition & 1 deletion annotator-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ dependencies {
}

// Should be the latest supporting version of NullAway.
def NULLAWAY_TEST = "0.10.6"
def NULLAWAY_TEST = "0.10.8"

tasks.test.dependsOn(':annotator-scanner:publishToMavenLocal')
tasks.test.dependsOn(':qual:publishToMavenLocal')
Expand Down
31 changes: 31 additions & 0 deletions annotator-core/src/main/java/edu/ucr/cs/riple/core/Annotator.java
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,21 @@ private void forceResolveRemainingErrors() {
.filter(Objects::nonNull)
.map(node -> new AddMarkerAnnotation(node.location, config.nullUnMarkedAnnotation))
.collect(Collectors.toSet());

// For errors within static initialization blocks, add a @NullUnmarked annotation on the
// enclosing class
nullUnMarkedAnnotations.addAll(
remainingErrors.stream()
.filter(
error ->
error.getRegion().isOnInitializationBlock()
&& !error.getRegion().isInAnonymousClass())
.map(
error ->
new AddMarkerAnnotation(
fieldDeclarationStore.getLocationOnClass(error.getRegion().clazz),
config.nullUnMarkedAnnotation))
.collect(Collectors.toSet()));
injector.injectAnnotations(nullUnMarkedAnnotations);
// Update log.
config.log.updateInjectedAnnotations(nullUnMarkedAnnotations);
Expand Down Expand Up @@ -316,5 +331,21 @@ private void forceResolveRemainingErrors() {
injector.injectAnnotations(initializationSuppressWarningsAnnotations);
// Update log.
config.log.updateInjectedAnnotations(initializationSuppressWarningsAnnotations);
// Collect @NullUnmarked annotations on classes for any remaining error.
Utility.buildTarget(config);
remainingErrors =
Utility.readErrorsFromOutputDirectory(config, config.target, fieldDeclarationStore);
nullUnMarkedAnnotations =
remainingErrors.stream()
.filter(error -> !error.getRegion().isInAnonymousClass())
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not for this PR, but long term, in theory, this should try to find the containing method/initializer and add @NullUnmarked there, no? (For anonymous inner classes). It can get complicated with nested anonymous classes and the places they can appear in, so definitely not asking for that in this PR, just wondering if that's in the roadmap for 1.3.6 or after 1.3.6.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes exactly. But I think the plan is to implement that in after 1.3.6.

.map(
error ->
new AddMarkerAnnotation(
fieldDeclarationStore.getLocationOnClass(error.getRegion().clazz),
config.nullUnMarkedAnnotation))
.collect(Collectors.toSet());
injector.injectAnnotations(nullUnMarkedAnnotations);
// Update log.
config.log.updateInjectedAnnotations(nullUnMarkedAnnotations);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import edu.ucr.cs.riple.core.metadata.MetaData;
import edu.ucr.cs.riple.injector.Helper;
import edu.ucr.cs.riple.injector.exceptions.TargetClassNotFound;
import edu.ucr.cs.riple.injector.location.OnClass;
import edu.ucr.cs.riple.injector.location.OnField;
import edu.ucr.cs.riple.scanner.AnnotatorScanner;
import java.io.FileNotFoundException;
Expand Down Expand Up @@ -90,7 +91,10 @@ protected FieldDeclarationInfo addNodeByLine(String[] values) {
.map(NodeWithSimpleName::getNameAsString)
.collect(ImmutableSet.toImmutableSet()));
}));
return info.isEmpty() ? null : info;
// We still want to keep the information about the class even if it has no field declarations,
// so we can retrieve tha path to the file from the given class flat name. This information is
// used in adding suppression annotations on class level.
return info;
} catch (FileNotFoundException e) {
return null;
} catch (IOException e) {
Expand Down Expand Up @@ -136,4 +140,22 @@ public OnField getLocationOnField(String clazz, String field) {
}
return new OnField(candidate.pathToSourceFile, candidate.clazz, fieldNames);
}

/**
* Creates a {@link edu.ucr.cs.riple.injector.location.OnClass} instance targeting the passed
* classes flat name.
*
* @param clazz Enclosing class of the field.
* @return {@link edu.ucr.cs.riple.injector.location.OnClass} instance targeting the passed
* classes flat name.
*/
public OnClass getLocationOnClass(String clazz) {
FieldDeclarationInfo candidate =
findNodeWithHashHint(node -> node.clazz.equals(clazz), FieldDeclarationInfo.hash(clazz));
if (candidate == null) {
// field is on byte code.
Copy link
Collaborator

Choose a reason for hiding this comment

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

So, this comment (and the name of the class), reference a field, but as far as I can tell this is looking for a class declaration and there is no field involved...

Copy link
Member Author

@nimakarimipour nimakarimipour Feb 15, 2023

Choose a reason for hiding this comment

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

Yes, this class has the required information which its main usage is to store information regarding existing classes and their fields. I have a plan for refactoring these data structures that store information regarding the structure of a code and combine them into one data structure. But the plan is to do that after the release of 1.3.6. Please let me know if you would like me to change it. Thank you.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok, that makes sense, but even given that, this comment right above is incorrect here, because this method is not dealing with any fields. Either remove or change the comment?

return null;
}
return new OnClass(candidate.pathToSourceFile, candidate.clazz);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,7 @@ public Node(Fix root) {
*/
public void setOrigins(ErrorStore errorStore) {
this.origins =
errorStore.getRegionsForElements(
error -> error.isSingleFix() && error.getResolvingFixes().contains(root));
errorStore.getRegionsForElements(error -> error.getResolvingFixes().contains(root));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Question: what is this change about? I don't understand either what the error.isSingleFix() check was ensuring before, nor why are we removing it now.

Copy link
Member Author

Choose a reason for hiding this comment

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

Origins are set of regions that triggered root fix. The condition isSingleFix was ensuring the error has exactly one fix which is not correct, initialization errors requires multiple fixes to get resolved, therefore I removed this condition.

Copy link
Member Author

Choose a reason for hiding this comment

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

This was fixed in #136 and the change of this PR now changed to branch in #136

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package edu.ucr.cs.riple.core.metadata.trackers;

import edu.ucr.cs.riple.injector.Helper;
import edu.ucr.cs.riple.injector.location.OnClass;
import edu.ucr.cs.riple.scanner.generatedcode.SourceType;
import java.util.Objects;

Expand Down Expand Up @@ -53,7 +54,7 @@ public enum Type {
METHOD,
FIELD,
CONSTRUCTOR,
STATIC_BLOCK
INIT_BLOCK
}

public Region(String encClass, String encMember, SourceType sourceType) {
Expand All @@ -70,12 +71,13 @@ public Region(String encClass, String encMember) {
/**
* Initializes {@link Region#type} based on the string representation of regionMember.
*
* @param regionMember Symbol of the region representative.
* @param regionClass Symbol of the region class in string.
* @param regionMember Symbol of the region representative in string.
* @return The corresponding Type.
*/
public static Type getType(String regionClass, String regionMember) {
if (regionMember.equals("null")) {
return Type.STATIC_BLOCK;
return Type.INIT_BLOCK;
}
if (regionMember.contains("(")) {
return Helper.extractCallableName(regionMember).equals(Helper.simpleName(regionClass))
Expand Down Expand Up @@ -121,6 +123,24 @@ public boolean isOnField() {
return type.equals(Type.FIELD);
}

/**
* Checks if region targets a static initialization block.
nimakarimipour marked this conversation as resolved.
Show resolved Hide resolved
*
* @return true, if region is targeting a static initialization block.
nimakarimipour marked this conversation as resolved.
Show resolved Hide resolved
*/
public boolean isOnInitializationBlock() {
return type.equals(Type.INIT_BLOCK);
}

/**
* Checks if region is inside an anonymous class.
*
* @return true, if region is inside an anonymous class.
*/
public boolean isInAnonymousClass() {
return OnClass.isAnonymousClassFlatName(clazz);
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down
19 changes: 18 additions & 1 deletion annotator-core/src/test/java/edu/ucr/cs/riple/core/CoreTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import edu.ucr.cs.riple.injector.changes.AddAnnotation;
import edu.ucr.cs.riple.injector.changes.AddMarkerAnnotation;
import edu.ucr.cs.riple.injector.changes.AddSingleElementAnnotation;
import edu.ucr.cs.riple.injector.location.OnClass;
import edu.ucr.cs.riple.injector.location.OnField;
import edu.ucr.cs.riple.injector.location.OnMethod;
import edu.ucr.cs.riple.injector.location.OnParameter;
Expand Down Expand Up @@ -421,7 +422,7 @@ public void initializationErrorWithMultipleConstructors() {
}

@Test
public void staticBlockLocalVariableInitializationTest() {
public void staticAndInstanceInitializerBlockTest() {
coreTestHelper
.addInputLines(
"A.java",
Expand All @@ -441,6 +442,9 @@ public void staticBlockLocalVariableInitializationTest() {
" }",
"}",
"class B {",
" {",
" foo().hashCode();",
" }",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we want to test static initializer block support and instance initializer block in the same test? (if so, the test case might need renaming). Or is it perhaps better to have two separate test cases for ease of reading/debugging?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think the decision for now is to have them in one test until #137 is finalized, therefore I renamed the test name 344007b

" @Nullable",
" public static Object foo() { return null; }",
" @Nullable",
Expand All @@ -450,5 +454,18 @@ public void staticBlockLocalVariableInitializationTest() {
.addExpectedReports(new TReport(new OnField("A.java", "test.A", singleton("f")), -3))
.enableForceResolve()
.start();
List<AddAnnotation> expectedAnnotations =
List.of(
new AddMarkerAnnotation(
new OnField(
coreTestHelper.getSourceRoot().resolve("A.java").toString(),
"test.A",
Set.of("f")),
"javax.annotation.Nullable"),
new AddMarkerAnnotation(
new OnClass(coreTestHelper.getSourceRoot().resolve("A.java").toString(), "test.B"),
"org.jspecify.nullness.NullUnmarked"));
Assert.assertEquals(
expectedAnnotations, coreTestHelper.getConfig().log.getInjectedAnnotations());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ public void publicMethodWithDownstreamDependencyEnabled() {
coreTestHelper
.addExpectedReports(
// Change reduces errors on target by -4, but increases them in downstream dependency
// DepA by 3, DepB by 4 and DepC by 2. Hence, the total effect is: 5.
new TReport(new OnMethod("Foo.java", "test.target.Foo", "returnNullableBad(int)"), 5),
// DepA by 3, DepB by 4 and DepC by 3. Hence, the total effect is: 6.
new TReport(new OnMethod("Foo.java", "test.target.Foo", "returnNullableBad(int)"), 6),
Copy link
Collaborator

Choose a reason for hiding this comment

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

What changed that changed the effects here and why is it part of this PR about @NullUnmarked classes? I thought effects were always computed before adding any suppressions/null-unmarked regions... is this related to the other change above I asked about? The error.isSingleFix() check? If so, should that fix be part of this PR?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes the bug fix is in #136 and the base of this PR now changed to branch in #136.

// Change reduces errors on target by -5, but increases them in downstream dependency
// DepA by 0, DepB by 1 and DepC by 0. Hence, the total effect is: -4.
new TReport(new OnMethod("Foo.java", "test.target.Foo", "returnNullableGood(int)"), -4),
Expand Down Expand Up @@ -78,13 +78,13 @@ public void publicMethodWithDownstreamDependencyDisabled() {
public void lowerBoundComputationTest() {
coreTestHelper
.addExpectedReports(
// Only returnNullableBad triggers new errors in this fix chain (+9), lower bound is 9.
new TReport(new OnMethod("Foo.java", "test.target.Foo", "returnNullableBad(int)"), 9),
// Only returnNullableBad triggers new errors in this fix chain (+9), lower bound is 10.
new TReport(new OnMethod("Foo.java", "test.target.Foo", "returnNullableBad(int)"), 10),
// Only returnNullableGood triggers new errors in this fix chain (+1), lower bound is 1.
new TReport(new OnMethod("Foo.java", "test.target.Foo", "returnNullableGood(int)"), 1),
// Root fix triggers 1 error on downstream dependency but returnNullableBad is
// present in the fix tree, therefore the lower bound effect for the tree should be 9.
new TReport(new OnMethod("Foo.java", "test.target.Foo", "bar()"), 9))
// present in the fix tree, therefore the lower bound effect for the tree should be 10.
new TReport(new OnMethod("Foo.java", "test.target.Foo", "bar()"), 10))
.setPredicate(
(expected, found) ->
expected.root.equals(found.root)
Expand All @@ -100,16 +100,17 @@ public void lowerBoundComputationTest() {
public void upperBoundComputationTest() {
coreTestHelper
.addExpectedReports(
// Only returnNullableBad triggers new errors in this fix chain (+9) and upper bound
// should be 9
new TReport(new OnMethod("Foo.java", "test.target.Foo", "returnNullableBad(int)"), 9),
// Only returnNullableBad triggers new errors in this fix chain (+10) and upper bound
// should be 10
new TReport(new OnMethod("Foo.java", "test.target.Foo", "returnNullableBad(int)"), 10),
// Only returnNullableGood triggers new errors in this fix chain (+1) and upper bound
// should be 1
new TReport(new OnMethod("Foo.java", "test.target.Foo", "returnNullableGood(int)"), 1),
// Root fix triggers 1 error on downstream dependency and returnNullableBad is
// present in the fix tree and triggers 9 errors on downstream dependency, therefore the
// upper bound effect for the tree should be 10.
new TReport(new OnMethod("Foo.java", "test.target.Foo", "bar()"), 10))
// present in the fix tree and triggers 10 errors on downstream dependency, therefore
// the
// upper bound effect for the tree should be 11.
new TReport(new OnMethod("Foo.java", "test.target.Foo", "bar()"), 11))
.setPredicate(
(expected, found) ->
expected.root.equals(found.root)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class DepC {
Foo foo = new Foo();
Object f;

// We get 2 errros here, assign_field_nullable and method_no_init for assigning nullable to f;
DepC() {
f = foo.returnNullableBad(0);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@
public class Foo {
Object field = new Object();

// Making this method will resolve 5 errors here and will introduce 1 new error here and 8 new
// Making this method will resolve 5 errors here and will introduce 1 new error here and 10 new
// errors on downstream dependencies.
// dependencies.
public Object returnNullableBad(int i) {
// Just to create 5 places where it returns nullable, so making this method @Nullable will
// resolve 5 NullAway errors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package edu.ucr.cs.riple.injector.location;

public enum LocationType {
CLASS,
FIELD,
METHOD,
PARAMETER;
Expand All @@ -39,6 +40,9 @@ public static LocationType getType(String type) {
if (type.equalsIgnoreCase("parameter")) {
return PARAMETER;
}
if (type.equalsIgnoreCase("class")) {
return CLASS;
}
throw new UnsupportedOperationException("Cannot detect type: " + type);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* MIT License
*
* Copyright (c) 2023 Nima Karimipour
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package edu.ucr.cs.riple.injector.location;

import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.BodyDeclaration;
import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations;
import edu.ucr.cs.riple.injector.Helper;
import edu.ucr.cs.riple.injector.changes.Change;
import edu.ucr.cs.riple.injector.modifications.Modification;
import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
import org.json.simple.JSONObject;

public class OnClass extends Location {

/**
* Pattern to detect if a class flat name is for an anonymous class. Anonymous classes flat names
* ends with a $ and one or more digits.
*/
public static final Pattern anonymousClassPattern = Pattern.compile(".*\\$\\d+$");

public OnClass(Path path, String clazz) {
super(LocationType.CLASS, path, clazz);
}

public OnClass(String path, String clazz) {
this(Helper.deserializePath(path), clazz);
}

@Override
protected Modification applyToMember(NodeList<BodyDeclaration<?>> declarations, Change change) {
if (isAnonymousClassFlatName(change.location.clazz)) {
return null;
}
final AtomicReference<Modification> ans = new AtomicReference<>();
Optional<Node> clazz = declarations.getParentNode();
clazz.ifPresent(
node ->
node.getRange()
.ifPresent(range -> ans.set(change.visit((NodeWithAnnotations<?>) node, range))));
return ans.get();
}

/**
* Checks if flat name is for an anonymous class.
*
* @return true, if flat name is for an anonymous class.
*/
public static boolean isAnonymousClassFlatName(String flatName) {
return anonymousClassPattern.matcher(flatName).matches();
}

@Override
protected void fillJsonInformation(JSONObject res) {
// no op
}

@Override
public String toString() {
return "OnClass{" + "type=" + type + ", clazz='" + clazz + '\'' + ", path=" + path + '}';
}
}
Loading