nokogiri 1.5.0.beta.1-java → 1.5.0.beta.2-java
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of nokogiri might be problematic. Click here for more details.
- data/CHANGELOG.ja.rdoc +28 -8
- data/CHANGELOG.rdoc +23 -0
- data/Manifest.txt +68 -1
- data/README.ja.rdoc +1 -1
- data/README.rdoc +22 -4
- data/Rakefile +6 -2
- data/ext/java/nokogiri/EncodingHandler.java +92 -0
- data/ext/java/nokogiri/HtmlDocument.java +116 -0
- data/ext/java/nokogiri/HtmlElementDescription.java +111 -0
- data/ext/java/nokogiri/HtmlEntityLookup.java +45 -0
- data/ext/java/nokogiri/HtmlSaxParserContext.java +218 -0
- data/ext/java/nokogiri/NokogiriService.java +370 -0
- data/ext/java/nokogiri/XmlAttr.java +147 -0
- data/ext/java/nokogiri/XmlAttributeDecl.java +98 -0
- data/ext/java/nokogiri/XmlCdata.java +50 -0
- data/ext/java/nokogiri/XmlComment.java +47 -0
- data/ext/java/nokogiri/XmlDocument.java +463 -0
- data/ext/java/nokogiri/XmlDocumentFragment.java +207 -0
- data/ext/java/nokogiri/XmlDtd.java +427 -0
- data/ext/java/nokogiri/XmlElement.java +172 -0
- data/ext/java/nokogiri/XmlElementContent.java +350 -0
- data/ext/java/nokogiri/XmlElementDecl.java +115 -0
- data/ext/java/nokogiri/XmlEntityDecl.java +129 -0
- data/ext/java/nokogiri/XmlEntityReference.java +42 -0
- data/ext/java/nokogiri/XmlNamespace.java +77 -0
- data/ext/java/nokogiri/XmlNode.java +1399 -0
- data/ext/java/nokogiri/XmlNodeSet.java +248 -0
- data/ext/java/nokogiri/XmlProcessingInstruction.java +70 -0
- data/ext/java/nokogiri/XmlReader.java +373 -0
- data/ext/java/nokogiri/XmlRelaxng.java +166 -0
- data/ext/java/nokogiri/XmlSaxParserContext.java +308 -0
- data/ext/java/nokogiri/XmlSaxPushParser.java +146 -0
- data/ext/java/nokogiri/XmlSchema.java +142 -0
- data/ext/java/nokogiri/XmlSyntaxError.java +84 -0
- data/ext/java/nokogiri/XmlText.java +96 -0
- data/ext/java/nokogiri/XmlXpathContext.java +130 -0
- data/ext/java/nokogiri/XsltStylesheet.java +126 -0
- data/ext/java/nokogiri/internals/HtmlDomParserContext.java +181 -0
- data/ext/java/nokogiri/internals/NokogiriDocumentCache.java +39 -0
- data/ext/java/nokogiri/internals/NokogiriErrorHandler.java +42 -0
- data/ext/java/nokogiri/internals/NokogiriHandler.java +251 -0
- data/ext/java/nokogiri/internals/NokogiriHelpers.java +526 -0
- data/ext/java/nokogiri/internals/NokogiriNamespaceCache.java +136 -0
- data/ext/java/nokogiri/internals/NokogiriNamespaceContext.java +80 -0
- data/ext/java/nokogiri/internals/NokogiriNonStrictErrorHandler.java +37 -0
- data/ext/java/nokogiri/internals/NokogiriNonStrictErrorHandler4NekoHtml.java +54 -0
- data/ext/java/nokogiri/internals/NokogiriStrictErrorHandler.java +49 -0
- data/ext/java/nokogiri/internals/NokogiriXPathFunction.java +88 -0
- data/ext/java/nokogiri/internals/NokogiriXPathFunctionResolver.java +23 -0
- data/ext/java/nokogiri/internals/ParserContext.java +235 -0
- data/ext/java/nokogiri/internals/PushInputStream.java +381 -0
- data/ext/java/nokogiri/internals/ReaderNode.java +431 -0
- data/ext/java/nokogiri/internals/SaveContext.java +249 -0
- data/ext/java/nokogiri/internals/SchemaErrorHandler.java +35 -0
- data/ext/java/nokogiri/internals/XmlDeclHandler.java +10 -0
- data/ext/java/nokogiri/internals/XmlDomParser.java +45 -0
- data/ext/java/nokogiri/internals/XmlDomParserContext.java +201 -0
- data/ext/java/nokogiri/internals/XmlSaxParser.java +33 -0
- data/ext/nokogiri/depend +32 -0
- data/ext/nokogiri/extconf.rb +61 -32
- data/ext/nokogiri/libcharset-1.dll +0 -0
- data/ext/nokogiri/libexslt.dll +0 -0
- data/ext/nokogiri/libiconv-2.dll +0 -0
- data/ext/nokogiri/libxml2.dll +0 -0
- data/ext/nokogiri/libxslt.dll +0 -0
- data/ext/nokogiri/nokogiri.c +0 -5
- data/ext/nokogiri/nokogiri.h +2 -2
- data/ext/nokogiri/xml_document.c +5 -0
- data/ext/nokogiri/xml_libxml2_hacks.c +112 -0
- data/ext/nokogiri/xml_libxml2_hacks.h +12 -0
- data/ext/nokogiri/xml_node.c +56 -16
- data/ext/nokogiri/xml_node_set.c +7 -7
- data/ext/nokogiri/xml_reader.c +20 -1
- data/ext/nokogiri/xml_relax_ng.c +0 -7
- data/ext/nokogiri/xml_xpath_context.c +2 -0
- data/ext/nokogiri/zlib1.dll +0 -0
- data/lib/nokogiri.rb +1 -2
- data/lib/nokogiri/css/generated_parser.rb +155 -148
- data/lib/nokogiri/css/generated_tokenizer.rb +2 -1
- data/lib/nokogiri/css/parser.y +3 -0
- data/lib/nokogiri/css/xpath_visitor.rb +1 -7
- data/lib/nokogiri/html.rb +2 -2
- data/lib/nokogiri/html/document_fragment.rb +7 -4
- data/lib/nokogiri/nokogiri.jar +0 -0
- data/lib/nokogiri/version.rb +3 -6
- data/lib/nokogiri/xml/builder.rb +1 -1
- data/lib/nokogiri/xml/document.rb +1 -2
- data/lib/nokogiri/xml/document_fragment.rb +7 -0
- data/lib/nokogiri/xml/node.rb +5 -3
- data/lib/nokogiri/xml/node_set.rb +25 -0
- data/lib/nokogiri/xml/reader.rb +2 -0
- data/lib/nokogiri/xml/sax/document.rb +3 -1
- data/spec/helper.rb +3 -0
- data/spec/xml/reader_spec.rb +307 -0
- data/tasks/test.rb +1 -1
- data/test/css/test_parser.rb +11 -1
- data/test/html/sax/test_parser_context.rb +2 -2
- data/test/html/test_document.rb +2 -2
- data/test/html/test_document_fragment.rb +34 -6
- data/test/test_memory_leak.rb +2 -2
- data/test/test_reader.rb +28 -6
- data/test/test_xslt_transforms.rb +2 -3
- data/test/xml/test_attr.rb +31 -4
- data/test/xml/test_builder.rb +5 -5
- data/test/xml/test_cdata.rb +3 -3
- data/test/xml/test_document.rb +8 -8
- data/test/xml/test_document_fragment.rb +4 -12
- data/test/xml/test_node.rb +1 -1
- data/test/xml/test_node_reparenting.rb +26 -11
- data/test/xml/test_node_set.rb +38 -2
- data/test/xml/test_text.rb +11 -2
- data/test/xml/test_unparented_node.rb +1 -1
- data/test/xml/test_xpath.rb +11 -7
- metadata +159 -100
- data.tar.gz.sig +0 -0
- data/lib/nokogiri/version_warning.rb +0 -14
- metadata.gz.sig +0 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
package nokogiri.internals;
|
2
|
+
|
3
|
+
import javax.xml.namespace.QName;
|
4
|
+
import javax.xml.xpath.XPathFunction;
|
5
|
+
import javax.xml.xpath.XPathFunctionResolver;
|
6
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
7
|
+
|
8
|
+
/**
|
9
|
+
*
|
10
|
+
* @author sergio
|
11
|
+
*/
|
12
|
+
public class NokogiriXPathFunctionResolver implements XPathFunctionResolver {
|
13
|
+
|
14
|
+
private IRubyObject handler;
|
15
|
+
|
16
|
+
public NokogiriXPathFunctionResolver(IRubyObject handler) {
|
17
|
+
this.handler = handler;
|
18
|
+
}
|
19
|
+
|
20
|
+
public XPathFunction resolveFunction(QName name, int arity) {
|
21
|
+
return new NokogiriXPathFunction(this.handler, name.getLocalPart(), arity);
|
22
|
+
}
|
23
|
+
}
|
@@ -0,0 +1,235 @@
|
|
1
|
+
package nokogiri.internals;
|
2
|
+
|
3
|
+
import static nokogiri.internals.NokogiriHelpers.rubyStringToString;
|
4
|
+
import static org.jruby.javasupport.util.RuntimeHelpers.invoke;
|
5
|
+
|
6
|
+
import java.io.ByteArrayInputStream;
|
7
|
+
import java.io.File;
|
8
|
+
import java.io.FileInputStream;
|
9
|
+
import java.io.IOException;
|
10
|
+
import java.io.InputStream;
|
11
|
+
|
12
|
+
import org.jruby.Ruby;
|
13
|
+
import org.jruby.RubyClass;
|
14
|
+
import org.jruby.RubyIO;
|
15
|
+
import org.jruby.RubyObject;
|
16
|
+
import org.jruby.RubyString;
|
17
|
+
import org.jruby.exceptions.RaiseException;
|
18
|
+
import org.jruby.runtime.ThreadContext;
|
19
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
20
|
+
import org.jruby.util.ByteList;
|
21
|
+
import org.jruby.util.TypeConverter;
|
22
|
+
import org.xml.sax.InputSource;
|
23
|
+
import org.xml.sax.SAXException;
|
24
|
+
import org.xml.sax.ext.EntityResolver2;
|
25
|
+
|
26
|
+
/**
|
27
|
+
* Base class for the various parser contexts. Handles converting
|
28
|
+
* Ruby objects to InputSource objects.
|
29
|
+
*
|
30
|
+
* @author Patrick Mahoney <pat@polycrystal.org>
|
31
|
+
*/
|
32
|
+
public class ParserContext extends RubyObject {
|
33
|
+
protected InputSource source = null;
|
34
|
+
|
35
|
+
/**
|
36
|
+
* Create a file base input source taking into account the current
|
37
|
+
* directory of <code>runtime</code>.
|
38
|
+
*/
|
39
|
+
public static InputSource resolveEntity(Ruby runtime,
|
40
|
+
String publicId,
|
41
|
+
String baseURI,
|
42
|
+
String systemId)
|
43
|
+
throws IOException {
|
44
|
+
String path;
|
45
|
+
|
46
|
+
if ((new File(systemId)).isAbsolute()) {
|
47
|
+
path = systemId;
|
48
|
+
} else if (baseURI != null) {
|
49
|
+
path = (new File(baseURI, systemId)).getAbsolutePath();
|
50
|
+
} else {
|
51
|
+
String rubyDir = runtime.getCurrentDirectory();
|
52
|
+
path = (new File(rubyDir, systemId)).getAbsolutePath();
|
53
|
+
}
|
54
|
+
|
55
|
+
InputSource s = new InputSource(new FileInputStream(path));
|
56
|
+
s.setSystemId(systemId);
|
57
|
+
s.setPublicId(publicId);
|
58
|
+
return s;
|
59
|
+
}
|
60
|
+
|
61
|
+
public ParserContext(Ruby runtime) {
|
62
|
+
// default to class 'Object' because this class isn't exposed to Ruby
|
63
|
+
super(runtime, runtime.getObject());
|
64
|
+
}
|
65
|
+
|
66
|
+
public ParserContext(Ruby runtime, RubyClass klass) {
|
67
|
+
super(runtime, klass);
|
68
|
+
}
|
69
|
+
|
70
|
+
protected InputSource getInputSource() {
|
71
|
+
return source;
|
72
|
+
}
|
73
|
+
|
74
|
+
/**
|
75
|
+
* Set the InputSource from <code>data</code> which may be an IO
|
76
|
+
* object, a String, or a StringIO.
|
77
|
+
*/
|
78
|
+
public void setInputSource(ThreadContext context,
|
79
|
+
IRubyObject data) {
|
80
|
+
Ruby ruby = context.getRuntime();
|
81
|
+
|
82
|
+
if (invoke(context, data, "respond_to?",
|
83
|
+
ruby.newSymbol("to_io").to_sym()).isTrue()) {
|
84
|
+
/* IO or other object that responds to :to_io */
|
85
|
+
RubyIO io =
|
86
|
+
(RubyIO) TypeConverter.convertToType(data,
|
87
|
+
ruby.getIO(),
|
88
|
+
"to_io");
|
89
|
+
source = new InputSource(io.getInStream());
|
90
|
+
} else {
|
91
|
+
RubyString str;
|
92
|
+
if (invoke(context, data, "respond_to?",
|
93
|
+
ruby.newSymbol("string").to_sym()).isTrue()) {
|
94
|
+
/* StringIO or other object that responds to :string */
|
95
|
+
str = invoke(context, data, "string").convertToString();
|
96
|
+
} else if (data instanceof RubyString) {
|
97
|
+
str = (RubyString) data;
|
98
|
+
} else {
|
99
|
+
throw ruby.newArgumentError(
|
100
|
+
"must be kind_of String or respond to :to_io or :string");
|
101
|
+
}
|
102
|
+
|
103
|
+
ByteList bytes = str.getByteList();
|
104
|
+
source = new InputSource(new ByteArrayInputStream(bytes.unsafeBytes(), bytes.begin(), bytes.length()));
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
/**
|
109
|
+
* Set the InputSource to read from <code>file</code>, a String filename.
|
110
|
+
*/
|
111
|
+
public void setInputSourceFile(ThreadContext context, IRubyObject file) {
|
112
|
+
String filename = rubyStringToString(file);
|
113
|
+
|
114
|
+
try{
|
115
|
+
source = resolveEntity(context.getRuntime(),
|
116
|
+
null, null, filename);
|
117
|
+
} catch (Exception e) {
|
118
|
+
throw RaiseException
|
119
|
+
.createNativeRaiseException(context.getRuntime(), e);
|
120
|
+
}
|
121
|
+
|
122
|
+
}
|
123
|
+
|
124
|
+
/**
|
125
|
+
* Set the InputSource from <code>stream</code>.
|
126
|
+
*/
|
127
|
+
public void setInputSource(InputStream stream) {
|
128
|
+
source = new InputSource(stream);
|
129
|
+
}
|
130
|
+
|
131
|
+
/**
|
132
|
+
* Wrap Nokogiri parser options in a utility class. This is
|
133
|
+
* read-only.
|
134
|
+
*/
|
135
|
+
public static class Options {
|
136
|
+
protected static final long STRICT = 0;
|
137
|
+
protected static final long RECOVER = 1;
|
138
|
+
protected static final long NOENT = 2;
|
139
|
+
protected static final long DTDLOAD = 4;
|
140
|
+
protected static final long DTDATTR = 8;
|
141
|
+
protected static final long DTDVALID = 16;
|
142
|
+
protected static final long NOERROR = 32;
|
143
|
+
protected static final long NOWARNING = 64;
|
144
|
+
protected static final long PEDANTIC = 128;
|
145
|
+
protected static final long NOBLANKS = 256;
|
146
|
+
protected static final long SAX1 = 512;
|
147
|
+
protected static final long XINCLUDE = 1024;
|
148
|
+
protected static final long NONET = 2048;
|
149
|
+
protected static final long NODICT = 4096;
|
150
|
+
protected static final long NSCLEAN = 8192;
|
151
|
+
protected static final long NOCDATA = 16384;
|
152
|
+
protected static final long NOXINCNODE = 32768;
|
153
|
+
|
154
|
+
public boolean strict;
|
155
|
+
public boolean recover;
|
156
|
+
public boolean noEnt;
|
157
|
+
public boolean dtdLoad;
|
158
|
+
public boolean dtdAttr;
|
159
|
+
public boolean dtdValid;
|
160
|
+
public boolean noError;
|
161
|
+
public boolean noWarning;
|
162
|
+
public boolean pedantic;
|
163
|
+
public boolean noBlanks;
|
164
|
+
public boolean sax1;
|
165
|
+
public boolean xInclude;
|
166
|
+
public boolean noNet;
|
167
|
+
public boolean noDict;
|
168
|
+
public boolean nsClean;
|
169
|
+
public boolean noCdata;
|
170
|
+
public boolean noXIncNode;
|
171
|
+
|
172
|
+
protected static boolean test(long options, long mask) {
|
173
|
+
return ((options & mask) == mask);
|
174
|
+
}
|
175
|
+
|
176
|
+
public Options(long options) {
|
177
|
+
strict = ((options & RECOVER) == STRICT);
|
178
|
+
recover = test(options, RECOVER);
|
179
|
+
noEnt = test(options, NOENT);
|
180
|
+
dtdLoad = test(options, DTDLOAD);
|
181
|
+
dtdAttr = test(options, DTDATTR);
|
182
|
+
dtdValid = test(options, DTDVALID);
|
183
|
+
noError = test(options, NOERROR);
|
184
|
+
noWarning = test(options, NOWARNING);
|
185
|
+
pedantic = test(options, PEDANTIC);
|
186
|
+
noBlanks = test(options, NOBLANKS);
|
187
|
+
sax1 = test(options, SAX1);
|
188
|
+
xInclude = test(options, XINCLUDE);
|
189
|
+
noNet = test(options, NONET);
|
190
|
+
noDict = test(options, NODICT);
|
191
|
+
nsClean = test(options, NSCLEAN);
|
192
|
+
noCdata = test(options, NOCDATA);
|
193
|
+
noXIncNode = test(options, NOXINCNODE);
|
194
|
+
}
|
195
|
+
}
|
196
|
+
|
197
|
+
/**
|
198
|
+
* An entity resolver aware of the fact that the Ruby runtime can
|
199
|
+
* change directory but the JVM cannot. Thus any file based
|
200
|
+
* entity resolution that uses relative paths must be translated
|
201
|
+
* to be relative to the current directory of the Ruby runtime.
|
202
|
+
*/
|
203
|
+
public static class ChdirEntityResolver implements EntityResolver2 {
|
204
|
+
protected Ruby runtime;
|
205
|
+
|
206
|
+
public ChdirEntityResolver(Ruby runtime) {
|
207
|
+
super();
|
208
|
+
this.runtime = runtime;
|
209
|
+
}
|
210
|
+
|
211
|
+
@Override
|
212
|
+
public InputSource getExternalSubset(String name, String baseURI)
|
213
|
+
throws SAXException, IOException {
|
214
|
+
return null;
|
215
|
+
}
|
216
|
+
|
217
|
+
@Override
|
218
|
+
public InputSource resolveEntity(String publicId, String systemId)
|
219
|
+
throws SAXException, IOException {
|
220
|
+
return resolveEntity(null, publicId, null, systemId);
|
221
|
+
}
|
222
|
+
|
223
|
+
@Override
|
224
|
+
public InputSource resolveEntity(String name,
|
225
|
+
String publicId,
|
226
|
+
String baseURI,
|
227
|
+
String systemId)
|
228
|
+
throws SAXException, IOException {
|
229
|
+
return ParserContext
|
230
|
+
.resolveEntity(runtime, publicId, baseURI, systemId);
|
231
|
+
}
|
232
|
+
|
233
|
+
}
|
234
|
+
|
235
|
+
}
|
@@ -0,0 +1,381 @@
|
|
1
|
+
package nokogiri.internals;
|
2
|
+
|
3
|
+
import java.io.InputStream;
|
4
|
+
import java.io.IOException;
|
5
|
+
import java.nio.channels.ClosedChannelException;
|
6
|
+
import java.lang.Math;
|
7
|
+
import java.lang.Thread;
|
8
|
+
import java.util.ArrayList;
|
9
|
+
|
10
|
+
|
11
|
+
/**
|
12
|
+
* Implements a "push" InputStream. An owner thread create an
|
13
|
+
* InputStream and passes it to a second thread. The owner thread
|
14
|
+
* calls PushInputStream.write() to write data to the stream. The
|
15
|
+
* second thread calls PushInputStream.read() and other InputStream
|
16
|
+
* methods.
|
17
|
+
*
|
18
|
+
* You should ensure that only one thread write to, and only one
|
19
|
+
* thread reads to, this stream, though nothing enforces this
|
20
|
+
* strictly.
|
21
|
+
*/
|
22
|
+
public class PushInputStream extends InputStream {
|
23
|
+
/**
|
24
|
+
* Current position in the stream relative to the start of the
|
25
|
+
* buffer.
|
26
|
+
*/
|
27
|
+
protected int pos;
|
28
|
+
|
29
|
+
/**
|
30
|
+
* Current mark position, or -1 if there is no mark.
|
31
|
+
*/
|
32
|
+
protected int mark;
|
33
|
+
|
34
|
+
protected int readlimit;
|
35
|
+
|
36
|
+
/**
|
37
|
+
* State is open or closed.
|
38
|
+
*/
|
39
|
+
protected boolean isOpen;
|
40
|
+
|
41
|
+
protected Buffer buffer;
|
42
|
+
|
43
|
+
public PushInputStream() {
|
44
|
+
pos = 0;
|
45
|
+
mark = -1;
|
46
|
+
readlimit = -1;
|
47
|
+
isOpen = true;
|
48
|
+
|
49
|
+
buffer = new Buffer(512);
|
50
|
+
}
|
51
|
+
|
52
|
+
protected synchronized void ensureOpen() throws IOException {
|
53
|
+
if (!isOpen) {
|
54
|
+
throw new ClosedChannelException();
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
/**
|
59
|
+
* Write data that can be read from the stream.
|
60
|
+
*/
|
61
|
+
public synchronized void write(byte[] b) {
|
62
|
+
if (buffer == null) System.out.println("BUFFER IS NULL");
|
63
|
+
if (b == null) System.out.println("BYTE ARRAY IS NILL");
|
64
|
+
buffer.put(b);
|
65
|
+
notifyAll(); // notify readers waiting
|
66
|
+
}
|
67
|
+
|
68
|
+
/**
|
69
|
+
* Write data and then wait until all the data has been read
|
70
|
+
* (waits until the thread reading from this stream is blocked in
|
71
|
+
* a read()).
|
72
|
+
*/
|
73
|
+
public synchronized void writeAndWaitForRead(byte[] b) throws IOException {
|
74
|
+
ensureOpen();
|
75
|
+
write(b);
|
76
|
+
for (;;) {
|
77
|
+
try {
|
78
|
+
wait();
|
79
|
+
break;
|
80
|
+
} catch (InterruptedException e) {
|
81
|
+
// continue waiting
|
82
|
+
}
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
/*
|
87
|
+
*------------------------------------------------------------
|
88
|
+
* InputStream methods
|
89
|
+
*------------------------------------------------------------
|
90
|
+
*/
|
91
|
+
|
92
|
+
/**
|
93
|
+
* @see InputStream.available()
|
94
|
+
*/
|
95
|
+
@Override
|
96
|
+
public synchronized int available() throws IOException {
|
97
|
+
ensureOpen();
|
98
|
+
return buffer.size() - pos;
|
99
|
+
}
|
100
|
+
|
101
|
+
int nClose = 0;
|
102
|
+
/**
|
103
|
+
* @see InputStream.close()
|
104
|
+
*/
|
105
|
+
@Override
|
106
|
+
public synchronized void close() throws IOException {
|
107
|
+
if (!isOpen) return;
|
108
|
+
isOpen = false;
|
109
|
+
buffer = null;
|
110
|
+
notifyAll();
|
111
|
+
}
|
112
|
+
|
113
|
+
/**
|
114
|
+
* @see InputStream.mark()
|
115
|
+
*/
|
116
|
+
@Override
|
117
|
+
public synchronized void mark(int readlimit) {
|
118
|
+
this.mark = pos;
|
119
|
+
this.readlimit = readlimit;
|
120
|
+
}
|
121
|
+
|
122
|
+
/**
|
123
|
+
* Mark the current position in this stream. Supported by
|
124
|
+
* PushInputStream.
|
125
|
+
*
|
126
|
+
* @see InputStream.markSupported()
|
127
|
+
*/
|
128
|
+
@Override
|
129
|
+
public synchronized boolean markSupported() {
|
130
|
+
return true;
|
131
|
+
}
|
132
|
+
|
133
|
+
/**
|
134
|
+
* @see InputStream.read()
|
135
|
+
*/
|
136
|
+
@Override
|
137
|
+
public synchronized int read() throws IOException {
|
138
|
+
ensureOpen();
|
139
|
+
byte[] b = new byte[1];
|
140
|
+
read(b, 0, 1);
|
141
|
+
return (int) b[0];
|
142
|
+
}
|
143
|
+
|
144
|
+
/**
|
145
|
+
* @see InputStream.read(byte[])
|
146
|
+
*/
|
147
|
+
@Override
|
148
|
+
public synchronized int read(byte[] b) throws IOException {
|
149
|
+
ensureOpen();
|
150
|
+
return read(b, 0, b.length);
|
151
|
+
}
|
152
|
+
|
153
|
+
protected synchronized boolean markIsValid() {
|
154
|
+
return (mark >= 0 && pos < mark+readlimit);
|
155
|
+
}
|
156
|
+
|
157
|
+
/**
|
158
|
+
* @see InputStream.read(byte[], int, int)
|
159
|
+
*/
|
160
|
+
@Override
|
161
|
+
public synchronized int read(byte[] b, int off, int len) throws IOException {
|
162
|
+
while (isOpen && available() == 0) {
|
163
|
+
/* block until data available */
|
164
|
+
try {
|
165
|
+
notifyAll(); // notify writers waiting
|
166
|
+
wait();
|
167
|
+
} catch (InterruptedException e) {
|
168
|
+
// continue waiting
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
if (!isOpen) {
|
173
|
+
return -1;
|
174
|
+
}
|
175
|
+
|
176
|
+
int readLen = Math.min(available(), len);
|
177
|
+
|
178
|
+
buffer.get(pos, readLen, b, off);
|
179
|
+
pos += readLen;
|
180
|
+
|
181
|
+
int reduce;
|
182
|
+
|
183
|
+
if (markIsValid()) {
|
184
|
+
reduce = mark;
|
185
|
+
} else {
|
186
|
+
reduce = pos;
|
187
|
+
}
|
188
|
+
|
189
|
+
buffer.truncateFromStart(buffer.size - reduce);
|
190
|
+
pos -= reduce;
|
191
|
+
mark -= reduce;
|
192
|
+
if (mark < 0) mark = -1; // don't wrap mark around?
|
193
|
+
|
194
|
+
return readLen;
|
195
|
+
}
|
196
|
+
|
197
|
+
/**
|
198
|
+
* @see InputStream.reset()
|
199
|
+
*/
|
200
|
+
@Override
|
201
|
+
public synchronized void reset() throws IOException {
|
202
|
+
ensureOpen();
|
203
|
+
if (markIsValid())
|
204
|
+
pos = mark;
|
205
|
+
}
|
206
|
+
|
207
|
+
/**
|
208
|
+
* @see InputStream.skip()
|
209
|
+
*/
|
210
|
+
@Override
|
211
|
+
public synchronized long skip(long n) throws IOException {
|
212
|
+
ensureOpen();
|
213
|
+
pos += n;
|
214
|
+
return n;
|
215
|
+
}
|
216
|
+
|
217
|
+
/*
|
218
|
+
*------------------------------------------------------------
|
219
|
+
* Data Buffer
|
220
|
+
*------------------------------------------------------------
|
221
|
+
*/
|
222
|
+
|
223
|
+
public static class Block {
|
224
|
+
protected byte[] data;
|
225
|
+
|
226
|
+
public Block(int size) {
|
227
|
+
data = new byte[size];
|
228
|
+
}
|
229
|
+
|
230
|
+
public void copyIn(byte[] src, int srcPos, int destPos, int length) {
|
231
|
+
System.arraycopy(src, srcPos, data, destPos, length);
|
232
|
+
}
|
233
|
+
|
234
|
+
public void copyOut(int srcPos, byte[] dest, int destPos, int length) {
|
235
|
+
System.arraycopy(data, srcPos, dest, destPos, length);
|
236
|
+
}
|
237
|
+
}
|
238
|
+
|
239
|
+
public static class BlockList extends ArrayList<Block> {
|
240
|
+
public BlockList() {
|
241
|
+
super();
|
242
|
+
}
|
243
|
+
|
244
|
+
@Override
|
245
|
+
public void removeRange(int fromIndex, int toIndex) {
|
246
|
+
super.removeRange(fromIndex, toIndex);
|
247
|
+
}
|
248
|
+
}
|
249
|
+
|
250
|
+
public static class Buffer {
|
251
|
+
protected int blockSize;
|
252
|
+
protected BlockList blocks;
|
253
|
+
|
254
|
+
/**
|
255
|
+
* Offset (position) to the first logical byte in the buffer.
|
256
|
+
*/
|
257
|
+
protected int offset;
|
258
|
+
|
259
|
+
/**
|
260
|
+
* Logical size of the buffer.
|
261
|
+
*/
|
262
|
+
protected int size;
|
263
|
+
|
264
|
+
public Buffer(int blockSize) {
|
265
|
+
this.blockSize = blockSize;
|
266
|
+
this.blocks = new BlockList();
|
267
|
+
this.offset = 0;
|
268
|
+
this.size = 0;
|
269
|
+
}
|
270
|
+
|
271
|
+
public int size() {
|
272
|
+
return size;
|
273
|
+
}
|
274
|
+
|
275
|
+
protected class Segment {
|
276
|
+
/**
|
277
|
+
* Block index.
|
278
|
+
*/
|
279
|
+
protected int block;
|
280
|
+
|
281
|
+
/**
|
282
|
+
* Offset into the block.
|
283
|
+
*/
|
284
|
+
protected int off;
|
285
|
+
|
286
|
+
/**
|
287
|
+
* Length of segment.
|
288
|
+
*/
|
289
|
+
protected int len;
|
290
|
+
|
291
|
+
/**
|
292
|
+
* Calculate the block number and block offset given a position.
|
293
|
+
*/
|
294
|
+
protected Segment(int pos) {
|
295
|
+
int absPos = offset + pos;
|
296
|
+
block = (int) (absPos / blockSize);
|
297
|
+
off = (int) (absPos % blockSize);
|
298
|
+
len = -1;
|
299
|
+
}
|
300
|
+
}
|
301
|
+
|
302
|
+
protected Segment[] accessList(int pos, int size) {
|
303
|
+
Segment start = new Segment(pos);
|
304
|
+
Segment end = new Segment(pos + size);
|
305
|
+
int nBlocks = end.block - start.block + 1;
|
306
|
+
Segment[] segs = new Segment[nBlocks];
|
307
|
+
|
308
|
+
start.len = Math.min(size, blockSize - start.off);
|
309
|
+
segs[0] = start;
|
310
|
+
int currPos = pos + start.len;
|
311
|
+
int currSize = start.len;
|
312
|
+
for (int i = 1; i < nBlocks; i++) {
|
313
|
+
Segment seg = new Segment(currPos);
|
314
|
+
seg.len = Math.min(blockSize, size - currSize);
|
315
|
+
segs[i] = seg;
|
316
|
+
currPos += seg.len;
|
317
|
+
currSize += seg.len;
|
318
|
+
}
|
319
|
+
|
320
|
+
return segs;
|
321
|
+
}
|
322
|
+
|
323
|
+
protected void ensureCapacity(int pos) {
|
324
|
+
Segment seg = new Segment(pos-1);
|
325
|
+
|
326
|
+
while (blocks.size() < (seg.block + 1))
|
327
|
+
blocks.add(new Block(blockSize));
|
328
|
+
}
|
329
|
+
|
330
|
+
public void put(byte b) {
|
331
|
+
byte[] buf = new byte[1];
|
332
|
+
buf[0] = b;
|
333
|
+
put(buf);
|
334
|
+
}
|
335
|
+
|
336
|
+
public void put(byte[] b) {
|
337
|
+
ensureCapacity(size + b.length);
|
338
|
+
Segment[] segs = accessList(size, b.length);
|
339
|
+
|
340
|
+
int off = 0;
|
341
|
+
for (int i = 0; i < segs.length; i++) {
|
342
|
+
Block block = blocks.get(segs[i].block);
|
343
|
+
block.copyIn(b, off, segs[i].off, segs[i].len);
|
344
|
+
}
|
345
|
+
|
346
|
+
size += b.length;
|
347
|
+
}
|
348
|
+
|
349
|
+
public byte[] get(int pos, int len) {
|
350
|
+
byte[] b = new byte[len];
|
351
|
+
get(pos, len, b, 0);
|
352
|
+
return b;
|
353
|
+
}
|
354
|
+
|
355
|
+
/**
|
356
|
+
* Throws IndexOutOfBoundsException.
|
357
|
+
*/
|
358
|
+
public void get(int pos, int len, byte[] b, int off) {
|
359
|
+
Segment[] segs = accessList(pos, len);
|
360
|
+
for (int i = 0; i < segs.length; i++) {
|
361
|
+
Block block = blocks.get(segs[i].block);
|
362
|
+
block.copyOut(segs[i].off, b, off, segs[i].len);
|
363
|
+
}
|
364
|
+
}
|
365
|
+
|
366
|
+
/**
|
367
|
+
* Truncate the buffer to <code>newSize</code> by removing
|
368
|
+
* data from the start of the buffer.
|
369
|
+
*/
|
370
|
+
public void truncateFromStart(int newSize) {
|
371
|
+
if (newSize > size || newSize < 0)
|
372
|
+
throw new RuntimeException("invalid size");
|
373
|
+
|
374
|
+
Segment newStart = new Segment(size - newSize);
|
375
|
+
blocks.removeRange(0, newStart.block);
|
376
|
+
|
377
|
+
size = newSize;
|
378
|
+
offset = newStart.off;
|
379
|
+
}
|
380
|
+
}
|
381
|
+
}
|