1    /* ====================================================================
2     * The Apache Software License, Version 1.1
3     *
4     * Copyright (c) 2002 The Apache Software Foundation.  All rights
5     * reserved.
6     *
7     * Redistribution and use in source and binary forms, with or without
8     * modification, are permitted provided that the following conditions
9     * are met:
10    *
11    * 1. Redistributions of source code must retain the above copyright
12    *    notice, this list of conditions and the following disclaimer.
13    *
14    * 2. Redistributions in binary form must reproduce the above copyright
15    *    notice, this list of conditions and the following disclaimer in
16    *    the documentation and/or other materials provided with the
17    *    distribution.
18    *
19    * 3. The end-user documentation included with the redistribution,
20    *    if any, must include the following acknowledgment:
21    *       "This product includes software developed by the
22    *        Apache Software Foundation (http://www.apache.org/)."
23    *    Alternately, this acknowledgment may appear in the software itself,
24    *    if and wherever such third-party acknowledgments normally appear.
25    *
26    * 4. The names "Apache" and "Apache Software Foundation" and
27    *    "Apache POI" must not be used to endorse or promote products
28    *    derived from this software without prior written permission. For
29    *    written permission, please contact apache@apache.org.
30    *
31    * 5. Products derived from this software may not be called "Apache",
32    *    "Apache POI", nor may "Apache" appear in their name, without
33    *    prior written permission of the Apache Software Foundation.
34    *
35    * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36    * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37    * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38    * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39    * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40    * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41    * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42    * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43    * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44    * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45    * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46    * SUCH DAMAGE.
47    * ====================================================================
48    *
49    * This software consists of voluntary contributions made by many
50    * individuals on behalf of the Apache Software Foundation.  For more
51    * information on the Apache Software Foundation, please see
52    * <http://www.apache.org/>.
53    */
54   
55   /*
56    * HSSFWorkbook.java
57    *
58    * Created on September 30, 2001, 3:37 PM
59    */
60   package org.apache.poi.hssf.usermodel;
61   
62   import org.apache.poi.util.POILogFactory;
63   import org.apache.poi.hssf.model.Sheet;
64   import org.apache.poi.hssf.model.Workbook;
65   import org.apache.poi.hssf.record.*;
66   import org.apache.poi.poifs.filesystem.POIFSFileSystem;
67   import org.apache.poi.poifs.filesystem.Entry;
68   import org.apache.poi.poifs.filesystem.DirectoryEntry;
69   import org.apache.poi.poifs.filesystem.DocumentEntry;
70   import org.apache.poi.poifs.filesystem.DocumentInputStream;
71   import org.apache.poi.util.POILogger;
72   
73   import java.io.ByteArrayInputStream;
74   import java.io.IOException;
75   import java.io.InputStream;
76   import java.io.OutputStream;
77   import java.util.ArrayList;
78   import java.util.List;
79   import java.util.Iterator;
80   
81   /**
82    * High level representation of a workbook.  This is the first object most users
83    * will construct whether they are reading or writing a workbook.  It is also the
84    * top level object for creating new sheets/etc.
85    *
86    * @see org.apache.poi.hssf.model.Workbook
87    * @see org.apache.poi.hssf.usermodel.HSSFSheet
88    * @author  Andrew C. Oliver (acoliver at apache dot org)
89    * @author  Glen Stampoultzis (glens at apache.org)
90    * @author  Shawn Laubach (shawnlaubach at cox.net)
91    * @version 2.0-pre
92    */
93   
94   public class HSSFWorkbook
95           extends java.lang.java.lang.Objectvate static final int DEBUG = POILogger.DEBUG;
96   
97       /**
98        * used for compile-time performance/memory optimization.  This determines the
99        * initial capacity for the sheet collection.  Its currently set to 3.
100       * Changing it in this release will decrease performance
101       * since you're never allowed to have more or less than three sheets!
102       */
103  
104      public final static int INITIAL_CAPACITY = 3;
105  
106      /**
107       * this is the reference to the low level Workbook object
108       */
109  
110      private Workbook workbook;
111  
112      /**
113       * this holds the HSSFSheet objects attached to this workbook
114       */
115  
116      private ArrayList sheets;
117      
118      /**
119       * this holds the HSSFName objects attached to this workbook
120       */
121  
122      private ArrayList names;
123   
124      /**
125       * holds whether or not to preserve other nodes in the POIFS.  Used
126       * for macros and embedded objects. 
127       */
128      private boolean   preserveNodes;
129  
130      /**
131       * if you do preserve the nodes, you'll need to hold the whole POIFS in
132       * memory.
133       */
134      private POIFSFileSystem poifs;
135      
136      private static POILogger log = POILogFactory.getLogger(HSSFWorkbook.class);
137  
138      /**
139       * Creates new HSSFWorkbook from scratch (start here!)
140       *
141       */
142  
143      public HSSFWorkbook()
144      {
145          workbook = Workbook.createWorkbook();
146          sheets = new ArrayList(INITIAL_CAPACITY);
147          names  = new ArrayList(INITIAL_CAPACITY);
148      }
149  
150      public HSSFWorkbook(POIFSFileSystem fs) throws IOException {
151        this(fs,true);
152      }
153  
154      /**
155       * given a POI POIFSFileSystem object, read in its Workbook and populate the high and
156       * low level models.  If you're reading in a workbook...start here.
157       *
158       * @param fs the POI filesystem that contains the Workbook stream.
159       * @param preserveNodes whether to preseve other nodes, such as 
160       *        macros.  This takes more memory, so only say yes if you
161       *        need to.
162       * @see org.apache.poi.poifs.filesystem.POIFSFileSystem
163       * @exception IOException if the stream cannot be read
164       */
165  
166      public HSSFWorkbook(POIFSFileSystem fs, boolean preserveNodes)
167              throws IOException
168      {
169          this.preserveNodes = preserveNodes;
170       
171          if (preserveNodes) {
172             this.poifs = fs; 
173          }
174  
175          sheets = new ArrayList(INITIAL_CAPACITY);
176          names  = new ArrayList(INITIAL_CAPACITY);
177          
178          InputStream stream = fs.createDocumentInputStream("Workbook");
179          List records = RecordFactory.createRecords(stream);
180  
181          workbook = Workbook.createWorkbook(records);
182          setPropertiesFromWorkbook(workbook);
183          int recOffset = workbook.getNumRecords();
184          int sheetNum = 0;
185  
186          while (recOffset < records.size())
187          {
188              Sheet sheet = Sheet.createSheet(records, sheetNum++, recOffset );
189  
190              recOffset = sheet.getEofLoc()+1;
191              sheet.convertLabelRecords(
192                      workbook);   // convert all LabelRecord records to LabelSSTRecord
193              HSSFSheet hsheet = new HSSFSheet(workbook, sheet);
194  
195              sheets.add(hsheet);
196  
197              // workbook.setSheetName(sheets.size() -1, "Sheet"+sheets.size());
198          }
199          
200          for (int i = 0 ; i < workbook.getNumNames() ; ++i){
201              HSSFName name = new HSSFName(workbook, workbook.getNameRecord(i));
202              names.add(name);
203          }
204      }
205  
206       public HSSFWorkbook(InputStream s) throws IOException {
207           this(s,true);
208       }
209  
210      /**
211       * Companion to HSSFWorkbook(POIFSFileSystem), this constructs the POI filesystem around your
212       * inputstream.
213       *
214       * @param s  the POI filesystem that contains the Workbook stream.
215       * @param preserveNodes whether to preseve other nodes, such as 
216       *        macros.  This takes more memory, so only say yes if you
217       *        need to.
218       * @see org.apache.poi.poifs.filesystem.POIFSFileSystem
219       * @see #HSSFWorkbook(POIFSFileSystem)
220       * @exception IOException if the stream cannot be read
221       */
222  
223      public HSSFWorkbook(InputStream s, boolean preserveNodes)
224              throws IOException
225      {
226          this(new POIFSFileSystem(s), preserveNodes);
227      }
228  
229      /**
230       * used internally to set the workbook properties.
231       */
232  
233      private void setPropertiesFromWorkbook(Workbook book)
234      {
235          this.workbook = book;
236  
237          // none currently
238      }
239  
240      public final static byte ENCODING_COMPRESSED_UNICODE = 0;
241      public final static byte ENCODING_UTF_16             = 1;
242      
243      /**
244       * set the sheet name.
245       * @param sheet number (0 based)
246       * @param sheet name
247       */
248  
249      public void setSheetName(int sheet, String name)
250      {
251          workbook.setSheetName( sheet, name, ENCODING_COMPRESSED_UNICODE );
252      }
253  
254      public void setSheetName( int sheet, String name, short encoding )
255      {
256          if (sheet > (sheets.size() - 1))
257          {
258              throw new RuntimeException("Sheet out of bounds");
259          }
260          
261          switch ( encoding ) {
262          case ENCODING_COMPRESSED_UNICODE:
263          case ENCODING_UTF_16:
264              break;
265              
266          default:
267              // TODO java.io.UnsupportedEncodingException
268              throw new RuntimeException( "Unsupported encoding" );
269          }
270          
271          workbook.setSheetName( sheet, name, encoding );
272      }
273  
274      /**
275       * get the sheet name
276       * @param sheet Number
277       * @return Sheet name
278       */
279  
280      public String getSheetName(int sheet)
281      {
282          if (sheet > (sheets.size() - 1))
283          {
284              throw new RuntimeException("Sheet out of bounds");
285          }
286          return workbook.getSheetName(sheet);
287      }
288  
289      /*
290       * get the sheet's index
291       * @param name  sheet name
292       * @return sheet index or -1 if it was not found.
293       */
294  
295      /** Returns the index of the sheet by his name
296       * @param name the sheet name
297       * @return index of the sheet (0 based)
298       */    
299      public int getSheetIndex(String name)
300      {
301          int retval = workbook.getSheetIndex(name);
302          
303          return retval;
304      }
305      
306      /**
307       * create an HSSFSheet for this HSSFWorkbook, adds it to the sheets and returns
308       * the high level representation.  Use this to create new sheets.
309       *
310       * @return HSSFSheet representing the new sheet.
311       */
312  
313      public HSSFSheet createSheet()
314      {
315  
316  //        if (getNumberOfSheets() == 3)
317  //            throw new RuntimeException("You cannot have more than three sheets in HSSF 1.0");
318          HSSFSheet sheet = new HSSFSheet(workbook);
319  
320          sheets.add(sheet);
321          workbook.setSheetName(sheets.size() - 1,
322                  "Sheet" + (sheets.size() - 1));
323          WindowTwoRecord windowTwo = (WindowTwoRecord) sheet.getSheet().findFirstRecordBySid(WindowTwoRecord.sid);
324          windowTwo.setSelected(sheets.size() == 1);
325          windowTwo.setPaged(sheets.size() == 1);
326          return sheet;
327      }
328  
329      /**
330       * create an HSSFSheet from an existing sheet in the HSSFWorkbook.
331       *
332       * @return HSSFSheet representing the cloned sheet.
333       */
334  
335      public HSSFSheet cloneSheet(int sheetNum) {
336        HSSFSheet srcSheet = (HSSFSheet)sheets.get(sheetNum);
337        String srcName = workbook.getSheetName(sheetNum);
338        if (srcSheet != null) {
339          HSSFSheet clonedSheet = srcSheet.cloneSheet(workbook);
340          WindowTwoRecord windowTwo = (WindowTwoRecord) clonedSheet.getSheet().findFirstRecordBySid(WindowTwoRecord.sid);
341          windowTwo.setSelected(sheets.size() == 1);
342          windowTwo.setPaged(sheets.size() == 1);
343  
344          sheets.add(clonedSheet);
345          workbook.setSheetName(sheets.size()-1, srcName+"[1]");
346          return clonedSheet;
347        }
348        return null;
349      }
350  
351      /**
352       * create an HSSFSheet for this HSSFWorkbook, adds it to the sheets and returns
353       * the high level representation.  Use this to create new sheets.
354       *
355       * @param sheetname     sheetname to set for the sheet.
356       * @return HSSFSheet representing the new sheet.
357       */
358  
359      public HSSFSheet createSheet(String sheetname)
360      {
361  
362  //        if (getNumberOfSheets() == 3)
363  //            throw new RuntimeException("You cannot have more than three sheets in HSSF 1.0");
364          HSSFSheet sheet = new HSSFSheet(workbook);
365  
366          sheets.add(sheet);
367          workbook.setSheetName(sheets.size() - 1, sheetname);
368          WindowTwoRecord windowTwo = (WindowTwoRecord) sheet.getSheet().findFirstRecordBySid(WindowTwoRecord.sid);
369          windowTwo.setSelected(sheets.size() == 1);
370          windowTwo.setPaged(sheets.size() == 1);
371          return sheet;
372      }
373  
374      /**
375       * get the number of spreadsheets in the workbook (this will be three after serialization)
376       * @return number of sheets
377       */
378  
379      public int getNumberOfSheets()
380      {
381          return sheets.size();
382      }
383  
384      /**
385       * Get the HSSFSheet object at the given index.
386       * @param index of the sheet number (0-based physical & logical)
387       * @return HSSFSheet at the provided index
388       */
389  
390      public HSSFSheet getSheetAt(int index)
391      {
392          return (HSSFSheet) sheets.get(index);
393      }
394  
395      /**
396       * Get sheet with the given name
397       * @param name of the sheet
398       * @return HSSFSheet with the name provided or null if it does not exist
399       */
400  
401      public HSSFSheet getSheet(String name)
402      {
403          HSSFSheet retval = null;
404  
405          for (int k = 0; k < sheets.size(); k++)
406          {
407              String sheetname = workbook.getSheetName(k);
408  
409              if (sheetname.equals(name))
410              {
411                  retval = (HSSFSheet) sheets.get(k);
412              }
413          }
414          return retval;
415      }
416  
417      /**
418       * removes sheet at the given index
419       * @param index of the sheet  (0-based)
420       */
421  
422      public void removeSheetAt(int index)
423      {
424          sheets.remove(index);
425          workbook.removeSheet(index);
426      }
427  
428      /**
429       * determine whether the Excel GUI will backup the workbook when saving.
430       *
431       * @param backupValue   true to indicate a backup will be performed.
432       */
433  
434      public void setBackupFlag(boolean backupValue)
435      {
436          BackupRecord backupRecord = workbook.getBackupRecord();
437  
438          backupRecord.setBackup(backupValue ? (short) 1
439                  : (short) 0);
440      }
441  
442      /**
443       * determine whether the Excel GUI will backup the workbook when saving.
444       *
445       * @return the current setting for backups.
446       */
447  
448      public boolean getBackupFlag()
449      {
450          BackupRecord backupRecord = workbook.getBackupRecord();
451  
452          return (backupRecord.getBackup() == 0) ? false
453                  : true;
454      }
455  
456      /**
457       * create a new Font and add it to the workbook's font table
458       * @return new font object
459       */
460  
461      public HSSFFont createFont()
462      {
463          FontRecord font = workbook.createNewFont();
464          short fontindex = (short) (getNumberOfFonts() - 1);
465  
466          if (fontindex > 3)
467          {
468              fontindex++;   // THERE IS NO FOUR!!
469          }
470          HSSFFont retval = new HSSFFont(fontindex, font);
471  
472          return retval;
473      }
474  
475      /**
476       * get the number of fonts in the font table
477       * @return number of fonts
478       */
479  
480      public short getNumberOfFonts()
481      {
482          return (short) workbook.getNumberOfFontRecords();
483      }
484  
485      /**
486       * get the font at the given index number
487       * @param idx  index number
488       * @return HSSFFont at the index
489       */
490  
491      public HSSFFont getFontAt(short idx)
492      {
493          FontRecord font = workbook.getFontRecordAt(idx);
494          HSSFFont retval = new HSSFFont(idx, font);
495  
496          return retval;
497      }
498  
499      /**
500       * create a new Cell style and add it to the workbook's style table
501       * @return the new Cell Style object
502       */
503  
504      public HSSFCellStyle createCellStyle()
505      {
506          ExtendedFormatRecord xfr = workbook.createCellXF();
507          short index = (short) (getNumCellStyles() - 1);
508          HSSFCellStyle style = new HSSFCellStyle(index, xfr);
509  
510          return style;
511      }
512  
513      /**
514       * get the number of styles the workbook contains
515       * @return count of cell styles
516       */
517  
518      public short getNumCellStyles()
519      {
520          return (short) workbook.getNumExFormats();
521      }
522  
523      /**
524       * get the cell style object at the given index
525       * @param idx  index within the set of styles
526       * @return HSSFCellStyle object at the index
527       */
528  
529      public HSSFCellStyle getCellStyleAt(short idx)
530      {
531          ExtendedFormatRecord xfr = workbook.getExFormatAt(idx);
532          HSSFCellStyle style = new HSSFCellStyle(idx, xfr);
533  
534          return style;
535      }
536  
537      /**
538       * Method write - write out this workbook to an Outputstream.  Constructs
539       * a new POI POIFSFileSystem, passes in the workbook binary representation  and
540       * writes it out.
541       *
542       * @param stream - the java OutputStream you wish to write the XLS to
543       *
544       * @exception IOException if anything can't be written.
545       * @see org.apache.poi.poifs.filesystem.POIFSFileSystem
546       */
547  
548      public void write(OutputStream stream)
549              throws IOException
550      {
551          byte[] bytes = getBytes();
552          POIFSFileSystem fs = new POIFSFileSystem();
553        
554          fs.createDocument(new ByteArrayInputStream(bytes), "Workbook");
555  
556          if (preserveNodes) { 
557              List excepts = new ArrayList(1);
558              excepts.add("Workbook");
559              copyNodes(this.poifs,fs,excepts);
560          }
561          fs.writeFilesystem(stream);
562          //poifs.writeFilesystem(stream);
563      }
564  
565      /**
566       * Method getBytes - get the bytes of just the HSSF portions of the XLS file.
567       * Use this to construct a POI POIFSFileSystem yourself.
568       *
569       *
570       * @return byte[] array containing the binary representation of this workbook and all contained
571       *         sheets, rows, cells, etc.
572       *
573       * @see org.apache.poi.hssf.model.Workbook
574       * @see org.apache.poi.hssf.model.Sheet
575       */
576  
577      public byte[] getBytes()
578      {
579          log.log(DEBUG, "HSSFWorkbook.getBytes()");
580          int wbsize = workbook.getSize();
581  
582          // log.debug("REMOVEME: old sizing method "+workbook.serialize().length);
583          // ArrayList sheetbytes = new ArrayList(sheets.size());
584          int totalsize = wbsize;
585  
586          for (int k = 0; k < sheets.size(); k++)
587          {
588              workbook.setSheetBof(k, totalsize);
589  
590              // sheetbytes.add((( HSSFSheet ) sheets.get(k)).getSheet().getSize());
591              totalsize += ((HSSFSheet) sheets.get(k)).getSheet().getSize();
592          }
593  /*        if (totalsize < 4096)
594          {
595              totalsize = 4096;
596          }*/
597          byte[] retval = new byte[totalsize];
598          int pos = workbook.serialize(0, retval);
599  
600          // System.arraycopy(wb, 0, retval, 0, wb.length);
601          for (int k = 0; k < sheets.size(); k++)
602          {
603  
604              // byte[] sb = (byte[])sheetbytes.get(k);
605              // System.arraycopy(sb, 0, retval, pos, sb.length);
606              pos += ((HSSFSheet) sheets.get(k)).getSheet().serialize(pos,
607                      retval);   // sb.length;
608          }
609  /*        for (int k = pos; k < totalsize; k++)
610          {
611              retval[k] = 0;
612          }*/
613          return retval;
614      }
615  
616      public int addSSTString(String string)
617      {
618          return workbook.addSSTString(string);
619      }
620  
621      public String getSSTString(int index)
622      {
623          return workbook.getSSTString(index);
624      }
625  
626      Workbook getWorkbook()
627      {
628          return workbook;
629      }
630      
631      /** gets the total number of named ranges in the workboko
632       * @return number of named ranges
633       */    
634      public int getNumberOfNames(){
635          int result = names.size();
636          return result;
637      }
638      
639      /** gets the Named range
640       * @param index position of the named range
641       * @return named range high level
642       */    
643      public HSSFName getNameAt(int index){
644          HSSFName result = (HSSFName) names.get(index);
645          
646          return result;
647      }
648      
649      /** gets the named range name
650       * @param index the named range index (0 based)
651       * @return named range name
652       */    
653      public String getNameName(int index){
654          String result = getNameAt(index).getNameName();
655                  
656          return result;
657      }
658      
659      
660      /** creates a new named range and add it to the model
661       * @return named range high level
662       */    
663      public HSSFName createName(){
664          NameRecord nameRecord = workbook.createName();
665          
666          HSSFName newName = new HSSFName(workbook, nameRecord);
667          
668          names.add(newName);
669          
670          return newName; 
671      }
672      
673      /** gets the named range index by his name
674       * @param name named range name
675       * @return named range index 
676       */    
677      public int getNameIndex(String name)
678      {
679          int retval = -1;
680  
681          for (int k = 0; k < names.size(); k++)
682          {
683              String nameName = getNameName(k);
684  
685              if (nameName.equals(name))
686              {
687                  retval = k;
688                  break;
689              }
690          }
691          return retval;
692      }
693  
694  
695      /** remove the named range by his index
696       * @param index named range index (0 based)
697       */    
698      public void removeName(int index){
699          names.remove(index);
700          workbook.removeName(index);        
701      }
702  
703          /**
704       * Creates an instance of HSSFDataFormat.
705       * @return the HSSFDataFormat object
706       * @see org.apache.poi.hssf.record.FormatRecord
707       * @see org.apache.poi.hssf.record.Record
708       */
709      public HSSFDataFormat createDataFormat() {
710  	return new HSSFDataFormat(workbook);
711      }
712  	
713      /** remove the named range by his name
714       * @param name named range name
715       */    
716      public void removeName(String name){
717          int index = getNameIndex(name);
718          
719          removeName(index);          
720          
721      }
722  
723     /**
724      * Copies nodes from one POIFS to the other minus the excepts
725      * @param source is the source POIFS to copy from
726      * @param target is the target POIFS to copy to 
727      * @param excepts is a list of Strings specifying what nodes NOT to copy 
728      */
729     private void copyNodes(POIFSFileSystem source, POIFSFileSystem target, 
730                            List excepts) throws IOException {
731        //System.err.println("CopyNodes called");
732  
733        DirectoryEntry root = source.getRoot();
734        DirectoryEntry newRoot = target.getRoot();
735  
736        Iterator entries = root.getEntries();
737         
738        while (entries.hasNext()) {
739           Entry entry = (Entry)entries.next();
740           if (!isInList(entry.getName(), excepts)) {
741               copyNodeRecursively(entry,newRoot);
742           }
743        } 
744     }
745  
746     private boolean isInList(String entry, List list) {
747         for (int k = 0; k < list.size(); k++) {
748            if (((String)list.get(k)).equals(entry)) {
749              return true;
750            }
751         }
752         return false;
753     }
754  
755     private void copyNodeRecursively(Entry entry, DirectoryEntry target) 
756     throws IOException {
757         //System.err.println("copyNodeRecursively called with "+entry.getName()+
758         //                   ","+target.getName());
759         DirectoryEntry newTarget = null; 
760         if (entry.isDirectoryEntry()) {
761             newTarget = target.createDirectory(entry.getName());
762             Iterator entries = ((DirectoryEntry)entry).getEntries();
763  
764             while (entries.hasNext()) {
765                copyNodeRecursively((Entry)entries.next(),newTarget);
766             } 
767         } else {
768           DocumentEntry dentry = (DocumentEntry)entry;
769           DocumentInputStream dstream = new DocumentInputStream(dentry);
770           target.createDocument(dentry.getName(),dstream);
771           dstream.close();
772         }
773     }
774  
775  }
776