47 |
* @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> |
* @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> |
48 |
* @version $Revision$ $Date$ |
* @version $Revision$ $Date$ |
49 |
*/ |
*/ |
50 |
public class FileNewsrc |
public class FileNewsrc implements Newsrc |
|
implements Newsrc |
|
51 |
{ |
{ |
52 |
|
|
53 |
private static final String NEWSRC_ENCODING = "US-ASCII"; |
private static final String NEWSRC_ENCODING = "US-ASCII"; |
54 |
|
|
55 |
protected File file; |
protected File file; |
56 |
|
|
57 |
protected List subs = null; |
protected List subs = null; |
59 |
protected Map lines = null; |
protected Map lines = null; |
60 |
protected boolean dirty; |
protected boolean dirty; |
61 |
protected boolean debug; |
protected boolean debug; |
62 |
|
|
63 |
/** |
/** |
64 |
* Constructs a FileNewsrc for the specified url. |
* Constructs a FileNewsrc for the specified url. |
65 |
*/ |
*/ |
66 |
public FileNewsrc(File file, boolean debug) |
public FileNewsrc(File file, boolean debug) |
67 |
{ |
{ |
68 |
this.file = file; |
this.file = file; |
69 |
this.debug = debug; |
this.debug = debug; |
70 |
} |
} |
71 |
|
|
72 |
public void close() |
public void close() |
73 |
{ |
{ |
74 |
if (!dirty) |
if (!dirty) |
75 |
return; |
return; |
82 |
void load() |
void load() |
83 |
{ |
{ |
84 |
long fs = file.length(); |
long fs = file.length(); |
85 |
long max = (long)Integer.MAX_VALUE; |
long max = (long) Integer.MAX_VALUE; |
86 |
int bs = (int)(fs>max ? max : fs); |
int bs = (int) (fs > max ? max : fs); |
87 |
|
|
88 |
groups = new LinkedList(); |
groups = new LinkedList(); |
89 |
lines = new HashMap(bs/20); |
lines = new HashMap(bs / 20); |
90 |
subs = new LinkedList(); |
subs = new LinkedList(); |
91 |
|
|
92 |
// Load |
// Load |
93 |
try |
try |
94 |
{ |
{ |
95 |
long t1 = System.currentTimeMillis(); |
long t1 = System.currentTimeMillis(); |
96 |
//long rcount = 0; |
//long rcount = 0; |
97 |
if (debug) |
if (debug) |
98 |
System.err.println("DEBUG: nntp: newsrc loading "+file.getPath()); |
System.err.println("DEBUG: nntp: newsrc loading " + file.getPath()); |
99 |
|
|
100 |
FileInputStream fr = new FileInputStream(file); |
FileInputStream fr = new FileInputStream(file); |
101 |
InputStreamReader ir = new InputStreamReader(fr, NEWSRC_ENCODING); |
InputStreamReader ir = new InputStreamReader(fr, NEWSRC_ENCODING); |
102 |
BufferedReader reader = new BufferedReader(ir, bs); |
BufferedReader reader = new BufferedReader(ir, bs); |
104 |
String line = reader.readLine(); |
String line = reader.readLine(); |
105 |
//long tt2 = System.currentTimeMillis(); |
//long tt2 = System.currentTimeMillis(); |
106 |
//rcount += (tt2-tt1); |
//rcount += (tt2-tt1); |
107 |
while (line!=null) |
while (line != null) |
108 |
{ |
{ |
109 |
int cp = line.indexOf(':'); |
int cp = line.indexOf(':'); |
110 |
if (cp>-1) |
if (cp > -1) |
111 |
{ |
{ |
112 |
// Subscribed newsgroup |
// Subscribed newsgroup |
113 |
String name = line.substring(0, cp); |
String name = line.substring(0, cp); |
114 |
groups.add(name); |
groups.add(name); |
115 |
subs.add(name); |
subs.add(name); |
116 |
cp++; |
cp++; |
117 |
if (cp<line.length()) |
if (cp < line.length()) |
118 |
{ |
{ |
119 |
String tail = line.substring(cp).trim(); |
String tail = line.substring(cp).trim(); |
120 |
if (tail.length()>0) |
if (tail.length() > 0) |
121 |
lines.put(name, tail); |
lines.put(name, tail); |
122 |
} |
} |
123 |
} |
} |
124 |
else |
else |
125 |
{ |
{ |
126 |
int pp = line.indexOf('!'); |
int pp = line.indexOf('!'); |
127 |
if (pp>-1) |
if (pp > -1) |
128 |
{ |
{ |
129 |
// Unsubscribed newsgroup |
// Unsubscribed newsgroup |
130 |
String name = line.substring(0, pp); |
String name = line.substring(0, pp); |
131 |
groups.add(name); |
groups.add(name); |
132 |
pp++; |
pp++; |
133 |
if (pp<line.length()) |
if (pp < line.length()) |
134 |
{ |
{ |
135 |
String tail = line.substring(pp).trim(); |
String tail = line.substring(pp).trim(); |
136 |
if (tail.length()>0) |
if (tail.length() > 0) |
137 |
lines.put(name, tail); |
lines.put(name, tail); |
138 |
} |
} |
139 |
} |
} |
147 |
reader.close(); |
reader.close(); |
148 |
|
|
149 |
long t2 = System.currentTimeMillis(); |
long t2 = System.currentTimeMillis(); |
150 |
|
|
151 |
//System.err.println("rcount="+rcount); |
//System.err.println("rcount="+rcount); |
152 |
//System.err.println("total="+(t2-t1)); |
//System.err.println("total="+(t2-t1)); |
153 |
|
|
154 |
if (debug) |
if (debug) |
155 |
System.err.println("DEBUG: nntp: newsrc load: "+ |
System.err.println("DEBUG: nntp: newsrc load: " + |
156 |
groups.size()+" groups in "+(t2-t1)+"ms"); |
groups.size() + " groups in " + (t2 - t1) + "ms"); |
157 |
} |
} |
158 |
catch (FileNotFoundException e) |
catch(FileNotFoundException e) |
159 |
{ |
{ |
160 |
} |
} |
161 |
catch (IOException e) |
catch(IOException e) |
162 |
{ |
{ |
163 |
System.err.println("WARNING: nntp: unable to read newsrc file"); |
System.err.println("WARNING: nntp: unable to read newsrc file"); |
164 |
if (debug) |
if (debug) |
165 |
e.printStackTrace(System.err); |
e.printStackTrace(System.err); |
166 |
} |
} |
167 |
catch (SecurityException e) |
catch(SecurityException e) |
168 |
{ |
{ |
169 |
System.err.println("WARNING: nntp: no read permission on newsrc file"); |
System.err.println("WARNING: nntp: no read permission on newsrc file"); |
170 |
} |
} |
171 |
dirty = false; |
dirty = false; |
172 |
} |
} |
173 |
|
|
174 |
/** |
/** |
175 |
* Save the file. |
* Save the file. |
176 |
*/ |
*/ |
177 |
void save() |
void save() |
178 |
{ |
{ |
179 |
try |
try |
180 |
{ |
{ |
181 |
long t1 = System.currentTimeMillis(); |
long t1 = System.currentTimeMillis(); |
182 |
if (debug) |
if (debug) |
183 |
System.err.println("DEBUG: nntp: newsrc saving "+file.getPath()); |
System.err.println("DEBUG: nntp: newsrc saving " + file.getPath()); |
184 |
|
|
185 |
int bs = (groups.size()*20); // guess an average line length |
int bs = (groups.size() * 20); // guess an average line length |
186 |
FileOutputStream fw = new FileOutputStream(file); |
FileOutputStream fw = new FileOutputStream(file); |
187 |
BufferedOutputStream writer = new BufferedOutputStream(fw, bs); |
BufferedOutputStream writer = new BufferedOutputStream(fw, bs); |
188 |
for (Iterator i = groups.iterator(); i.hasNext(); ) |
for (Iterator i = groups.iterator(); i.hasNext();) |
189 |
{ |
{ |
190 |
String group = (String)i.next(); |
String group = (String) i.next(); |
191 |
StringBuffer buffer = new StringBuffer(group); |
StringBuffer buffer = new StringBuffer(group); |
192 |
if (subs.contains(group)) |
if (subs.contains(group)) |
193 |
buffer.append(':'); |
buffer.append(':'); |
195 |
buffer.append('!'); |
buffer.append('!'); |
196 |
Object r = lines.get(group); |
Object r = lines.get(group); |
197 |
if (r instanceof String) |
if (r instanceof String) |
198 |
buffer.append((String)r); |
buffer.append((String) r); |
199 |
else |
else |
200 |
{ |
{ |
201 |
RangeList ranges = (RangeList)r; |
RangeList ranges = (RangeList) r; |
202 |
if (ranges!=null) |
if (ranges != null) |
203 |
buffer.append(ranges.toString()); |
buffer.append(ranges.toString()); |
204 |
} |
} |
205 |
buffer.append('\n'); |
buffer.append('\n'); |
206 |
|
|
207 |
byte[] bytes = buffer.toString().getBytes(NEWSRC_ENCODING); |
byte[]bytes = buffer.toString().getBytes(NEWSRC_ENCODING); |
208 |
writer.write(bytes); |
writer.write(bytes); |
209 |
} |
} |
210 |
writer.flush(); |
writer.flush(); |
212 |
|
|
213 |
long t2 = System.currentTimeMillis(); |
long t2 = System.currentTimeMillis(); |
214 |
if (debug) |
if (debug) |
215 |
System.err.println("DEBUG: nntp: newsrc save: "+ |
System.err.println("DEBUG: nntp: newsrc save: " + |
216 |
groups.size()+" groups in "+(t2-t1)+"ms"); |
groups.size() + " groups in " + (t2 - t1) + "ms"); |
217 |
} |
} |
218 |
catch (IOException e) |
catch(IOException e) |
219 |
{ |
{ |
220 |
System.err.println("WARNING: nntp: unable to save newsrc file"); |
System.err.println("WARNING: nntp: unable to save newsrc file"); |
221 |
if (debug) |
if (debug) |
223 |
} |
} |
224 |
dirty = false; |
dirty = false; |
225 |
} |
} |
226 |
|
|
227 |
/** |
/** |
228 |
* Returns an iterator over the names of the currently subscribed |
* Returns an iterator over the names of the currently subscribed |
229 |
* newsgroups. |
* newsgroups. |
230 |
*/ |
*/ |
231 |
public Iterator list() |
public Iterator list() |
232 |
{ |
{ |
233 |
if (subs==null) |
if (subs == null) |
234 |
load(); |
load(); |
235 |
return subs.iterator(); |
return subs.iterator(); |
236 |
} |
} |
237 |
|
|
238 |
public boolean isSubscribed(String newsgroup) |
public boolean isSubscribed(String newsgroup) |
239 |
{ |
{ |
240 |
if (subs==null) |
if (subs == null) |
241 |
load(); |
load(); |
242 |
return (subs.contains(newsgroup)); |
return (subs.contains(newsgroup)); |
243 |
} |
} |
244 |
|
|
245 |
public void setSubscribed(String newsgroup, boolean flag) |
public void setSubscribed(String newsgroup, boolean flag) |
246 |
{ |
{ |
247 |
if (subs==null) |
if (subs == null) |
248 |
load(); |
load(); |
249 |
boolean subscribed = subs.contains(newsgroup); |
boolean subscribed = subs.contains(newsgroup); |
250 |
if (flag && !subscribed) |
if (flag && !subscribed) |
258 |
dirty = true; |
dirty = true; |
259 |
} |
} |
260 |
} |
} |
261 |
|
|
262 |
public boolean isSeen(String newsgroup, int article) |
public boolean isSeen(String newsgroup, int article) |
263 |
{ |
{ |
264 |
if (subs==null) |
if (subs == null) |
265 |
load(); |
load(); |
266 |
Object value = lines.get(newsgroup); |
Object value = lines.get(newsgroup); |
267 |
if (value instanceof String) |
if (value instanceof String) |
268 |
value = new RangeList((String)value); |
value = new RangeList((String) value); |
269 |
RangeList ranges = (RangeList)value; |
RangeList ranges = (RangeList) value; |
270 |
if (ranges!=null) |
if (ranges != null) |
271 |
return ranges.isSeen(article); |
return ranges.isSeen(article); |
272 |
return false; |
return false; |
273 |
} |
} |
274 |
|
|
275 |
public void setSeen(String newsgroup, int article, boolean flag) |
public void setSeen(String newsgroup, int article, boolean flag) |
276 |
{ |
{ |
277 |
if (subs==null) |
if (subs == null) |
278 |
load(); |
load(); |
279 |
Object value = lines.get(newsgroup); |
Object value = lines.get(newsgroup); |
280 |
if (value instanceof String) |
if (value instanceof String) |
281 |
value = new RangeList((String)value); |
value = new RangeList((String) value); |
282 |
RangeList ranges = (RangeList)value; |
RangeList ranges = (RangeList) value; |
283 |
if (ranges==null) |
if (ranges == null) |
284 |
{ |
{ |
285 |
ranges = new RangeList(); |
ranges = new RangeList(); |
286 |
lines.put(newsgroup, ranges); |
lines.put(newsgroup, ranges); |
287 |
} |
} |
288 |
if (ranges.isSeen(article)!=flag) |
if (ranges.isSeen(article) != flag) |
289 |
{ |
{ |
290 |
ranges.setSeen(article, flag); |
ranges.setSeen(article, flag); |
291 |
dirty = true; |
dirty = true; |
292 |
} |
} |
293 |
} |
} |
294 |
|
|
295 |
/** |
/** |
296 |
* A RangeList holds a series of ranges that are ordered and |
* A RangeList holds a series of ranges that are ordered and |
297 |
* non-overlapping. |
* non-overlapping. |
298 |
*/ |
*/ |
299 |
static class RangeList |
static class RangeList |
300 |
{ |
{ |
301 |
|
|
302 |
List seen; |
List seen; |
303 |
|
|
304 |
RangeList() |
RangeList() |
305 |
{ |
{ |
306 |
seen = new LinkedList(); |
seen = new LinkedList(); |
307 |
} |
} |
308 |
|
|
309 |
RangeList(String line) |
RangeList(String line) |
310 |
{ |
{ |
311 |
this(); |
this(); |
312 |
try |
try |
313 |
{ |
{ |
314 |
// Parse the line at comma delimiters. |
// Parse the line at comma delimiters. |
315 |
int start = 0; |
int start = 0; |
316 |
int end = line.indexOf(','); |
int end = line.indexOf(','); |
317 |
while (end>start) |
while (end > start) |
318 |
{ |
{ |
319 |
String token = line.substring(start, end); |
String token = line.substring(start, end); |
320 |
addToken(token); |
addToken(token); |
321 |
start = end+1; |
start = end + 1; |
322 |
end = line.indexOf(',', start); |
end = line.indexOf(',', start); |
323 |
} |
} |
324 |
addToken(line.substring(start)); |
addToken(line.substring(start)); |
325 |
} |
} |
326 |
catch (NumberFormatException e) |
catch(NumberFormatException e) |
327 |
{ |
{ |
328 |
System.err.println("ERROR: nntp: bad newsrc format: "+line); |
System.err.println("ERROR: nntp: bad newsrc format: " + line); |
329 |
} |
} |
330 |
} |
} |
331 |
|
|
332 |
/* |
/* |
333 |
* Used during initial parse. |
* Used during initial parse. |
334 |
*/ |
*/ |
335 |
private void addToken(String token) |
private void addToken(String token) throws NumberFormatException |
|
throws NumberFormatException |
|
336 |
{ |
{ |
337 |
int hp = token.indexOf('-'); |
int hp = token.indexOf('-'); |
338 |
if (hp>-1) |
if (hp > -1) |
339 |
{ |
{ |
340 |
// Range |
// Range |
341 |
String fs = token.substring(0, hp); |
String fs = token.substring(0, hp); |
342 |
String ts = token.substring(hp+1); |
String ts = token.substring(hp + 1); |
343 |
int from = Integer.parseInt(fs); |
int from = Integer.parseInt(fs); |
344 |
int to = Integer.parseInt(ts); |
int to = Integer.parseInt(ts); |
345 |
if (from>-1 && to>-1) |
if (from > -1 && to > -1) |
346 |
insert(from, to); |
insert(from, to); |
347 |
} |
} |
348 |
else |
else |
349 |
{ |
{ |
350 |
// Single number |
// Single number |
351 |
int number = Integer.parseInt(token); |
int number = Integer.parseInt(token); |
352 |
if (number>-1) |
if (number > -1) |
353 |
insert(number); |
insert(number); |
354 |
} |
} |
355 |
} |
} |
356 |
|
|
357 |
/** |
/** |
358 |
* Indicates whether the specified article is seen. |
* Indicates whether the specified article is seen. |
359 |
*/ |
*/ |
360 |
public boolean isSeen(int num) |
public boolean isSeen(int num) |
361 |
{ |
{ |
362 |
int len = seen.size(); |
int len = seen.size(); |
363 |
Range[] r = new Range[len]; |
Range[]r = new Range[len]; |
364 |
seen.toArray(r); |
seen.toArray(r); |
365 |
for (int i=0; i<len; i++) |
for (int i = 0; i < len; i++) |
366 |
{ |
{ |
367 |
if (r[i].contains(num)) |
if (r[i].contains(num)) |
368 |
return true; |
return true; |
369 |
} |
} |
370 |
return false; |
return false; |
371 |
} |
} |
372 |
|
|
373 |
/** |
/** |
374 |
* Sets whether the specified article is seen. |
* Sets whether the specified article is seen. |
375 |
*/ |
*/ |
376 |
public void setSeen(int num, boolean flag) |
public void setSeen(int num, boolean flag) |
377 |
{ |
{ |
378 |
if (flag) |
if (flag) |
379 |
insert(num); |
insert(num); |
380 |
else |
else |
381 |
remove(num); |
remove(num); |
382 |
} |
} |
383 |
|
|
384 |
/* |
/* |
385 |
* Find the index within seen to insert the specified article. |
* Find the index within seen to insert the specified article. |
386 |
* The range object at the returned index may already contain num. |
* The range object at the returned index may already contain num. |
387 |
*/ |
*/ |
388 |
int indexOf(int num) |
int indexOf(int num) |
389 |
{ |
{ |
390 |
int len = seen.size(); |
int len = seen.size(); |
391 |
Range[] r = new Range[len]; |
Range[]r = new Range[len]; |
392 |
seen.toArray(r); |
seen.toArray(r); |
393 |
for (int i=0; i<len; i++) |
for (int i = 0; i < len; i++) |
394 |
{ |
{ |
395 |
if (r[i].contains(num)) |
if (r[i].contains(num)) |
396 |
return i; |
return i; |
397 |
if (r[i].from>num) |
if (r[i].from > num) |
398 |
return i; |
return i; |
399 |
if (r[i].to==num-1) |
if (r[i].to == num - 1) |
400 |
return i; |
return i; |
401 |
} |
} |
402 |
return len; |
return len; |
403 |
} |
} |
404 |
|
|
405 |
void insert(int start, int end) |
void insert(int start, int end) |
406 |
{ |
{ |
407 |
Range range = new Range(start, end); |
Range range = new Range(start, end); |
408 |
int i1 = indexOf(range.from); |
int i1 = indexOf(range.from); |
409 |
// range is at end |
// range is at end |
410 |
if (i1==seen.size()) |
if (i1 == seen.size()) |
411 |
{ |
{ |
412 |
seen.add(range); |
seen.add(range); |
413 |
return; |
return; |
414 |
} |
} |
415 |
Range r1 = (Range)seen.get(i1); |
Range r1 = (Range) seen.get(i1); |
416 |
// range is before r1 |
// range is before r1 |
417 |
if (range.to<r1.from) |
if (range.to < r1.from) |
418 |
{ |
{ |
419 |
seen.add(i1, range); |
seen.add(i1, range); |
420 |
return; |
return; |
421 |
} |
} |
422 |
// range is a subset of r1 |
// range is a subset of r1 |
423 |
if (r1.from<=range.from && r1.to>=range.to) |
if (r1.from <= range.from && r1.to >= range.to) |
424 |
return; |
return; |
425 |
// range is a superset of r1 |
// range is a superset of r1 |
426 |
int i2 = indexOf(range.to); |
int i2 = indexOf(range.to); |
427 |
Range r2 = (Range)seen.get(i2); |
Range r2 = (Range) seen.get(i2); |
428 |
System.err.println("r2 "+r2+" i2 "+i2); |
System.err.println("r2 " + r2 + " i2 " + i2); |
429 |
// remove all ranges between |
// remove all ranges between |
430 |
for (int i=i2; i>=i1; i--) |
for (int i = i2; i >= i1; i--) |
431 |
seen.remove(i); |
seen.remove(i); |
432 |
// merge |
// merge |
433 |
int f = (range.from<r1.from) ? range.from : r1.from; |
int f = (range.from < r1.from) ? range.from : r1.from; |
434 |
int t = (range.to>r2.to) ? range.to : r2.to; |
int t = (range.to > r2.to) ? range.to : r2.to; |
435 |
range = new Range(f, t); |
range = new Range(f, t); |
436 |
seen.add(i1, range); |
seen.add(i1, range); |
437 |
} |
} |
438 |
|
|
439 |
void insert(int num) |
void insert(int num) |
440 |
{ |
{ |
441 |
insert(num, num); |
insert(num, num); |
442 |
} |
} |
443 |
|
|
444 |
void remove(int num) |
void remove(int num) |
445 |
{ |
{ |
446 |
int i = indexOf(num); |
int i = indexOf(num); |
447 |
Range r = (Range)seen.get(i); |
Range r = (Range) seen.get(i); |
448 |
seen.remove(i); |
seen.remove(i); |
449 |
// num == r |
// num == r |
450 |
if ((r.from==r.to) && (r.to==num)) |
if ((r.from == r.to) && (r.to == num)) |
451 |
return; |
return; |
452 |
// split r |
// split r |
453 |
if (r.to>num) |
if (r.to > num) |
454 |
{ |
{ |
455 |
Range r2 = new Range(num+1, r.to); |
Range r2 = new Range(num + 1, r.to); |
456 |
seen.add(i, r2); |
seen.add(i, r2); |
457 |
} |
} |
458 |
if (r.from<num) |
if (r.from < num) |
459 |
{ |
{ |
460 |
Range r2 = new Range(r.from, num-1); |
Range r2 = new Range(r.from, num - 1); |
461 |
seen.add(i, r2); |
seen.add(i, r2); |
462 |
} |
} |
463 |
} |
} |
464 |
|
|
465 |
public String toString() |
public String toString() |
466 |
{ |
{ |
467 |
StringBuffer buf = new StringBuffer(); |
StringBuffer buf = new StringBuffer(); |
468 |
int len = seen.size(); |
int len = seen.size(); |
469 |
for (int i=0; i<len; i++) |
for (int i = 0; i < len; i++) |
470 |
{ |
{ |
471 |
Range range = (Range)seen.get(i); |
Range range = (Range) seen.get(i); |
472 |
if (i>0) |
if (i > 0) |
473 |
buf.append(','); |
buf.append(','); |
474 |
buf.append(range.toString()); |
buf.append(range.toString()); |
475 |
} |
} |
476 |
return buf.toString(); |
return buf.toString(); |
477 |
} |
} |
478 |
|
|
479 |
} |
} |
480 |
|
|
481 |
/** |
/** |
482 |
* A range is either a single integer or a range between two integers. |
* A range is either a single integer or a range between two integers. |
483 |
*/ |
*/ |
485 |
{ |
{ |
486 |
int from; |
int from; |
487 |
int to; |
int to; |
488 |
|
|
489 |
public Range(int i) |
public Range(int i) |
490 |
{ |
{ |
491 |
from = to = i; |
from = to = i; |
492 |
} |
} |
493 |
|
|
494 |
public Range(int f, int t) |
public Range(int f, int t) |
495 |
{ |
{ |
496 |
if (f>t) |
if (f > t) |
497 |
{ |
{ |
498 |
from = t; |
from = t; |
499 |
to = f; |
to = f; |
500 |
} |
} |
501 |
else |
else |
502 |
{ |
{ |
503 |
from = f; |
from = f; |
504 |
to = t; |
to = t; |
505 |
} |
} |
506 |
} |
} |
507 |
|
|
508 |
public boolean contains(int num) |
public boolean contains(int num) |
509 |
{ |
{ |
510 |
return (num>=from && num<=to); |
return (num >= from && num <= to); |
511 |
} |
} |
512 |
|
|
513 |
public String toString() |
public String toString() |
514 |
{ |
{ |
515 |
StringBuffer buf = new StringBuffer(); |
StringBuffer buf = new StringBuffer(); |
516 |
buf.append(from); |
buf.append(from); |
518 |
buf.append("-").append(to); |
buf.append("-").append(to); |
519 |
return buf.toString(); |
return buf.toString(); |
520 |
} |
} |
521 |
|
|
522 |
} |
} |
523 |
|
|
524 |
} |
} |