src.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 Show documentation
Show all versions of jython 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 #isValid()} method, or by client code. A non-null value will
* cause {@link #isValid()} to return false.
*/
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 #isValid()} 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:
*
* - universal & (writing | appending)),
* - text & binary
,
* - reading & writing,
* - 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 (ValueError) if the mode string was invalid.
*/
public void checkValid() throws PyException {
// Actually peform 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, and not the same as {@link #raw()}.
*
* @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();
}
}