-
Notifications
You must be signed in to change notification settings - Fork 227
Expand file tree
/
Copy pathOpenMode.java
More file actions
271 lines (239 loc) · 9.44 KB
/
OpenMode.java
File metadata and controls
271 lines (239 loc) · 9.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
package org.python.modules._io;
import org.python.core.Py;
import org.python.core.PyException;
/**
* An object able to check a file access mode provided as a String and represent it as boolean
* attributes and in a normalised form. Such a string is the the mode argument of the several open()
* functions available in Python and certain constructors for streams-like objects.
*/
public class OpenMode {
/** Original string supplied as the mode */
public final String originalModeString;
/** Whether this file is opened for reading ('r') */
public boolean reading;
/** Whether this file is opened for writing ('w') */
public boolean writing;
/** Whether this file is opened in appending mode ('a') */
public boolean appending;
/** Whether this file is opened for updating ('+') */
public boolean updating;
/** Whether this file is opened in binary mode ('b') */
public boolean binary;
/** Whether this file is opened in text mode ('t') */
public boolean text;
/** Whether this file is opened in universal newlines mode ('U') */
public boolean universal;
/** Whether the mode contained some other symbol from the allowed ones */
public boolean other;
/** Set true when any invalid symbol or combination is discovered */
public boolean invalid;
/**
* Error message describing the way in which the mode is invalid, or null if no problem has been
* found. This field may be set by the constructor (in the case of duplicate or unrecognised
* mode letters), by the {@link #validate()} method, or by client code.
*/
public String message;
/**
* Decode the given string to an OpenMode object, checking for duplicate or unrecognised mode
* letters. Valid letters are those in "rwa+btU". Errors in the mode string do not raise an
* exception, they simply generate an appropriate error message in {@link #message}. After
* construction, a client should always call {@link #validate()} to complete validity checks.
*
* @param mode
*/
public OpenMode(String mode) {
originalModeString = mode;
int n = mode.length();
boolean duplicate = false;
for (int i = 0; i < n; i++) {
char c = mode.charAt(i);
switch (c) {
case 'r':
duplicate = reading;
reading = true;
break;
case 'w':
duplicate = writing;
writing = true;
break;
case 'a':
duplicate = appending;
appending = true;
break;
case '+':
duplicate = updating;
updating = true;
break;
case 't':
duplicate = text;
text = true;
break;
case 'b':
duplicate = binary;
binary = true;
break;
case 'U':
duplicate = universal;
universal = true;
break;
default:
other = true;
}
// duplicate is set iff c was encountered previously */
if (duplicate) {
invalid = true;
break;
}
}
}
/**
* Adjust and validate the flags decoded from the mode string. The method affects the flags
* where the presence of one flag implies another, then if the {@link #invalid} flag is not
* already <code>true</code>, it checks the validity of the flags against combinations allowed
* by the Python <code>io.open()</code> function. In the case of a violation, it sets the
* <code>invalid</code> flag, and sets {@link #message} to a descriptive message. The point of
* the qualification "if the <code>invalid</code> flag is not already <code>true</code>" is that
* the message should always describe the first problem discovered. If left blank, as in fact
* the constructor does, it will be filled by the generic message when {@link #checkValid()} is
* finally called. Clients may override this method (by sub-classing) to express the validation
* correct in their context.
* <p>
* The invalid combinations enforced here are those for the "raw" (ie non-text) file types:
* <ul>
* <li>{@code universal & (writing | appending)},</li>
* <li>{@code text & binary},</li>
* <li>{@code reading & writing},</li>
* <li>{@code appending & (reading | writing)}</li>
* </ul>
* See also {@link #validate(String, String, String)} for additional checks relevant to text
* files.
*/
public void validate() {
// Implications
reading |= universal;
// Standard tests
if (!invalid) {
if (universal && (writing || appending)) {
message = "can't use U and writing mode at once";
} else if (text && binary) {
message = "can't have text and binary mode at once";
} else {
// How many of r/U, w and a were given?
int rwa = 0;
if (reading) {
rwa += 1;
}
if (writing) {
rwa += 1;
}
if (appending) {
rwa += 1;
}
if (rwa != 1) {
message = "must have exactly one of read/write/append mode";
}
}
invalid |= (message != null);
}
}
/**
* Perform additional validation of the flags relevant to text files. If {@link #invalid} is not
* already <code>true</code>, and the mode includes {@link #binary}, then all the arguments to
* this call must be <code>null</code>. If the criterion is not met, then on return from the
* method, <code>invalid==true</code> and {@link #message} is set to a standard error message.
* This is the standard additional validation applicable to text files. (By "standard" we mean
* the test and messages that CPython <code>io.open</code> uses.)
*
* @param encoding argument to <code>open()</code>
* @param errors argument to <code>open()</code>
* @param newline argument to <code>open()</code>
*/
public void validate(String encoding, String errors, String newline) {
// If the basic tests passed and binary mode is set one check text arguments null
if (!invalid && binary) {
if (encoding != null) {
message = "binary mode doesn't take an encoding argument";
} else if (errors != null) {
message = "binary mode doesn't take an errors argument";
} else if (newline != null) {
message = "binary mode doesn't take a newline argument";
}
invalid = (message != null);
}
}
/**
* Call {@link #validate()} and raise an exception if the mode string is not valid, as signalled
* by either {@link #invalid} or {@link #other} being <code>true</code> after that call. If no
* more specific message has been assigned in {@link #message}, report the original mode string.
*
* @throws PyException {@code ValueError} if the mode string was invalid.
*/
public void checkValid() throws PyException {
// Actually perform the check
validate();
// The 'other' flag reports alien symbols in the original mode string
invalid |= other;
// Finally, if invalid, report this as an error
if (invalid) {
if (message == null) {
// Duplicates discovered in the constructor or invalid symbols
message = String.format("invalid mode: '%.20s'", originalModeString);
}
throw Py.ValueError(message);
}
}
/**
* The mode string we need when constructing a <code>FileIO</code> initialised with the present
* mode. Note that this is not the same as the full open mode because it omits the text-based
* attributes.
*
* @return "r", "w", or "a" with optional "+".
*/
public String forFileIO() {
StringBuilder m = new StringBuilder(2);
if (appending) {
m.append('a');
} else if (writing) {
m.append('w');
} else {
m.append('r');
}
if (updating) {
m.append('+');
}
return m.toString();
}
/**
* The mode string that a text file should claim to have, when initialised with the present
* mode. Note that this only contains text-based attributes. Since mode 't' has no effect,
* except to produce an error if specified with 'b', we don't reproduce it.
*
* @return "", or "U".
*/
public String text() {
return universal ? "U" : "";
}
@Override
public String toString() {
StringBuilder m = new StringBuilder(4);
if (appending) {
m.append('a');
} else if (writing) {
m.append('w');
} else {
m.append('r');
}
if (updating) {
m.append('+');
}
if (text) {
m.append('t');
} else if (binary) {
m.append('b');
}
if (universal) {
m.append('U');
}
return m.toString();
}
}