KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > lib > cvsclient > file > DefaultFileHandler


1 /*****************************************************************************
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is the CVS Client Library.
16  * The Initial Developer of the Original Software is Robert Greig.
17  * Portions created by Robert Greig are Copyright (C) 2000.
18  * All Rights Reserved.
19  *
20  * Contributor(s): Robert Greig.
21  *****************************************************************************/

22 package org.netbeans.lib.cvsclient.file;
23
24 import java.io.*;
25 import java.util.*;
26
27 import org.netbeans.lib.cvsclient.command.*;
28 import org.netbeans.lib.cvsclient.request.*;
29 import org.netbeans.lib.cvsclient.util.*;
30
31 /**
32  * Provides a basic implementation of FileHandler, and does much of the
33  * handling of reading and writing files and performing CRLF conversions.
34  * @author Robert Greig
35  */

36 public class DefaultFileHandler
37         implements FileHandler {
38     /**
39      * Whether to emit debug information.
40      */

41     private static final boolean DEBUG = false;
42
43     /**
44      * The size of chunks read from disk.
45      */

46     private static final int CHUNK_SIZE = 32768;
47
48     /**
49      * The date the next file written should be marked as being modified on.
50      */

51     private Date modifiedDate;
52
53     private TransmitTextFilePreprocessor transmitTextFilePreprocessor;
54     private WriteTextFilePreprocessor writeTextFilePreprocessor;
55     private WriteTextFilePreprocessor writeRcsDiffFilePreprocessor;
56
57     private GlobalOptions globalOptions;
58
59     /**
60      * Creates a DefaultFileHandler.
61      */

62     public DefaultFileHandler() {
63         setTransmitTextFilePreprocessor(new DefaultTransmitTextFilePreprocessor());
64         setWriteTextFilePreprocessor(new DefaultWriteTextFilePreprocessor());
65         setWriteRcsDiffFilePreprocessor(new WriteRcsDiffFilePreprocessor());
66     }
67
68     /**
69      * Returns the preprocessor for transmitting text files.
70      */

71     public TransmitTextFilePreprocessor getTransmitTextFilePreprocessor() {
72         return transmitTextFilePreprocessor;
73     }
74
75     /**
76      * Sets the preprocessor for transmitting text files.
77      * The default one changes all line endings to Unix-lineendings (cvs default).
78      */

79     public void setTransmitTextFilePreprocessor(TransmitTextFilePreprocessor transmitTextFilePreprocessor) {
80         this.transmitTextFilePreprocessor = transmitTextFilePreprocessor;
81     }
82
83     /**
84      * Gets the preprocessor for writing text files after getting (and un-gzipping) from server.
85      */

86     public WriteTextFilePreprocessor getWriteTextFilePreprocessor() {
87         return writeTextFilePreprocessor;
88     }
89
90     /**
91      * Sets the preprocessor for writing text files after getting (and un-gzipping) from server.
92      */

93     public void setWriteTextFilePreprocessor(WriteTextFilePreprocessor writeTextFilePreprocessor) {
94         this.writeTextFilePreprocessor = writeTextFilePreprocessor;
95     }
96
97     /**
98      * Gets the preprocessor for merging text files after getting
99      * (and un-gzipping) the diff received from server.
100      */

101     public WriteTextFilePreprocessor getWriteRcsDiffFilePreprocessor() {
102         return writeRcsDiffFilePreprocessor;
103     }
104
105     /**
106      * Sets the preprocessor for merging text files after getting
107      * (and un-gzipping) the diff received from server.
108      */

109     public void setWriteRcsDiffFilePreprocessor(WriteTextFilePreprocessor writeRcsDiffFilePreprocessor) {
110         this.writeRcsDiffFilePreprocessor = writeRcsDiffFilePreprocessor;
111     }
112
113     /**
114      * Get the string to transmit containing the file transmission length.
115      *
116      * @return a String to transmit to the server (including carriage return)
117      * @param length the amount of data that will be sent
118      */

119     protected String JavaDoc getLengthString(long length) {
120         return String.valueOf(length) + "\n"; //NOI18N
121
}
122
123     protected Reader getProcessedReader(File f)
124             throws IOException {
125         return new FileReader(f);
126     }
127
128     protected InputStream getProcessedInputStream(File file) throws IOException {
129         return new FileInputStream(file);
130     }
131
132     /**
133      * Get any requests that must be sent before commands are sent, to init
134      * this file handler.
135      *
136      * @return an array of Requests that must be sent
137      */

138     public Request[] getInitialisationRequests() {
139         return null;
140     }
141
142     /**
143      * Transmit a text file to the server, using the standard CVS protocol
144      * conventions. CR/LFs are converted to the Unix format.
145      *
146      * @param file the file to transmit
147      * @param dos the data outputstream on which to transmit the file
148      */

149     public void transmitTextFile(File file, LoggedDataOutputStream dos)
150             throws IOException {
151         if (file == null || !file.exists()) {
152             throw new IllegalArgumentException JavaDoc("File is either null or " +
153                                                "does not exist. Cannot transmit.");
154         }
155
156         File fileToSend = file;
157
158         final TransmitTextFilePreprocessor transmitTextFilePreprocessor =
159                                               getTransmitTextFilePreprocessor();
160
161         if (transmitTextFilePreprocessor != null) {
162             fileToSend = transmitTextFilePreprocessor.getPreprocessedTextFile(file);
163         }
164
165         BufferedInputStream bis = null;
166         try {
167             // first write the length of the file
168
long length = fileToSend.length();
169             dos.writeBytes(getLengthString(length), "US-ASCII");
170
171             bis = new BufferedInputStream(new FileInputStream(fileToSend));
172             // now transmit the file itself
173
byte[] chunk = new byte[CHUNK_SIZE];
174             while (length > 0) {
175                 int bytesToRead = (length >= CHUNK_SIZE) ? CHUNK_SIZE
176                                                          : (int)length;
177                 int count = bis.read(chunk, 0, bytesToRead);
178                 if (count == -1) {
179                     throw new IOException("Unexpected end of stream from "+fileToSend+".");
180                 }
181                 length -= count;
182                 dos.write(chunk, 0, count);
183             }
184             dos.flush();
185         }
186         finally {
187             if (bis != null) {
188                 try {
189                     bis.close();
190                 }
191                 catch (IOException ex) {
192                     // ignore
193
}
194             }
195             if (transmitTextFilePreprocessor != null) {
196                 transmitTextFilePreprocessor.cleanup(fileToSend);
197             }
198         }
199     }
200
201     /**
202      * Transmit a binary file to the server, using the standard CVS protocol
203      * conventions.
204      * @param file the file to transmit
205      * @param dos the data outputstream on which to transmit the file
206      */

207     public void transmitBinaryFile(File file, LoggedDataOutputStream dos)
208             throws IOException {
209         if (file == null || !file.exists()) {
210             throw new IllegalArgumentException JavaDoc("File is either null or " +
211                                                "does not exist. Cannot transmit.");
212         }
213
214         BufferedInputStream bis = null;
215
216         try {
217             bis = new BufferedInputStream(new FileInputStream(file));
218             // first write the length of the file
219
long length = file.length();
220
221             dos.writeBytes(getLengthString(length), "US-ASCII");
222
223             // now transmit the file itself
224
byte[] chunk = new byte[CHUNK_SIZE];
225             while (length > 0) {
226                 int bytesToRead = (length >= CHUNK_SIZE) ? CHUNK_SIZE
227                                                          : (int)length;
228                 int count = bis.read(chunk, 0, bytesToRead);
229                 if (count == -1) {
230                     throw new IOException("Unexpected end of stream from "+file+".");
231                 }
232                 length -= count;
233                 dos.write(chunk, 0, count);
234             }
235             dos.flush();
236         }
237         finally {
238             if (bis != null) {
239                 try {
240                     bis.close();
241                 }
242                 catch (IOException ex) {
243                     ex.printStackTrace();
244                 }
245             }
246         }
247     }
248
249     /**
250      * Write (either create or replace) a file on the local machine with
251      * one read from the server.
252      * @param path the absolute path of the file, (including the file name).
253      * @param mode the mode of the file
254      * @param dis the stream to read the file from, as bytes
255      * @param length the number of bytes to read
256      */

257     public void writeTextFile(String JavaDoc path, String JavaDoc mode,
258                               LoggedDataInputStream dis, int length)
259             throws IOException {
260         writeAndPostProcessTextFile(path, mode, dis, length,
261                                     getWriteTextFilePreprocessor());
262     }
263
264     /**
265      * Merge a text file on the local machine with
266      * the diff from the server. (it uses the RcsDiff response format
267      * - see cvsclient.ps for details)
268      * @param path the absolute path of the file, (including the file name).
269      * @param mode the mode of the file
270      * @param dis the stream to read the file from, as bytes
271      * @param length the number of bytes to read
272      */

273     public void writeRcsDiffFile(String JavaDoc path, String JavaDoc mode, LoggedDataInputStream dis,
274                                  int length) throws IOException {
275         writeAndPostProcessTextFile(path, mode, dis, length,
276                                     getWriteRcsDiffFilePreprocessor());
277     }
278
279     /**
280      * Common code for writeTextFile() and writeRcsDiffFile() methods.
281      * Differs only in the passed file processor.
282      */

283     private void writeAndPostProcessTextFile(String JavaDoc path, String JavaDoc mode, LoggedDataInputStream dis,
284                                              int length, WriteTextFilePreprocessor processor) throws IOException {
285         if (DEBUG) {
286             System.err.println("[writeTextFile] writing: " + path); //NOI18N
287
System.err.println("[writeTextFile] length: " + length); //NOI18N
288
System.err.println("Reader object is: " + dis.hashCode()); //NOI18N
289
}
290
291         File file = new File(path);
292
293         boolean readOnly = resetReadOnly(file);
294
295         createNewFile(file);
296         // For CRLF conversion, we have to read the file
297
// into a temp file, then do the conversion. This is because we cannot
298
// perform a sequence of readLines() until we've read the file from
299
// the server - the file transmission is not followed by a newline.
300
// Bah.
301
File tempFile = File.createTempFile("cvsCRLF", "tmp"); //NOI18N
302

303         try {
304             OutputStream os = null;
305             try {
306                 os = new BufferedOutputStream(new FileOutputStream(tempFile));
307                 byte[] chunk = new byte[CHUNK_SIZE];
308                 while (length > 0) {
309                     int count = (length >= CHUNK_SIZE) ? CHUNK_SIZE
310                                                        :length;
311                     count = dis.read(chunk, 0, count);
312                     if (count == -1) {
313                         throw new IOException("Unexpected end of stream: " + path + "\nMissing " + length + " bytes. Probably network communication failure.\nPlease try again."); // NOI18N
314
}
315                     length -= count;
316                     if (DEBUG) {
317                         System.err.println("Still got: " + length + " to read"); //NOI18N
318
}
319                     os.write(chunk, 0, count);
320                 }
321             }
322             finally {
323                 if (os != null) {
324                     try {
325                         os.close();
326                     }
327                     catch (IOException ex) {
328                         // ignore
329
}
330                 }
331             }
332
333             // Here we read the temp file in again, doing any processing required
334
// (for example, unzipping). We must not convert bytes to characters
335
// because it would break characters that are not in the current encoding
336
InputStream tempInput = getProcessedInputStream(tempFile);
337
338             try {
339                 //BUGLOG - assert the processor is not null..
340
processor.copyTextFileToLocation(tempInput, file, new StreamProvider(file));
341             } finally {
342                 tempInput.close();
343             }
344
345             if (modifiedDate != null) {
346                 file.setLastModified(modifiedDate.getTime());
347                 modifiedDate = null;
348             }
349         }
350         finally {
351             tempFile.delete();
352         }
353
354         if (readOnly) {
355             FileUtils.setFileReadOnly(file, true);
356         }
357     }
358
359     /**
360      * Write (either create or replace) a binary file on the local machine with
361      * one read from the server.
362      * @param path the absolute path of the file, (including the file name).
363      * @param mode the mode of the file
364      * @param dis the stream to read the file from, as bytes
365      * @param length the number of bytes to read
366      */

367     public void writeBinaryFile(String JavaDoc path, String JavaDoc mode,
368                                 LoggedDataInputStream dis, int length)
369             throws IOException {
370         if (DEBUG) {
371             System.err.println("[writeBinaryFile] writing: " + path); //NOI18N
372
System.err.println("[writeBinaryFile] length: " + length); //NOI18N
373
System.err.println("Reader object is: " + dis.hashCode()); //NOI18N
374
}
375
376         File file = new File(path);
377
378         boolean readOnly = resetReadOnly(file);
379
380         createNewFile(file);
381         // FUTURE: optimisation possible - no need to use a temp file if there
382
// is no post processing required (e.g. unzipping). So perhaps enhance
383
// the interface to allow this stage to be optional
384
File cvsDir = new File(file.getParentFile(), "CVS");
385         cvsDir.mkdir();
386         File tempFile = File.createTempFile("cvsPostConversion", "tmp", cvsDir); //NOI18N
387

388         try {
389             BufferedOutputStream bos =
390             new BufferedOutputStream(new FileOutputStream(tempFile));
391
392             byte[] chunk = new byte[CHUNK_SIZE];
393             try {
394                 while (length > 0) {
395                     int bytesToRead = (length >= CHUNK_SIZE) ? CHUNK_SIZE
396                             : (int)length;
397                     int count = dis.read(chunk, 0, bytesToRead);
398                     if (count == -1) {
399                         throw new IOException("Unexpected end of stream: " + path + "\nMissing " + length + " bytes. Probably network communication failure.\nPlease try again."); // NOI18N
400
}
401                     if (count < 0) {
402                         break;
403                     }
404
405                     length -= count;
406                     if (DEBUG) {
407                         System.err.println("Still got: " + length + " to read"); //NOI18N
408
}
409                     bos.write(chunk, 0, count);
410                 }
411             } finally {
412                 bos.close();
413             }
414
415             // Here we read the temp file in, taking the opportunity to process
416
// the file, e.g. unzip the data
417
BufferedInputStream tempIS =
418                      new BufferedInputStream(getProcessedInputStream(tempFile));
419             bos = new BufferedOutputStream(createOutputStream(file));
420
421             try {
422                 for (int count = tempIS.read(chunk, 0, CHUNK_SIZE);
423                      count > 0;
424                      count = tempIS.read(chunk, 0, CHUNK_SIZE)) {
425                     bos.write(chunk, 0, count);
426                 }
427             } finally {
428                 bos.close();
429                 tempIS.close();
430             }
431
432             // now we need to modifiy the timestamp on the file, if specified
433
if (modifiedDate != null) {
434                 file.setLastModified(modifiedDate.getTime());
435                 modifiedDate = null;
436             }
437         }
438         finally {
439             tempFile.delete();
440         }
441
442         if (readOnly) {
443             FileUtils.setFileReadOnly(file, true);
444         }
445     }
446
447     /** Extension point allowing subclasses to change file creation logic.*/
448     protected boolean createNewFile(File file) throws IOException {
449         file.getParentFile().mkdirs();
450         return file.createNewFile();
451     }
452
453     /**
454      * Extension point allowing subclasses to change file write logic.
455      * The stream is close()d after usage.
456      */

457     protected OutputStream createOutputStream(File file) throws IOException {
458         return new FileOutputStream(file);
459     }
460
461     private class StreamProvider implements OutputStreamProvider {
462         private final File file;
463
464         public StreamProvider(File file) {
465             this.file = file;
466         }
467
468         public OutputStream createOutputStream() throws IOException {
469             return DefaultFileHandler.this.createOutputStream(file);
470         }
471     }
472
473     private boolean resetReadOnly(File file) throws java.io.IOException JavaDoc {
474         boolean readOnly = globalOptions != null && globalOptions.isCheckedOutFilesReadOnly();
475         if (file.exists() && readOnly) {
476             readOnly = !file.canWrite();
477             if (readOnly) {
478                 FileUtils.setFileReadOnly(file, false);
479             }
480         }
481  
482         return readOnly;
483     }
484
485     /**
486      * Remove the specified file from the local disk.
487      *
488      * @param pathname the full path to the file to remove
489      * @throws IOException if an IO error occurs while removing the file
490      */

491     public void removeLocalFile(String JavaDoc pathname)
492             throws IOException {
493         File fileToDelete = new File(pathname);
494         if (fileToDelete.exists() && !fileToDelete.delete()) {
495             System.err.println("Could not delete file " +
496                                fileToDelete.getAbsolutePath());
497         }
498     }
499
500     /**
501      * Rename the local file.
502      * If the destination file exists, the operation does nothing.
503      *
504      * @param pathname the full path to the file to rename
505      * @param newName the new name of the file (not the full path)
506      * @throws IOException if an IO error occurs while renaming the file
507      */

508     public void renameLocalFile(String JavaDoc pathname, String JavaDoc newName)
509             throws IOException {
510         File sourceFile = new File(pathname);
511         File destinationFile = new File(sourceFile.getParentFile(), newName);
512         sourceFile.renameTo(destinationFile);
513     }
514
515     /**
516      * Set the modified date of the next file to be written.
517      * The next call to writeFile will use this date.
518      *
519      * @param modifiedDate the date the file should be marked as modified
520      */

521     public void setNextFileDate(Date modifiedDate) {
522         this.modifiedDate = modifiedDate;
523     }
524
525     /**
526      * Sets the global options.
527      * This can be useful to detect, whether local files should be made read-only.
528      */

529     public void setGlobalOptions(GlobalOptions globalOptions) {
530         BugLog.getInstance().assertNotNull(globalOptions);
531
532         this.globalOptions = globalOptions;
533         transmitTextFilePreprocessor.setTempDir(globalOptions.getTempDir());
534     }
535 }
536
Popular Tags