Commit b3cfa1010f95514a13590266dd50677445f74309

Authored by Jay Berkenbilt
Committed by GitHub
2 parents e4e03e9a 07bb5c3d

Merge pull request #902 from m-holger/od

Refactor creation of object descriptions
include/qpdf/QPDF.hh
... ... @@ -1108,71 +1108,7 @@ class QPDF
1108 1108 std::set<QPDFObjGen>::const_iterator iter;
1109 1109 };
1110 1110  
1111   - class JSONReactor: public JSON::Reactor
1112   - {
1113   - public:
1114   - JSONReactor(
1115   - QPDF&, std::shared_ptr<InputSource> is, bool must_be_complete);
1116   - virtual ~JSONReactor() = default;
1117   - virtual void dictionaryStart() override;
1118   - virtual void arrayStart() override;
1119   - virtual void containerEnd(JSON const& value) override;
1120   - virtual void topLevelScalar() override;
1121   - virtual bool
1122   - dictionaryItem(std::string const& key, JSON const& value) override;
1123   - virtual bool arrayItem(JSON const& value) override;
1124   -
1125   - bool anyErrors() const;
1126   -
1127   - private:
1128   - enum state_e {
1129   - st_initial,
1130   - st_top,
1131   - st_qpdf,
1132   - st_qpdf_meta,
1133   - st_objects,
1134   - st_trailer,
1135   - st_object_top,
1136   - st_stream,
1137   - st_object,
1138   - st_ignore,
1139   - };
1140   -
1141   - void containerStart();
1142   - void nestedState(std::string const& key, JSON const& value, state_e);
1143   - void setObjectDescription(QPDFObjectHandle& oh, JSON const& value);
1144   - QPDFObjectHandle makeObject(JSON const& value);
1145   - void error(qpdf_offset_t offset, std::string const& message);
1146   - QPDFObjectHandle reserveObject(int obj, int gen);
1147   - void replaceObject(
1148   - QPDFObjectHandle to_replace,
1149   - QPDFObjectHandle replacement,
1150   - JSON const& value);
1151   -
1152   - QPDF& pdf;
1153   - std::shared_ptr<InputSource> is;
1154   - bool must_be_complete;
1155   - bool errors;
1156   - bool parse_error;
1157   - bool saw_qpdf;
1158   - bool saw_qpdf_meta;
1159   - bool saw_objects;
1160   - bool saw_json_version;
1161   - bool saw_pdf_version;
1162   - bool saw_trailer;
1163   - state_e state;
1164   - state_e next_state;
1165   - std::string cur_object;
1166   - bool saw_value;
1167   - bool saw_stream;
1168   - bool saw_dict;
1169   - bool saw_data;
1170   - bool saw_datafile;
1171   - bool this_stream_needs_data;
1172   - std::vector<state_e> state_stack;
1173   - std::vector<QPDFObjectHandle> object_stack;
1174   - std::set<QPDFObjGen> reserved;
1175   - };
  1111 + class JSONReactor;
1176 1112  
1177 1113 void parse(char const* password);
1178 1114 void inParse(bool);
... ...
libqpdf/QPDFObjectHandle.cc
... ... @@ -36,6 +36,8 @@
36 36 #include <stdexcept>
37 37 #include <stdlib.h>
38 38  
  39 +using namespace std::literals;
  40 +
39 41 namespace
40 42 {
41 43 class TerminateParsing
... ... @@ -800,12 +802,10 @@ QPDFObjectHandle::getArrayNItems()
800 802 QPDFObjectHandle
801 803 QPDFObjectHandle::getArrayItem(int n)
802 804 {
803   - QPDFObjectHandle result;
804 805 auto array = asArray();
805 806 if (array && (n < array->getNItems()) && (n >= 0)) {
806   - result = array->getItem(n);
  807 + return array->getItem(n);
807 808 } else {
808   - result = newNull();
809 809 if (array) {
810 810 objectWarning("returning null for out of bounds array access");
811 811 QTC::TC("qpdf", "QPDFObjectHandle array bounds");
... ... @@ -813,15 +813,10 @@ QPDFObjectHandle::getArrayItem(int n)
813 813 typeWarning("array", "returning null");
814 814 QTC::TC("qpdf", "QPDFObjectHandle array null for non-array");
815 815 }
816   - QPDF* context = nullptr;
817   - std::string description;
818   - if (obj->getDescription(context, description)) {
819   - result.setObjectDescription(
820   - context,
821   - description + " -> null returned from invalid array access");
822   - }
  816 + static auto constexpr msg =
  817 + " -> null returned from invalid array access"sv;
  818 + return QPDF_Null::create(obj, msg, "");
823 819 }
824   - return result;
825 820 }
826 821  
827 822 bool
... ... @@ -1030,24 +1025,15 @@ QPDFObjectHandle::hasKey(std::string const&amp; key)
1030 1025 QPDFObjectHandle
1031 1026 QPDFObjectHandle::getKey(std::string const& key)
1032 1027 {
1033   - QPDFObjectHandle result;
1034   - auto dict = asDictionary();
1035   - if (dict) {
1036   - result = dict->getKey(key);
  1028 + if (auto dict = asDictionary()) {
  1029 + return dict->getKey(key);
1037 1030 } else {
1038 1031 typeWarning("dictionary", "returning null for attempted key retrieval");
1039 1032 QTC::TC("qpdf", "QPDFObjectHandle dictionary null for getKey");
1040   - result = newNull();
1041   - QPDF* qpdf = nullptr;
1042   - std::string description;
1043   - if (obj->getDescription(qpdf, description)) {
1044   - result.setObjectDescription(
1045   - qpdf,
1046   - (description + " -> null returned from getting key " + key +
1047   - " from non-Dictionary"));
1048   - }
  1033 + static auto constexpr msg =
  1034 + " -> null returned from getting key $VD from non-Dictionary"sv;
  1035 + return QPDF_Null::create(obj, msg, "");
1049 1036 }
1050   - return result;
1051 1037 }
1052 1038  
1053 1039 QPDFObjectHandle
... ... @@ -2176,7 +2162,8 @@ QPDFObjectHandle::setObjectDescription(
2176 2162 // This is called during parsing on newly created direct objects,
2177 2163 // so we can't call dereference() here.
2178 2164 if (isInitialized() && obj.get()) {
2179   - auto descr = std::make_shared<std::string>(object_description);
  2165 + auto descr =
  2166 + std::make_shared<QPDFValue::Description>(object_description);
2180 2167 obj->setDescription(owning_qpdf, descr);
2181 2168 }
2182 2169 }
... ...
libqpdf/QPDFValue.cc
... ... @@ -9,3 +9,58 @@ QPDFValue::do_create(QPDFValue* object)
9 9 obj->value = std::shared_ptr<QPDFValue>(object);
10 10 return obj;
11 11 }
  12 +
  13 +std::string
  14 +QPDFValue::getDescription()
  15 +{
  16 + if (object_description) {
  17 + switch (object_description->index()) {
  18 + case 0:
  19 + {
  20 + // Simple template string
  21 + auto description = std::get<0>(*object_description);
  22 +
  23 + if (auto pos = description.find("$OG");
  24 + pos != std::string::npos) {
  25 + description.replace(pos, 3, og.unparse(' '));
  26 + }
  27 + if (auto pos = description.find("$PO");
  28 + pos != std::string::npos) {
  29 + qpdf_offset_t shift = (type_code == ::ot_dictionary) ? 2
  30 + : (type_code == ::ot_array) ? 1
  31 + : 0;
  32 +
  33 + description.replace(
  34 + pos, 3, std::to_string(parsed_offset + shift));
  35 + }
  36 + return description;
  37 + }
  38 + case 1:
  39 + {
  40 + // QPDF::JSONReactor generated description
  41 + auto j_descr = std::get<1>(*object_description);
  42 + return (
  43 + *j_descr.input +
  44 + (j_descr.object.empty() ? "" : ", " + j_descr.object) +
  45 + " at offset " + std::to_string(parsed_offset));
  46 + }
  47 + case 2:
  48 + {
  49 + // Child object description
  50 + auto j_descr = std::get<2>(*object_description);
  51 + std::string result;
  52 + if (auto p = j_descr.parent.lock()) {
  53 + result = p->getDescription();
  54 + }
  55 + result += j_descr.static_descr;
  56 + if (auto pos = result.find("$VD"); pos != std::string::npos) {
  57 + result.replace(pos, 3, j_descr.var_descr);
  58 + }
  59 + return result;
  60 + }
  61 + }
  62 + } else if (og.isIndirect()) {
  63 + return "object " + og.unparse(' ');
  64 + }
  65 + return {};
  66 +}
... ...
libqpdf/QPDF_Dictionary.cc
1 1 #include <qpdf/QPDF_Dictionary.hh>
2 2  
  3 +#include <qpdf/QPDFObject_private.hh>
3 4 #include <qpdf/QPDF_Name.hh>
  5 +#include <qpdf/QPDF_Null.hh>
  6 +
  7 +using namespace std::literals;
4 8  
5 9 QPDF_Dictionary::QPDF_Dictionary(
6 10 std::map<std::string, QPDFObjectHandle> const& items) :
... ... @@ -97,12 +101,8 @@ QPDF_Dictionary::getKey(std::string const&amp; key)
97 101 // May be a null object
98 102 return item->second;
99 103 } else {
100   - auto null = QPDFObjectHandle::newNull();
101   - if (qpdf != nullptr) {
102   - null.setObjectDescription(
103   - qpdf, getDescription() + " -> dictionary key " + key);
104   - }
105   - return null;
  104 + static auto constexpr msg = " -> dictionary key $VD"sv;
  105 + return QPDF_Null::create(shared_from_this(), msg, key);
106 106 }
107 107 }
108 108  
... ...
libqpdf/QPDF_Null.cc
1 1 #include <qpdf/QPDF_Null.hh>
2 2  
  3 +#include <qpdf/QPDFObject_private.hh>
  4 +
3 5 QPDF_Null::QPDF_Null() :
4 6 QPDFValue(::ot_null, "null")
5 7 {
... ... @@ -12,6 +14,28 @@ QPDF_Null::create()
12 14 }
13 15  
14 16 std::shared_ptr<QPDFObject>
  17 +QPDF_Null::create(
  18 + std::shared_ptr<QPDFObject> parent,
  19 + std::string_view const& static_descr,
  20 + std::string var_descr)
  21 +{
  22 + auto n = do_create(new QPDF_Null());
  23 + n->setChildDescription(parent, static_descr, var_descr);
  24 + return n;
  25 +}
  26 +
  27 +std::shared_ptr<QPDFObject>
  28 +QPDF_Null::create(
  29 + std::shared_ptr<QPDFValue> parent,
  30 + std::string_view const& static_descr,
  31 + std::string var_descr)
  32 +{
  33 + auto n = do_create(new QPDF_Null());
  34 + n->setChildDescription(parent, static_descr, var_descr);
  35 + return n;
  36 +}
  37 +
  38 +std::shared_ptr<QPDFObject>
15 39 QPDF_Null::copy(bool shallow)
16 40 {
17 41 return create();
... ...
libqpdf/QPDF_Stream.cc
... ... @@ -123,7 +123,7 @@ QPDF_Stream::QPDF_Stream(
123 123 throw std::logic_error("stream object instantiated with non-dictionary "
124 124 "object for dictionary");
125 125 }
126   - auto descr = std::make_shared<std::string>(
  126 + auto descr = std::make_shared<QPDFValue::Description>(
127 127 qpdf->getFilename() + ", stream object " + og.unparse(' '));
128 128 setDescription(qpdf, descr, offset);
129 129 }
... ... @@ -283,7 +283,9 @@ QPDF_Stream::getStreamJSON(
283 283  
284 284 void
285 285 QPDF_Stream::setDescription(
286   - QPDF* qpdf, std::shared_ptr<std::string>& description, qpdf_offset_t offset)
  286 + QPDF* qpdf,
  287 + std::shared_ptr<QPDFValue::Description>& description,
  288 + qpdf_offset_t offset)
287 289 {
288 290 this->QPDFValue::setDescription(qpdf, description, offset);
289 291 setDictDescription();
... ...
libqpdf/QPDF_json.cc
... ... @@ -4,6 +4,8 @@
4 4 #include <qpdf/Pl_Base64.hh>
5 5 #include <qpdf/Pl_StdioFile.hh>
6 6 #include <qpdf/QIntC.hh>
  7 +#include <qpdf/QPDFObject_private.hh>
  8 +#include <qpdf/QPDFValue.hh>
7 9 #include <qpdf/QTC.hh>
8 10 #include <qpdf/QUtil.hh>
9 11 #include <algorithm>
... ... @@ -221,11 +223,79 @@ provide_data(
221 223 };
222 224 }
223 225  
  226 +class QPDF::JSONReactor: public JSON::Reactor
  227 +{
  228 + public:
  229 + JSONReactor(QPDF&, std::shared_ptr<InputSource> is, bool must_be_complete);
  230 + virtual ~JSONReactor() = default;
  231 + virtual void dictionaryStart() override;
  232 + virtual void arrayStart() override;
  233 + virtual void containerEnd(JSON const& value) override;
  234 + virtual void topLevelScalar() override;
  235 + virtual bool
  236 + dictionaryItem(std::string const& key, JSON const& value) override;
  237 + virtual bool arrayItem(JSON const& value) override;
  238 +
  239 + bool anyErrors() const;
  240 +
  241 + private:
  242 + enum state_e {
  243 + st_initial,
  244 + st_top,
  245 + st_qpdf,
  246 + st_qpdf_meta,
  247 + st_objects,
  248 + st_trailer,
  249 + st_object_top,
  250 + st_stream,
  251 + st_object,
  252 + st_ignore,
  253 + };
  254 +
  255 + void containerStart();
  256 + void nestedState(std::string const& key, JSON const& value, state_e);
  257 + void setObjectDescription(QPDFObjectHandle& oh, JSON const& value);
  258 + QPDFObjectHandle makeObject(JSON const& value);
  259 + void error(qpdf_offset_t offset, std::string const& message);
  260 + QPDFObjectHandle reserveObject(int obj, int gen);
  261 + void replaceObject(
  262 + QPDFObjectHandle to_replace,
  263 + QPDFObjectHandle replacement,
  264 + JSON const& value);
  265 +
  266 + QPDF& pdf;
  267 + std::shared_ptr<InputSource> is;
  268 + bool must_be_complete;
  269 + std::shared_ptr<QPDFValue::Description> descr;
  270 + bool errors;
  271 + bool parse_error;
  272 + bool saw_qpdf;
  273 + bool saw_qpdf_meta;
  274 + bool saw_objects;
  275 + bool saw_json_version;
  276 + bool saw_pdf_version;
  277 + bool saw_trailer;
  278 + state_e state;
  279 + state_e next_state;
  280 + std::string cur_object;
  281 + bool saw_value;
  282 + bool saw_stream;
  283 + bool saw_dict;
  284 + bool saw_data;
  285 + bool saw_datafile;
  286 + bool this_stream_needs_data;
  287 + std::vector<state_e> state_stack;
  288 + std::vector<QPDFObjectHandle> object_stack;
  289 + std::set<QPDFObjGen> reserved;
  290 +};
  291 +
224 292 QPDF::JSONReactor::JSONReactor(
225 293 QPDF& pdf, std::shared_ptr<InputSource> is, bool must_be_complete) :
226 294 pdf(pdf),
227 295 is(is),
228 296 must_be_complete(must_be_complete),
  297 + descr(std::make_shared<QPDFValue::Description>(QPDFValue::JSON_Descr(
  298 + std::make_shared<std::string>(is->getName()), ""))),
229 299 errors(false),
230 300 parse_error(false),
231 301 saw_qpdf(false),
... ... @@ -675,12 +745,13 @@ QPDF::JSONReactor::arrayItem(JSON const&amp; value)
675 745 void
676 746 QPDF::JSONReactor::setObjectDescription(QPDFObjectHandle& oh, JSON const& value)
677 747 {
678   - std::string description = this->is->getName();
679   - if (!this->cur_object.empty()) {
680   - description += ", " + this->cur_object;
  748 + auto j_descr = std::get<QPDFValue::JSON_Descr>(*descr);
  749 + if (j_descr.object != cur_object) {
  750 + descr = std::make_shared<QPDFValue::Description>(
  751 + QPDFValue::JSON_Descr(j_descr.input, cur_object));
681 752 }
682   - description += " at offset " + std::to_string(value.getStart());
683   - oh.setObjectDescription(&this->pdf, description);
  753 +
  754 + oh.getObjectPtr()->setDescription(&pdf, descr, value.getStart());
684 755 }
685 756  
686 757 QPDFObjectHandle
... ...
libqpdf/qpdf/QPDFObject_private.hh
... ... @@ -12,6 +12,7 @@
12 12 #include <qpdf/Types.h>
13 13  
14 14 #include <string>
  15 +#include <string_view>
15 16  
16 17 class QPDF;
17 18 class QPDFObjectHandle;
... ... @@ -71,11 +72,30 @@ class QPDFObject
71 72 void
72 73 setDescription(
73 74 QPDF* qpdf,
74   - std::shared_ptr<std::string>& description,
  75 + std::shared_ptr<QPDFValue::Description>& description,
75 76 qpdf_offset_t offset = -1)
76 77 {
77 78 return value->setDescription(qpdf, description, offset);
78 79 }
  80 + void
  81 + setChildDescription(
  82 + std::shared_ptr<QPDFObject> parent,
  83 + std::string_view const& static_descr,
  84 + std::string var_descr)
  85 + {
  86 + auto qpdf = parent ? parent->value->qpdf : nullptr;
  87 + value->setChildDescription(
  88 + qpdf, parent->value, static_descr, var_descr);
  89 + }
  90 + void
  91 + setChildDescription(
  92 + std::shared_ptr<QPDFValue> parent,
  93 + std::string_view const& static_descr,
  94 + std::string var_descr)
  95 + {
  96 + auto qpdf = parent ? parent->qpdf : nullptr;
  97 + value->setChildDescription(qpdf, parent, static_descr, var_descr);
  98 + }
79 99 bool
80 100 getDescription(QPDF*& qpdf, std::string& description)
81 101 {
... ...
libqpdf/qpdf/QPDFParser.hh
... ... @@ -2,6 +2,7 @@
2 2 #define QPDFPARSER_HH
3 3  
4 4 #include <qpdf/QPDFObjectHandle.hh>
  5 +#include <qpdf/QPDFValue.hh>
5 6  
6 7 #include <memory>
7 8 #include <string>
... ... @@ -21,8 +22,8 @@ class QPDFParser
21 22 tokenizer(tokenizer),
22 23 decrypter(decrypter),
23 24 context(context),
24   - description(std::make_shared<std::string>(
25   - input->getName() + ", " + object_description + " at offset $PO"))
  25 + description(std::make_shared<QPDFValue::Description>(std::string(
  26 + input->getName() + ", " + object_description + " at offset $PO")))
26 27 {
27 28 }
28 29 virtual ~QPDFParser() = default;
... ... @@ -49,7 +50,7 @@ class QPDFParser
49 50 QPDFTokenizer& tokenizer;
50 51 QPDFObjectHandle::StringDecrypter* decrypter;
51 52 QPDF* context;
52   - std::shared_ptr<std::string> description;
  53 + std::shared_ptr<QPDFValue::Description> description;
53 54 };
54 55  
55 56 #endif // QPDFPARSER_HH
... ...
libqpdf/qpdf/QPDFValue.hh
... ... @@ -8,12 +8,14 @@
8 8 #include <qpdf/Types.h>
9 9  
10 10 #include <string>
  11 +#include <string_view>
  12 +#include <variant>
11 13  
12 14 class QPDF;
13 15 class QPDFObjectHandle;
14 16 class QPDFObject;
15 17  
16   -class QPDFValue
  18 +class QPDFValue: public std::enable_shared_from_this<QPDFValue>
17 19 {
18 20 friend class QPDFObject;
19 21  
... ... @@ -23,10 +25,43 @@ class QPDFValue
23 25 virtual std::shared_ptr<QPDFObject> copy(bool shallow = false) = 0;
24 26 virtual std::string unparse() = 0;
25 27 virtual JSON getJSON(int json_version) = 0;
  28 +
  29 + struct JSON_Descr
  30 + {
  31 + JSON_Descr(
  32 + std::shared_ptr<std::string> input, std::string const& object) :
  33 + input(input),
  34 + object(object)
  35 + {
  36 + }
  37 +
  38 + std::shared_ptr<std::string> input;
  39 + std::string object;
  40 + };
  41 +
  42 + struct ChildDescr
  43 + {
  44 + ChildDescr(
  45 + std::shared_ptr<QPDFValue> parent,
  46 + std::string_view const& static_descr,
  47 + std::string var_descr) :
  48 + parent(parent),
  49 + static_descr(static_descr),
  50 + var_descr(var_descr)
  51 + {
  52 + }
  53 +
  54 + std::weak_ptr<QPDFValue> parent;
  55 + std::string_view const& static_descr;
  56 + std::string var_descr;
  57 + };
  58 +
  59 + using Description = std::variant<std::string, JSON_Descr, ChildDescr>;
  60 +
26 61 virtual void
27 62 setDescription(
28 63 QPDF* qpdf_p,
29   - std::shared_ptr<std::string>& description,
  64 + std::shared_ptr<Description>& description,
30 65 qpdf_offset_t offset)
31 66 {
32 67 qpdf = qpdf_p;
... ... @@ -36,35 +71,25 @@ class QPDFValue
36 71 void
37 72 setDefaultDescription(QPDF* a_qpdf, QPDFObjGen const& a_og)
38 73 {
39   - static auto default_description{
40   - std::make_shared<std::string>("object $OG")};
41   - if (!object_description) {
42   - object_description = default_description;
43   - }
44 74 qpdf = a_qpdf;
45 75 og = a_og;
46 76 }
47   - std::string
48   - getDescription()
  77 + void
  78 + setChildDescription(
  79 + QPDF* a_qpdf,
  80 + std::shared_ptr<QPDFValue> parent,
  81 + std::string_view const& static_descr,
  82 + std::string var_descr)
49 83 {
50   - auto description = object_description ? *object_description : "";
51   - if (auto pos = description.find("$OG"); pos != std::string::npos) {
52   - description.replace(pos, 3, og.unparse(' '));
53   - }
54   - if (auto pos = description.find("$PO"); pos != std::string::npos) {
55   - qpdf_offset_t shift = (type_code == ::ot_dictionary) ? 2
56   - : (type_code == ::ot_array) ? 1
57   - : 0;
58   -
59   - description.replace(pos, 3, std::to_string(parsed_offset + shift));
60   - }
61   - return description;
  84 + object_description = std::make_shared<Description>(
  85 + ChildDescr(parent, static_descr, var_descr));
  86 + qpdf = a_qpdf;
62 87 }
  88 + std::string getDescription();
63 89 bool
64 90 hasDescription()
65 91 {
66   - return qpdf != nullptr && object_description &&
67   - !object_description->empty();
  92 + return object_description || og.isIndirect();
68 93 }
69 94 void
70 95 setParsedOffset(qpdf_offset_t offset)
... ... @@ -123,7 +148,7 @@ class QPDFValue
123 148 private:
124 149 QPDFValue(QPDFValue const&) = delete;
125 150 QPDFValue& operator=(QPDFValue const&) = delete;
126   - std::shared_ptr<std::string> object_description;
  151 + std::shared_ptr<Description> object_description;
127 152  
128 153 const qpdf_object_type_e type_code{::ot_uninitialized};
129 154 char const* type_name{"uninitialized"};
... ...
libqpdf/qpdf/QPDF_Null.hh
... ... @@ -8,6 +8,14 @@ class QPDF_Null: public QPDFValue
8 8 public:
9 9 virtual ~QPDF_Null() = default;
10 10 static std::shared_ptr<QPDFObject> create();
  11 + static std::shared_ptr<QPDFObject> create(
  12 + std::shared_ptr<QPDFObject> parent,
  13 + std::string_view const& static_descr,
  14 + std::string var_descr);
  15 + static std::shared_ptr<QPDFObject> create(
  16 + std::shared_ptr<QPDFValue> parent,
  17 + std::string_view const& static_descr,
  18 + std::string var_descr);
11 19 virtual std::shared_ptr<QPDFObject> copy(bool shallow = false);
12 20 virtual std::string unparse();
13 21 virtual JSON getJSON(int json_version);
... ...
libqpdf/qpdf/QPDF_Stream.hh
... ... @@ -27,7 +27,9 @@ class QPDF_Stream: public QPDFValue
27 27 virtual std::string unparse();
28 28 virtual JSON getJSON(int json_version);
29 29 virtual void setDescription(
30   - QPDF*, std::shared_ptr<std::string>& description, qpdf_offset_t offset);
  30 + QPDF*,
  31 + std::shared_ptr<QPDFValue::Description>& description,
  32 + qpdf_offset_t offset);
31 33 virtual void disconnect();
32 34 QPDFObjectHandle getDict() const;
33 35 bool isDataModified() const;
... ...