OR-Tools  9.1
file_util.cc
Go to the documentation of this file.
1 // Copyright 2010-2021 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 #include "ortools/util/file_util.h"
15 
16 #include "absl/status/statusor.h"
17 #include "absl/strings/str_cat.h"
18 #include "google/protobuf/descriptor.h"
19 #include "google/protobuf/io/zero_copy_stream_impl_lite.h"
20 #include "google/protobuf/message.h"
21 #include "google/protobuf/text_format.h"
22 #include "google/protobuf/util/json_util.h"
23 #include "ortools/base/file.h"
25 #include "ortools/base/logging.h"
27 
28 namespace operations_research {
29 
30 using ::google::protobuf::TextFormat;
31 using google::protobuf::util::JsonParseOptions;
32 using google::protobuf::util::JsonStringToMessage;
33 
34 absl::StatusOr<std::string> ReadFileToString(absl::string_view filename) {
35  std::string contents;
36  RETURN_IF_ERROR(file::GetContents(filename, &contents, file::Defaults()));
37  // Try decompressing it.
38  {
39  std::string uncompressed;
40  if (GunzipString(contents, &uncompressed)) contents.swap(uncompressed);
41  }
42  return contents;
43 }
44 
45 bool ReadFileToProto(absl::string_view filename,
46  google::protobuf::Message* proto) {
47  std::string data;
48  CHECK_OK(file::GetContents(filename, &data, file::Defaults()));
49  // Try decompressing it.
50  {
51  std::string uncompressed;
52  if (GunzipString(data, &uncompressed)) {
53  VLOG(1) << "ReadFileToProto(): input is gzipped";
54  data.swap(uncompressed);
55  }
56  }
57  // Try binary format first, then text format, then JSON, then proto3 JSON,
58  // then give up.
59  // For some of those, like binary format and proto3 JSON, we perform
60  // additional checks to verify that we have the right proto: it can happen
61  // to try to read a proto of type Foo as a proto of type Bar, by mistake, and
62  // we'd rather have this function fail rather than silently accept it, because
63  // the proto parser is too lenient with unknown fields.
64  // We don't require ByteSizeLong(parsed) == input.size(), because it may be
65  // the case that the proto version changed and some fields are dropped.
66  // We just fail when the difference is too large.
67  constexpr double kMaxBinaryProtoParseShrinkFactor = 2;
68  if (proto->ParseFromString(data)) {
69  // NOTE(user): When using ParseFromString() from a generic
70  // google::protobuf::Message, like we do here, all fields are stored, even
71  // if they are unknown in the underlying proto type. Unless we explicitly
72  // discard those 'unknown fields' here, our call to ByteSizeLong() will
73  // still count the unknown payload.
74  proto->DiscardUnknownFields();
75  if (proto->ByteSizeLong() <
76  data.size() / kMaxBinaryProtoParseShrinkFactor) {
77  VLOG(1) << "ReadFileToProto(): input may be a binary proto, but of a "
78  "different proto";
79  } else {
80  VLOG(1) << "ReadFileToProto(): input seems to be a binary proto";
81  return true;
82  }
83  }
84  if (google::protobuf::TextFormat::ParseFromString(data, proto)) {
85  VLOG(1) << "ReadFileToProto(): input is a text proto";
86  return true;
87  }
88  if (JsonStringToMessage(data, proto, JsonParseOptions()).ok()) {
89  // NOTE(user): We protect against the JSON proto3 parser being very lenient
90  // and easily accepting any JSON as a valid JSON for our proto: if the
91  // parsed proto's size is too small compared to the JSON, we probably parsed
92  // a JSON that wasn't representing a valid proto.
93  constexpr int kMaxJsonToBinaryShrinkFactor = 30;
94  if (proto->ByteSizeLong() < data.size() / kMaxJsonToBinaryShrinkFactor) {
95  VLOG(1) << "ReadFileToProto(): input is probably JSON, but probably not"
96  " of the right proto";
97  } else {
98  VLOG(1) << "ReadFileToProto(): input is a proto JSON";
99  return true;
100  }
101  }
102  LOG(WARNING) << "Could not parse protocol buffer";
103  return false;
104 }
105 
106 bool WriteProtoToFile(absl::string_view filename,
107  const google::protobuf::Message& proto,
108  ProtoWriteFormat proto_write_format, bool gzipped,
109  bool append_extension_to_file_name) {
110  std::string file_type_suffix;
111  std::string output_string;
112  google::protobuf::io::StringOutputStream stream(&output_string);
113  switch (proto_write_format) {
115  if (!proto.SerializeToZeroCopyStream(&stream)) {
116  LOG(WARNING) << "Serialize to stream failed.";
117  return false;
118  }
119  file_type_suffix = ".bin";
120  break;
122  if (!google::protobuf::TextFormat::PrintToString(proto, &output_string)) {
123  LOG(WARNING) << "Printing to string failed.";
124  return false;
125  }
126  break;
128  google::protobuf::util::JsonPrintOptions options;
129  options.add_whitespace = true;
130  options.always_print_primitive_fields = true;
131  options.preserve_proto_field_names = true;
132  if (!google::protobuf::util::MessageToJsonString(proto, &output_string,
133  options)
134  .ok()) {
135  LOG(WARNING) << "Printing to stream failed.";
136  return false;
137  }
138  file_type_suffix = ".json";
139  break;
140  }
142  google::protobuf::util::JsonPrintOptions options;
143  options.add_whitespace = true;
144  if (!google::protobuf::util::MessageToJsonString(proto, &output_string,
145  options)
146  .ok()) {
147  LOG(WARNING) << "Printing to stream failed.";
148  return false;
149  }
150  file_type_suffix = ".json";
151  break;
152  }
153  if (gzipped) {
154  std::string gzip_string;
155  GzipString(output_string, &gzip_string);
156  output_string.swap(gzip_string);
157  file_type_suffix += ".gz";
158  }
159  std::string output_filename(filename);
160  if (append_extension_to_file_name) output_filename += file_type_suffix;
161  VLOG(1) << "Writing " << output_string.size() << " bytes to "
162  << output_filename;
163  if (!file::SetContents(output_filename, output_string, file::Defaults())
164  .ok()) {
165  LOG(WARNING) << "Writing to file failed.";
166  return false;
167  }
168  return true;
169 }
170 
171 } // namespace operations_research
#define CHECK_OK(x)
Definition: base/logging.h:42
absl::StatusOr< std::string > ReadFileToString(absl::string_view filename)
Definition: file_util.cc:34
#define VLOG(verboselevel)
Definition: base/logging.h:979
bool GunzipString(const std::string &str, std::string *out)
Definition: gzipstring.h:22
void GzipString(absl::string_view uncompressed, std::string *compressed)
Definition: gzipstring.h:63
#define LOG(severity)
Definition: base/logging.h:416
CpModelProto proto
const int WARNING
Definition: log_severity.h:31
int Defaults()
Definition: base/file.h:119
bool WriteProtoToFile(absl::string_view filename, const google::protobuf::Message &proto, ProtoWriteFormat proto_write_format, bool gzipped, bool append_extension_to_file_name)
Definition: file_util.cc:106
absl::Status SetContents(const absl::string_view &filename, const absl::string_view &contents, int flags)
Definition: base/file.cc:196
absl::Status GetContents(const absl::string_view &filename, std::string *output, int flags)
Definition: base/file.cc:163
bool ReadFileToProto(absl::string_view filename, google::protobuf::Message *proto)
Definition: file_util.cc:45
Collection of objects used to extend the Constraint Solver library.
#define RETURN_IF_ERROR(expr)
Definition: status_macros.h:29