diff --git a/eng/lintingconfigs/checkstyle/clientcore/checkstyle.xml b/eng/lintingconfigs/checkstyle/clientcore/checkstyle.xml index 18abe9430410..e5f2cafd3123 100644 --- a/eng/lintingconfigs/checkstyle/clientcore/checkstyle.xml +++ b/eng/lintingconfigs/checkstyle/clientcore/checkstyle.xml @@ -426,5 +426,8 @@ + + + diff --git a/eng/lintingconfigs/checkstyle/track2/checkstyle.xml b/eng/lintingconfigs/checkstyle/track2/checkstyle.xml index 6c6c6082878e..ea5bc3edb785 100644 --- a/eng/lintingconfigs/checkstyle/track2/checkstyle.xml +++ b/eng/lintingconfigs/checkstyle/track2/checkstyle.xml @@ -420,5 +420,8 @@ + + + diff --git a/eng/lintingconfigs/checkstyle/vnext/checkstyle.xml b/eng/lintingconfigs/checkstyle/vnext/checkstyle.xml index 6432e8cc91ba..efd19a736c8a 100644 --- a/eng/lintingconfigs/checkstyle/vnext/checkstyle.xml +++ b/eng/lintingconfigs/checkstyle/vnext/checkstyle.xml @@ -425,5 +425,8 @@ + + + diff --git a/eng/versioning/version_client.txt b/eng/versioning/version_client.txt index 57c7afc7de10..f5278c2caf7c 100644 --- a/eng/versioning/version_client.txt +++ b/eng/versioning/version_client.txt @@ -549,11 +549,12 @@ io.clientcore:optional-dependency-tests;1.0.0-beta.1;1.0.0-beta.1 # In the pom, the version update tag after the version should name the unreleased package and the dependency version: # +unreleased_com.azure.resourcemanager:azure-resourcemanager-containerregistry;2.55.0 unreleased_com.azure.v2:azure-core;2.0.0-beta.1 unreleased_com.azure.v2:azure-identity;2.0.0-beta.1 unreleased_com.azure.v2:azure-data-appconfiguration;2.0.0-beta.1 unreleased_io.clientcore:http-netty4;1.0.0-beta.1 -unreleased_com.azure.resourcemanager:azure-resourcemanager-containerregistry;2.55.0 +unreleased_io.clientcore:linting-extensions;1.0.0-beta.2 # Released Beta dependencies: Copy the entry from above, prepend "beta_", remove the current # version and set the version to the released beta. Released beta dependencies are only valid diff --git a/sdk/keyvault-v2/azure-security-keyvault-administration/checkstyle-suppressions.xml b/sdk/keyvault-v2/azure-security-keyvault-administration/checkstyle-suppressions.xml index b1b2b42206a4..6929837bf10e 100644 --- a/sdk/keyvault-v2/azure-security-keyvault-administration/checkstyle-suppressions.xml +++ b/sdk/keyvault-v2/azure-security-keyvault-administration/checkstyle-suppressions.xml @@ -3,22 +3,24 @@ - - - - - - + + + + + + - + - + - - + + + - - + + + diff --git a/sdk/keyvault-v2/azure-security-keyvault-certificates/checkstyle-suppressions.xml b/sdk/keyvault-v2/azure-security-keyvault-certificates/checkstyle-suppressions.xml index c3ba6b3d5de1..4be50bb27716 100644 --- a/sdk/keyvault-v2/azure-security-keyvault-certificates/checkstyle-suppressions.xml +++ b/sdk/keyvault-v2/azure-security-keyvault-certificates/checkstyle-suppressions.xml @@ -5,12 +5,12 @@ - - + + - - - + + + diff --git a/sdk/keyvault-v2/azure-security-keyvault-keys/checkstyle-suppressions.xml b/sdk/keyvault-v2/azure-security-keyvault-keys/checkstyle-suppressions.xml index e4db86f0597a..f5dc6622661d 100644 --- a/sdk/keyvault-v2/azure-security-keyvault-keys/checkstyle-suppressions.xml +++ b/sdk/keyvault-v2/azure-security-keyvault-keys/checkstyle-suppressions.xml @@ -3,17 +3,17 @@ - - - - + + + + - - - + + + - - + + diff --git a/sdk/keyvault-v2/azure-security-keyvault-secrets/checkstyle-suppressions.xml b/sdk/keyvault-v2/azure-security-keyvault-secrets/checkstyle-suppressions.xml index b564c4aedda0..9a31b590b18b 100644 --- a/sdk/keyvault-v2/azure-security-keyvault-secrets/checkstyle-suppressions.xml +++ b/sdk/keyvault-v2/azure-security-keyvault-secrets/checkstyle-suppressions.xml @@ -3,12 +3,12 @@ - - + + - - - + + + diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/quickpulse/model/QuickPulseMonitoringDataPoints.java b/sdk/monitor/azure-monitor-opentelemetry-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/quickpulse/model/QuickPulseMonitoringDataPoints.java index f22590bb5c9e..60ed7a3654f5 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/quickpulse/model/QuickPulseMonitoringDataPoints.java +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/src/main/java/com/azure/monitor/opentelemetry/exporter/implementation/quickpulse/model/QuickPulseMonitoringDataPoints.java @@ -1,5 +1,6 @@ package com.azure.monitor.opentelemetry.exporter.implementation.quickpulse.model; +import com.azure.json.JsonReader; import com.azure.json.JsonSerializable; import com.azure.json.JsonWriter; @@ -17,4 +18,9 @@ public QuickPulseMonitoringDataPoints(List monitoringDataPoi public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { return jsonWriter.writeArray(monitoringDataPoints, JsonWriter::writeJson, false); } + + public static QuickPulseMonitoringDataPoints fromJson(JsonReader jsonReader) throws IOException { + List dataPoints = jsonReader.readArray(QuickPulseEnvelope::fromJson); + return new QuickPulseMonitoringDataPoints(dataPoints); + } } diff --git a/sdk/openai/azure-ai-openai-stainless/src/samples/java/com/azure/ai/openai/stainless/FunctionCallingAsyncSample.java b/sdk/openai/azure-ai-openai-stainless/src/samples/java/com/azure/ai/openai/stainless/FunctionCallingAsyncSample.java index 3d6974d6da83..3d92143391f1 100644 --- a/sdk/openai/azure-ai-openai-stainless/src/samples/java/com/azure/ai/openai/stainless/FunctionCallingAsyncSample.java +++ b/sdk/openai/azure-ai-openai-stainless/src/samples/java/com/azure/ai/openai/stainless/FunctionCallingAsyncSample.java @@ -25,7 +25,16 @@ @JsonClassDescription("Gets the quality of the given SDK.") class GetSdkQualityAsync { @JsonPropertyDescription("The name of the SDK.") - public String name; + private String name; + + public String getName() { + return name; + } + + public GetSdkQualityAsync setName(String name) { + this.name = name; + return this; + } public String execute() { return name.contains("OpenAI") ? "Excellent quality and robust implementation!" : "Unknown quality"; diff --git a/sdk/openai/azure-ai-openai-stainless/src/samples/java/com/azure/ai/openai/stainless/FunctionCallingSample.java b/sdk/openai/azure-ai-openai-stainless/src/samples/java/com/azure/ai/openai/stainless/FunctionCallingSample.java index 8b432c4750a9..c68dac190df3 100644 --- a/sdk/openai/azure-ai-openai-stainless/src/samples/java/com/azure/ai/openai/stainless/FunctionCallingSample.java +++ b/sdk/openai/azure-ai-openai-stainless/src/samples/java/com/azure/ai/openai/stainless/FunctionCallingSample.java @@ -25,7 +25,16 @@ @JsonClassDescription("Gets the quality of the given SDK.") class GetSdkQuality { @JsonPropertyDescription("The name of the SDK.") - public String name; + private String name; + + public String getName() { + return name; + } + + public GetSdkQuality setName(String name) { + this.name = name; + return this; + } public String execute() { return name.contains("OpenAI") ? "Excellent quality and robust implementation!" : "Unknown quality"; diff --git a/sdk/openai/azure-ai-openai-stainless/src/test/java/com/azure/ai/openai/stainless/OpenAIOkHttpClientTestBase.java b/sdk/openai/azure-ai-openai-stainless/src/test/java/com/azure/ai/openai/stainless/OpenAIOkHttpClientTestBase.java index 6283d2fa2810..68d603730b60 100644 --- a/sdk/openai/azure-ai-openai-stainless/src/test/java/com/azure/ai/openai/stainless/OpenAIOkHttpClientTestBase.java +++ b/sdk/openai/azure-ai-openai-stainless/src/test/java/com/azure/ai/openai/stainless/OpenAIOkHttpClientTestBase.java @@ -69,10 +69,28 @@ @JsonClassDescription("Get the current weather in a given location") class GetCurrentWeather { @JsonPropertyDescription("The city and state, e.g. San Francisco, CA") - public String location; + private String location; @JsonPropertyDescription("Temperature unit (celsius or fahrenheit)") - public String unit = "celsius"; + private String unit = "celsius"; + + public String getLocation() { + return location; + } + + public GetCurrentWeather setLocation(String location) { + this.location = location; + return this; + } + + public String getUnit() { + return unit; + } + + public GetCurrentWeather setUnit(String unit) { + this.unit = unit; + return this; + } public String execute() { return "The weather in " + location + " is 72 degrees " + unit; diff --git a/sdk/parents/azure-client-sdk-parent-v2/pom.xml b/sdk/parents/azure-client-sdk-parent-v2/pom.xml index a8ee8c08ff77..5d38ac091892 100644 --- a/sdk/parents/azure-client-sdk-parent-v2/pom.xml +++ b/sdk/parents/azure-client-sdk-parent-v2/pom.xml @@ -708,7 +708,7 @@ io.clientcore linting-extensions - 1.0.0-beta.1 + 1.0.0-beta.2 com.puppycrawl.tools diff --git a/sdk/parents/azure-client-sdk-parent/pom.xml b/sdk/parents/azure-client-sdk-parent/pom.xml index 680523d726fd..d4bb259f2310 100644 --- a/sdk/parents/azure-client-sdk-parent/pom.xml +++ b/sdk/parents/azure-client-sdk-parent/pom.xml @@ -720,7 +720,7 @@ io.clientcore linting-extensions - 1.0.0-beta.1 + 1.0.0-beta.2 com.puppycrawl.tools diff --git a/sdk/parents/azure-data-sdk-parent/pom.xml b/sdk/parents/azure-data-sdk-parent/pom.xml index 4288c6d213a7..216d5c5b2bf4 100644 --- a/sdk/parents/azure-data-sdk-parent/pom.xml +++ b/sdk/parents/azure-data-sdk-parent/pom.xml @@ -78,7 +78,7 @@ io.clientcore linting-extensions - 1.0.0-beta.1 + 1.0.0-beta.2 com.puppycrawl.tools diff --git a/sdk/parents/clientcore-parent/pom.xml b/sdk/parents/clientcore-parent/pom.xml index 333fe1c6d6aa..83ef7489e96a 100644 --- a/sdk/parents/clientcore-parent/pom.xml +++ b/sdk/parents/clientcore-parent/pom.xml @@ -788,7 +788,7 @@ io.clientcore linting-extensions - 1.0.0-beta.1 + 1.0.0-beta.2 com.puppycrawl.tools diff --git a/sdk/search/azure-search-documents/checkstyle-suppressions.xml b/sdk/search/azure-search-documents/checkstyle-suppressions.xml index 47a4f50ab30c..49c0537c61ec 100644 --- a/sdk/search/azure-search-documents/checkstyle-suppressions.xml +++ b/sdk/search/azure-search-documents/checkstyle-suppressions.xml @@ -15,4 +15,6 @@ + + diff --git a/sdk/tools/linting-extensions/src/main/java/io/clientcore/linting/extensions/checkstyle/checks/SerializableMethodsCheck.java b/sdk/tools/linting-extensions/src/main/java/io/clientcore/linting/extensions/checkstyle/checks/SerializableMethodsCheck.java new file mode 100644 index 000000000000..33ee79601fa3 --- /dev/null +++ b/sdk/tools/linting-extensions/src/main/java/io/clientcore/linting/extensions/checkstyle/checks/SerializableMethodsCheck.java @@ -0,0 +1,173 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package io.clientcore.linting.extensions.checkstyle.checks; + +import com.puppycrawl.tools.checkstyle.api.AbstractCheck; +import com.puppycrawl.tools.checkstyle.api.DetailAST; +import com.puppycrawl.tools.checkstyle.api.TokenTypes; + +import java.util.ArrayList; +import java.util.List; + +/** + * Verifies that classes implementing JsonSerializable or XmlSerializable provide a static fromJson or fromXml method. + */ +public class SerializableMethodsCheck extends AbstractCheck { + static final String ERR_NO_FROM_JSON = "Class implementing JsonSerializable must provide a static fromJson method."; + static final String ERR_NO_FROM_XML = "Class implementing XmlSerializable must provide a static fromXml method."; + + private List snapshotArchive; + + public SerializableMethodsCheck() { + } + + @Override + public int[] getDefaultTokens() { + return getRequiredTokens(); + } + + @Override + public int[] getAcceptableTokens() { + return getRequiredTokens(); + } + + @Override + public int[] getRequiredTokens() { + return new int[] { TokenTypes.CLASS_DEF, TokenTypes.METHOD_DEF }; + } + + @Override + public void beginTree(DetailAST rootNode) { + snapshotArchive = new ArrayList<>(); + } + + @Override + public void visitToken(DetailAST currentNode) { + int tokenKind = currentNode.getType(); + + if (tokenKind == TokenTypes.CLASS_DEF) { + TypeSnapshot snapshot = captureTypeSnapshot(currentNode); + snapshotArchive.add(snapshot); + } else if (tokenKind == TokenTypes.METHOD_DEF) { + integrateMethodIntoSnapshot(currentNode); + } + } + + @Override + public void leaveToken(DetailAST currentNode) { + if (currentNode.getType() == TokenTypes.CLASS_DEF) { + performSnapshotAudit(currentNode); + } + } + + private TypeSnapshot captureTypeSnapshot(DetailAST classNode) { + TypeSnapshot snapshot = new TypeSnapshot(); + snapshot.classNode = classNode; + + // Check if the class is abstract - skip validation for abstract types + snapshot.isAbstract = isAbstractType(classNode); + + DetailAST interfaceSection = classNode.findFirstToken(TokenTypes.IMPLEMENTS_CLAUSE); + if (interfaceSection != null) { + digestInterfaceSection(interfaceSection, snapshot); + } + + return snapshot; + } + + private boolean isAbstractType(DetailAST classNode) { + DetailAST modifierBlock = classNode.findFirstToken(TokenTypes.MODIFIERS); + if (modifierBlock == null) { + return false; + } + return modifierBlock.findFirstToken(TokenTypes.ABSTRACT) != null; + } + + private void digestInterfaceSection(DetailAST interfaceSection, TypeSnapshot snapshot) { + DetailAST cursor = interfaceSection.getFirstChild(); + + while (cursor != null) { + if (cursor.getType() == TokenTypes.IDENT) { + String interfaceLabel = cursor.getText(); + if ("JsonSerializable".equals(interfaceLabel)) { + snapshot.implementsJsonSerializable = true; + } else if ("XmlSerializable".equals(interfaceLabel)) { + snapshot.implementsXmlSerializable = true; + } + } + cursor = cursor.getNextSibling(); + } + } + + private void integrateMethodIntoSnapshot(DetailAST methodNode) { + if (snapshotArchive.isEmpty()) { + return; + } + + TypeSnapshot latestSnapshot = snapshotArchive.get(snapshotArchive.size() - 1); + + if (!latestSnapshot.implementsJsonSerializable && !latestSnapshot.implementsXmlSerializable) { + return; + } + + DetailAST nameNode = methodNode.findFirstToken(TokenTypes.IDENT); + if (nameNode == null) { + return; + } + + String methodLabel = nameNode.getText(); + boolean markedStatic = probeForStaticMarker(methodNode); + + latestSnapshot.digestMethod(methodLabel, markedStatic); + } + + private boolean probeForStaticMarker(DetailAST methodNode) { + DetailAST modifierBlock = methodNode.findFirstToken(TokenTypes.MODIFIERS); + + if (modifierBlock == null) { + return false; + } + + return modifierBlock.findFirstToken(TokenTypes.LITERAL_STATIC) != null; + } + + private void performSnapshotAudit(DetailAST classNode) { + if (snapshotArchive.isEmpty()) { + return; + } + + TypeSnapshot snapshot = snapshotArchive.remove(snapshotArchive.size() - 1); + + // Skip validation for abstract types + if (snapshot.isAbstract) { + return; + } + + if (snapshot.implementsJsonSerializable && !snapshot.observedFromJson) { + log(classNode, ERR_NO_FROM_JSON); + } + + if (snapshot.implementsXmlSerializable && !snapshot.observedFromXml) { + log(classNode, ERR_NO_FROM_XML); + } + } + + private static class TypeSnapshot { + DetailAST classNode; + boolean isAbstract; + boolean extendsAnotherType; + boolean implementsJsonSerializable; + boolean implementsXmlSerializable; + boolean observedFromJson; + boolean observedFromXml; + + void digestMethod(String methodLabel, boolean markedStatic) { + if ("fromJson".equals(methodLabel) && markedStatic) { + observedFromJson = true; + } else if ("fromXml".equals(methodLabel) && markedStatic) { + observedFromXml = true; + } + } + } +} diff --git a/sdk/tools/linting-extensions/src/main/java/io/clientcore/linting/extensions/checkstyle/checks/ServiceClientCheck.java b/sdk/tools/linting-extensions/src/main/java/io/clientcore/linting/extensions/checkstyle/checks/ServiceClientCheck.java index e39617454a2e..a1e378620f23 100644 --- a/sdk/tools/linting-extensions/src/main/java/io/clientcore/linting/extensions/checkstyle/checks/ServiceClientCheck.java +++ b/sdk/tools/linting-extensions/src/main/java/io/clientcore/linting/extensions/checkstyle/checks/ServiceClientCheck.java @@ -46,6 +46,7 @@ public class ServiceClientCheck extends AbstractCheck { private static final String IS_ASYNC = "isAsync"; private static final String CONTEXT = "Context"; private static final String REQUEST_OPTIONS = "RequestOptions"; + private static final String REQUEST_CONTEXT = "RequestContext"; private static final String RESPONSE_BRACKET = "Response<"; private static final String MONO_BRACKET = "Mono<"; @@ -77,7 +78,7 @@ public class ServiceClientCheck extends AbstractCheck { private static final String ASYNC_CONTEXT_ERROR = "Asynchronous method with annotation @ServiceMethod must not have ''%s'' as a method parameter."; private static final String SYNC_CONTEXT_ERROR - = "Synchronous method with annotation @ServiceMethod must have ''%s'' or ''%s'' as a method parameter."; + = "Synchronous method with annotation @ServiceMethod must have ''%s'', ''%s'' or ''%s'' as a method parameter."; // Add all imported classes into a map, key is the name of class and value is the full package path of class. private final Map simpleClassNameToQualifiedNameMap = new HashMap<>(); @@ -399,6 +400,16 @@ private void checkContextInRightPlace(DetailAST methodDefToken) { return paramTypeIdentToken != null && REQUEST_OPTIONS.equals(paramTypeIdentToken.getText()); }).isPresent(); + boolean containsRequestContextParameter + = TokenUtil.findFirstTokenByPredicate(parametersToken, parameterToken -> { + if (parameterToken.getType() != TokenTypes.PARAMETER_DEF) { + return false; + } + final DetailAST paramTypeIdentToken + = parameterToken.findFirstToken(TokenTypes.TYPE).findFirstToken(TokenTypes.IDENT); + return paramTypeIdentToken != null && REQUEST_CONTEXT.equals(paramTypeIdentToken.getText()); + }).isPresent(); + if (containsContextParameter) { // MONO and PagedFlux return type implies Asynchronous method if (returnType.startsWith(MONO_BRACKET) @@ -406,11 +417,11 @@ private void checkContextInRightPlace(DetailAST methodDefToken) { || returnType.startsWith(POLLER_FLUX_BRACKET)) { log(methodDefToken, String.format(ASYNC_CONTEXT_ERROR, CONTEXT)); } - } else if (!containsRequestOptionsParameter) { + } else if (!(containsRequestOptionsParameter || containsRequestContextParameter)) { // Context or RequestOptions should be passed in as an argument to all public methods // annotated with @ServiceMethod that return Response in sync clients. if (returnType.startsWith(RESPONSE_BRACKET)) { - log(methodDefToken, String.format(SYNC_CONTEXT_ERROR, CONTEXT, REQUEST_OPTIONS)); + log(methodDefToken, String.format(SYNC_CONTEXT_ERROR, CONTEXT, REQUEST_OPTIONS, REQUEST_CONTEXT)); } } } diff --git a/sdk/tools/linting-extensions/src/test/java/io/clientcore/linting/extensions/checkstyle/checks/SerializableMethodsCheckTest.java b/sdk/tools/linting-extensions/src/test/java/io/clientcore/linting/extensions/checkstyle/checks/SerializableMethodsCheckTest.java new file mode 100644 index 000000000000..b9aca71a0728 --- /dev/null +++ b/sdk/tools/linting-extensions/src/test/java/io/clientcore/linting/extensions/checkstyle/checks/SerializableMethodsCheckTest.java @@ -0,0 +1,227 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package io.clientcore.linting.extensions.checkstyle.checks; + +import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport; +import com.puppycrawl.tools.checkstyle.Checker; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static io.clientcore.linting.extensions.checkstyle.checks.SerializableMethodsCheck.ERR_NO_FROM_JSON; +import static io.clientcore.linting.extensions.checkstyle.checks.SerializableMethodsCheck.ERR_NO_FROM_XML; + +/** + * Tests {@link SerializableMethodsCheck}. + */ +public class SerializableMethodsCheckTest extends AbstractModuleTestSupport { + private Checker lintingChecker; + + @BeforeEach + public void setupChecker() throws Exception { + lintingChecker = createChecker(createModuleConfig(SerializableMethodsCheck.class)); + } + + @AfterEach + public void teardownChecker() { + lintingChecker.destroy(); + } + + @Override + protected String getPackageLocation() { + return "io/clientcore/linting/extensions/checkstyle/checks/SerializableMethodsCheck"; + } + + @Test + public void jsonSerializableWithBothMethods() throws Exception { + File testFile = TestUtils.createCheckFile("jsonComplete", "package com.azure;", + "public class JsonComplete implements JsonSerializable {", " public void toJson() {}", + " public static JsonComplete fromJson() { return null; }", "}"); + + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath()); + } + + @Test + public void jsonSerializableMissingFromJson() throws Exception { + File testFile = TestUtils.createCheckFile("jsonMissingFrom", "package com.azure;", + "public class JsonMissingFrom implements JsonSerializable {", " public void toJson() {}", "}"); + + String[] expectedErrors = { "2:1: " + ERR_NO_FROM_JSON }; + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors); + } + + @Test + public void jsonSerializableWithNonStaticFromJson() throws Exception { + File testFile = TestUtils.createCheckFile("jsonNonStaticFrom", "package com.azure;", + "public class JsonNonStaticFrom implements JsonSerializable {", " public void toJson() {}", + " public JsonNonStaticFrom fromJson() { return null; }", "}"); + + String[] expectedErrors = { "2:1: " + ERR_NO_FROM_JSON }; + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors); + } + + @Test + public void xmlSerializableWithBothMethods() throws Exception { + File testFile = TestUtils.createCheckFile("xmlComplete", "package com.azure;", + "public class XmlComplete implements XmlSerializable {", " public void toXml() {}", + " public static XmlComplete fromXml() { return null; }", "}"); + + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath()); + } + + @Test + public void xmlSerializableMissingFromXml() throws Exception { + File testFile = TestUtils.createCheckFile("xmlMissingFrom", "package com.azure;", + "public class XmlMissingFrom implements XmlSerializable {", " public void toXml() {}", "}"); + + String[] expectedErrors = { "2:1: " + ERR_NO_FROM_XML }; + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors); + } + + @Test + public void xmlSerializableWithNonStaticFromXml() throws Exception { + File testFile = TestUtils.createCheckFile("xmlNonStaticFrom", "package com.azure;", + "public class XmlNonStaticFrom implements XmlSerializable {", " public void toXml() {}", + " public XmlNonStaticFrom fromXml() { return null; }", "}"); + + String[] expectedErrors = { "2:1: " + ERR_NO_FROM_XML }; + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors); + } + + @Test + public void bothInterfacesWithAllMethods() throws Exception { + File testFile = TestUtils.createCheckFile("bothComplete", "package com.azure;", + "public class BothComplete implements JsonSerializable, XmlSerializable {", " public void toJson() {}", + " public static BothComplete fromJson() { return null; }", " public void toXml() {}", + " public static BothComplete fromXml() { return null; }", "}"); + + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath()); + } + + @Test + public void bothInterfacesMissingAllMethods() throws Exception { + File testFile = TestUtils.createCheckFile("bothMissing", "package com.azure;", + "public class BothMissing implements JsonSerializable, XmlSerializable {", "}"); + + String[] expectedErrors = { "2:1: " + ERR_NO_FROM_JSON, "2:1: " + ERR_NO_FROM_XML }; + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors); + } + + @Test + public void bothInterfacesMissingJsonMethods() throws Exception { + File testFile = TestUtils.createCheckFile("bothMissingJson", "package com.azure;", + "public class BothMissingJson implements JsonSerializable, XmlSerializable {", " public void toXml() {}", + " public static BothMissingJson fromXml() { return null; }", "}"); + + String[] expectedErrors = { "2:1: " + ERR_NO_FROM_JSON }; + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors); + } + + @Test + public void bothInterfacesMissingXmlMethods() throws Exception { + File testFile = TestUtils.createCheckFile("bothMissingXml", "package com.azure;", + "public class BothMissingXml implements JsonSerializable, XmlSerializable {", " public void toJson() {}", + " public static BothMissingXml fromJson() { return null; }", "}"); + + String[] expectedErrors = { "2:1: " + ERR_NO_FROM_XML }; + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors); + } + + @Test + public void classNotImplementingInterface() throws Exception { + File testFile = TestUtils.createCheckFile("noInterface", "package com.azure;", "public class NoInterface {", + " public void someMethod() {}", "}"); + + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath()); + } + + @Test + public void nestedClassWithJsonSerializable() throws Exception { + File testFile = TestUtils.createCheckFile("nestedJson", "package com.azure;", "public class OuterClass {", + " public static class InnerClass implements JsonSerializable {", " public void toJson() {}", + " public static InnerClass fromJson() { return null; }", " }", "}"); + + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath()); + } + + @Test + public void nestedClassMissingMethods() throws Exception { + File testFile = TestUtils.createCheckFile("nestedMissing", "package com.azure;", "public class OuterClass {", + " public static class InnerClass implements JsonSerializable {", " }", "}"); + + String[] expectedErrors = { "3:5: " + ERR_NO_FROM_JSON }; + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors); + } + + @Test + public void classWithExtraMethodsAndCorrectSerializationMethods() throws Exception { + File testFile = TestUtils.createCheckFile("extraMethods", "package com.azure;", + "public class ExtraMethods implements JsonSerializable {", " public void toJson() {}", + " public static ExtraMethods fromJson() { return null; }", " public void otherMethod() {}", + " public String getData() { return null; }", "}"); + + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath()); + } + + @Test + public void abstractClassImplementingJsonSerializable() throws Exception { + File testFile = TestUtils.createCheckFile("abstractJson", "package com.azure;", + "public abstract class AbstractJson implements JsonSerializable {", " public void toJson() {}", "}"); + + // Abstract classes should not require fromJson method + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath()); + } + + @Test + public void abstractClassImplementingXmlSerializable() throws Exception { + File testFile = TestUtils.createCheckFile("abstractXml", "package com.azure;", + "public abstract class AbstractXml implements XmlSerializable {", " public void toXml() {}", "}"); + + // Abstract classes should not require fromXml method + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath()); + } + + @Test + public void abstractClassImplementingBothInterfaces() throws Exception { + File testFile = TestUtils.createCheckFile("abstractBoth", "package com.azure;", + "public abstract class AbstractBoth implements JsonSerializable, XmlSerializable {", + " public void toJson() {}", " public void toXml() {}", "}"); + + // Abstract classes should not require fromJson or fromXml methods + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath()); + } + + @Test + public void classExtendsAnotherTypeAndImplementsJsonSerializable() throws Exception { + File testFile = TestUtils.createCheckFile("extendsJson", "package com.azure;", + "public class ExtendsJson extends BaseClass implements JsonSerializable {", " public void toJson() {}", + "}"); + + // Classes extending another type and implementing JsonSerializable should require fromJson method + String[] expectedErrors = { "2:1: " + ERR_NO_FROM_JSON }; + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath(), expectedErrors); + } + + @Test + public void classExtendsTypeAndImplementsJsonSerializableWithFromJson() throws Exception { + File testFile = TestUtils.createCheckFile("concreteExtendsAbstract", "package com.azure;", + "public class ConcreteClass extends AbstractBase implements JsonSerializable {", + " public void toJson() {}", " public static ConcreteClass fromJson() { return null; }", "}"); + + // Classes extending any type should pass (validation is skipped) + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath()); + } + + @Test + public void nestedAbstractClassImplementingJsonSerializable() throws Exception { + File testFile = TestUtils.createCheckFile("nestedAbstract", "package com.azure;", "public class OuterClass {", + " public abstract static class InnerAbstract implements JsonSerializable {", + " public void toJson() {}", " }", "}"); + + // Nested abstract classes should not require fromJson method + verify(lintingChecker, new File[] { testFile }, testFile.getAbsolutePath()); + } +}