org.python.modules._io.OpenMode Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jython-slim Show documentation
Show all versions of jython-slim Show documentation
Jython is an implementation of the high-level, dynamic, object-oriented
language Python written in 100% Pure Java, and seamlessly integrated with
the Java platform. It thus allows you to run Python on any Java platform.
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 true
, it checks the validity of the flags against combinations allowed
* by the Python io.open()
function. In the case of a violation, it sets the
* invalid
flag, and sets {@link #message} to a descriptive message. The point of
* the qualification "if the invalid
flag is not already true
" 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.
*
* The invalid combinations enforced here are those for the "raw" (ie non-text) file types:
*
* - {@code universal & (writing | appending)},
* - {@code text & binary},
* - {@code reading & writing},
* - {@code appending & (reading | writing)}
*
* 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 true
, and the mode includes {@link #binary}, then all the arguments to
* this call must be null
. If the criterion is not met, then on return from the
* method, invalid==true
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 io.open
uses.)
*
* @param encoding argument to open()
* @param errors argument to open()
* @param newline argument to open()
*/
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 true
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 FileIO
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();
}
}