message: [TH2] Add declared message length check for mold-udp protocol

diff:
/******************************************************************************
- * Copyright 2009-2021 Exactpro (Exactpro Systems Limited)
+ * Copyright 2009-2022 Exactpro (Exactpro Systems Limited)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -280,11 +280,20 @@ public class SOUPCodec extends AbstractCodec {
Role role = session.containsAttribute(IExternalCodecContext.class) ? ((IExternalCodecContext)session.getAttribute(IExternalCodecContext.class)).getRole() : Role.RECEIVER;
IMessageStructure msgStructure = msgTypeToMsgStruct.get(messageType, role);
+ int declaredLimit = in.limit();
+ int limit = 0;
+ if (length != 0) {
+ declaredLimit = startPosition + length;
+ if (hasDataPacketHeader()) {
+ declaredLimit += MESSAGE_LENGTH_SIZE;
+ }
+ limit = in.limit();
+ in.limit(declaredLimit);
+ }
if (msgStructure == null) {
String errStr = "Unknown messageType = [" + messageType + ']';
addErrorMessage(in, startPosition, out, errStr);
-
- in.position(length == 0 ? in.limit() : startPosition + length); // consider that all left bytes are incorrect
+ in.position(declaredLimit); // consider that all left bytes are incorrect
return;
}
@@ -297,6 +306,7 @@ public class SOUPCodec extends AbstractCodec {
in.position(), in.remaining());
+
try {
IMessageStructureVisitor msgStructVisitor = new SOUPVisitorDecode(in, byteOrder, message, msgFactory);
MessageStructureWriter.WRITER.traverse(msgStructVisitor, msgStructure);
@@ -306,11 +316,24 @@ public class SOUPCodec extends AbstractCodec {
// because we won't be able to decode the rest of the payload anyway
// so we skip the whole payload to be able to continue the decoding
// the limit was set in SOUPCodec#readHeader method
- int lengthRaw = length == 0 ? in.limit() - startPosition : length;
+
+ int lengthRaw = declaredLimit - startPosition;
message.getMetaData().setRawMessage(rawData(in, startPosition, lengthRaw));
- in.position(in.limit());
+ in.position(declaredLimit);
}
+ if (length != 0) {
+ if (in.hasRemaining()) {
+ int realLength = in.position() - startPosition;
+ if (hasDataPacketHeader()) {
+ realLength -= MESSAGE_LENGTH_SIZE;
+ }
+ message.getMetaData().setRejectReason(
+ String.format("Declared message length {%s} isn't equal to real one {%s}", length, realLength));
+ in.position(declaredLimit);
+ }
+ in.limit(limit);
+ }
if (hasSoupMessageHeader(msgStructure) && header != null
&& SEQUENCED_DATA_PACKET.equalsIgnoreCase(header.getName())) {
message.addField(SEQUENCED_HEADER_MESSAGE, header);

diff:
/******************************************************************************
- * Copyright 2009-2021 Exactpro (Exactpro Systems Limited)
+ * Copyright 2009-2022 Exactpro (Exactpro Systems Limited)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -274,4 +274,162 @@ public class TestSOUPCodec extends AbstractTest {
// no more messages
Assert.assertTrue(decoderOutput.getMessageQueue().isEmpty());
}
+
+ @Test
+ public void testDecodeMessageBatch() throws Exception {
+ byte[] raw = {
+ // Packet Header:
+ 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42, // Session = 'AAAAAAAAAB'
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // Sequence = 1
+ 0x00, 0x02, // Count = 2
+ // Data Message Header:
+ 0x00, 0x14, // Length: 20
+ 0x44, // MessageType = 'D' - OrderDeleted
+ // Message Payload
+ 0x00, 0x00, 0x00, 0x03, // Timestamp = 3
+ 0x00, 0x04, // TradeDate = 4
+ 0x00, 0x00, 0x00, 0x05, // TradeableInstrumentId = 5
+ 0x53, // Side = 'S' = Sell
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, // OrderID = 6
+ // Data Message Header:
+ 0x00, 0x14, // Length: 20
+ 0x44, // MessageType = 'D' - OrderDeleted
+ // Message Payload
+ 0x00, 0x00, 0x00, 0x03, // Timestamp = 3
+ 0x00, 0x04, // TradeDate = 4
+ 0x00, 0x00, 0x00, 0x05, // TradeableInstrumentId = 5
+ 0x53, // Side = 'S' = Sell
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06 // OrderID = 6
+
+ };
+ SOUPCodec codec = (SOUPCodec)messageHelper.getCodec(serviceContext);
+
+ // Decode
+ IoBuffer toDecode = IoBuffer.wrap(raw);
+ IoSession decodeSession = new DummySession();
+
+ MockProtocolDecoderOutput decoderOutput = new MockProtocolDecoderOutput();
+ codec.decode(decodeSession, toDecode, decoderOutput);
+ // PacketHeader
+ IMessage actual = (IMessage) decoderOutput.getMessageQueue().poll();
+ Assert.assertEquals((Integer)2, actual.getField("PHCount"));
+
+ // OrderDeleted
+ actual = (IMessage) decoderOutput.getMessageQueue().poll();
+
+ Assert.assertFalse(decoderOutput.getMessageQueue().isEmpty());
+
+ IMessage secondMsg = (IMessage)decoderOutput.getMessageQueue().poll();
+ Assert.assertNotEquals(actual.getField("MessageSequenceNumber"), secondMsg.getField("MessageSequenceNumber"));
+ }
+ @Test
+ public void testDecodeMessageBatchWithLengthLack() throws Exception {
+ byte[] raw = {
+ // Packet Header:
+ 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42, // Session = 'AAAAAAAAAB'
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // Sequence = 1
+ 0x00, 0x02, // Count = 2
+ // Data Message Header:
+ 0x00, 0x15, // Length: 21
+ 0x44, // MessageType = 'D' - OrderDeleted
+ // Message Payload
+ 0x00, 0x00, 0x00, 0x03, // Timestamp = 3
+ 0x00, 0x04, // TradeDate = 4
+ 0x00, 0x00, 0x00, 0x05, // TradeableInstrumentId = 5
+ 0x53, // Side = 'S' = Sell
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, // OrderID = 6
+ 0x00,
+ // Data Message Header:
+ 0x00, 0x14, // Length: 20
+ 0x44, // MessageType = 'D' - OrderDeleted
+ // Message Payload
+ 0x00, 0x00, 0x00, 0x03, // Timestamp = 3
+ 0x00, 0x04, // TradeDate = 4
+ 0x00, 0x00, 0x00, 0x05, // TradeableInstrumentId = 5
+ 0x53, // Side = 'S' = Sell
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06 // OrderID = 6
+
+ };
+ SOUPCodec codec = (SOUPCodec)messageHelper.getCodec(serviceContext);
+
+ // Decode
+ IoBuffer toDecode = IoBuffer.wrap(raw);
+ IoSession decodeSession = new DummySession();
+
+ MockProtocolDecoderOutput decoderOutput = new MockProtocolDecoderOutput();
+ codec.decode(decodeSession, toDecode, decoderOutput);
+ // PacketHeader
+ IMessage actual = (IMessage) decoderOutput.getMessageQueue().poll();
+ Assert.assertEquals((Integer)2, actual.getField("PHCount"));
+
+ // OrderDeleted
+ actual = (IMessage) decoderOutput.getMessageQueue().poll();
+ Assert.assertEquals("Declared message length {21} isn't equal to real one {20}", actual.getMetaData().getRejectReason());
+
+ Assert.assertFalse(decoderOutput.getMessageQueue().isEmpty());
+
+ IMessage secondMsg = (IMessage)decoderOutput.getMessageQueue().poll();
+ Assert.assertEquals("OrderDeleted", secondMsg.getName());
+ Assert.assertEquals("AAAAAAAAAB", secondMsg.getField("PHSession"));
+ Assert.assertEquals((Long)1L, secondMsg.getField("PHSequence"));
+ Assert.assertEquals((Integer)2, secondMsg.getField("PHCount"));
+
+ Assert.assertEquals("D", secondMsg.getField(MESSAGE_TYPE_FIELD));
+ Assert.assertEquals((Long)3L, secondMsg.getField("Timestamp"));
+ Assert.assertEquals((Integer)4, secondMsg.getField("TradeDate"));
+ Assert.assertEquals((Long)5L, secondMsg.getField("TradeableInstrumentId"));
+ Assert.assertEquals("S", secondMsg.getField("Side"));
+ Assert.assertEquals((Long)6L, secondMsg.getField("OrderId"));
+ Assert.assertNotEquals(actual.getField("MessageSequenceNumber"), secondMsg.getField("MessageSequenceNumber"));
+ }
+
+ @Test
+ public void testDecodeMessageBatchWithOverLength() throws Exception {
+ byte[] raw = {
+ // Packet Header:
+ 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42, // Session = 'AAAAAAAAAB'
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // Sequence = 1
+ 0x00, 0x02, // Count = 2
+ // Data Message Header:
+ 0x00, 0x13, // Length: 19
+ 0x44, // MessageType = 'D' - OrderDeleted
+ // Message Payload
+ 0x00, 0x00, 0x00, 0x03, // Timestamp = 3
+ 0x00, 0x04, // TradeDate = 4
+ 0x00, 0x00, 0x00, 0x05, // TradeableInstrumentId = 5
+ 0x53, // Side = 'S' = Sell
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, // OrderID = 6
+ // Data Message Header:
+ 0x00, 0x14, // Length: 20
+ 0x44, // MessageType = 'D' - OrderDeleted
+ // Message Payload
+ 0x00, 0x00, 0x00, 0x03, // Timestamp = 3
+ 0x00, 0x04, // TradeDate = 4
+ 0x00, 0x00, 0x00, 0x05, // TradeableInstrumentId = 5
+ 0x53, // Side = 'S' = Sell
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06 // OrderID = 6
+
+ };
+ SOUPCodec codec = (SOUPCodec)messageHelper.getCodec(serviceContext);
+
+ // Decode
+ IoBuffer toDecode = IoBuffer.wrap(raw);
+ IoSession decodeSession = new DummySession();
+
+ MockProtocolDecoderOutput decoderOutput = new MockProtocolDecoderOutput();
+ codec.decode(decodeSession, toDecode, decoderOutput);
+ // PacketHeader
+ IMessage actual = (IMessage) decoderOutput.getMessageQueue().poll();
+ Assert.assertEquals((Integer)2, actual.getField("PHCount"));
+
+ // OrderDeleted
+ actual = (IMessage) decoderOutput.getMessageQueue().poll();
+ Assert.assertEquals(". in field name = [OrderId]. in MessageStructure Name = [OrderDeleted]: BufferUnderflowException: ", actual.getMetaData().getRejectReason());
+ Assert.assertFalse(decoderOutput.getMessageQueue().isEmpty());
+ IMessage secondMsg = (IMessage)decoderOutput.getMessageQueue().poll();
+
+ Assert.assertEquals("D", secondMsg.getField(MESSAGE_TYPE_FIELD));
+ Assert.assertEquals((Long)3L, secondMsg.getField("Timestamp"));
+ Assert.assertEquals((Integer)4, secondMsg.getField("TradeDate"));
+ }
}

original_message:
[TH2] #2701. Add declared message length check for mold-udp protocol

Change-Id: I0a4ef3d8b808cac726dda5fad7ff775e5839ddff


message: [RM]Feature Change the logging level for MLWorker predictions from INFO to DEBUG

change_type: MODIFY

diff:
@@ -145,7 +145,7 @@ public class MLWorker {
}
private Stream<PredictionResultEntry> getPredictionResultEntries(Integer actionId, Map<?, ?> map) {
- logger.info("mlplugin returns prediction {}", map);
+ logger.debug("mlplugin returns prediction {}", map);
return map.entrySet().stream().map(entry -> convertOldFormatPrediction(actionId, entry));
}

license: Apache License 2.0

repo: exactpro/sailfish-core

original_message:
[RM]Feature #80873. Change the logging level for MLWorker predictions from INFO to DEBUG

Change-Id: I7fa8e385a18dd302ab4e7cf769bfdd79c8301189


message:
Feature: integration with webimpress/safe-writer
Use external library to write files

diff:
"require": {
"php": "7.4.*",
"zendframework/zend-code": "^3.4.0",
- "ocramius/package-versions": "^1.5.1"
+ "ocramius/package-versions": "^1.5.1",
+ "webimpress/safe-writer": "^1.0"
},
"conflict": {
"zendframework/zend-stdlib": "<3.2.1",

diff:
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace ProxyManager\Exception;
use UnexpectedValueException;
+use Webimpress\SafeWriter\Exception\ExceptionInterface as FileWriterException;
use function sprintf;
/**
@@ -30,4 +31,9 @@ class FileNotWritableException extends UnexpectedValueException implements Excep
$directory
));
}
+
+ public static function exception(FileWriterException $e) : self
+ {
+ return new self($e->getMessage(), $e->getCode(), $e);
+ }
}

diff:
@@ -7,17 +7,11 @@ namespace ProxyManager\GeneratorStrategy;
use Closure;
use ProxyManager\Exception\FileNotWritableException;
use ProxyManager\FileLocator\FileLocatorInterface;
+use Webimpress\SafeWriter\Exception\ExceptionInterface as FileWriterException;
+use Webimpress\SafeWriter\FileWriter;
use Zend\Code\Generator\ClassGenerator;
-use function chmod;
-use function dirname;
-use function file_put_contents;
-use function rename;
use function restore_error_handler;
use function set_error_handler;
-use function tempnam;
-use function trim;
-use function umask;
-use function unlink;
/**
* Generator strategy that writes the generated classes to disk while generating them
@@ -53,36 +47,13 @@ class FileWriterGeneratorStrategy implements GeneratorStrategyInterface
set_error_handler($this->emptyErrorHandler);
try {
- $this->writeFile("<?php\n\n" . $generatedCode, $fileName);
+ FileWriter::writeFile($fileName, "<?php\n\n" . $generatedCode);
return $generatedCode;
+ } catch (FileWriterException $e) {
+ throw FileNotWritableException::exception($e);
} finally {
restore_error_handler();
}
}
-
- /**
- * Writes the source file in such a way that race conditions are avoided when the same file is written
- * multiple times in a short time period
- *
- * @throws FileNotWritableException
- */
- private function writeFile(string $source, string $location) : void
- {
- $directory = dirname($location);
- $tmpFileName = tempnam($directory, 'temporaryProxyManagerFile');
-
- if ($tmpFileName === false) {
- throw FileNotWritableException::fromNotWritableDirectory($directory);
- }
-
- file_put_contents($tmpFileName, $source);
- chmod($tmpFileName, 0666 & ~umask());
-
- if (! rename($tmpFileName, $location)) {
- unlink($tmpFileName);
-
- throw FileNotWritableException::fromInvalidMoveOperation($tmpFileName, $location);
- }
- }
}

license: MIT License

repo: friendsofphp/proxy-manager-lts


message: Update to webimpress/safe-writer 2.0

diff:
"php": "7.4.*",
"zendframework/zend-code": "^3.4.0",
"ocramius/package-versions": "^1.5.1",
- "webimpress/safe-writer": "^1.0"
+ "webimpress/safe-writer": "^2.0"
},
"conflict": {
"zendframework/zend-stdlib": "<3.2.1",

original_message: Update to webimpress/safe-writer 2.0


message: Update EvaluatingGeneratorStrategy to use SafeWriter

diff:
@@ -4,14 +4,11 @@ declare(strict_types=1);
namespace ProxyManager\GeneratorStrategy;
+use Webimpress\SafeWriter\FileWriter;
use Zend\Code\Generator\ClassGenerator;
-use function assert;
-use function file_put_contents;
use function ini_get;
-use function is_string;
-use function sys_get_temp_dir;
-use function tempnam;
use function unlink;
+use const DIRECTORY_SEPARATOR;
/**
* Generator strategy that produces the code and evaluates it at runtime
@@ -43,11 +40,9 @@ class EvaluatingGeneratorStrategy implements GeneratorStrategyInterface
// @codeCoverageIgnoreStart
if (! $this->canEval) {
- $fileName = tempnam(sys_get_temp_dir(), 'EvaluatingGeneratorStrategy.php.tmp.');
+ $fileName = __DIR__ . DIRECTORY_SEPARATOR . 'EvaluatingGeneratorStrategy.php.tmp';
+ FileWriter::writeFile($fileName, "<?php\n" . $code);
- assert(is_string($fileName));
-
- file_put_contents($fileName, "<?php\n" . $code);
/* @noinspection PhpIncludeInspection */
require $fileName;
unlink($fileName);


message: Adds type casting before getCode

diff:
@@ -34,6 +34,6 @@ class FileNotWritableException extends UnexpectedValueException implements Excep
public static function exception(FileWriterException $e) : self
{
- return new self($e->getMessage(), $e->getCode(), $e);
+ return new self($e->getMessage(), (int) $e->getCode(), $e);
}
}

original_message: Adds type casting before getCode


message: Mark unused methods in FileNotWritableException as deprecated

diff:
@@ -13,6 +13,9 @@ use function sprintf;
*/
class FileNotWritableException extends UnexpectedValueException implements ExceptionInterface
{
+ /**
+ * @deprecated
+ */
public static function fromInvalidMoveOperation(string $fromPath, string $toPath) : self
{
return new self(sprintf(
@@ -23,6 +26,9 @@ class FileNotWritableException extends UnexpectedValueException implements Excep
));
}
+ /**
+ * @deprecated
+ */
public static function fromNotWritableDirectory(string $directory) : self
{
return new self(sprintf(


message:
Rename FileNotWritableException::exception to FileNotWritableException::fromPrevious
Changed parameter name, use default code 0 instead of code of the
previous exception.

diff:
@@ -38,8 +38,8 @@ class FileNotWritableException extends UnexpectedValueException implements Excep
));
}
- public static function exception(FileWriterException $e) : self
+ public static function fromPrevious(FileWriterException $previous) : self
{
- return new self($e->getMessage(), (int) $e->getCode(), $e);
+ return new self($previous->getMessage(), 0, $previous);
}
}

diff:
@@ -51,7 +51,7 @@ class FileWriterGeneratorStrategy implements GeneratorStrategyInterface
return $generatedCode;
} catch (FileWriterException $e) {
- throw FileNotWritableException::exception($e);
+ throw FileNotWritableException::fromPrevious($e);
} finally {
restore_error_handler();
}


message: Replace DIRECTORY_SEPARATOR with /

diff:
@@ -8,7 +8,6 @@ use Webimpress\SafeWriter\FileWriter;
use Zend\Code\Generator\ClassGenerator;
use function ini_get;
use function unlink;
-use const DIRECTORY_SEPARATOR;
/**
* Generator strategy that produces the code and evaluates it at runtime
@@ -40,7 +39,7 @@ class EvaluatingGeneratorStrategy implements GeneratorStrategyInterface
// @codeCoverageIgnoreStart
if (! $this->canEval) {
- $fileName = __DIR__ . DIRECTORY_SEPARATOR . 'EvaluatingGeneratorStrategy.php.tmp';
+ $fileName = __DIR__ . '/EvaluatingGeneratorStrategy.php.tmp';
FileWriter::writeFile($fileName, "<?php\n" . $code);
/* @noinspection PhpIncludeInspection */

original_message: Replace DIRECTORY_SEPARATOR with /


message: Remove tests for deprecated method and added test for new method in FileNotWritableExceptionTest

diff:
@@ -6,6 +6,7 @@ namespace ProxyManagerTest\Exception;
use PHPUnit\Framework\TestCase;
use ProxyManager\Exception\FileNotWritableException;
+use Webimpress\SafeWriter\Exception\ExceptionInterface as FileWriterException;
/**
* Tests for {@see \ProxyManager\Exception\FileNotWritableException}
@@ -15,25 +16,16 @@ use ProxyManager\Exception\FileNotWritableException;
*/
final class FileNotWritableExceptionTest extends TestCase
{
- public function testFromInvalidMoveOperation() : void
+ public function testFromPrevious() : void
{
- $exception = FileNotWritableException::fromInvalidMoveOperation('/tmp/a', '/tmp/b');
+ $previous = $this->getMockBuilder(FileWriterException::class)
+ ->enableOriginalConstructor()
+ ->setConstructorArgs(['Previous exception message'])
+ ->getMock();
- self::assertSame(
- 'Could not move file "/tmp/a" to location "/tmp/b": either the source file is not readable,'
- . ' or the destination is not writable',
- $exception->getMessage()
- );
- }
-
- public function testFromNotWritableDirectory() : void
- {
- $exception = FileNotWritableException::fromNotWritableDirectory('/tmp/a');
+ $exception = FileNotWritableException::fromPrevious($previous);
- self::assertSame(
- 'Could not create temp file in directory "/tmp/a" '
- . 'either the directory does not exist, or it is not writable',
- $exception->getMessage()
- );
+ self::assertSame('Previous exception message', $exception->getMessage());
+ self::assertSame($previous, $exception->getPrevious());
}
}


message:
Drop usageof fluent interface on mocking
Psalm is not failing now !

diff:
@@ -18,14 +18,14 @@ final class FileNotWritableExceptionTest extends TestCase
{
public function testFromPrevious() : void
{
- $previous = $this->getMockBuilder(FileWriterException::class)
- ->enableOriginalConstructor()
- ->setConstructorArgs(['Previous exception message'])
- ->getMock();
+ $previousExceptionMock = $this->getMockBuilder(FileWriterException::class);
+ $previousExceptionMock->enableOriginalConstructor();
+ $previousExceptionMock->setConstructorArgs(['Previous exception message']);
+ $previousException = $previousExceptionMock->getMock();
- $exception = FileNotWritableException::fromPrevious($previous);
+ $exception = FileNotWritableException::fromPrevious($previousException);
self::assertSame('Previous exception message', $exception->getMessage());
- self::assertSame($previous, $exception->getPrevious());
+ self::assertSame($previousException, $exception->getPrevious());
}
}


message:
Remove redundant error handler in generate
Relates to webimpress/safe-writer#49
The problem is with testing in ocramius/proxy-manager, not in safe-writer
itself.

diff:
@@ -4,16 +4,12 @@ declare(strict_types=1);
namespace ProxyManager\GeneratorStrategy;
-use Closure;
use Laminas\Code\Generator\ClassGenerator;
use ProxyManager\Exception\FileNotWritableException;
use ProxyManager\FileLocator\FileLocatorInterface;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
-use function restore_error_handler;
-use function set_error_handler;
-
/**
* Generator strategy that writes the generated classes to disk while generating them
*
@@ -27,11 +23,6 @@ class FileWriterGeneratorStrategy implements GeneratorStrategyInterface
public function __construct(FileLocatorInterface $fileLocator)
{
$this->fileLocator = $fileLocator;
- $this->emptyErrorHandler = static function (int $type, string $message, string $file, int $line) {
- if (error_reporting() & $type) {
- throw new \ErrorException($message, 0, $type, $file, $line);
- }
- };
}
/**
@@ -47,16 +38,12 @@ class FileWriterGeneratorStrategy implements GeneratorStrategyInterface
$className = (string) $classGenerator->getNamespaceName() . '\\' . $classGenerator->getName();
$fileName = $this->fileLocator->getProxyFileName($className);
- set_error_handler($this->emptyErrorHandler);
-
try {
(new Filesystem())->dumpFile($fileName, "<?php\n\n" . $generatedCode);
return $generatedCode;
} catch (IOException $e) {
throw FileNotWritableException::fromPrevious($e);
- } finally {
- restore_error_handler();
}
}
}

diff:
@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace ProxyManagerTest\GeneratorStrategy;
-use Closure;
-use ErrorException;
use Laminas\Code\Generator\ClassGenerator;
use PHPUnit\Framework\TestCase;
use ProxyManager\Exception\FileNotWritableException;
@@ -18,10 +16,8 @@ use function clearstatcache;
use function decoct;
use function fileperms;
use function mkdir;
-use function restore_error_handler;
use function rmdir;
use function scandir;
-use function set_error_handler;
use function strpos;
use function sys_get_temp_dir;
use function tempnam;
@@ -42,31 +38,15 @@ use const SCANDIR_SORT_ASCENDING;
final class FileWriterGeneratorStrategyTest extends TestCase
{
private $tempDir;
- private $originalErrorHandler;
protected function setUp(): void
{
parent::setUp();
$this->tempDir = tempnam(sys_get_temp_dir(), 'FileWriterGeneratorStrategyTest');
- $this->originalErrorHandler = static function (): bool {
- throw new ErrorException();
- };
unlink($this->tempDir);
mkdir($this->tempDir);
- set_error_handler($this->originalErrorHandler);
- }
-
- protected function tearDown(): void
- {
- self::assertSame($this->originalErrorHandler, set_error_handler(static function (): bool {
- return true;
- }));
- restore_error_handler();
- restore_error_handler();
-
- parent::tearDown();
}
public function testGenerate(): void


message:
Add ServiceClient tests for shared channels
Adds tests for support added in that allows `ServiceClient` instances to share a single `Channel`.

diff:
@@ -33,20 +33,19 @@ extension Echo_EchoResponse {
class BasicEchoTestCase: XCTestCase {
func makeProvider() -> Echo_EchoProvider { return EchoProvider() }
- var defaultTimeout: TimeInterval { return 1.0 }
-
var provider: Echo_EchoProvider!
var server: Echo_EchoServer!
var client: Echo_EchoServiceClient!
+ var defaultTimeout: TimeInterval { return 1.0 }
var secure: Bool { return false }
+ var address: String { return "localhost:5050" }
override func setUp() {
super.setUp()
provider = makeProvider()
- let address = "localhost:5050"
if secure {
let certificateString = String(data: certificateForTests, encoding: .utf8)!
server = Echo_EchoServer(address: address,

change_type: ADD

diff:
+/*
+ * Copyright 2018, gRPC Authors 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
+ *
+ * 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.
+ */
+import Foundation
+@testable import SwiftGRPC
+import XCTest
+
+class ServiceClientTests: BasicEchoTestCase {
+ private lazy var sharedChannel = Channel(address: address, secure: false)
+
+ override func setUp() {
+ super.setUp()
+ sharedChannel = Channel(address: address, secure: false)
+ }
+
+ func testSharingChannelBetweenClientsUnaryAsync() {
+ let firstCallExpectation = expectation(description: "First call completes successfully")
+ let secondCallExpectation = expectation(description: "Second call completes successfully")
+
+ do {
+ let client1 = Echo_EchoServiceClient(channel: sharedChannel)
+ try _ = client1.get(Echo_EchoRequest(text: "foo")) { _, callResult in
+ XCTAssertEqual(.ok, callResult.statusCode)
+ firstCallExpectation.fulfill()
+ }
+
+ let client2 = Echo_EchoServiceClient(channel: sharedChannel)
+ try _ = client2.get(Echo_EchoRequest(text: "foo")) { _, callResult in
+ XCTAssertEqual(.ok, callResult.statusCode)
+ secondCallExpectation.fulfill()
+ }
+ } catch let error {
+ XCTFail(error.localizedDescription)
+ }
+
+ waitForExpectations(timeout: defaultTimeout)
+ }
+
+ func testSharedChannelStillWorksAfterFirstUnaryClientCompletes() {
+ do {
+ let client1 = Echo_EchoServiceClient(channel: sharedChannel)
+ let response1 = try client1.get(Echo_EchoRequest(text: "foo")).text
+ XCTAssertEqual("Swift echo get: foo", response1)
+ } catch let error {
+ XCTFail(error.localizedDescription)
+ }
+
+ do {
+ let client2 = Echo_EchoServiceClient(channel: sharedChannel)
+ let response2 = try client2.get(Echo_EchoRequest(text: "foo")).text
+ XCTAssertEqual("Swift echo get: foo", response2)
+ } catch let error {
+ XCTFail(error.localizedDescription)
+ }
+ }
+}

repo: grpc/grpc-swift

original_message:
Add ServiceClient tests for shared channels

Adds tests for support added in https://github.com/grpc/grpc-swift/pull/202 that allows `ServiceClient` instances to share a single `Channel`.


message:
Update podspec to v0.4.1 for core v1.11.0
Google has released v1.11.0 of gRPC core. We can now move off of the pre-release and bump the podspec version.

diff:
Pod::Spec.new do |s|
s.name = 'SwiftGRPC'
- s.version = '0.4.0'
+ s.version = '0.4.1'
s.license = 'New BSD'
s.summary = 'Swift gRPC code generator plugin and runtime library'
s.homepage = 'http://www.grpc.io'
@@ -45,7 +45,7 @@ Pod::Spec.new do |s|
s.source_files = 'Sources/SwiftGRPC/*.swift', 'Sources/SwiftGRPC/**/*.swift', 'Sources/CgRPC/shim/*.[ch]'
s.public_header_files = 'Sources/CgRPC/shim/cgrpc.h'
- s.dependency 'gRPC-Core', '~> 1.11.0-pre2'
+ s.dependency 'gRPC-Core', '~> 1.11.0'
s.dependency 'BoringSSL', '~> 10.0'
s.dependency 'SwiftProtobuf', '~> 1.0.3'
end


message: Update README swift-protobuf version

diff:
@@ -101,12 +101,12 @@ For an example of this in Swift, please see the
## Having build problems?
-grpc-swift depends on Swift, Xcode, and swift-proto. We are currently
+grpc-swift depends on Swift, Xcode, and swift-protobuf. We are currently
testing with the following versions:
- Xcode 9.1
- Swift 4.0-dev
-- swift-protobuf 1.0.2
+- swift-protobuf 1.0.3
## License

original_message: Update README swift-protobuf version


message: Add CocoaPods info to README

diff:
@@ -27,13 +27,35 @@ Swift Package Manager builds may also be made on Linux
systems. Please see [DOCKER.md](DOCKER.md) and
[LINUX.md](LINUX.md) for details.
-## gRPC dependencies are vendored
+## CocoaPods integration
-Swift gRPC includes vendored copies of the gRPC core
-library and **BoringSSL**, an OpenSSL fork that is used by
-the gRPC Core. These are built automatically in Swift Package
+Swift gRPC is currently available [from CocoaPods](https://cocoapods.org/pods/SwiftGRPC).
+To integrate, add the following line to your `Podfile`:
+
+ pod 'SwiftGRPC'
+
+Then, run `pod install` from command line and use your project's generated
+`.xcworkspace` file.
+
+## Manual integration
+
+When not using CocoaPods, Swift gRPC includes **vendored copies** of the
+gRPC Core library and BoringSSL (an OpenSSL fork that is used by
+the gRPC Core). These are built automatically in Swift Package
Manager builds.
+After [building your project](#building-your-project), add the generated
+`SwiftGRPC.xcodeproj` to your project, and add build dependencies
+on **BoringSSL**, **CgRPC**, **gRPC**, and **CzLib**.
+
+Please also note that your project will need to include the
+`SwiftProtobuf.xcodeproj` from
+[Swift Protobuf](https://github.com/apple/swift-protobuf) and
+the source files that you generate with `protoc`/[plugins](#getting-the-plugins).
+
+See [Echo](Examples/Echo) for a working Xcode-based
+example, and don't hesitate to file issues if you find any problems.
+
## Usage
The recommended way to use Swift gRPC is to first define an API using the
@@ -44,7 +66,7 @@ and the [Swift Protobuf](https://github.com/apple/swift-protobuf)
and [Swift gRPC](https://github.com/grpc/grpc-swift) plugins to
generate the necessary support code.
-### Getting the dependencies
+### Getting the plugins
Binary releases of `protoc`, the Protocol Buffer Compiler, are
available on [GitHub](https://github.com/google/protobuf/releases).
@@ -78,16 +100,8 @@ generate an Xcode project for the SwiftGRPC package:
$ make
This will create `SwiftGRPC.xcodeproj`, which you should
-add to your project, along with setting build dependencies
-on **BoringSSL**, **CgRPC**, **gRPC**, and **CzLib**.
-
-Please also note that your project will need to include the
-**SwiftProtobuf.xcodeproj** from
-[Swift Protobuf](https://github.com/apple/swift-protobuf) and
-the source files that you generated with `protoc` and the plugins.
-
-Please see [Echo](Examples/Echo) for a working Xcode-based
-example and file issues if you find any problems.
+add to your project, along with setting the necessary build dependencies
+mentioned [above](#manual-integration).
### Low-level gRPC

original_message: Add CocoaPods info to README


diff:
@@ -46,7 +46,7 @@ Manager builds.
After [building your project](#building-your-project), add the generated
`SwiftGRPC.xcodeproj` to your project, and add build dependencies
-on **BoringSSL**, **CgRPC**, **gRPC**, and **CzLib**.
+on **BoringSSL**, **CgRPC**, and **SwiftGRPC**.
Please also note that your project will need to include the
`SwiftProtobuf.xcodeproj` from
@@ -119,7 +119,7 @@ grpc-swift depends on Swift, Xcode, and swift-protobuf. We are currently
testing with the following versions:
- Xcode 9.1
-- Swift 4.0-dev
+- Swift 4.0
- swift-protobuf 1.0.3
## License

original_message:
Update dependency list in README

Based on comments from https://github.com/grpc/grpc-swift/pull/214


message:
Add server/client codegen options to protoc
Implements support for specifying additional options for `protoc` gRPC codegen to exclude client and/or server code.
Usage:
```
protoc TestEvent.proto --swiftgrpc_out=Client=true,Server=false:.
```

diff:
@@ -31,57 +31,61 @@ enum GenerationError: Error {
}
}
-class GeneratorOptions {
+final class GeneratorOptions {
enum Visibility: String {
- case Internal
- case Public
+ case `internal` = "Internal"
+ case `public` = "Public"
var sourceSnippet: String {
switch self {
- case .Internal:
+ case .internal:
return "internal"
- case .Public:
+ case .public:
return "public"
}
}
}
- let visibility: Visibility
- let generateTestStubs: Bool
- let generateClient: Bool
- let generateServer: Bool
+ private(set) var visibility = Visibility.internal
+ private(set) var generateServer = true
+ private(set) var generateClient = true
+ private(set) var generateTestStubs = false
init(parameter: String?) throws {
- var visibility: Visibility = .Internal
-
- var generateTestStubs = false
-
for pair in GeneratorOptions.parseParameter(string: parameter) {
switch pair.key {
case "Visibility":
if let value = Visibility(rawValue: pair.value) {
visibility = value
} else {
- throw GenerationError.invalidParameterValue(name: pair.key,
- value: pair.value)
+ throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
+ }
+
+ case "Server":
+ if let value = Bool(pair.value) {
+ generateServer = value
+ } else {
+ throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
}
+
+ case "Client":
+ if let value = Bool(pair.value) {
+ generateClient = value
+ } else {
+ throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
+ }
+
case "TestStubs":
- switch pair.value {
- case "true": generateTestStubs = true
- case "false": generateTestStubs = false
- default: throw GenerationError.invalidParameterValue(name: pair.key,
- value: pair.value)
+ if let value = Bool(pair.value) {
+ generateTestStubs = value
+ } else {
+ throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
}
default:
throw GenerationError.unknownParameter(name: pair.key)
}
}
-
- self.visibility = visibility
- self.generateTestStubs = generateTestStubs
- self.generateClient = true
- self.generateServer = true
}
static func parseParameter(string: String?) -> [(key: String, value: String)] {


message: Add docs for protoc parameter usage

diff:
@@ -88,6 +88,21 @@ search path. Invoke them with commands like the following:
By convention the `--swift_out` option invokes the `protoc-gen-swift`
plugin and `--swiftgrpc_out` invokes `protoc-gen-swiftgrpc`.
+#### Parameters
+To pass extra parameters to the plugin, use a comma-separated parameter list
+separated from the output directory by a colon.
+
+| Flag | Values | Default | Description |
+|:-|:-|:-|:-|
+| `Visibility` | `Internal`/`Public` | `Internal` | ACL of generated code |
+| `Server` | `true`/`false` | `true` | Whether to generate server code |
+| `Client` | `true`/`false` | `true` | Whether to generate client code |
+| `TestStubs` | `true`/`false` | `false` | Whether to generate test stub code |
+
+Example:
+
+ $ protoc <your proto> --swiftgrpc_out=Client=true,Server=false:.
+
### Building your project
Most `grpc-swift` development is done with the Swift Package Manager.

original_message: Add docs for protoc parameter usage


message:
Add "Releasing" section for CocoaPods to the ReadMe
From the discussion [here](https://github.com/grpc/grpc-swift/issues/279#issuecomment-412572907).

diff:
@@ -146,3 +146,14 @@ grpc-swift is released under the same license as
## Contributing
Please get involved! See our [guidelines for contributing](CONTRIBUTING.md).
+
+### Releasing
+
+Prior to creating a new release tag for SwiftGRPC, the `.podspec` file's version should be bumped, and the
+CocoaPods spec linter should be run to ensure that there are no new warnings/errors:
+
+ $ pod spec lint SwiftGRPC.podspec
+
+Once a new release tag is created, the updated CocoaPods spec should also be pushed to the master specs repo:
+
+ $ pod trunk push SwiftGRPC.podspec


message:
Bump podspec to 0.5.1
We should issue a new release which includes (the fix for https://github.com/grpc/grpc-swift/issues/288).

diff:
Pod::Spec.new do |s|
s.name = 'SwiftGRPC'
- s.version = '0.5.0'
+ s.version = '0.5.1'
s.license = { :type => 'Apache License, Version 2.0',
:text => <<-LICENSE
Copyright 2018, gRPC Authors. All rights reserved.

original_message:
Bump podspec to 0.5.1

We should issue a new release which includes https://github.com/grpc/grpc-swift/pull/289 (the fix for https://github.com/grpc/grpc-swift/issues/288).


diff:
@@ -203,7 +203,7 @@ void cgrpc_server_start(cgrpc_server *s);
cgrpc_completion_queue *cgrpc_server_get_completion_queue(cgrpc_server *s);
// completion queues
-cgrpc_completion_queue *cgrpc_completion_queue_create_for_next();
+cgrpc_completion_queue *cgrpc_completion_queue_create_for_next(void);
grpc_event cgrpc_completion_queue_get_next_event(cgrpc_completion_queue *cq,
double timeout);
void cgrpc_completion_queue_drain(cgrpc_completion_queue *cq);

diff:
#include <stdio.h>
-grpc_completion_queue *cgrpc_completion_queue_create_for_next() {
+grpc_completion_queue *cgrpc_completion_queue_create_for_next(void) {
return grpc_completion_queue_create_for_next(NULL);
}

original_message:
Fix -Wstrict-prototypes warning

Resolves https://github.com/grpc/grpc-swift/issues/294.


message:
Remove Linux RUNME.sh script
This is outdated and doesn't seem necessary to properly set up gRPC in a Linux environment. I also updated the LINUX.md readme to reflect this change, and tested the new flow on a Linux machine.

diff:
@@ -38,6 +38,7 @@ Start a docker instance with the following command:
ln -s swift-3.0.2-RELEASE-ubuntu16.04 swift
## Add Swift to your path
+
# add swift to your path by adding this to your .bashrc
export PATH=/root/swift/usr/bin:$PATH
@@ -50,21 +51,23 @@ Start a docker instance with the following command:
git config --global user.name "<your name>"
## Fetch and build grpc
+
git clone https://github.com/grpc/grpc-swift
- cd grpc-swift
- cd third_party
- sh RUNME.sh
+ cd grpc-swift/third_party
+ git clone https://github.com/grpc/grpc.git
cd grpc
git submodule update --init
make
make install
## Build the Echo sample
+
cd
cd grpc-swift/Examples/Echo/PackageManager
make
## Run the test client and server
+
# start the server
.build/debug/Echo serve &
# run the client to test each Echo API
@@ -73,8 +76,8 @@ Start a docker instance with the following command:
.build/debug/Echo collect
.build/debug/Echo update
-
## To test the plugin
+
# build and install protoc
cd
cd grpc-swift/third_party/grpc/third_party/protobuf
@@ -83,4 +86,3 @@ Start a docker instance with the following command:
cd
cd grpc-swift/Plugin
make test
-

change_type: DELETE

diff:
-#!/bin/sh
-#
-# Copyright 2017, gRPC Authors 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
-#
-# 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.
-#
-printf "\033c"
-read -p "Would you like to download swift-protobuf / grpc [y/N]" CONDITION;
-if [ "$CONDITION" == "y" ] ; then
- read -p "Use last known stable swift-protobuf - 1.0.2 ? or latest master ? [S/l] " CONDITION;
- if [ "$CONDITION" == "l" ] ; then
- git clone https://github.com/apple/swift-protobuf.git
- else
- git clone -b 1.0.2 https://github.com/apple/swift-protobuf.git
- fi
- git clone https://github.com/grpc/grpc.git
- cd grpc
- # this step is slow and only needed to rebuild the vendored Sources/BoringSSL code.
- #git submodule update --init
-fi


message: Update SwiftProtobuf to 1.1.1

diff:
import PackageDescription
var packageDependencies: [Package.Dependency] = [
- .package(url: "https://github.com/apple/swift-protobuf.git", .upToNextMinor(from: "1.0.3")),
+ .package(url: "https://github.com/apple/swift-protobuf.git", .upToNextMinor(from: "1.1.1")),
.package(url: "https://github.com/kylef/Commander.git", from: "0.8.0"),
.package(url: "https://github.com/apple/swift-nio-zlib-support.git", from: "1.0.0")
]

diff:
@@ -41,5 +41,5 @@ Pod::Spec.new do |s|
s.dependency 'gRPC-Core', '~> 1.12.0'
s.dependency 'BoringSSL', '~> 10.0'
- s.dependency 'SwiftProtobuf', '~> 1.0.3'
+ s.dependency 'SwiftProtobuf', '~> 1.1.1'
end

original_message: Update SwiftProtobuf to 1.1.1


message: Update examples to SwiftProtobuf 1.1.1

diff:
@@ -21,7 +21,7 @@ let package = Package(
name: "Datastore",
dependencies: [
.package(url: "../../..", .branch("HEAD")),
- .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.0.2"),
+ .package(url: "https://github.com/apple/swift-protobuf.git", .upToNextMinor(from: "1.1.1")),
.package(url: "https://github.com/kylef/Commander.git", from: "0.8.0"),
.package(url: "https://github.com/google/auth-library-swift.git", from: "0.3.6")
],

diff:
@@ -21,7 +21,7 @@ let package = Package(
name: "Spanner",
dependencies: [
.package(url: "../../..", .branch("HEAD")),
- .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.0.2"),
+ .package(url: "https://github.com/apple/swift-protobuf.git", .upToNextMinor(from: "1.1.1")),
.package(url: "https://github.com/kylef/Commander.git", from: "0.8.0"),
.package(url: "https://github.com/google/auth-library-swift.git", from: "0.3.6")
],

original_message: Update examples to SwiftProtobuf 1.1.1


message: Bump podspec to 0.7.0

diff:
Pod::Spec.new do |s|
s.name = 'SwiftGRPC'
- s.version = '0.6.0'
+ s.version = '0.7.0'
s.license = { :type => 'Apache License, Version 2.0',
:text => <<-LICENSE
Copyright 2018, gRPC Authors. All rights reserved.

original_message: Bump podspec to 0.7.0


message:
Move ConnectivityState to new file & update initializer
Move `ConnectivityState` to its own file since this isn't a very small entity
Change the `static func connectivityState` to an initializer
Remove a redundant case check for `queueTimeout`

diff:
@@ -120,7 +120,7 @@ public class Channel {
/// - Parameter tryToConnect: boolean value to indicate if should try to connect if channel's connectivity state is idle
/// - Returns: a ConnectivityState value representing the current connectivity state of the channel
public func connectivityState(tryToConnect: Bool = false) -> ConnectivityState {
- return ConnectivityState.connectivityState(cgrpc_channel_check_connectivity_state(underlyingChannel, tryToConnect ? 1 : 0))
+ return ConnectivityState(cgrpc_channel_check_connectivity_state(underlyingChannel, tryToConnect ? 1 : 0))
}
/// Subscribe to connectivity state changes
@@ -175,17 +175,16 @@ private extension Channel {
switch event.type {
case .complete:
- let newState = ConnectivityState.connectivityState(cgrpc_channel_check_connectivity_state(self.underlyingChannel, 0))
+ let newState = ConnectivityState(cgrpc_channel_check_connectivity_state(self.underlyingChannel, 0))
if newState != self.lastState {
self.callback(newState)
}
-
self.lastState = newState
- case .queueTimeout:
- continue
+
case .queueShutdown:
return
+
default:
continue
}
@@ -201,60 +200,3 @@ private extension Channel {
}
}
}
-
-extension Channel {
- public enum ConnectivityState {
- /// Channel has just been initialized
- case initialized
- /// Channel is idle
- case idle
- /// Channel is connecting
- case connecting
- /// Channel is ready for work
- case ready
- /// Channel has seen a failure but expects to recover
- case transientFailure
- /// Channel has seen a failure that it cannot recover from
- case shutdown
- /// Channel connectivity state is unknown
- case unknown
-
- fileprivate static func connectivityState(_ value: grpc_connectivity_state) -> ConnectivityState {
- switch value {
- case GRPC_CHANNEL_INIT:
- return .initialized
- case GRPC_CHANNEL_IDLE:
- return .idle
- case GRPC_CHANNEL_CONNECTING:
- return .connecting
- case GRPC_CHANNEL_READY:
- return .ready
- case GRPC_CHANNEL_TRANSIENT_FAILURE:
- return .transientFailure
- case GRPC_CHANNEL_SHUTDOWN:
- return .shutdown
- default:
- return .unknown
- }
- }
-
- fileprivate var underlyingState: grpc_connectivity_state? {
- switch self {
- case .initialized:
- return GRPC_CHANNEL_INIT
- case .idle:
- return GRPC_CHANNEL_IDLE
- case .connecting:
- return GRPC_CHANNEL_CONNECTING
- case .ready:
- return GRPC_CHANNEL_READY
- case .transientFailure:
- return GRPC_CHANNEL_TRANSIENT_FAILURE
- case .shutdown:
- return GRPC_CHANNEL_SHUTDOWN
- default:
- return nil
- }
- }
- }
-}

diff:
+/*
+ * Copyright 2016, gRPC Authors 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
+ *
+ * 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.
+ */
+#if SWIFT_PACKAGE
+import CgRPC
+#endif
+
+extension Channel {
+ /// The connectivity state of a given gRPC channel.
+ public enum ConnectivityState {
+ /// Channel has just been initialized
+ case initialized
+ /// Channel is idle
+ case idle
+ /// Channel is connecting
+ case connecting
+ /// Channel is ready for work
+ case ready
+ /// Channel has seen a failure but expects to recover
+ case transientFailure
+ /// Channel has seen a failure that it cannot recover from
+ case shutdown
+ /// Channel connectivity state is unknown
+ case unknown
+
+ init(_ underlyingState: grpc_connectivity_state) {
+ switch underlyingState {
+ case GRPC_CHANNEL_INIT:
+ self = .initialized
+ case GRPC_CHANNEL_IDLE:
+ self = .idle
+ case GRPC_CHANNEL_CONNECTING:
+ self = .connecting
+ case GRPC_CHANNEL_READY:
+ self = .ready
+ case GRPC_CHANNEL_TRANSIENT_FAILURE:
+ self = .transientFailure
+ case GRPC_CHANNEL_SHUTDOWN:
+ self = .shutdown
+ default:
+ self = .unknown
+ }
+ }
+
+ var underlyingState: grpc_connectivity_state? {
+ switch self {
+ case .initialized:
+ return GRPC_CHANNEL_INIT
+ case .idle:
+ return GRPC_CHANNEL_IDLE
+ case .connecting:
+ return GRPC_CHANNEL_CONNECTING
+ case .ready:
+ return GRPC_CHANNEL_READY
+ case .transientFailure:
+ return GRPC_CHANNEL_TRANSIENT_FAILURE
+ case .shutdown:
+ return GRPC_CHANNEL_SHUTDOWN
+ default:
+ return nil
+ }
+ }
+ }
+}

original_message:
Move ConnectivityState to new file & update initializer (#376)

- Move `ConnectivityState` to its own file since this isn't a very small entity
- Change the `static func connectivityState` to an initializer
- Remove a redundant case check for `queueTimeout`


message:
Make ConnectivityObserver class final
* Make ConnectivityObserver class final
This is `private` to the file and will never be subclassed.
Also moved the setting of `underlyingCompletionQueue` inline to where it's declared.
* CR

diff:
@@ -132,7 +132,7 @@ public class Channel {
}
private extension Channel {
- class ConnectivityObserver {
+ final class ConnectivityObserver {
private let completionQueue: CompletionQueue
private let underlyingChannel: UnsafeMutableRawPointer
private let underlyingCompletionQueue: UnsafeMutableRawPointer

original_message:
Make ConnectivityObserver class final (#375)

* Make ConnectivityObserver class final

This is `private` to the file and will never be subclassed.

Also moved the setting of `underlyingCompletionQueue` inline to where it's declared.

* CR


message:
Update releasing instructions
Update the instructions to include step-by-step, as well as mentioning Carthage project re-generation.

diff:
@@ -170,11 +170,20 @@ Please get involved! See our [guidelines for contributing](CONTRIBUTING.md).
### Releasing
-Prior to creating a new release tag for SwiftGRPC, the `.podspec` file's version should be bumped, and the
-CocoaPods spec linter should be run to ensure that there are no new warnings/errors:
+When issuing a new release, the following steps should be followed:
- $ pod spec lint SwiftGRPC.podspec
+1. Run the CocoaPods linter to ensure that there are no new warnings/errors:
-Once a new release tag is created, the updated CocoaPods spec should also be pushed to the master specs repo:
+ `$ pod spec lint SwiftGRPC.podspec`
- $ pod trunk push SwiftGRPC.podspec
+1. Update the Carthage Xcode project (diff will need to be checked in with the version bump):
+
+ `$ make project-carthage`
+
+1. Bump the version in the `SwiftGRPC.podspec` file
+
+1. Merge these changes, then create a new `Release` with corresponding `Tag`. Be sure to include a list of changes in the message
+
+1. Push the update to the CocoaPods specs repo:
+
+ `$ pod trunk push`

original_message:
Update releasing instructions (#383)

Update the instructions to include step-by-step, as well as mentioning Carthage project re-generation.


message: Clean up gRPC.swift

diff:
#if SWIFT_PACKAGE
import CgRPC
#endif
-import Foundation // for String.Encoding
public final class gRPC {
private init() { } // Static members only.
@@ -32,17 +31,13 @@ public final class gRPC {
}
/// Returns version of underlying gRPC library
- ///
- /// Returns: gRPC version string
public static var version: String {
// These two should always be valid UTF-8 strings, so we can forcibly unwrap them.
- return String(cString: grpc_version_string(), encoding: String.Encoding.utf8)!
+ return String(cString: grpc_version_string(), encoding: .utf8)!
}
/// Returns name associated with gRPC version
- ///
- /// Returns: gRPC version name
public static var gStandsFor: String {
- return String(cString: grpc_g_stands_for(), encoding: String.Encoding.utf8)!
+ return String(cString: grpc_g_stands_for(), encoding: .utf8)!
}
}

original_message: Clean up gRPC.swift (#386)


message: remove unnecessary return type notation

diff:
@@ -159,25 +159,25 @@ class TestPropertyRequiredness {
"z".isRequiredForSerializationOf(testClass, mapper)
}
- private fun String.isRequiredForSerializationOf(type: Class<*>, mapper: ObjectMapper): Unit {
+ private fun String.isRequiredForSerializationOf(type: Class<*>, mapper: ObjectMapper) {
assertTrue("Property $this should be required for serialization!"){
introspectSerialization(type, mapper).isRequired(this)
}
}
- private fun String.isRequiredForDeserializationOf(type: Class<*>, mapper: ObjectMapper): Unit {
+ private fun String.isRequiredForDeserializationOf(type: Class<*>, mapper: ObjectMapper) {
assertTrue("Property $this should be required for deserialization!"){
introspectDeserialization(type, mapper).isRequired(this)
}
}
- private fun String.isOptionalForSerializationOf(type: Class<*>, mapper: ObjectMapper): Unit {
+ private fun String.isOptionalForSerializationOf(type: Class<*>, mapper: ObjectMapper) {
assertFalse("Property $this should be optional for serialization!"){
introspectSerialization(type, mapper).isRequired(this)
}
}
- private fun String.isOptionalForDeserializationOf(type: Class<*>, mapper: ObjectMapper): Unit {
+ private fun String.isOptionalForDeserializationOf(type: Class<*>, mapper: ObjectMapper) {
assertFalse("Property $this should be optional for deserialization of ${type.simpleName}!"){
introspectDeserialization(type, mapper).isRequired(this)
}

repo: fasterxml/jackson-module-kotlin

original_message: remove unnecessary return type notation


message:
Fix findSubtypes returning wrong values in super edge cases.
It's simpler to look at sealedSubclasses directly than to determine isSealed, so I fixed it while I was at it.

diff:
@@ -63,20 +63,13 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon
* Subclasses can be detected automatically for sealed classes, since all possible subclasses are known
* at compile-time to Kotlin. This makes [com.fasterxml.jackson.annotation.JsonSubTypes] redundant.
*/
- override fun findSubtypes(a: Annotated): MutableList<NamedType>? {
-
- val rawType = a.rawType
- if (rawType.isKotlinClass()) {
- val kClass = rawType.kotlin
- if (kClass.isSealed) {
- return kClass.sealedSubclasses
+ override fun findSubtypes(a: Annotated): MutableList<NamedType>? = a.rawType
+ .takeIf { it.isKotlinClass() }
+ ?.let { rawType ->
+ rawType.kotlin.sealedSubclasses
.map { NamedType(it.java) }
.toMutableList()
- }
- }
-
- return null
-
+ .ifEmpty { null }
}
private fun AnnotatedField.hasRequiredMarker(): Boolean? {


message: add serialization settings

diff:
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.ser.Serializers
import com.fasterxml.jackson.databind.ser.std.StdSerializer
+import com.fasterxml.jackson.module.kotlin.ValueClassUnboxSerializer.isUnboxableValueClass
import java.math.BigInteger
object SequenceSerializer : StdSerializer<Sequence<*>>(Sequence::class.java) {
@@ -40,6 +41,25 @@ object ULongSerializer : StdSerializer<ULong>(ULong::class.java) {
}
}
+object ValueClassUnboxSerializer : StdSerializer<Any>(Any::class.java) {
+ override fun serialize(value: Any, gen: JsonGenerator, provider: SerializerProvider) {
+ val unboxed = value::class.java.getMethod("unbox-impl").invoke(value)
+
+ if (unboxed == null) {
+ gen.writeNull()
+ return
+ }
+
+ provider.findValueSerializer(unboxed::class.java).serialize(unboxed, gen, provider)
+ }
+
+ // In the future, value class without JvmInline will be available, and unbox may not be able to handle it.
+ // https://github.com/FasterXML/jackson-module-kotlin/issues/464
+ // The JvmInline annotation can be given to Java class,
+ // so the isKotlinClass decision is necessary (the order is preferable in terms of possible frequency).
+ fun Class<*>.isUnboxableValueClass() = annotations.any { it is JvmInline } && this.isKotlinClass()
+}
+
@Suppress("EXPERIMENTAL_API_USAGE")
internal class KotlinSerializers : Serializers.Base() {
override fun findSerializer(
@@ -52,6 +72,8 @@ internal class KotlinSerializers : Serializers.Base() {
UShort::class.java.isAssignableFrom(type.rawClass) -> UShortSerializer
UInt::class.java.isAssignableFrom(type.rawClass) -> UIntSerializer
ULong::class.java.isAssignableFrom(type.rawClass) -> ULongSerializer
+ // The priority of Unboxing needs to be lowered so as not to break the serialization of Unsigned Integers.
+ type.rawClass.isUnboxableValueClass() -> ValueClassUnboxSerializer
else -> null
}
}

original_message: add serialization settings


message: optimize findImplicitPropertyName

diff:
@@ -28,26 +28,16 @@ import kotlin.reflect.jvm.kotlinFunction
internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val cache: ReflectionCache, val ignoredClassesForImplyingJsonCreator: Set<KClass<*>>) : NopAnnotationIntrospector() {
// since 2.4
- override fun findImplicitPropertyName(member: AnnotatedMember): String? {
- if (member is AnnotatedMethod && member.isInlineClass()) {
- if (member.name.startsWith("get") &&
- member.name.contains('-') &&
- member.parameterCount == 0) {
- return member.name.substringAfter("get")
- .replaceFirstChar { it.lowercase(Locale.getDefault()) }
- .substringBefore('-')
- } else if (member.name.startsWith("is") &&
- member.name.contains('-') &&
- member.parameterCount == 0) {
- return member.name.substringAfter("is")
- .replaceFirstChar { it.lowercase(Locale.getDefault()) }
- .substringBefore('-')
- }
- } else if (member is AnnotatedParameter) {
- return findKotlinParameterName(member)
- }
-
- return null
+ override fun findImplicitPropertyName(member: AnnotatedMember): String? = when (member) {
+ is AnnotatedMethod -> if (member.name.contains('-') && member.parameterCount == 0) {
+ when {
+ member.name.startsWith("get") -> member.name.substringAfter("get")
+ member.name.startsWith("is") -> member.name.substringAfter("is")
+ else -> null
+ }?.replaceFirstChar { it.lowercase(Locale.getDefault()) }?.substringBefore('-')
+ } else null
+ is AnnotatedParameter -> findKotlinParameterName(member)
+ else -> null
}
// since 2.11: support Kotlin's way of handling "isXxx" backed properties where
@@ -169,5 +159,3 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c
}
}
}
-
-private fun AnnotatedMethod.isInlineClass() = declaringClass.declaredMethods.any { it.name.contains('-') }

original_message: optimize findImplicitPropertyName


message: add unbox key serializer

diff:
+package com.fasterxml.jackson.module.kotlin
+
+import com.fasterxml.jackson.core.JsonGenerator
+import com.fasterxml.jackson.databind.*
+import com.fasterxml.jackson.databind.ser.Serializers
+import com.fasterxml.jackson.databind.ser.std.StdSerializer
+import com.fasterxml.jackson.databind.type.TypeFactory
+
+internal object ValueClassUnboxKeySerializer : StdSerializer<Any>(Any::class.java) {
+ override fun serialize(value: Any, gen: JsonGenerator, provider: SerializerProvider) {
+ val method = value::class.java.getMethod("unbox-impl")
+ val unboxed = method.invoke(value)
+
+ if (unboxed == null) {
+ val javaType = provider.typeFactory.constructType(method.genericReturnType)
+ provider.findNullKeySerializer(javaType, null).serialize(null, gen, provider)
+ return
+ }
+
+ provider.findKeySerializer(unboxed::class.java, null).serialize(unboxed, gen, provider)
+ }
+}
+
+class KotlinKeySerializers : Serializers.Base() {
+ override fun findSerializer(
+ config: SerializationConfig,
+ type: JavaType,
+ beanDesc: BeanDescription
+ ): JsonSerializer<*>? = when {
+ type.rawClass.isUnboxableValueClass() -> ValueClassUnboxKeySerializer
+ else -> null
+ }
+}

diff:
@@ -123,6 +123,7 @@ class KotlinModule @Deprecated(
context.addDeserializers(KotlinDeserializers())
context.addSerializers(KotlinSerializers())
+ context.addKeySerializers(KotlinKeySerializers())
fun addMixIn(clazz: Class<*>, mixin: Class<*>) {
context.setMixInAnnotations(clazz, mixin)

original_message: add unbox key serializer


message: Fixed an issue where NullValueSerializer was not being applied.

diff:
@@ -45,7 +45,7 @@ object ValueClassUnboxSerializer : StdSerializer<Any>(Any::class.java) {
val unboxed = value::class.java.getMethod("unbox-impl").invoke(value)
if (unboxed == null) {
- gen.writeNull()
+ provider.findNullValueSerializer(null).serialize(unboxed, gen, provider)
return
}

diff:
@@ -23,11 +23,6 @@ class Github464 {
}
}
- @Suppress("UNCHECKED_CAST")
- private val writer: ObjectWriter = jacksonObjectMapper()
- .apply { serializerProvider.setNullKeySerializer(NullValueClassKeySerializer as JsonSerializer<Any?>) }
- .writerWithDefaultPrettyPrinter()
-
@JvmInline
value class ValueClass(val value: Int?)
data class WrapperClass(val inlineField: ValueClass)
@@ -44,13 +39,11 @@ class Github464 {
val garply: Map<ValueClass, ValueClass?>
)
- @Test
- fun test() {
- val zeroValue = ValueClass(0)
- val oneValue = ValueClass(1)
- val nullValue = ValueClass(null)
+ private val zeroValue = ValueClass(0)
+ private val oneValue = ValueClass(1)
+ private val nullValue = ValueClass(null)
- val target = Poko(
+ private val target = Poko(
foo = zeroValue,
bar = null,
baz = zeroValue,
@@ -61,6 +54,13 @@ class Github464 {
garply = mapOf(zeroValue to zeroValue, oneValue to null, nullValue to nullValue)
)
+ @Test
+ fun test() {
+ @Suppress("UNCHECKED_CAST")
+ val writer: ObjectWriter = jacksonObjectMapper()
+ .apply { serializerProvider.setNullKeySerializer(NullValueClassKeySerializer as JsonSerializer<Any?>) }
+ .writerWithDefaultPrettyPrinter()
+
assertEquals(
"""
{
@@ -83,6 +83,44 @@ class Github464 {
writer.writeValueAsString(target)
)
}
+
+ object NullValueSerializer : StdSerializer<Any>(Any::class.java) {
+ override fun serialize(value: Any?, gen: JsonGenerator, provider: SerializerProvider) {
+ gen.writeString("null-value")
+ }
+ }
+
+ @Test
+ fun nullValueSerializerTest() {
+ @Suppress("UNCHECKED_CAST")
+ val writer = jacksonObjectMapper()
+ .apply {
+ serializerProvider.setNullKeySerializer(NullValueClassKeySerializer as JsonSerializer<Any?>)
+ serializerProvider.setNullValueSerializer(NullValueSerializer)
+ }.writerWithDefaultPrettyPrinter()
+
+ assertEquals(
+ """
+ {
+ "foo" : 0,
+ "bar" : "null-value",
+ "baz" : 0,
+ "qux" : [ 0, "null-value" ],
+ "quux" : [ 0, "null-value" ],
+ "corge" : {
+ "inlineField" : 0
+ },
+ "grault" : "null-value",
+ "garply" : {
+ "0" : 0,
+ "1" : "null-value",
+ "null-key" : "null-value"
+ }
+ }
+ """.trimIndent(),
+ writer.writeValueAsString(target)
+ )
+ }
}
class SerializerPriorityTest {


message: optimize getting hasRequiredMarker process on CorrespondingAccessor

diff:
@@ -13,7 +13,6 @@ import java.lang.reflect.Field
import java.lang.reflect.Method
import kotlin.reflect.KFunction
import kotlin.reflect.KMutableProperty1
-import kotlin.reflect.KProperty1
import kotlin.reflect.KType
import kotlin.reflect.full.createType
import kotlin.reflect.full.declaredMemberProperties
@@ -100,17 +99,7 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon
private fun AnnotatedMethod.hasRequiredMarker(): Boolean? {
// This could be a setter or a getter of a class property or
// a setter-like/getter-like method.
- val paramGetter = this.getCorrespondingGetter()
- if (paramGetter != null) {
- val byAnnotation = paramGetter.javaGetter?.isRequiredByAnnotation()
- return requiredAnnotationOrNullability(byAnnotation, paramGetter.returnType.isRequired())
- }
-
- val paramSetter = this.getCorrespondingSetter()
- if (paramSetter != null) {
- val byAnnotation = paramSetter.javaMethod?.isRequiredByAnnotation()
- return requiredAnnotationOrNullability(byAnnotation, paramSetter.isMethodParameterRequired(0))
- }
+ this.getFromCorrespondingAccessor()?.let { return it }
// Is the member method a regular method of the data class or
val method = this.member.kotlinFunction
@@ -132,21 +121,23 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon
private fun KFunction<*>.isSetterLike(): Boolean =
parameters.size == 2 && returnType == Unit::class.createType()
-
- private fun AnnotatedMethod.getCorrespondingGetter(): KProperty1<out Any, Any?>? =
- member.declaringClass.kotlin.declaredMemberProperties.find {
- it.getter.javaMethod == this.member
+ private fun AnnotatedMethod.getFromCorrespondingAccessor(): Boolean? {
+ member.declaringClass.kotlin.declaredMemberProperties.forEach { kProperty1 ->
+ kProperty1.javaGetter
+ ?.takeIf { it == this.member }
+ ?.let {
+ val byAnnotation = it.isRequiredByAnnotation()
+ return requiredAnnotationOrNullability(byAnnotation, kProperty1.returnType.isRequired())
}
- @Suppress("UNCHECKED_CAST")
- private fun AnnotatedMethod.getCorrespondingSetter(): KMutableProperty1.Setter<out Any, Any?>? {
- val mutableProperty = member.declaringClass.kotlin.declaredMemberProperties.find {
- when (it) {
- is KMutableProperty1 -> it.javaSetter == this.member
- else -> false
+ (kProperty1 as? KMutableProperty1)?.javaSetter
+ ?.takeIf { it == this.member }
+ ?.let {
+ val byAnnotation = it.isRequiredByAnnotation()
+ return requiredAnnotationOrNullability(byAnnotation, kProperty1.setter.isMethodParameterRequired(0))
}
}
- return (mutableProperty as? KMutableProperty1<out Any, Any?>)?.setter
+ return null
}
private fun AnnotatedParameter.hasRequiredMarker(): Boolean? {


message: Fixed the order of definitions to the order in which they are used

diff:
@@ -110,10 +110,6 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon
}
}
- private fun KFunction<*>.isGetterLike(): Boolean = parameters.size == 1
- private fun KFunction<*>.isSetterLike(): Boolean =
- parameters.size == 2 && returnType == Unit::class.createType()
-
private fun AnnotatedMethod.getFromCorrespondingAccessor(): Boolean? {
member.declaringClass.kotlin.declaredMemberProperties.forEach { kProperty1 ->
kProperty1.javaGetter
@@ -133,6 +129,10 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon
return null
}
+ private fun KFunction<*>.isGetterLike(): Boolean = parameters.size == 1
+ private fun KFunction<*>.isSetterLike(): Boolean =
+ parameters.size == 2 && returnType == Unit::class.createType()
+
private fun AnnotatedParameter.hasRequiredMarker(): Boolean? {
val member = this.member
val byAnnotation = this.getAnnotation(JsonProperty::class.java)?.required


message: split to method

diff:
@@ -99,16 +99,7 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon
// This could be a setter or a getter of a class property or
// a setter-like/getter-like method.
private fun AnnotatedMethod.hasRequiredMarker(): Boolean? = this.getRequiredMarkerFromCorrespondingAccessor()
- ?: this.member.kotlinFunction?.let { method -> // Is the member method a regular method of the data class or
- val byAnnotation = method.javaMethod?.isRequiredByAnnotation()
- when {
- method.isGetterLike() ->
- requiredAnnotationOrNullability(byAnnotation, method.returnType.isRequired())
- method.isSetterLike() ->
- requiredAnnotationOrNullability(byAnnotation, method.isMethodParameterRequired(0))
- else -> null
- }
- }
+ ?: this.member.kotlinFunction?.getRequiredMarkerFromAccessorLikeMethod()
private fun AnnotatedMethod.getRequiredMarkerFromCorrespondingAccessor(): Boolean? {
member.declaringClass.kotlin.declaredMemberProperties.forEach { kProperty1 ->
@@ -129,6 +120,16 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon
return null
}
+ // Is the member method a regular method of the data class or
+ private fun KFunction<*>.getRequiredMarkerFromAccessorLikeMethod(): Boolean? {
+ val byAnnotation = this.javaMethod?.isRequiredByAnnotation()
+ return when {
+ this.isGetterLike() -> requiredAnnotationOrNullability(byAnnotation, this.returnType.isRequired())
+ this.isSetterLike() -> requiredAnnotationOrNullability(byAnnotation, this.isMethodParameterRequired(0))
+ else -> null
+ }
+ }
+
private fun KFunction<*>.isGetterLike(): Boolean = parameters.size == 1
private fun KFunction<*>.isSetterLike(): Boolean =
parameters.size == 2 && returnType == Unit::class.createType()

original_message: split to method


message: cache Unit::class.createType()

diff:
@@ -131,8 +131,7 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon
}
private fun KFunction<*>.isGetterLike(): Boolean = parameters.size == 1
- private fun KFunction<*>.isSetterLike(): Boolean =
- parameters.size == 2 && returnType == Unit::class.createType()
+ private fun KFunction<*>.isSetterLike(): Boolean = parameters.size == 2 && returnType == UNIT_TYPE
private fun AnnotatedParameter.hasRequiredMarker(): Boolean? {
val member = this.member
@@ -170,4 +169,7 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon
private fun KType.isRequired(): Boolean = !isMarkedNullable
+ companion object {
+ val UNIT_TYPE: KType = Unit::class.createType()
+ }
}

original_message: cache Unit::class.createType()


message: optimize for nullability

diff:
@@ -99,7 +99,7 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon
// This could be a setter or a getter of a class property or
// a setter-like/getter-like method.
private fun AnnotatedMethod.hasRequiredMarker(): Boolean? = this.getRequiredMarkerFromCorrespondingAccessor()
- ?: this.member.kotlinFunction?.getRequiredMarkerFromAccessorLikeMethod()
+ ?: this.member.getRequiredMarkerFromAccessorLikeMethod()
private fun AnnotatedMethod.getRequiredMarkerFromCorrespondingAccessor(): Boolean? {
member.declaringClass.kotlin.declaredMemberProperties.forEach { kProperty1 ->
@@ -121,11 +121,11 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon
}
// Is the member method a regular method of the data class or
- private fun KFunction<*>.getRequiredMarkerFromAccessorLikeMethod(): Boolean? {
- val byAnnotation = this.javaMethod?.isRequiredByAnnotation()
+ private fun Method.getRequiredMarkerFromAccessorLikeMethod(): Boolean? = this.kotlinFunction?.let { method ->
+ val byAnnotation = this.isRequiredByAnnotation()
return when {
- this.isGetterLike() -> requiredAnnotationOrNullability(byAnnotation, this.returnType.isRequired())
- this.isSetterLike() -> requiredAnnotationOrNullability(byAnnotation, this.isMethodParameterRequired(0))
+ method.isGetterLike() -> requiredAnnotationOrNullability(byAnnotation, method.returnType.isRequired())
+ method.isSetterLike() -> requiredAnnotationOrNullability(byAnnotation, method.isMethodParameterRequired(0))
else -> null
}
}

original_message: optimize for nullability


message: Cut out conditionals and reduce nesting

diff:
@@ -58,13 +58,15 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c
return null
}
+ // if has parameters, is a Kotlin class, and the parameters all have parameter annotations, then pretend we have a JsonCreator
+ private fun AnnotatedConstructor.isKotlinConstructorWithParameters(): Boolean =
+ parameterCount > 0 && declaringClass.isKotlinClass() && !declaringClass.isEnum
+
@Suppress("UNCHECKED_CAST")
override fun hasCreatorAnnotation(member: Annotated): Boolean {
// don't add a JsonCreator to any constructor if one is declared already
- if (member is AnnotatedConstructor && !member.declaringClass.isEnum) {
- // if has parameters, is a Kotlin class, and the parameters all have parameter annotations, then pretend we have a JsonCreator
- if (member.parameterCount > 0 && member.declaringClass.isKotlinClass()) {
+ if (member is AnnotatedConstructor && member.isKotlinConstructorWithParameters()) {
return cache.checkConstructorIsCreatorAnnotated(member) {
val kClass = cache.kotlinFromJava(member.declaringClass as Class<Any>)
val kConstructor = cache.kotlinFromJava(member.annotated as Constructor<Any>)
@@ -117,7 +119,6 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c
}
}
}
- }
return false
}

original_message: Cut out conditionals and reduce nesting


message: Change to if expression

diff:
@@ -66,8 +66,8 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c
override fun hasCreatorAnnotation(member: Annotated): Boolean {
// don't add a JsonCreator to any constructor if one is declared already
- if (member is AnnotatedConstructor && member.isKotlinConstructorWithParameters()) {
- return cache.checkConstructorIsCreatorAnnotated(member) {
+ return if (member is AnnotatedConstructor && member.isKotlinConstructorWithParameters()) {
+ cache.checkConstructorIsCreatorAnnotated(member) {
val kClass = cache.kotlinFromJava(member.declaringClass as Class<Any>)
val kConstructor = cache.kotlinFromJava(member.annotated as Constructor<Any>)
@@ -118,8 +118,7 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c
false
}
}
- }
- return false
+ } else false
}
@Suppress("UNCHECKED_CAST")

original_message: Change to if expression


message: Cut out the isPossibleSingleString function

diff:
@@ -62,6 +62,11 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c
private fun AnnotatedConstructor.isKotlinConstructorWithParameters(): Boolean =
parameterCount > 0 && declaringClass.isKotlinClass() && !declaringClass.isEnum
+ private fun KFunction<*>.isPossibleSingleString(propertyNames: Set<String>): Boolean = parameters.size == 1 &&
+ parameters[0].name !in propertyNames &&
+ parameters[0].type.javaType == String::class.java &&
+ parameters[0].annotations.none { it.annotationClass.java == JsonProperty::class.java }
+
@Suppress("UNCHECKED_CAST")
override fun hasCreatorAnnotation(member: Annotated): Boolean {
// don't add a JsonCreator to any constructor if one is declared already
@@ -77,16 +82,8 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c
val propertyNames = kClass.memberProperties.map { it.name }.toSet()
- fun KFunction<*>.isPossibleSingleString(): Boolean {
- val result = parameters.size == 1 &&
- parameters[0].name !in propertyNames &&
- parameters[0].type.javaType == String::class.java &&
- parameters[0].annotations.none { it.annotationClass.java == JsonProperty::class.java }
- return result
- }
-
fun Collection<KFunction<*>>.filterOutSingleStringCallables(): Collection<KFunction<*>> {
- return this.filter { !it.isPossibleSingleString() }
+ return this.filter { !it.isPossibleSingleString(propertyNames) }
}
val anyConstructorHasJsonCreator = kClass.constructors.filterOutSingleStringCallables()


message: Move to toplevel

diff:
@@ -55,15 +55,6 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c
return null
}
- // if has parameters, is a Kotlin class, and the parameters all have parameter annotations, then pretend we have a JsonCreator
- private fun AnnotatedConstructor.isKotlinConstructorWithParameters(): Boolean =
- parameterCount > 0 && declaringClass.isKotlinClass() && !declaringClass.isEnum
-
- private fun KFunction<*>.isPossibleSingleString(propertyNames: Set<String>): Boolean = parameters.size == 1 &&
- parameters[0].name !in propertyNames &&
- parameters[0].type.javaType == String::class.java &&
- !parameters[0].hasAnnotation<JsonProperty>()
-
@Suppress("UNCHECKED_CAST")
override fun hasCreatorAnnotation(member: Annotated): Boolean {
// don't add a JsonCreator to any constructor if one is declared already
@@ -159,3 +150,12 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c
ReplaceWith("with(receiver) { declaringClass.declaredMethods.any { it.name.contains('-') } }")
)
private fun AnnotatedMethod.isInlineClass() = declaringClass.declaredMethods.any { it.name.contains('-') }
+
+// if has parameters, is a Kotlin class, and the parameters all have parameter annotations, then pretend we have a JsonCreator
+private fun AnnotatedConstructor.isKotlinConstructorWithParameters(): Boolean =
+ parameterCount > 0 && declaringClass.isKotlinClass() && !declaringClass.isEnum
+
+private fun KFunction<*>.isPossibleSingleString(propertyNames: Set<String>): Boolean = parameters.size == 1 &&
+ parameters[0].name !in propertyNames &&
+ parameters[0].type.javaType == String::class.java &&
+ !parameters[0].hasAnnotation<JsonProperty>()

original_message: Move to toplevel


message: Cut out the filterOutSingleStringCallables function

diff:
@@ -70,16 +70,12 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c
val propertyNames = kClass.memberProperties.map { it.name }.toSet()
- fun Collection<KFunction<*>>.filterOutSingleStringCallables(): Collection<KFunction<*>> {
- return this.filter { !it.isPossibleSingleString(propertyNames) }
- }
-
- val anyConstructorHasJsonCreator = kClass.constructors.filterOutSingleStringCallables()
+ val anyConstructorHasJsonCreator = kClass.constructors.filterOutSingleStringCallables(propertyNames)
.any { it.annotations.any { it.annotationClass.java == JsonCreator::class.java }
}
val anyCompanionMethodIsJsonCreator = member.type.rawClass.kotlin.companionObject?.declaredFunctions
- ?.filterOutSingleStringCallables()?.any {
+ ?.filterOutSingleStringCallables(propertyNames)?.any {
it.annotations.any { it.annotationClass.java == JvmStatic::class.java } &&
it.annotations.any { it.annotationClass.java == JsonCreator::class.java }
} ?: false
@@ -90,7 +86,7 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c
val areAllParametersValid = kConstructor.parameters.size == kConstructor.parameters.count { it.name != null }
- val isSingleStringConstructor = kConstructor.isPossibleSingleString()
+ val isSingleStringConstructor = kConstructor.isPossibleSingleString(propertyNames)
val implyCreatorAnnotation = isPrimaryConstructor
&& !(anyConstructorHasJsonCreator || anyCompanionMethodIsJsonCreator)
@@ -159,3 +155,6 @@ private fun KFunction<*>.isPossibleSingleString(propertyNames: Set<String>): Boo
parameters[0].name !in propertyNames &&
parameters[0].type.javaType == String::class.java &&
!parameters[0].hasAnnotation<JsonProperty>()
+
+private fun Collection<KFunction<*>>.filterOutSingleStringCallables(propertyNames: Set<String>): Collection<KFunction<*>> =
+ this.filter { !it.isPossibleSingleString(propertyNames) }


message: Remove unnecessary assignments to temporary variables

diff:
@@ -87,13 +87,11 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c
val isSingleStringConstructor = kConstructor.isPossibleSingleString(propertyNames)
- val implyCreatorAnnotation = isPrimaryConstructor
+ isPrimaryConstructor
&& !(anyConstructorHasJsonCreator || anyCompanionMethodIsJsonCreator)
&& areAllParametersValid
&& !isSingleStringConstructor
&& kClass !in ignoredClassesForImplyingJsonCreator
-
- implyCreatorAnnotation
} else {
false
}


message: Cut out a large process into a function

diff:
@@ -56,15 +56,13 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c
}
@Suppress("UNCHECKED_CAST")
- override fun hasCreatorAnnotation(member: Annotated): Boolean {
+ private fun hasCreatorAnnotation(member: AnnotatedConstructor): Boolean {
// don't add a JsonCreator to any constructor if one is declared already
- return if (member is AnnotatedConstructor && member.isKotlinConstructorWithParameters()) {
- cache.checkConstructorIsCreatorAnnotated(member) {
val kClass = cache.kotlinFromJava(member.declaringClass as Class<Any>)
val kConstructor = cache.kotlinFromJava(member.annotated as Constructor<Any>)
- if (kConstructor != null) {
+ return if (kConstructor != null) {
val isPrimaryConstructor = kClass.primaryConstructor == kConstructor ||
(kClass.primaryConstructor == null && kClass.constructors.size == 1)
@@ -96,8 +94,12 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c
false
}
}
- } else false
- }
+
+ override fun hasCreatorAnnotation(member: Annotated): Boolean =
+ if (member is AnnotatedConstructor && member.isKotlinConstructorWithParameters())
+ cache.checkConstructorIsCreatorAnnotated(member) { hasCreatorAnnotation(it) }
+ else
+ false
@Suppress("UNCHECKED_CAST")
private fun findKotlinParameterName(param: AnnotatedParameter): String? {

original_message: Cut out a large process into a function


message: Cut out the judgment process of PrimaryConstructor

diff:
@@ -63,8 +63,7 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c
.apply { if (this in ignoredClassesForImplyingJsonCreator) return false }
val kConstructor = cache.kotlinFromJava(member.annotated as Constructor<Any>) ?: return false
- val isPrimaryConstructor = kClass.primaryConstructor == kConstructor ||
- (kClass.primaryConstructor == null && kClass.constructors.size == 1)
+ val isPrimaryConstructor = kClass.isPrimaryConstructor(kConstructor)
val propertyNames = kClass.memberProperties.map { it.name }.toSet()
@@ -153,3 +152,6 @@ private fun KFunction<*>.isPossibleSingleString(propertyNames: Set<String>): Boo
private fun Collection<KFunction<*>>.filterOutSingleStringCallables(propertyNames: Set<String>): Collection<KFunction<*>> =
this.filter { !it.isPossibleSingleString(propertyNames) }
+
+private fun KClass<*>.isPrimaryConstructor(kConstructor: KFunction<*>) = this.primaryConstructor == kConstructor ||
+ (this.primaryConstructor == null && this.constructors.size == 1)


message: Change to an early return

diff:
@@ -63,10 +63,17 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c
.apply { if (this in ignoredClassesForImplyingJsonCreator) return false }
val kConstructor = cache.kotlinFromJava(member.annotated as Constructor<Any>) ?: return false
- val isPrimaryConstructor = kClass.isPrimaryConstructor(kConstructor)
+ // TODO: should we do this check or not? It could cause failures if we miss another way a property could be set
+ // val requiredProperties = kClass.declaredMemberProperties.filter {!it.returnType.isMarkedNullable }.map { it.name }.toSet()
+ // val areAllRequiredParametersInConstructor = kConstructor.parameters.all { requiredProperties.contains(it.name) }
val propertyNames = kClass.memberProperties.map { it.name }.toSet()
+ return when {
+ kConstructor.isPossibleSingleString(propertyNames) -> false
+ kConstructor.parameters.any { it.name == null } -> false
+ !kClass.isPrimaryConstructor(kConstructor) -> false
+ else -> {
val anyConstructorHasJsonCreator = kClass.constructors
.filterOutSingleStringCallables(propertyNames)
.any { it.hasAnnotation<JsonCreator>() }
@@ -76,18 +83,9 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule, val c
?.any { it.hasAnnotation<JsonCreator>() && it.hasAnnotation<JvmStatic>() }
?: false
- // TODO: should we do this check or not? It could cause failures if we miss another way a property could be set
- // val requiredProperties = kClass.declaredMemberProperties.filter {!it.returnType.isMarkedNullable }.map { it.name }.toSet()
- // val areAllRequiredParametersInConstructor = kConstructor.parameters.all { requiredProperties.contains(it.name) }
-
- val areAllParametersValid = kConstructor.parameters.all { it.name != null }
-
- val isSingleStringConstructor = kConstructor.isPossibleSingleString(propertyNames)
-
- return isPrimaryConstructor
- && !(anyConstructorHasJsonCreator || anyCompanionMethodIsJsonCreator)
- && areAllParametersValid
- && !isSingleStringConstructor
+ !(anyConstructorHasJsonCreator || anyCompanionMethodIsJsonCreator)
+ }
+ }
}
override fun hasCreatorAnnotation(member: Annotated): Boolean =

original_message: Change to an early return


message: add Value Creators

diff:
+package com.fasterxml.jackson.module.kotlin
+
+import kotlin.reflect.KFunction
+import kotlin.reflect.jvm.isAccessible
+
+internal class ConstructorValueCreator<T>(override val callable: KFunction<T>) : ValueCreator<T>() {
+ override val accessible: Boolean = callable.isAccessible
+
+ init {
+ callable.isAccessible = true
+ }
+}

diff:
+package com.fasterxml.jackson.module.kotlin
+
+import kotlin.reflect.KFunction
+import kotlin.reflect.KParameter
+import kotlin.reflect.full.extensionReceiverParameter
+import kotlin.reflect.full.instanceParameter
+import kotlin.reflect.jvm.isAccessible
+
+internal class MethodValueCreator<T> private constructor(
+ override val callable: KFunction<T>,
+ override val accessible: Boolean,
+ val companionObjectInstance: Any
+) : ValueCreator<T>() {
+ val instanceParameter: KParameter = callable.instanceParameter!!
+
+ companion object {
+ fun <T> of(callable: KFunction<T>): MethodValueCreator<T>? {
+ // we shouldn't have an instance or receiver parameter and if we do, just go with default Java-ish behavior
+ if (callable.extensionReceiverParameter != null) return null
+
+ val possibleCompanion = callable.instanceParameter!!.type.erasedType().kotlin
+
+ // abort, we have some unknown case here
+ if (!possibleCompanion.isCompanion) return null
+
+ val (companionObjectInstance: Any, accessible: Boolean) = try {
+ // throws ex
+ val instance = possibleCompanion.objectInstance!!
+ // If an instance of the companion object can be obtained, accessibility depends on the KFunction
+ instance to callable.isAccessible
+ } catch (ex: IllegalAccessException) {
+ // fallback for when an odd access exception happens through Kotlin reflection
+ possibleCompanion.java.enclosingClass.fields
+ .firstOrNull { it.type.kotlin.isCompanion }
+ ?.let {
+ it.isAccessible = true
+
+ // If the instance of the companion object cannot be obtained, accessibility will always be false
+ it.get(null) to false
+ } ?: throw ex
+ }
+
+ return MethodValueCreator(callable, accessible, companionObjectInstance)
+ }
+ }
+}

diff:
+package com.fasterxml.jackson.module.kotlin
+
+import com.fasterxml.jackson.databind.DeserializationContext
+import com.fasterxml.jackson.databind.MapperFeature
+import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor
+import com.fasterxml.jackson.databind.introspect.AnnotatedMethod
+import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams
+import java.lang.reflect.Constructor
+import java.lang.reflect.Method
+import kotlin.reflect.KFunction
+import kotlin.reflect.KParameter
+import kotlin.reflect.full.valueParameters
+
+/**
+ * A class that abstracts the creation of instances by calling KFunction.
+ * @see KotlinValueInstantiator
+ */
+internal sealed class ValueCreator<T> {
+ /**
+ * Function to be call.
+ */
+ protected abstract val callable: KFunction<T>
+
+ /**
+ * Initial value for accessibility by reflection.
+ */
+ protected abstract val accessible: Boolean
+
+ /**
+ * ValueParameters of the KFunction to be called.
+ */
+ val valueParameters: List<KParameter> by lazy { callable.valueParameters }
+
+ /**
+ * Checking process to see if access from context is possible.
+ * @throws IllegalAccessException
+ */
+ fun checkAccessibility(ctxt: DeserializationContext) {
+ if ((!accessible && ctxt.config.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) ||
+ (accessible && ctxt.config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS))) {
+ return
+ }
+
+ throw IllegalAccessException("Cannot access to function or companion object instance, target: $callable")
+ }
+
+ /**
+ * Function call with default values enabled.
+ */
+ fun callBy(args: Map<KParameter, Any?>): T = callable.callBy(args)
+
+ companion object {
+ @Suppress("UNCHECKED_CAST")
+ fun of(
+ _withArgsCreator: AnnotatedWithParams, cache: ReflectionCache
+ ): ValueCreator<*>? = when (_withArgsCreator) {
+ is AnnotatedConstructor -> cache.kotlinFromJava(_withArgsCreator.annotated as Constructor<Any>)
+ ?.let { ConstructorValueCreator(it) }
+ is AnnotatedMethod -> cache.kotlinFromJava(_withArgsCreator.annotated as Method)
+ ?.let { MethodValueCreator.of(it) }
+ else -> throw IllegalStateException("Expected a constructor or method to create a Kotlin object, instead found ${_withArgsCreator.annotated.javaClass.name}")
+ } // we cannot reflect this method so do the default Java-ish behavior
+ }
+}

original_message: add Value Creators


message: use ValueCreator temp

diff:
@@ -3,24 +3,15 @@ package com.fasterxml.jackson.module.kotlin
import com.fasterxml.jackson.databind.BeanDescription
import com.fasterxml.jackson.databind.DeserializationConfig
import com.fasterxml.jackson.databind.DeserializationContext
-import com.fasterxml.jackson.databind.MapperFeature
import com.fasterxml.jackson.databind.deser.SettableBeanProperty
import com.fasterxml.jackson.databind.deser.ValueInstantiator
import com.fasterxml.jackson.databind.deser.ValueInstantiators
import com.fasterxml.jackson.databind.deser.impl.NullsAsEmptyProvider
import com.fasterxml.jackson.databind.deser.impl.PropertyValueBuffer
import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator
-import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor
-import com.fasterxml.jackson.databind.introspect.AnnotatedMethod
-import java.lang.reflect.Constructor
-import java.lang.reflect.Method
import java.lang.reflect.TypeVariable
import kotlin.reflect.KParameter
import kotlin.reflect.KType
-import kotlin.reflect.full.extensionReceiverParameter
-import kotlin.reflect.full.instanceParameter
-import kotlin.reflect.full.valueParameters
-import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.javaType
internal class KotlinValueInstantiator(
@@ -31,62 +22,35 @@ internal class KotlinValueInstantiator(
private val nullIsSameAsDefault: Boolean,
private val strictNullChecks: Boolean
) : StdValueInstantiator(src) {
- @Suppress("UNCHECKED_CAST")
override fun createFromObjectWith(
ctxt: DeserializationContext,
props: Array<out SettableBeanProperty>,
buffer: PropertyValueBuffer
): Any? {
- val callable = when (_withArgsCreator) {
- is AnnotatedConstructor -> cache.kotlinFromJava(_withArgsCreator.annotated as Constructor<Any>)
- is AnnotatedMethod -> cache.kotlinFromJava(_withArgsCreator.annotated as Method)
- else -> throw IllegalStateException("Expected a constructor or method to create a Kotlin object, instead found ${_withArgsCreator.annotated.javaClass.name}")
- } ?: return super.createFromObjectWith(
- ctxt,
- props,
- buffer
- ) // we cannot reflect this method so do the default Java-ish behavior
-
- if (callable.extensionReceiverParameter != null) {
- // we shouldn't have an instance or receiver parameter and if we do, just go with default Java-ish behavior
- return super.createFromObjectWith(ctxt, props, buffer)
- }
-
- val propCount = props.size + if (callable.instanceParameter != null) 1 else 0
-
- var numCallableParameters = 0
- val callableParameters = arrayOfNulls<KParameter>(propCount)
- val jsonParamValueList = arrayOfNulls<Any>(propCount)
-
- if (callable.instanceParameter != null) {
- val possibleCompanion = callable.instanceParameter!!.type.erasedType().kotlin
-
- if (!possibleCompanion.isCompanion) {
- // abort, we have some unknown case here
- return super.createFromObjectWith(ctxt, props, buffer)
- }
-
- // TODO: cache this lookup since the exception throwing/catching can be expensive
- jsonParamValueList[numCallableParameters] = try {
- possibleCompanion.objectInstance
- } catch (ex: IllegalAccessException) {
- // fallback for when an odd access exception happens through Kotlin reflection
- val companionField = possibleCompanion.java.enclosingClass.fields.firstOrNull { it.type.kotlin.isCompanion }
- ?: throw ex
- val accessible = companionField.isAccessible
- if ((!accessible && ctxt.config.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) ||
- (accessible && ctxt.config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS))
- ) {
- companionField.isAccessible = true
- }
- companionField.get(null) ?: throw ex
- }
-
- callableParameters[numCallableParameters] = callable.instanceParameter
- numCallableParameters++
+ // TODO: cache ValueCreator
+ val valueCreator: ValueCreator<*> = ValueCreator.of(_withArgsCreator, cache)
+ ?: return super.createFromObjectWith(ctxt, props, buffer)
+
+ val propCount: Int
+ var numCallableParameters: Int
+ val callableParameters: Array<KParameter?>
+ val jsonParamValueList: Array<Any?>
+
+ if (valueCreator is MethodValueCreator) {
+ propCount = props.size + 1
+ numCallableParameters = 1
+ callableParameters = arrayOfNulls<KParameter>(propCount)
+ .apply { this[0] = valueCreator.instanceParameter }
+ jsonParamValueList = arrayOfNulls<Any>(propCount)
+ .apply { this[0] = valueCreator.companionObjectInstance }
+ } else {
+ propCount = props.size
+ numCallableParameters = 0
+ callableParameters = arrayOfNulls(propCount)
+ jsonParamValueList = arrayOfNulls(propCount)
}
- callable.valueParameters.forEachIndexed { idx, paramDef ->
+ valueCreator.valueParameters.forEachIndexed { idx, paramDef ->
val jsonProp = props[idx]
val isMissing = !buffer.hasParameter(jsonProp)
@@ -157,23 +121,17 @@ internal class KotlinValueInstantiator(
numCallableParameters++
}
- return if (numCallableParameters == jsonParamValueList.size && callable.instanceParameter == null) {
+ return if (numCallableParameters == jsonParamValueList.size && valueCreator is ConstructorValueCreator) {
// we didn't do anything special with default parameters, do a normal call
super.createFromObjectWith(ctxt, jsonParamValueList)
} else {
- val accessible = callable.isAccessible
- if ((!accessible && ctxt.config.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) ||
- (accessible && ctxt.config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS))
- ) {
- callable.isAccessible = true
- }
val callableParametersByName = linkedMapOf<KParameter, Any?>()
callableParameters.mapIndexed { idx, paramDef ->
if (paramDef != null) {
callableParametersByName[paramDef] = jsonParamValueList[idx]
}
}
- callable.callBy(callableParametersByName)
+ valueCreator.callBy(callableParametersByName)
}
}

original_message: use ValueCreator temp


message: fix to use cache

diff:
@@ -27,8 +27,7 @@ internal class KotlinValueInstantiator(
props: Array<out SettableBeanProperty>,
buffer: PropertyValueBuffer
): Any? {
- // TODO: cache ValueCreator
- val valueCreator: ValueCreator<*> = ValueCreator.of(_withArgsCreator, cache)
+ val valueCreator: ValueCreator<*> = cache.valueCreatorFromJava(_withArgsCreator)
?: return super.createFromObjectWith(ctxt, props, buffer)
val propCount: Int

diff:
@@ -3,6 +3,7 @@ package com.fasterxml.jackson.module.kotlin
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor
import com.fasterxml.jackson.databind.introspect.AnnotatedMember
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod
+import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams
import com.fasterxml.jackson.databind.util.LRUMap
import java.lang.reflect.Constructor
import java.lang.reflect.Method
@@ -35,6 +36,8 @@ internal class ReflectionCache(reflectionCacheSize: Int) {
private val javaClassToKotlin = LRUMap<Class<Any>, KClass<Any>>(reflectionCacheSize, reflectionCacheSize)
private val javaConstructorToKotlin = LRUMap<Constructor<Any>, KFunction<Any>>(reflectionCacheSize, reflectionCacheSize)
private val javaMethodToKotlin = LRUMap<Method, KFunction<*>>(reflectionCacheSize, reflectionCacheSize)
+ private val javaConstructorToValueCreator = LRUMap<Constructor<Any>, ConstructorValueCreator<*>>(reflectionCacheSize, reflectionCacheSize)
+ private val javaMethodToValueCreator = LRUMap<Method, MethodValueCreator<*>>(reflectionCacheSize, reflectionCacheSize)
private val javaConstructorIsCreatorAnnotated = LRUMap<AnnotatedConstructor, Boolean>(reflectionCacheSize, reflectionCacheSize)
private val javaMemberIsRequired = LRUMap<AnnotatedMember, BooleanTriState?>(reflectionCacheSize, reflectionCacheSize)
private val kotlinGeneratedMethod = LRUMap<AnnotatedMethod, Boolean>(reflectionCacheSize, reflectionCacheSize)
@@ -49,6 +52,37 @@ internal class ReflectionCache(reflectionCacheSize: Int) {
fun kotlinFromJava(key: Method): KFunction<*>? = javaMethodToKotlin.get(key)
?: key.kotlinFunction?.let { javaMethodToKotlin.putIfAbsent(key, it) ?: it }
+ /**
+ * return null if...
+ * - can't get kotlinFunction
+ * - contains extensionReceiverParameter
+ * - instance parameter is not companion object or can't get
+ */
+ @Suppress("UNCHECKED_CAST")
+ fun valueCreatorFromJava(_withArgsCreator: AnnotatedWithParams): ValueCreator<*>? = when (_withArgsCreator) {
+ is AnnotatedConstructor -> {
+ val constructor = _withArgsCreator.annotated as Constructor<Any>
+
+ javaConstructorToValueCreator.get(constructor)
+ ?: kotlinFromJava(constructor)?.let {
+ val value = ConstructorValueCreator(it)
+
+ javaConstructorToValueCreator.putIfAbsent(constructor, value) ?: value
+ }
+ }
+ is AnnotatedMethod -> {
+ val method = _withArgsCreator.annotated as Method
+
+ javaMethodToValueCreator.get(method)
+ ?: kotlinFromJava(method)?.let {
+ val value = MethodValueCreator.of(it)
+
+ javaMethodToValueCreator.putIfAbsent(method, value) ?: value
+ }
+ }
+ else -> throw IllegalStateException("Expected a constructor or method to create a Kotlin object, instead found ${_withArgsCreator.annotated.javaClass.name}")
+ } // we cannot reflect this method so do the default Java-ish behavior
+
fun checkConstructorIsCreatorAnnotated(key: AnnotatedConstructor, calc: (AnnotatedConstructor) -> Boolean): Boolean = javaConstructorIsCreatorAnnotated.get(key)
?: calc(key).let { javaConstructorIsCreatorAnnotated.putIfAbsent(key, it) ?: it }

diff:
@@ -2,11 +2,6 @@ package com.fasterxml.jackson.module.kotlin
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.MapperFeature
-import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor
-import com.fasterxml.jackson.databind.introspect.AnnotatedMethod
-import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams
-import java.lang.reflect.Constructor
-import java.lang.reflect.Method
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.full.valueParameters
@@ -48,17 +43,4 @@ internal sealed class ValueCreator<T> {
* Function call with default values enabled.
*/
fun callBy(args: Map<KParameter, Any?>): T = callable.callBy(args)
-
- companion object {
- @Suppress("UNCHECKED_CAST")
- fun of(
- _withArgsCreator: AnnotatedWithParams, cache: ReflectionCache
- ): ValueCreator<*>? = when (_withArgsCreator) {
- is AnnotatedConstructor -> cache.kotlinFromJava(_withArgsCreator.annotated as Constructor<Any>)
- ?.let { ConstructorValueCreator(it) }
- is AnnotatedMethod -> cache.kotlinFromJava(_withArgsCreator.annotated as Method)
- ?.let { MethodValueCreator.of(it) }
- else -> throw IllegalStateException("Expected a constructor or method to create a Kotlin object, instead found ${_withArgsCreator.annotated.javaClass.name}")
- } // we cannot reflect this method so do the default Java-ish behavior
- }
}

original_message: fix to use cache


message: add key deserializers

diff:
+package com.fasterxml.jackson.module.kotlin
+
+import com.fasterxml.jackson.core.JsonToken
+import com.fasterxml.jackson.core.exc.InputCoercionException
+import com.fasterxml.jackson.databind.*
+import com.fasterxml.jackson.databind.deser.std.StdKeyDeserializer
+import com.fasterxml.jackson.databind.deser.std.StdKeyDeserializers
+
+// The reason why key is treated as nullable is to match the tentative behavior of StdKeyDeserializer.
+// If StdKeyDeserializer is modified, need to modify this too.
+
+internal object UByteKeyDeserializer : StdKeyDeserializer(TYPE_SHORT, UByte::class.java) {
+ override fun deserializeKey(key: String?, ctxt: DeserializationContext): UByte? = super.deserializeKey(key, ctxt)
+ ?.let {
+ (it as Short).asUByte() ?: throw InputCoercionException(
+ null,
+ "Numeric value (${key}) out of range of UByte (0 - ${UByte.MAX_VALUE}).",
+ JsonToken.VALUE_NUMBER_INT,
+ UByte::class.java
+ )
+ }
+}
+
+internal object UShortKeyDeserializer : StdKeyDeserializer(TYPE_INT, UShort::class.java) {
+ override fun deserializeKey(key: String?, ctxt: DeserializationContext): UShort? = super.deserializeKey(key, ctxt)
+ ?.let {
+ (it as Int).asUShort() ?: throw InputCoercionException(
+ null,
+ "Numeric value (${key}) out of range of UShort (0 - ${UShort.MAX_VALUE}).",
+ JsonToken.VALUE_NUMBER_INT,
+ UShort::class.java
+ )
+ }
+}
+
+internal object UIntKeyDeserializer : StdKeyDeserializer(TYPE_LONG, UInt::class.java) {
+ override fun deserializeKey(key: String?, ctxt: DeserializationContext): UInt? = super.deserializeKey(key, ctxt)
+ ?.let {
+ (it as Long).asUInt() ?: throw InputCoercionException(
+ null,
+ "Numeric value (${key}) out of range of UInt (0 - ${UInt.MAX_VALUE}).",
+ JsonToken.VALUE_NUMBER_INT,
+ UInt::class.java
+ )
+ }
+}
+
+// kind parameter is dummy.
+internal object ULongKeyDeserializer : StdKeyDeserializer(TYPE_LONG, ULong::class.java) {
+ override fun deserializeKey(key: String?, ctxt: DeserializationContext): ULong? = key?.let {
+ it.toBigInteger().asULong() ?: throw InputCoercionException(
+ null,
+ "Numeric value (${key}) out of range of ULong (0 - ${ULong.MAX_VALUE}).",
+ JsonToken.VALUE_NUMBER_INT,
+ ULong::class.java
+ )
+ }
+}
+
+internal object KotlinKeyDeserializers : StdKeyDeserializers() {
+ override fun findKeyDeserializer(
+ type: JavaType,
+ config: DeserializationConfig?,
+ beanDesc: BeanDescription?
+ ): KeyDeserializer? = when (type.rawClass) {
+ UByte::class.java -> UByteKeyDeserializer
+ UShort::class.java -> UShortKeyDeserializer
+ UInt::class.java -> UIntKeyDeserializer
+ ULong::class.java -> ULongKeyDeserializer
+ else -> null
+ }
+}

original_message: add key deserializers


message:
Removed Suppression in line with Kotlin update.
The unsigned integers have been stabled since Kotlin 1.5.

diff:
-@file:Suppress("EXPERIMENTAL_API_USAGE")
-
package com.fasterxml.jackson.module.kotlin
import com.fasterxml.jackson.core.JsonParser

diff:
-@file:Suppress("EXPERIMENTAL_API_USAGE")
-
package com.fasterxml.jackson.module.kotlin
import com.fasterxml.jackson.core.JsonGenerator
@@ -57,7 +55,6 @@ object ValueClassUnboxSerializer : StdSerializer<Any>(Any::class.java) {
}
}
-@Suppress("EXPERIMENTAL_API_USAGE")
internal class KotlinSerializers : Serializers.Base() {
override fun findSerializer(
config: SerializationConfig?,

diff:
-@file:Suppress("EXPERIMENTAL_API_USAGE")
-
package com.fasterxml.jackson.module.kotlin
import java.math.BigInteger

diff:
-@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
-
package com.fasterxml.jackson.module.kotlin.test
import com.fasterxml.jackson.core.exc.InputCoercionException

diff:
@@ -52,7 +52,6 @@ data class ClassWithInlineMember(val inlineClassProperty: InlineClass) {
}
}
-@Suppress("EXPERIMENTAL_FEATURE_WARNING") // Enabled in test-compile
@JvmInline
value class ValueClass(val value: String)