-
Notifications
You must be signed in to change notification settings - Fork 230
Expand file tree
/
Copy pathPyFile.java
More file actions
742 lines (626 loc) · 23 KB
/
Copy pathPyFile.java
File metadata and controls
742 lines (626 loc) · 23 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
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
/*
* Copyright (c) Corporation for National Research Initiatives
* Copyright (c) Jython Developers
*/
package org.python.core;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.Callable;
import org.python.core.finalization.FinalizableBuiltin;
import org.python.core.finalization.FinalizeTrigger;
import org.python.core.io.BinaryIOWrapper;
import org.python.core.io.BufferedIOBase;
import org.python.core.io.BufferedRandom;
import org.python.core.io.BufferedReader;
import org.python.core.io.BufferedWriter;
import org.python.core.io.FileIO;
import org.python.core.io.IOBase;
import org.python.core.io.LineBufferedRandom;
import org.python.core.io.LineBufferedWriter;
import org.python.core.io.RawIOBase;
import org.python.core.io.StreamIO;
import org.python.core.io.TextIOBase;
import org.python.core.io.TextIOWrapper;
import org.python.core.io.UniversalIOWrapper;
import org.python.expose.ExposedDelete;
import org.python.expose.ExposedGet;
import org.python.expose.ExposedMethod;
import org.python.expose.ExposedNew;
import org.python.expose.ExposedSet;
import org.python.expose.ExposedType;
/**
* The Python file type. Wraps an {@link TextIOBase} object.
*/
@ExposedType(name = "file", doc = BuiltinDocs.file_doc)
public class PyFile extends PyObject implements FinalizableBuiltin, Traverseproc {
public static final PyType TYPE = PyType.fromClass(PyFile.class);
/** The filename */
@ExposedGet(doc = BuiltinDocs.file_name_doc)
public PyObject name;
/** The mode string */
@ExposedGet(doc = BuiltinDocs.file_mode_doc)
public String mode;
@ExposedGet(doc = BuiltinDocs.file_encoding_doc)
public String encoding;
@ExposedGet(doc = BuiltinDocs.file_errors_doc)
public String errors;
/** Indicator dictating whether a space should be written to this
* file on the next print statement (not currently implemented in
* print ) */
public boolean softspace = false;
/** Whether this file is opened for reading */
private boolean reading = false;
/** Whether this file is opened for writing */
private boolean writing = false;
/** Whether this file is opened in appending mode */
private boolean appending = false;
/** Whether this file is opened for updating */
private boolean updating = false;
/** Whether this file is opened in binary mode */
private boolean binary = false;
/** Whether this file is opened in universal newlines mode */
private boolean universal = false;
/** The underlying IO object */
private TextIOBase file;
/** The file's closer object; ensures the file is closed at
* shutdown */
private Closer closer;
public PyFile() {FinalizeTrigger.ensureFinalizer(this);}
public PyFile(PyType subType) {
super(subType);
FinalizeTrigger.ensureFinalizer(this);
}
public PyFile(RawIOBase raw, String name, String mode, int bufsize) {
parseMode(mode);
file___init__(raw, name, mode, bufsize);
FinalizeTrigger.ensureFinalizer(this);
}
public PyFile(InputStream istream, String name, String mode, int bufsize, boolean closefd) {
parseMode(mode);
file___init__(new StreamIO(istream, closefd), name, mode, bufsize);
FinalizeTrigger.ensureFinalizer(this);
}
/**
* Creates a file object wrapping the given <code>InputStream</code>. The builtin
* method <code>file</code> doesn't expose this functionality (<code>open</code> does
* albeit deprecated) as it isn't available to regular Python code. To wrap an
* InputStream in a file from Python, use
* {@link org.python.core.util.FileUtil#wrap(InputStream, String, int)}
* {@link org.python.core.util.FileUtil#wrap(InputStream, String)}
* {@link org.python.core.util.FileUtil#wrap(InputStream, int)}
* {@link org.python.core.util.FileUtil#wrap(InputStream)}
*/
public PyFile(InputStream istream, String mode, int bufsize) {
this(istream, "<Java InputStream '" + istream + "' as file>", mode, bufsize, true);
}
public PyFile(InputStream istream, String mode) {
this(istream, mode, -1);
}
public PyFile(InputStream istream, int bufsize) {
this(istream, "r", bufsize);
}
public PyFile(InputStream istream) {
this(istream, -1);
}
public PyFile(OutputStream ostream, String name, String mode, int bufsize, boolean closefd) {
parseMode(mode);
file___init__(new StreamIO(ostream, closefd), name, mode, bufsize);
FinalizeTrigger.ensureFinalizer(this);
}
/**
* Creates a file object wrapping the given <code>OutputStream</code>. The builtin
* method <code>file</code> doesn't expose this functionality (<code>open</code> does
* albeit deprecated) as it isn't available to regular Python code. To wrap an
* OutputStream in a file from Python, use
* {@link org.python.core.util.FileUtil#wrap(OutputStream, String, int)}
* {@link org.python.core.util.FileUtil#wrap(OutputStream, String)}
* {@link org.python.core.util.FileUtil#wrap(OutputStream, int)}
* {@link org.python.core.util.FileUtil#wrap(OutputStream)}
*/
public PyFile(OutputStream ostream, String mode, int bufsize) {
this(ostream, "<Java OutputStream '" + ostream + "' as file>", mode, bufsize, true);
}
public PyFile(OutputStream ostream, int bufsize) {
this(ostream, "w", bufsize);
}
public PyFile(OutputStream ostream) {
this(ostream, -1);
}
public PyFile(String name, String mode, int bufsize) {
file___init__(new FileIO(name, parseMode(mode)), name, mode, bufsize);
FinalizeTrigger.ensureFinalizer(this);
}
@ExposedNew
@ExposedMethod(doc = BuiltinDocs.file___init___doc)
final void file___init__(PyObject[] args, String[] kwds) {
ArgParser ap = new ArgParser("file", args, kwds, new String[] {"name", "mode", "buffering"},
1);
PyObject name = ap.getPyObject(0);
String mode = ap.getString(1, "r");
int bufsize = ap.getInt(2, -1);
file___init__(new FileIO((PyString) name, parseMode(mode)), name, mode, bufsize);
closer = new Closer(file, Py.getSystemState());
}
private void file___init__(RawIOBase raw, String name, String mode, int bufsize) {
file___init__(raw, Py.newStringOrUnicode(name), mode, bufsize);
}
private void file___init__(RawIOBase raw, PyObject name, String mode, int bufsize) {
this.name = name;
this.mode = mode;
BufferedIOBase buffer = createBuffer(raw, bufsize);
if (universal) {
this.file = new UniversalIOWrapper(buffer);
} else if (!binary) {
this.file = new TextIOWrapper(buffer);
} else {
this.file = new BinaryIOWrapper(buffer);
}
}
/**
* Set the strings defining the encoding and error handling policy. Setting these strings
* affects behaviour of the {@link #writelines(PyObject)} when passed a {@link PyUnicode} value.
*
* @param encoding the <code>encoding</code> property of <code>file</code>.
* @param errors the <code>errors</code> property of <code>file</code> (or <code>null</code>).
*/
void setEncoding(String encoding, String errors) {
this.encoding = encoding;
this.errors = errors;
}
/**
* Wrap the given RawIOBase with a BufferedIOBase according to the
* mode and given bufsize.
*
* @param raw a RawIOBase value
* @param bufsize an int size of the buffer
* @return a BufferedIOBase wrapper
*/
private BufferedIOBase createBuffer(RawIOBase raw, int bufsize) {
if (bufsize < 0) {
bufsize = IOBase.DEFAULT_BUFFER_SIZE;
}
boolean lineBuffered = bufsize == 1;
BufferedIOBase buffer;
if (updating) {
buffer = lineBuffered ? new LineBufferedRandom(raw) : new BufferedRandom(raw, bufsize);
} else if (writing || appending) {
buffer = lineBuffered ? new LineBufferedWriter(raw) : new BufferedWriter(raw, bufsize);
} else if (reading) {
// Line buffering is for output only
buffer = new BufferedReader(raw, lineBuffered ? IOBase.DEFAULT_BUFFER_SIZE : bufsize);
} else {
// Should never happen
throw Py.ValueError("unknown mode: '" + mode + "'");
}
return buffer;
}
/**
* Parse and validate the python file mode, returning a cleaned file mode suitable for FileIO.
*
* @param mode a python file mode String
* @return a RandomAccessFile mode String
*/
private String parseMode(String mode) {
String message = null;
boolean duplicate = false, invalid = false, text_intent = false;
int n = mode.length();
// Convert the letters to booleans, noticing duplicates
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 'b':
duplicate = binary;
binary = true;
break;
case 't':
duplicate = text_intent;
text_intent = true;
binary = false;
break;
case 'U':
duplicate = universal;
universal = true;
break;
default:
invalid = true;
}
// duplicate is set iff c was encountered previously */
if (duplicate) {
invalid = true;
break;
}
}
// Implications
reading |= universal;
binary |= universal;
// Standard tests and the mode for FileIO
StringBuilder fileioMode = new StringBuilder();
if (!invalid) {
if (universal && (writing || appending)) {
// Not quite true, consider 'Ub', but it's what CPython says:
message = "universal newline mode can only be used with modes starting with 'r'";
} else {
// Build the FileIO mode string
if (reading) {
fileioMode.append('r');
}
if (writing) {
fileioMode.append('w');
}
if (appending) {
fileioMode.append('a');
}
if (fileioMode.length() != 1) {
// We should only have added one of the above
message = "mode string must begin with one of 'r', 'w', 'a' or 'U', not '" //
+ mode + "'";
}
if (updating) {
fileioMode.append('+');
}
}
invalid |= (message != null);
}
// Finally, if invalid, report this as an error
if (invalid) {
if (message == null) {
// Duplicates discovered or invalid symbols
message = String.format("invalid mode: '%.20s'", mode);
}
throw Py.ValueError(message);
}
return fileioMode.toString();
}
@ExposedMethod(defaults = {"-1"}, doc = BuiltinDocs.file_read_doc)
final synchronized PyString file_read(int size) {
checkClosed();
return new PyString(file.read(size));
}
public PyString read(int size) {
return file_read(size);
}
public PyString read() {
return file_read(-1);
}
@ExposedMethod(doc = BuiltinDocs.file_readinto_doc)
final synchronized int file_readinto(PyObject buf) {
checkClosed();
return file.readinto(buf);
}
public int readinto(PyObject buf) {
return file_readinto(buf);
}
@ExposedMethod(defaults = {"-1"}, doc = BuiltinDocs.file_readline_doc)
final synchronized PyString file_readline(int max) {
checkClosed();
return new PyString(file.readline(max));
}
public PyString readline(int max) {
return file_readline(max);
}
public PyString readline() {
return file_readline(-1);
}
@ExposedMethod(defaults = {"0"}, doc = BuiltinDocs.file_readlines_doc)
final synchronized PyObject file_readlines(int sizehint) {
checkClosed();
PyList list = new PyList();
int count = 0;
do {
String line = file.readline(-1);
int len = line.length();
if (len == 0) {
// EOF
break;
}
count += len;
list.append(new PyString(line));
} while (sizehint <= 0 || count < sizehint);
return list;
}
public PyObject readlines(int sizehint) {
return file_readlines(sizehint);
}
public PyObject readlines() {
return file_readlines(0);
}
@Override
public PyObject __iternext__() {
return file___iternext__();
}
final synchronized PyObject file___iternext__() {
checkClosed();
String next = file.readline(-1);
if (next.length() == 0) {
return null;
}
return new PyString(next);
}
@ExposedMethod(doc = BuiltinDocs.file_next_doc)
final PyObject file_next() {
PyObject ret = file___iternext__();
if (ret == null) {
throw Py.StopIteration("");
}
return ret;
}
public PyObject next() {
return file_next();
}
@ExposedMethod(names = {"__enter__", "__iter__", "xreadlines"},
doc = BuiltinDocs.file___iter___doc)
final PyObject file_self() {
checkClosed();
return this;
}
public PyObject __enter__() {
return file_self();
}
@Override
public PyObject __iter__() {
return file_self();
}
public PyObject xreadlines() {
return file_self();
}
@ExposedMethod(doc = BuiltinDocs.file_write_doc)
final void file_write(PyObject obj) {
file_write(asWritable(obj, null));
}
final synchronized void file_write(String string) {
checkClosed();
softspace = false;
file.write(string);
}
public void write(String string) {
file_write(string);
}
@ExposedMethod(doc = BuiltinDocs.file_writelines_doc)
final synchronized void file_writelines(PyObject lines) {
checkClosed();
PyObject iter = Py.iter(lines, "writelines() requires an iterable argument");
for (PyObject item = null; (item = iter.__iternext__()) != null;) {
checkClosed(); // ... in case a nasty iterable closed this file
softspace = false;
file.write(asWritable(item, "writelines() argument must be a sequence of strings"));
}
}
public void writelines(PyObject lines) {
file_writelines(lines);
}
/**
* Return a String for writing to the underlying file from obj. This is a helper for {@link file_write}
* and {@link file_writelines}.
*
* @param obj to write
* @param message for TypeError if raised (or null for default message)
* @return bytes representing the value (as a String in the Jython convention)
*/
private String asWritable(PyObject obj, String message) {
if (obj instanceof PyUnicode) {
// Unicode must be encoded into bytes (null arguments here invoke the default values)
return ((PyUnicode)obj).encode(encoding, errors);
} else if (obj instanceof PyString) {
// Take a short cut
return ((PyString)obj).getString();
} else if (obj instanceof PyArray && !binary) {
// Fall through to TypeError. (If binary, BufferProtocol takes care of PyArray.)
} else {
// Try to get a simple byte-oriented buffer
try (PyBuffer buf = ((BufferProtocol)obj).getBuffer(PyBUF.SIMPLE)) {
// ... and treat those bytes as a String
return buf.toString();
} catch (ClassCastException e) {
// Does not implement BufferProtocol (in reality). Fall through to message.
}
}
if (message == null) {
// Messages differ for text or binary streams (CPython) but we always add the type
String fmt = "expected a string or%s buffer, not %.200s";
message = String.format(fmt, (binary ? "" : " character"), obj.getType().fastGetName());
}
throw Py.TypeError(message);
}
@ExposedMethod(doc = BuiltinDocs.file_tell_doc)
final synchronized long file_tell() {
checkClosed();
return file.tell();
}
public long tell() {
return file_tell();
}
@ExposedMethod(defaults = {"0"}, doc = BuiltinDocs.file_seek_doc)
final synchronized void file_seek(long pos, int how) {
checkClosed();
file.seek(pos, how);
}
public void seek(long pos, int how) {
file_seek(pos, how);
}
public void seek(long pos) {
file_seek(pos, 0);
}
@ExposedMethod(doc = BuiltinDocs.file_flush_doc)
final synchronized void file_flush() {
checkClosed();
file.flush();
}
public void flush() {
file_flush();
}
@ExposedMethod(doc = BuiltinDocs.file_close_doc)
final synchronized void file_close() {
if (closer != null) {
closer.close();
closer = null;
} else {
file.close();
}
}
public void close() {
file_close();
}
@ExposedMethod(doc = BuiltinDocs.file___exit___doc)
final void file___exit__(PyObject type, PyObject value, PyObject traceback) {
close();
}
public void __exit__(PyObject type, PyObject value, PyObject traceback) {
file___exit__(type, value, traceback);
}
@ExposedMethod(defaults = {"null"}, doc = BuiltinDocs.file_truncate_doc)
final void file_truncate(PyObject position) {
if (position == null) {
file_truncate();
return;
}
file_truncate(position.asLong());
}
final synchronized void file_truncate(long position) {
file.truncate(position);
}
public void truncate(long position) {
file_truncate(position);
}
final synchronized void file_truncate() {
file.truncate(file.tell());
}
public void truncate() {
file_truncate();
}
public boolean isatty() {
return file_isatty();
}
@ExposedMethod(doc = BuiltinDocs.file_isatty_doc)
final boolean file_isatty() {
return file.isatty();
}
public PyObject fileno() {
return file_fileno();
}
@ExposedMethod(doc = BuiltinDocs.file_fileno_doc)
final PyObject file_fileno() {
return PyJavaType.wrapJavaObject(file.fileno());
}
@ExposedMethod(names = {"__str__", "__repr__"}, doc = BuiltinDocs.file___str___doc)
final String file_toString() {
String state = file.closed() ? "closed" : "open";
String id = Py.idstr(this);
String escapedName;
if (name instanceof PyUnicode) {
// unicode: always uses the format u'%s', and the escaped value thus:
escapedName = "u'"+PyString.encode_UnicodeEscape(name.toString(), false)+"'";
} else {
// anything else: uses repr(), which for str (common case) is smartly quoted
escapedName = name.__repr__().getString();
}
return String.format("<%s file %s, mode '%s' at %s>", state, escapedName, mode, id);
}
@Override
public String toString() {
return file_toString();
}
private void checkClosed() {
file.checkClosed();
}
@ExposedGet(name = "closed", doc = BuiltinDocs.file_closed_doc)
public boolean getClosed() {
return file.closed();
}
@ExposedGet(name = "newlines", doc = BuiltinDocs.file_newlines_doc)
public PyObject getNewlines() {
return file.getNewlines();
}
@ExposedGet(name = "softspace", doc = BuiltinDocs.file_softspace_doc)
public PyObject getSoftspace() {
// NOTE: not actual bools because CPython is this way
return softspace ? Py.One : Py.Zero;
}
@ExposedSet(name = "softspace")
public void setSoftspace(PyObject obj) {
softspace = obj.__nonzero__();
}
@ExposedDelete(name = "softspace")
public void delSoftspace() {
throw Py.TypeError("can't delete numeric/char attribute");
}
@Override
public Object __tojava__(Class<?> cls) {
Object obj = null;
if (InputStream.class.isAssignableFrom(cls)) {
obj = file.asInputStream();
} else if (OutputStream.class.isAssignableFrom(cls)) {
obj = file.asOutputStream();
}
if (obj == null) {
obj = super.__tojava__(cls);
}
return obj;
}
@Override
public void __del_builtin__() {
if (closer != null) {
closer.close();
}
}
/**
* XXX update docs - A mechanism to make sure PyFiles are closed on exit. On creation Closer adds itself
* to a list of Closers that will be run by PyFileCloser on JVM shutdown. When a
* PyFile's close or finalize methods are called, PyFile calls its Closer.close which
* clears Closer out of the shutdown queue.
*
* We use a regular object here rather than WeakReferences and their ilk as they may
* be collected before the shutdown hook runs. There's no guarantee that finalize will
* be called during shutdown, so we can't use it. It's vital that this Closer has no
* reference to the PyFile it's closing so the PyFile remains garbage collectable.
*/
private static class Closer implements Callable<Void> {
/**
* The underlying file
*/
private final TextIOBase file;
private PySystemState sys;
public Closer(TextIOBase file, PySystemState sys) {
this.file = file;
this.sys = sys;
sys.registerCloser(this);
}
/** For closing directly */
public void close() {
sys.unregisterCloser(this);
file.close();
sys = null;
}
/** For closing as part of a shutdown process */
@Override
public Void call() {
file.close();
sys = null;
return null;
}
}
/* Traverseproc implementation */
@Override
public int traverse(Visitproc visit, Object arg) {
return name == null ? 0 : visit.visit(name, arg);
}
@Override
public boolean refersDirectlyTo(PyObject ob) {
return ob != null && ob == name;
}
}