Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(7)

Side by Side Diff: pkg/analyzer/lib/src/summary/flat_buffers.dart

Issue 1998833002: Use BufferContext + offset instead of BufferPointer. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: tweaks Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | pkg/analyzer/lib/src/summary/format.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library analyzer.src.summary.flat_buffers; 5 library analyzer.src.summary.flat_buffers;
6 6
7 import 'dart:collection'; 7 import 'dart:collection';
8 import 'dart:convert'; 8 import 'dart:convert';
9 import 'dart:math'; 9 import 'dart:math';
10 import 'dart:typed_data'; 10 import 'dart:typed_data';
11 11
12 /** 12 /**
13 * Reader of lists of boolean values. 13 * Reader of lists of boolean values.
14 * 14 *
15 * The returned unmodifiable lists lazily read values on access. 15 * The returned unmodifiable lists lazily read values on access.
16 */ 16 */
17 class BoolListReader extends Reader<List<bool>> { 17 class BoolListReader extends Reader<List<bool>> {
18 const BoolListReader(); 18 const BoolListReader();
19 19
20 @override 20 @override
21 int get size => 4; 21 int get size => 4;
22 22
23 @override 23 @override
24 List<bool> read(BufferPointer bp) => new _FbBoolList(bp.derefObject()); 24 List<bool> read(BufferContext bc, int offset) =>
25 new _FbBoolList(bc, bc.derefObject(offset));
25 } 26 }
26 27
27 /** 28 /**
28 * The reader of booleans. 29 * The reader of booleans.
29 */ 30 */
30 class BoolReader extends Reader<bool> { 31 class BoolReader extends Reader<bool> {
31 const BoolReader() : super(); 32 const BoolReader() : super();
32 33
33 @override 34 @override
34 int get size => 1; 35 int get size => 1;
35 36
36 @override 37 @override
37 bool read(BufferPointer bp) => bp._getInt8() != 0; 38 bool read(BufferContext bc, int offset) => bc._getInt8(offset) != 0;
38 } 39 }
39 40
40 /** 41 /**
41 * A pointer to some data. 42 * Buffer with data and some context about it.
42 */ 43 */
43 class BufferPointer { 44 class BufferContext {
44 final ByteData _buffer; 45 final ByteData _buffer;
45 final int _offset;
46 46
47 factory BufferPointer.fromBytes(List<int> byteList, [int offset = 0]) { 47 factory BufferContext.fromBytes(List<int> byteList) {
48 Uint8List uint8List = _asUint8List(byteList); 48 Uint8List uint8List = _asUint8List(byteList);
49 ByteData buf = new ByteData.view(uint8List.buffer); 49 ByteData buf = new ByteData.view(uint8List.buffer, uint8List.offsetInBytes);
50 return new BufferPointer._(buf, uint8List.offsetInBytes + offset); 50 return new BufferContext._(buf);
51 } 51 }
52 52
53 BufferPointer._(this._buffer, this._offset); 53 BufferContext._(this._buffer);
54 54
55 BufferPointer derefObject() { 55 int derefObject(int offset) {
56 int uOffset = _getUint32(); 56 return offset + _getUint32(offset);
57 return _advance(uOffset);
58 } 57 }
59 58
60 @override 59 Uint8List _asUint8LIst(int offset, int length) =>
61 String toString() => _offset.toString(); 60 _buffer.buffer.asUint8List(_buffer.offsetInBytes + offset, length);
62 61
63 BufferPointer _advance(int delta) { 62 double _getFloat64(int offset) =>
64 return new BufferPointer._(_buffer, _offset + delta); 63 _buffer.getFloat64(offset, Endianness.LITTLE_ENDIAN);
65 }
66 64
67 double _getFloat64([int delta = 0]) => 65 int _getInt32(int offset) =>
68 _buffer.getFloat64(_offset + delta, Endianness.LITTLE_ENDIAN); 66 _buffer.getInt32(offset, Endianness.LITTLE_ENDIAN);
69 67
70 int _getInt32([int delta = 0]) => 68 int _getInt8(int offset) => _buffer.getInt8(offset);
71 _buffer.getInt32(_offset + delta, Endianness.LITTLE_ENDIAN);
72 69
73 int _getInt8([int delta = 0]) => _buffer.getInt8(_offset + delta); 70 int _getUint16(int offset) =>
71 _buffer.getUint16(offset, Endianness.LITTLE_ENDIAN);
74 72
75 int _getUint16([int delta = 0]) => 73 int _getUint32(int offset) =>
76 _buffer.getUint16(_offset + delta, Endianness.LITTLE_ENDIAN); 74 _buffer.getUint32(offset, Endianness.LITTLE_ENDIAN);
77 75
78 int _getUint32([int delta = 0]) => 76 int _getUint8(int offset) => _buffer.getUint8(offset);
79 _buffer.getUint32(_offset + delta, Endianness.LITTLE_ENDIAN);
80
81 int _getUint8([int delta = 0]) => _buffer.getUint8(_offset + delta);
82 77
83 /** 78 /**
84 * If the [byteList] is already a [Uint8List] return it. 79 * If the [byteList] is already a [Uint8List] return it.
85 * Otherwise return a [Uint8List] copy of the [byteList]. 80 * Otherwise return a [Uint8List] copy of the [byteList].
86 */ 81 */
87 static Uint8List _asUint8List(List<int> byteList) { 82 static Uint8List _asUint8List(List<int> byteList) {
88 if (byteList is Uint8List) { 83 if (byteList is Uint8List) {
89 return byteList; 84 return byteList;
90 } else { 85 } else {
91 return new Uint8List.fromList(byteList); 86 return new Uint8List.fromList(byteList);
(...skipping 464 matching lines...) Expand 10 before | Expand all | Expand 10 after
556 * 551 *
557 * The returned unmodifiable lists lazily read values on access. 552 * The returned unmodifiable lists lazily read values on access.
558 */ 553 */
559 class Float64ListReader extends Reader<List<double>> { 554 class Float64ListReader extends Reader<List<double>> {
560 const Float64ListReader(); 555 const Float64ListReader();
561 556
562 @override 557 @override
563 int get size => 4; 558 int get size => 4;
564 559
565 @override 560 @override
566 List<double> read(BufferPointer bp) => new _FbFloat64List(bp.derefObject()); 561 List<double> read(BufferContext bc, int offset) =>
562 new _FbFloat64List(bc, bc.derefObject(offset));
567 } 563 }
568 564
569 /** 565 /**
570 * The reader of signed 32-bit integers. 566 * The reader of signed 32-bit integers.
571 */ 567 */
572 class Int32Reader extends Reader<int> { 568 class Int32Reader extends Reader<int> {
573 const Int32Reader() : super(); 569 const Int32Reader() : super();
574 570
575 @override 571 @override
576 int get size => 4; 572 int get size => 4;
577 573
578 @override 574 @override
579 int read(BufferPointer bp) => bp._getInt32(); 575 int read(BufferContext bc, int offset) => bc._getInt32(offset);
580 } 576 }
581 577
582 /** 578 /**
583 * The reader of 8-bit signed integers. 579 * The reader of 8-bit signed integers.
584 */ 580 */
585 class Int8Reader extends Reader<int> { 581 class Int8Reader extends Reader<int> {
586 const Int8Reader() : super(); 582 const Int8Reader() : super();
587 583
588 @override 584 @override
589 int get size => 1; 585 int get size => 1;
590 586
591 @override 587 @override
592 int read(BufferPointer bp) => bp._getInt8(); 588 int read(BufferContext bc, int offset) => bc._getInt8(offset);
593 } 589 }
594 590
595 /** 591 /**
596 * The reader of lists of objects. 592 * The reader of lists of objects.
597 * 593 *
598 * The returned unmodifiable lists lazily read objects on access. 594 * The returned unmodifiable lists lazily read objects on access.
599 */ 595 */
600 class ListReader<E> extends Reader<List<E>> { 596 class ListReader<E> extends Reader<List<E>> {
601 final Reader<E> _elementReader; 597 final Reader<E> _elementReader;
602 598
603 const ListReader(this._elementReader); 599 const ListReader(this._elementReader);
604 600
605 @override 601 @override
606 int get size => 4; 602 int get size => 4;
607 603
608 @override 604 @override
609 List<E> read(BufferPointer bp) => 605 List<E> read(BufferContext bc, int offset) =>
610 new _FbGenericList<E>(_elementReader, bp.derefObject()); 606 new _FbGenericList<E>(_elementReader, bc, bc.derefObject(offset));
611 } 607 }
612 608
613 /** 609 /**
614 * The offset from the end of the buffer to a serialized object of the type [T]. 610 * The offset from the end of the buffer to a serialized object of the type [T].
615 */ 611 */
616 class Offset<T> { 612 class Offset<T> {
617 final int _tail; 613 final int _tail;
618 614
619 Offset(this._tail); 615 Offset(this._tail);
620 } 616 }
621 617
622 /** 618 /**
623 * Object that can read a value at a [BufferPointer]. 619 * Object that can read a value at a [BufferContext].
624 */ 620 */
625 abstract class Reader<T> { 621 abstract class Reader<T> {
626 const Reader(); 622 const Reader();
627 623
628 /** 624 /**
629 * The size of the value in bytes. 625 * The size of the value in bytes.
630 */ 626 */
631 int get size; 627 int get size;
632 628
633 /** 629 /**
634 * Read the value at the given pointer. 630 * Read the value at the given [offset] in [bc].
635 */ 631 */
636 T read(BufferPointer bp); 632 T read(BufferContext bc, int offset);
637 633
638 /** 634 /**
639 * Read the value of the given [field] in the given [object]. 635 * Read the value of the given [field] in the given [object].
640 */ 636 */
641 T vTableGet(BufferPointer object, int field, [T defaultValue]) { 637 T vTableGet(BufferContext object, int offset, int field, [T defaultValue]) {
642 int vTableSOffset = object._getInt32(); 638 int vTableSOffset = object._getInt32(offset);
643 BufferPointer vTable = object._advance(-vTableSOffset); 639 int vTableOffset = offset - vTableSOffset;
644 int vTableSize = vTable._getUint16(); 640 int vTableSize = object._getUint16(vTableOffset);
645 int vTableFieldOffset = (1 + 1 + field) * 2; 641 int vTableFieldOffset = (1 + 1 + field) * 2;
646 if (vTableFieldOffset < vTableSize) { 642 if (vTableFieldOffset < vTableSize) {
647 int fieldOffsetInObject = vTable._getUint16(vTableFieldOffset); 643 int fieldOffsetInObject =
644 object._getUint16(vTableOffset + vTableFieldOffset);
648 if (fieldOffsetInObject != 0) { 645 if (fieldOffsetInObject != 0) {
649 BufferPointer fieldPointer = object._advance(fieldOffsetInObject); 646 return read(object, offset + fieldOffsetInObject);
650 return read(fieldPointer);
651 } 647 }
652 } 648 }
653 return defaultValue; 649 return defaultValue;
654 } 650 }
655 } 651 }
656 652
657 /** 653 /**
658 * The reader of string values. 654 * The reader of string values.
659 */ 655 */
660 class StringReader extends Reader<String> { 656 class StringReader extends Reader<String> {
661 const StringReader() : super(); 657 const StringReader() : super();
662 658
663 @override 659 @override
664 int get size => 4; 660 int get size => 4;
665 661
666 @override 662 @override
667 String read(BufferPointer ref) { 663 String read(BufferContext bc, int offset) {
668 BufferPointer object = ref.derefObject(); 664 int strOffset = bc.derefObject(offset);
669 int length = object._getUint32(); 665 int length = bc._getUint32(strOffset);
670 Uint8List bytes = ref._buffer.buffer.asUint8List(object._offset + 4, length) ; 666 Uint8List bytes = bc._asUint8LIst(strOffset + 4, length);
671 if (_isLatin(bytes)) { 667 if (_isLatin(bytes)) {
672 return new String.fromCharCodes(bytes); 668 return new String.fromCharCodes(bytes);
673 } 669 }
674 return UTF8.decode(bytes); 670 return UTF8.decode(bytes);
675 } 671 }
676 672
677 static bool _isLatin(Uint8List bytes) { 673 static bool _isLatin(Uint8List bytes) {
678 int length = bytes.length; 674 int length = bytes.length;
679 for (int i = 0; i < length; i++) { 675 for (int i = 0; i < length; i++) {
680 if (bytes[i] > 127) { 676 if (bytes[i] > 127) {
681 return false; 677 return false;
682 } 678 }
683 } 679 }
684 return true; 680 return true;
685 } 681 }
686 } 682 }
687 683
688 /** 684 /**
689 * An abstract reader for tables. 685 * An abstract reader for tables.
690 */ 686 */
691 abstract class TableReader<T> extends Reader<T> { 687 abstract class TableReader<T> extends Reader<T> {
692 const TableReader(); 688 const TableReader();
693 689
694 @override 690 @override
695 int get size => 4; 691 int get size => 4;
696 692
697 /** 693 /**
698 * Return the object at [bp]. 694 * Return the object at [offset].
699 */ 695 */
700 T createObject(BufferPointer bp); 696 T createObject(BufferContext bc, int offset);
701 697
702 @override 698 @override
703 T read(BufferPointer bp) { 699 T read(BufferContext bp, int offset) {
704 bp = bp.derefObject(); 700 int objectOffset = bp.derefObject(offset);
705 return createObject(bp); 701 return createObject(bp, objectOffset);
706 } 702 }
707 } 703 }
708 704
709 /** 705 /**
710 * Reader of lists of unsigned 32-bit integer values. 706 * Reader of lists of unsigned 32-bit integer values.
711 * 707 *
712 * The returned unmodifiable lists lazily read values on access. 708 * The returned unmodifiable lists lazily read values on access.
713 */ 709 */
714 class Uint32ListReader extends Reader<List<int>> { 710 class Uint32ListReader extends Reader<List<int>> {
715 const Uint32ListReader(); 711 const Uint32ListReader();
716 712
717 @override 713 @override
718 int get size => 4; 714 int get size => 4;
719 715
720 @override 716 @override
721 List<int> read(BufferPointer bp) => new _FbUint32List(bp.derefObject()); 717 List<int> read(BufferContext bc, int offset) =>
718 new _FbUint32List(bc, bc.derefObject(offset));
722 } 719 }
723 720
724 /** 721 /**
725 * The reader of unsigned 32-bit integers. 722 * The reader of unsigned 32-bit integers.
726 */ 723 */
727 class Uint32Reader extends Reader<int> { 724 class Uint32Reader extends Reader<int> {
728 const Uint32Reader() : super(); 725 const Uint32Reader() : super();
729 726
730 @override 727 @override
731 int get size => 4; 728 int get size => 4;
732 729
733 @override 730 @override
734 int read(BufferPointer bp) => bp._getUint32(); 731 int read(BufferContext bc, int offset) => bc._getUint32(offset);
735 } 732 }
736 733
737 /** 734 /**
738 * Reader of lists of unsigned 8-bit integer values. 735 * Reader of lists of unsigned 8-bit integer values.
739 * 736 *
740 * The returned unmodifiable lists lazily read values on access. 737 * The returned unmodifiable lists lazily read values on access.
741 */ 738 */
742 class Uint8ListReader extends Reader<List<int>> { 739 class Uint8ListReader extends Reader<List<int>> {
743 const Uint8ListReader(); 740 const Uint8ListReader();
744 741
745 @override 742 @override
746 int get size => 4; 743 int get size => 4;
747 744
748 @override 745 @override
749 List<int> read(BufferPointer bp) => new _FbUint8List(bp.derefObject()); 746 List<int> read(BufferContext bc, int offset) =>
747 new _FbUint8List(bc, bc.derefObject(offset));
750 } 748 }
751 749
752 /** 750 /**
753 * The reader of unsigned 8-bit integers. 751 * The reader of unsigned 8-bit integers.
754 */ 752 */
755 class Uint8Reader extends Reader<int> { 753 class Uint8Reader extends Reader<int> {
756 const Uint8Reader() : super(); 754 const Uint8Reader() : super();
757 755
758 @override 756 @override
759 int get size => 1; 757 int get size => 1;
760 758
761 @override 759 @override
762 int read(BufferPointer bp) => bp._getUint8(); 760 int read(BufferContext bc, int offset) => bc._getUint8(offset);
763 } 761 }
764 762
765 /** 763 /**
766 * List of booleans backed by 8-bit unsigned integers. 764 * List of booleans backed by 8-bit unsigned integers.
767 */ 765 */
768 class _FbBoolList extends Object with ListMixin<bool> implements List<bool> { 766 class _FbBoolList extends Object with ListMixin<bool> implements List<bool> {
769 final BufferPointer bp; 767 final BufferContext bc;
768 final int offset;
770 int _length; 769 int _length;
771 770
772 _FbBoolList(this.bp); 771 _FbBoolList(this.bc, this.offset);
773 772
774 @override 773 @override
775 int get length { 774 int get length {
776 if (_length == null) { 775 if (_length == null) {
777 int byteLength = bp._getUint32(); 776 int byteLength = bc._getUint32(offset);
778 _length = (byteLength - 1) * 8 - _getByte(byteLength - 1); 777 _length = (byteLength - 1) * 8 - _getByte(byteLength - 1);
779 } 778 }
780 return _length; 779 return _length;
781 } 780 }
782 781
783 @override 782 @override
784 void set length(int i) => 783 void set length(int i) =>
785 throw new StateError('Attempt to modify immutable list'); 784 throw new StateError('Attempt to modify immutable list');
786 785
787 @override 786 @override
788 bool operator [](int i) { 787 bool operator [](int i) {
789 int index = i ~/ 8; 788 int index = i ~/ 8;
790 int mask = 1 << i % 8; 789 int mask = 1 << i % 8;
791 return _getByte(index) & mask != 0; 790 return _getByte(index) & mask != 0;
792 } 791 }
793 792
794 @override 793 @override
795 void operator []=(int i, bool e) => 794 void operator []=(int i, bool e) =>
796 throw new StateError('Attempt to modify immutable list'); 795 throw new StateError('Attempt to modify immutable list');
797 796
798 int _getByte(int index) => bp._getUint8(4 + index); 797 int _getByte(int index) => bc._getUint8(offset + 4 + index);
799 } 798 }
800 799
801 /** 800 /**
802 * The list backed by 64-bit values - Uint64 length and Float64. 801 * The list backed by 64-bit values - Uint64 length and Float64.
803 */ 802 */
804 class _FbFloat64List extends _FbList<double> { 803 class _FbFloat64List extends _FbList<double> {
805 _FbFloat64List(BufferPointer bp) : super(bp); 804 _FbFloat64List(BufferContext bc, int offset) : super(bc, offset);
806 805
807 @override 806 @override
808 double operator [](int i) { 807 double operator [](int i) {
809 return bp._getFloat64(8 + 8 * i); 808 return bc._getFloat64(offset + 8 + 8 * i);
810 } 809 }
811 } 810 }
812 811
813 /** 812 /**
814 * List backed by a generic object which may have any size. 813 * List backed by a generic object which may have any size.
815 */ 814 */
816 class _FbGenericList<E> extends _FbList<E> { 815 class _FbGenericList<E> extends _FbList<E> {
817 final Reader<E> elementReader; 816 final Reader<E> elementReader;
818 817
819 List<E> _items; 818 List<E> _items;
820 819
821 _FbGenericList(this.elementReader, BufferPointer bp) : super(bp); 820 _FbGenericList(this.elementReader, BufferContext bp, int offset)
821 : super(bp, offset);
822 822
823 @override 823 @override
824 E operator [](int i) { 824 E operator [](int i) {
825 _items ??= new List<E>(length); 825 _items ??= new List<E>(length);
826 E item = _items[i]; 826 E item = _items[i];
827 if (item == null) { 827 if (item == null) {
828 BufferPointer ref = bp._advance(4 + elementReader.size * i); 828 item = elementReader.read(bc, offset + 4 + elementReader.size * i);
829 item = elementReader.read(ref);
830 _items[i] = item; 829 _items[i] = item;
831 } 830 }
832 return item; 831 return item;
833 } 832 }
834 } 833 }
835 834
836 /** 835 /**
837 * The base class for immutable lists read from flat buffers. 836 * The base class for immutable lists read from flat buffers.
838 */ 837 */
839 abstract class _FbList<E> extends Object with ListMixin<E> implements List<E> { 838 abstract class _FbList<E> extends Object with ListMixin<E> implements List<E> {
840 final BufferPointer bp; 839 final BufferContext bc;
840 final int offset;
841 int _length; 841 int _length;
842 842
843 _FbList(this.bp); 843 _FbList(this.bc, this.offset);
844 844
845 @override 845 @override
846 int get length { 846 int get length {
847 _length ??= bp._getUint32(); 847 _length ??= bc._getUint32(offset);
848 return _length; 848 return _length;
849 } 849 }
850 850
851 @override 851 @override
852 void set length(int i) => 852 void set length(int i) =>
853 throw new StateError('Attempt to modify immutable list'); 853 throw new StateError('Attempt to modify immutable list');
854 854
855 @override 855 @override
856 void operator []=(int i, E e) => 856 void operator []=(int i, E e) =>
857 throw new StateError('Attempt to modify immutable list'); 857 throw new StateError('Attempt to modify immutable list');
858 } 858 }
859 859
860 /** 860 /**
861 * List backed by 32-bit unsigned integers. 861 * List backed by 32-bit unsigned integers.
862 */ 862 */
863 class _FbUint32List extends _FbList<int> { 863 class _FbUint32List extends _FbList<int> {
864 _FbUint32List(BufferPointer bp) : super(bp); 864 _FbUint32List(BufferContext bc, int offset) : super(bc, offset);
865 865
866 @override 866 @override
867 int operator [](int i) { 867 int operator [](int i) {
868 return bp._getUint32(4 + 4 * i); 868 return bc._getUint32(offset + 4 + 4 * i);
869 } 869 }
870 } 870 }
871 871
872 /** 872 /**
873 * List backed by 8-bit unsigned integers. 873 * List backed by 8-bit unsigned integers.
874 */ 874 */
875 class _FbUint8List extends _FbList<int> { 875 class _FbUint8List extends _FbList<int> {
876 _FbUint8List(BufferPointer bp) : super(bp); 876 _FbUint8List(BufferContext bc, int offset) : super(bc, offset);
877 877
878 @override 878 @override
879 int operator [](int i) { 879 int operator [](int i) {
880 return bp._getUint8(4 + i); 880 return bc._getUint8(offset + 4 + i);
881 } 881 }
882 } 882 }
883 883
884 /** 884 /**
885 * Class that describes the structure of a table. 885 * Class that describes the structure of a table.
886 */ 886 */
887 class _VTable { 887 class _VTable {
888 final List<int> fieldTails = <int>[]; 888 final List<int> fieldTails = <int>[];
889 final List<int> fieldOffsets = <int>[]; 889 final List<int> fieldOffsets = <int>[];
890 890
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
948 // Table size. 948 // Table size.
949 buf.setUint16(bufOffset, tableSize, Endianness.LITTLE_ENDIAN); 949 buf.setUint16(bufOffset, tableSize, Endianness.LITTLE_ENDIAN);
950 bufOffset += 2; 950 bufOffset += 2;
951 // Field offsets. 951 // Field offsets.
952 for (int fieldOffset in fieldOffsets) { 952 for (int fieldOffset in fieldOffsets) {
953 buf.setUint16(bufOffset, fieldOffset, Endianness.LITTLE_ENDIAN); 953 buf.setUint16(bufOffset, fieldOffset, Endianness.LITTLE_ENDIAN);
954 bufOffset += 2; 954 bufOffset += 2;
955 } 955 }
956 } 956 }
957 } 957 }
OLDNEW
« no previous file with comments | « no previous file | pkg/analyzer/lib/src/summary/format.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698