View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2.provider;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.OutputStream;
22  import java.security.cert.Certificate;
23  import java.util.Collections;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import org.apache.commons.vfs2.FileContent;
28  import org.apache.commons.vfs2.FileContentInfo;
29  import org.apache.commons.vfs2.FileContentInfoFactory;
30  import org.apache.commons.vfs2.FileObject;
31  import org.apache.commons.vfs2.FileSystemException;
32  import org.apache.commons.vfs2.RandomAccessContent;
33  import org.apache.commons.vfs2.util.MonitorInputStream;
34  import org.apache.commons.vfs2.util.MonitorOutputStream;
35  import org.apache.commons.vfs2.util.MonitorRandomAccessContent;
36  import org.apache.commons.vfs2.util.RandomAccessMode;
37  
38  /**
39   * The content of a file.
40   */
41  public final class DefaultFileContent implements FileContent {
42  
43      /*
44       * static final int STATE_NONE = 0; static final int STATE_READING = 1; static final int STATE_WRITING = 2; static
45       * final int STATE_RANDOM_ACCESS = 3;
46       */
47  
48      static final int STATE_CLOSED = 0;
49      static final int STATE_OPENED = 1;
50  
51      /**
52       * The default buffer size for {@link #write(OutputStream)}
53       */
54      private static final int WRITE_BUFFER_SIZE = 4096;
55  
56      private final AbstractFileObject fileObject;
57      private Map<String, Object> attrs;
58      private Map<String, Object> roAttrs;
59      private FileContentInfo fileContentInfo;
60      private final FileContentInfoFactory fileContentInfoFactory;
61  
62      private final ThreadLocal<FileContentThreadData> threadLocal = new ThreadLocal<>();
63      private boolean resetAttributes;
64  
65      /**
66       * Counts open streams for this file.
67       */
68      private int openStreams;
69  
70      public DefaultFileContent(final AbstractFileObject file, final FileContentInfoFactory fileContentInfoFactory) {
71          this.fileObject = file;
72          this.fileContentInfoFactory = fileContentInfoFactory;
73      }
74  
75      private FileContentThreadData getOrCreateThreadData() {
76          FileContentThreadData data = this.threadLocal.get();
77          if (data == null) {
78              data = new FileContentThreadData();
79              this.threadLocal.set(data);
80          }
81          return data;
82      }
83  
84      void streamOpened() {
85          synchronized (this) {
86              openStreams++;
87          }
88          ((AbstractFileSystem) fileObject.getFileSystem()).streamOpened();
89      }
90  
91      void streamClosed() {
92          synchronized (this) {
93              if (openStreams > 0) {
94                  openStreams--;
95                  if (openStreams < 1) {
96                      fileObject.notifyAllStreamsClosed();
97                  }
98              }
99          }
100         ((AbstractFileSystem) fileObject.getFileSystem()).streamClosed();
101     }
102 
103     /**
104      * Returns the file that this is the content of.
105      *
106      * @return the FileObject.
107      */
108     @Override
109     public FileObject getFile() {
110         return fileObject;
111     }
112 
113     /**
114      * Returns the size of the content (in bytes).
115      *
116      * @return The size of the content (in bytes).
117      * @throws FileSystemException if an error occurs.
118      */
119     @Override
120     public long getSize() throws FileSystemException {
121         // Do some checking
122         if (!fileObject.getType().hasContent()) {
123             throw new FileSystemException("vfs.provider/get-size-not-file.error", fileObject);
124         }
125         /*
126          * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw
127          * new FileSystemException("vfs.provider/get-size-write.error", file); }
128          */
129 
130         try {
131             // Get the size
132             return fileObject.doGetContentSize();
133         } catch (final Exception exc) {
134             throw new FileSystemException("vfs.provider/get-size.error", exc, fileObject);
135         }
136     }
137 
138     /**
139      * Returns the last-modified timestamp.
140      *
141      * @return The last modified timestamp.
142      * @throws FileSystemException if an error occurs.
143      */
144     @Override
145     public long getLastModifiedTime() throws FileSystemException {
146         /*
147          * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw
148          * new FileSystemException("vfs.provider/get-last-modified-writing.error", file); }
149          */
150         if (!fileObject.getType().hasAttributes()) {
151             throw new FileSystemException("vfs.provider/get-last-modified-no-exist.error", fileObject);
152         }
153         try {
154             return fileObject.doGetLastModifiedTime();
155         } catch (final Exception e) {
156             throw new FileSystemException("vfs.provider/get-last-modified.error", fileObject, e);
157         }
158     }
159 
160     /**
161      * Sets the last-modified timestamp.
162      *
163      * @param modTime The last modified timestamp.
164      * @throws FileSystemException if an error occurs.
165      */
166     @Override
167     public void setLastModifiedTime(final long modTime) throws FileSystemException {
168         /*
169          * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw
170          * new FileSystemException("vfs.provider/set-last-modified-writing.error", file); }
171          */
172         if (!fileObject.getType().hasAttributes()) {
173             throw new FileSystemException("vfs.provider/set-last-modified-no-exist.error", fileObject);
174         }
175         try {
176             if (!fileObject.doSetLastModifiedTime(modTime)) {
177                 throw new FileSystemException("vfs.provider/set-last-modified.error", fileObject);
178             }
179         } catch (final Exception e) {
180             throw new FileSystemException("vfs.provider/set-last-modified.error", fileObject, e);
181         }
182     }
183 
184     /**
185      * Checks if an attribute exists.
186      *
187      * @param attrName The name of the attribute to check.
188      * @return true if the attribute is associated with the file.
189      * @throws FileSystemException if an error occurs.
190      * @since 2.0
191      */
192     @Override
193     public boolean hasAttribute(final String attrName) throws FileSystemException {
194         if (!fileObject.getType().hasAttributes()) {
195             throw new FileSystemException("vfs.provider/exists-attributes-no-exist.error", fileObject);
196         }
197         getAttributes();
198         return attrs.containsKey(attrName);
199     }
200 
201     /**
202      * Returns a read-only map of this file's attributes.
203      *
204      * @return a Map of the file's attributes.
205      * @throws FileSystemException if an error occurs.
206      */
207     @Override
208     public Map<String, Object> getAttributes() throws FileSystemException {
209         if (!fileObject.getType().hasAttributes()) {
210             throw new FileSystemException("vfs.provider/get-attributes-no-exist.error", fileObject);
211         }
212         if (resetAttributes || roAttrs == null) {
213             try {
214                 synchronized (this) {
215                     attrs = fileObject.doGetAttributes();
216                     roAttrs = Collections.unmodifiableMap(attrs);
217                     resetAttributes = false;
218                 }
219             } catch (final Exception e) {
220                 throw new FileSystemException("vfs.provider/get-attributes.error", fileObject, e);
221             }
222         }
223         return roAttrs;
224     }
225 
226     /**
227      * Used internally to flag situations where the file attributes should be reretrieved.
228      *
229      * @since 2.0
230      */
231     public void resetAttributes() {
232         resetAttributes = true;
233     }
234 
235     /**
236      * Lists the attributes of this file.
237      *
238      * @return An array of attribute names.
239      * @throws FileSystemException if an error occurs.
240      */
241     @Override
242     public String[] getAttributeNames() throws FileSystemException {
243         getAttributes();
244         final Set<String> names = attrs.keySet();
245         return names.toArray(new String[names.size()]);
246     }
247 
248     /**
249      * Gets the value of an attribute.
250      *
251      * @param attrName The attribute name.
252      * @return The value of the attribute or null.
253      * @throws FileSystemException if an error occurs.
254      */
255     @Override
256     public Object getAttribute(final String attrName) throws FileSystemException {
257         getAttributes();
258         return attrs.get(attrName);
259     }
260 
261     /**
262      * Sets the value of an attribute.
263      *
264      * @param attrName The name of the attribute to add.
265      * @param value The value of the attribute.
266      * @throws FileSystemException if an error occurs.
267      */
268     @Override
269     public void setAttribute(final String attrName, final Object value) throws FileSystemException {
270         if (!fileObject.getType().hasAttributes()) {
271             throw new FileSystemException("vfs.provider/set-attribute-no-exist.error", attrName, fileObject);
272         }
273         try {
274             fileObject.doSetAttribute(attrName, value);
275         } catch (final Exception e) {
276             throw new FileSystemException("vfs.provider/set-attribute.error", e, attrName, fileObject);
277         }
278 
279         if (attrs != null) {
280             attrs.put(attrName, value);
281         }
282     }
283 
284     /**
285      * Removes an attribute.
286      *
287      * @param attrName The name of the attribute to remove.
288      * @throws FileSystemException if an error occurs.
289      * @since 2.0
290      */
291     @Override
292     public void removeAttribute(final String attrName) throws FileSystemException {
293         if (!fileObject.getType().hasAttributes()) {
294             throw new FileSystemException("vfs.provider/remove-attribute-no-exist.error", fileObject);
295         }
296 
297         try {
298             fileObject.doRemoveAttribute(attrName);
299         } catch (final Exception e) {
300             throw new FileSystemException("vfs.provider/remove-attribute.error", e, attrName, fileObject);
301         }
302 
303         if (attrs != null) {
304             attrs.remove(attrName);
305         }
306     }
307 
308     /**
309      * Returns the certificates used to sign this file.
310      *
311      * @return An array of Certificates.
312      * @throws FileSystemException if an error occurs.
313      */
314     @Override
315     public Certificate[] getCertificates() throws FileSystemException {
316         if (!fileObject.exists()) {
317             throw new FileSystemException("vfs.provider/get-certificates-no-exist.error", fileObject);
318         }
319         /*
320          * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw
321          * new FileSystemException("vfs.provider/get-certificates-writing.error", file); }
322          */
323 
324         try {
325             final Certificate[] certs = fileObject.doGetCertificates();
326             if (certs != null) {
327                 return certs;
328             }
329             return new Certificate[0];
330         } catch (final Exception e) {
331             throw new FileSystemException("vfs.provider/get-certificates.error", fileObject, e);
332         }
333     }
334 
335     /**
336      * Returns an input stream for reading the content.
337      *
338      * @return The InputStream
339      * @throws FileSystemException if an error occurs.
340      */
341     @Override
342     public InputStream getInputStream() throws FileSystemException {
343         return buildInputStream(0);
344     }
345 
346     /**
347      * Returns an input stream for reading the content.
348      *
349      * @param bufferSize The buffer size to use.
350      * @return The InputStream
351      * @throws FileSystemException if an error occurs.
352      * @since 2.4
353      */
354     @Override
355     public InputStream getInputStream(final int bufferSize) throws FileSystemException {
356         return buildInputStream(bufferSize);
357     }
358 
359     /**
360      * Returns an input/output stream to use to read and write the content of the file in an random manner.
361      *
362      * @param mode The RandomAccessMode.
363      * @return A RandomAccessContent object to access the file.
364      * @throws FileSystemException if an error occurs.
365      */
366     @Override
367     public RandomAccessContent getRandomAccessContent(final RandomAccessMode mode) throws FileSystemException {
368         /*
369          * if (getThreadData().getState() != STATE_NONE) { throw new
370          * FileSystemException("vfs.provider/read-in-use.error", file); }
371          */
372 
373         // Get the content
374         final RandomAccessContent rastr = fileObject.getRandomAccessContent(mode);
375 
376         final FileRandomAccessContent rac = new FileRandomAccessContent(fileObject, rastr);
377 
378         getOrCreateThreadData().addRastr(rac);
379         streamOpened();
380 
381         return rac;
382     }
383 
384     /**
385      * Returns an output stream for writing the content.
386      *
387      * @return The OutputStream for the file.
388      * @throws FileSystemException if an error occurs.
389      */
390     @Override
391     public OutputStream getOutputStream() throws FileSystemException {
392         return getOutputStream(false);
393     }
394 
395     /**
396      * Returns an output stream for writing the content in append mode.
397      *
398      * @param bAppend true if the data written should be appended.
399      * @return The OutputStream for the file.
400      * @throws FileSystemException if an error occurs.
401      */
402     @Override
403     public OutputStream getOutputStream(final boolean bAppend) throws FileSystemException {
404         return buildOutputStream(bAppend, 0);
405     }
406 
407     /**
408      * Returns an output stream for writing the content.
409      *
410      * @param bufferSize The buffer size to use.
411      * @return The OutputStream for the file.
412      * @throws FileSystemException if an error occurs.
413      * @since 2.4
414      */
415     @Override
416     public OutputStream getOutputStream(final int bufferSize) throws FileSystemException {
417         return buildOutputStream(false, bufferSize);
418     }
419 
420     /**
421      * Returns an output stream for writing the content in append mode.
422      *
423      * @param bAppend true if the data written should be appended.
424      * @param bufferSize The buffer size to use.
425      * @return The OutputStream for the file.
426      * @throws FileSystemException if an error occurs.
427      * @since 2.4
428      */
429     @Override
430     public OutputStream getOutputStream(final boolean bAppend, final int bufferSize) throws FileSystemException {
431         return buildOutputStream(bAppend, bufferSize);
432     }
433 
434     /**
435      * Closes all resources used by the content, including all streams, readers and writers.
436      *
437      * @throws FileSystemException if an error occurs.
438      */
439     @Override
440     public void close() throws FileSystemException {
441         FileSystemException caught = null;
442         try {
443             final FileContentThreadData fileContentThreadData = getOrCreateThreadData();
444 
445             // Close the input stream
446             while (fileContentThreadData.getInstrsSize() > 0) {
447                 final FileContentInputStream inputStream = (FileContentInputStream) fileContentThreadData
448                         .removeInstr(0);
449                 try {
450                     inputStream.close();
451                 } catch (final FileSystemException ex) {
452                     caught = ex;
453 
454                 }
455             }
456 
457             // Close the randomAccess stream
458             while (fileContentThreadData.getRastrsSize() > 0) {
459                 final FileRandomAccessContent randomAccessContent = (FileRandomAccessContent) fileContentThreadData
460                         .removeRastr(0);
461                 try {
462                     randomAccessContent.close();
463                 } catch (final FileSystemException ex) {
464                     caught = ex;
465                 }
466             }
467 
468             // Close the output stream
469             final FileContentOutputStream outputStream = fileContentThreadData.getOutstr();
470             if (outputStream != null) {
471                 fileContentThreadData.setOutstr(null);
472                 try {
473                     outputStream.close();
474                 } catch (final FileSystemException ex) {
475                     caught = ex;
476                 }
477             }
478         } finally {
479             threadLocal.remove();
480         }
481 
482         // throw last error (out >> rac >> input) after all closes have been tried
483         if (caught != null) {
484             throw caught;
485         }
486     }
487 
488     private InputStream buildInputStream(final int bufferSize) throws FileSystemException {
489         /*
490          * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw
491          * new FileSystemException("vfs.provider/read-in-use.error", file); }
492          */
493 
494         // Get the raw input stream
495         final InputStream inputStream = fileObject.getInputStream();
496 
497         final InputStream wrappedInputStream = bufferSize == 0 ?
498             new FileContentInputStream(fileObject, inputStream) :
499             new FileContentInputStream(fileObject, inputStream, bufferSize);
500 
501         getOrCreateThreadData().addInstr(wrappedInputStream);
502         streamOpened();
503 
504         return wrappedInputStream;
505     }
506 
507     private OutputStream buildOutputStream(final boolean bAppend, final int bufferSize) throws FileSystemException {
508         /*
509          * if (getThreadData().getState() != STATE_NONE)
510          */
511         final FileContentThreadData streams = getOrCreateThreadData();
512 
513         if (streams.getOutstr() != null) {
514             throw new FileSystemException("vfs.provider/write-in-use.error", fileObject);
515         }
516 
517         // Get the raw output stream
518         final OutputStream outstr = fileObject.getOutputStream(bAppend);
519 
520         // Create and set wrapper
521         final FileContentOutputStream wrapped = bufferSize == 0 ?
522             new FileContentOutputStream(fileObject, outstr) :
523             new FileContentOutputStream(fileObject, outstr, bufferSize);
524         streams.setOutstr(wrapped);
525         streamOpened();
526 
527         return wrapped;
528     }
529 
530     /**
531      * Handles the end of input stream.
532      */
533     private void endInput(final FileContentInputStream instr) {
534         final FileContentThreadData fileContentThreadData = threadLocal.get();
535         if (fileContentThreadData != null) {
536             fileContentThreadData.removeInstr(instr);
537         }
538         if (fileContentThreadData == null || !fileContentThreadData.hasStreams()) {
539             // remove even when no value is set to remove key
540             threadLocal.remove();
541         }
542         streamClosed();
543     }
544 
545     /**
546      * Handles the end of random access.
547      */
548     private void endRandomAccess(final RandomAccessContent rac) {
549         final FileContentThreadData fileContentThreadData = threadLocal.get();
550         if (fileContentThreadData != null) {
551             fileContentThreadData.removeRastr(rac);
552         }
553         if (fileContentThreadData == null || !fileContentThreadData.hasStreams()) {
554             // remove even when no value is set to remove key
555             threadLocal.remove();
556         }
557         streamClosed();
558     }
559 
560     /**
561      * Handles the end of output stream.
562      */
563     private void endOutput() throws Exception {
564         final FileContentThreadData fileContentThreadData = threadLocal.get();
565         if (fileContentThreadData != null) {
566             fileContentThreadData.setOutstr(null);
567         }
568         if (fileContentThreadData == null || !fileContentThreadData.hasStreams()) {
569             // remove even when no value is set to remove key
570             threadLocal.remove();
571         }
572         streamClosed();
573         fileObject.endOutput();
574     }
575 
576     /**
577      * Checks if a input and/or output stream is open.
578      * <p>
579      * This checks only the scope of the current thread.
580      * </p>
581      *
582      * @return true if this is the case
583      */
584     @Override
585     public boolean isOpen() {
586         final FileContentThreadData fileContentThreadData = threadLocal.get();
587         if (fileContentThreadData != null && fileContentThreadData.hasStreams()) {
588             return true;
589         }
590         // threadData.get() created empty entry
591         threadLocal.remove();
592         return false;
593     }
594 
595     /**
596      * Checks if an input or output stream is open. This checks all threads.
597      *
598      * @return true if this is the case
599      */
600     public boolean isOpenGlobal() {
601         synchronized (this) {
602             return openStreams > 0;
603         }
604     }
605 
606     /**
607      * An input stream for reading content. Provides buffering, and end-of-stream monitoring.
608      */
609     private final class FileContentInputStream extends MonitorInputStream {
610         // avoid gc
611         private final FileObject file;
612 
613         FileContentInputStream(final FileObject file, final InputStream instr) {
614             super(instr);
615             this.file = file;
616         }
617 
618         FileContentInputStream(final FileObject file, final InputStream instr, final int bufferSize) {
619             super(instr, bufferSize);
620             this.file = file;
621         }
622 
623         /**
624          * Closes this input stream.
625          */
626         @Override
627         public void close() throws FileSystemException {
628             try {
629                 super.close();
630             } catch (final IOException e) {
631                 throw new FileSystemException("vfs.provider/close-instr.error", file, e);
632             }
633         }
634 
635         /**
636          * Called after the stream has been closed.
637          */
638         @Override
639         protected void onClose() throws IOException {
640             try {
641                 super.onClose();
642             } finally {
643                 endInput(this);
644             }
645         }
646     }
647 
648     /**
649      * An input/output stream for reading/writing content on random positions
650      */
651     private final class FileRandomAccessContent extends MonitorRandomAccessContent {
652         // also avoids gc
653         private final FileObject file;
654 
655         FileRandomAccessContent(final FileObject file, final RandomAccessContent content) {
656             super(content);
657             this.file = file;
658         }
659 
660         /**
661          * Called after the stream has been closed.
662          */
663         @Override
664         protected void onClose() throws IOException {
665             try {
666                 super.onClose();
667             } finally {
668                 endRandomAccess(this);
669             }
670         }
671 
672         @Override
673         public void close() throws FileSystemException {
674             try {
675                 super.close();
676             } catch (final IOException e) {
677                 throw new FileSystemException("vfs.provider/close-rac.error", file, e);
678             }
679         }
680     }
681 
682     /**
683      * An output stream for writing content.
684      */
685     final class FileContentOutputStream extends MonitorOutputStream {
686         // avoid gc
687         private final FileObject file;
688 
689         FileContentOutputStream(final FileObject file, final OutputStream outstr) {
690             super(outstr);
691             this.file = file;
692         }
693 
694         FileContentOutputStream(final FileObject file, final OutputStream outstr, final int bufferSize) {
695             super(outstr, bufferSize);
696             this.file = file;
697         }
698 
699         /**
700          * Closes this output stream.
701          */
702         @Override
703         public void close() throws FileSystemException {
704             try {
705                 super.close();
706             } catch (final IOException e) {
707                 throw new FileSystemException("vfs.provider/close-outstr.error", file, e);
708             }
709         }
710 
711         /**
712          * Called after this stream is closed.
713          */
714         @Override
715         protected void onClose() throws IOException {
716             try {
717                 super.onClose();
718             } finally {
719                 try {
720                     endOutput();
721                 } catch (final Exception e) {
722                     throw new FileSystemException("vfs.provider/close-outstr.error", file, e);
723                 }
724             }
725         }
726     }
727 
728     /**
729      * Gets the FileContentInfo which describes the content-type, content-encoding
730      *
731      * @return The FileContentInfo.
732      * @throws FileSystemException if an error occurs.
733      */
734     @Override
735     public FileContentInfo getContentInfo() throws FileSystemException {
736         if (fileContentInfo == null) {
737             fileContentInfo = fileContentInfoFactory.create(this);
738         }
739 
740         return fileContentInfo;
741     }
742 
743     /**
744      * Writes this content to another FileContent.
745      *
746      * @param fileContent The target FileContent.
747      * @return the total number of bytes written
748      * @throws IOException if an error occurs writing the content.
749      * @since 2.1
750      */
751     @Override
752     public long write(final FileContent fileContent) throws IOException {
753         final OutputStream output = fileContent.getOutputStream();
754         try {
755             return this.write(output);
756         } finally {
757             output.close();
758         }
759     }
760 
761     /**
762      * Writes this content to another FileObject.
763      *
764      * @param file The target FileObject.
765      * @return the total number of bytes written
766      * @throws IOException if an error occurs writing the content.
767      * @since 2.1
768      */
769     @Override
770     public long write(final FileObject file) throws IOException {
771         return write(file.getContent());
772     }
773 
774     /**
775      * Writes this content to an OutputStream.
776      *
777      * @param output The target OutputStream.
778      * @return the total number of bytes written
779      * @throws IOException if an error occurs writing the content.
780      * @since 2.1
781      */
782     @Override
783     public long write(final OutputStream output) throws IOException {
784         return write(output, WRITE_BUFFER_SIZE);
785     }
786 
787     /**
788      * Writes this content to an OutputStream.
789      *
790      * @param output The target OutputStream.
791      * @param bufferSize The buffer size to write data chunks.
792      * @return the total number of bytes written
793      * @throws IOException if an error occurs writing the file.
794      * @since 2.1
795      */
796     @Override
797     public long write(final OutputStream output, final int bufferSize) throws IOException {
798         final InputStream input = this.getInputStream();
799         long count = 0;
800         try {
801             // This read/write code from Apache Commons IO
802             final byte[] buffer = new byte[bufferSize];
803             int n = 0;
804             while (-1 != (n = input.read(buffer))) {
805                 output.write(buffer, 0, n);
806                 count += n;
807             }
808         } finally {
809             input.close();
810         }
811         return count;
812     }
813 }