001    package org.LiveGraph.dataFile.write;
002    
003    import java.io.File;
004    import java.io.FileOutputStream;
005    import java.io.IOException;
006    
007    import org.LiveGraph.dataFile.read.PipedInputStream;
008    
009    import com.softnetConsult.utils.files.FileTools;
010    import com.softnetConsult.utils.sys.SystemTools;
011    
012    
013    
014    /**
015     * This class provides static convenience methods for creating dedicated data stream writers.
016     * Given just a directory on the local hard drive, this class can automatically
017     * choose a descriptive and unique name for a data file and return an appropriate
018     * {@link DataStreamWriter} object.<br />
019     * <br />
020     * An example of how to use this class can be found in
021     * {@link org.LiveGraph.demoDataSource.LiveGraphDemo}.<br />
022     * 
023     * <p style="font-size:smaller;">This product includes software developed by the
024     *    <strong>LiveGraph</strong> project and its contributors.<br />
025     *    (<a href="http://www.live-graph.org" target="_blank">http://www.live-graph.org</a>)<br />
026     *    Copyright (c) 2007-2008 G. Paperin.<br />
027     *    All rights reserved.
028     * </p>
029     * <p style="font-size:smaller;">File: DataStreamWriterFactory.java</p> 
030     * <p style="font-size:smaller;">Redistribution and use in source and binary forms, with or
031     *    without modification, are permitted provided that the following terms and conditions are met:
032     * </p>
033     * <p style="font-size:smaller;">1. Redistributions of source code must retain the above
034     *    acknowledgement of the LiveGraph project and its web-site, the above copyright notice,
035     *    this list of conditions and the following disclaimer.<br />
036     *    2. Redistributions in binary form must reproduce the above acknowledgement of the
037     *    LiveGraph project and its web-site, the above copyright notice, this list of conditions
038     *    and the following disclaimer in the documentation and/or other materials provided with
039     *    the distribution.<br />
040     *    3. All advertising materials mentioning features or use of this software or any derived
041     *    software must display the following acknowledgement:<br />
042     *    <em>This product includes software developed by the LiveGraph project and its
043     *    contributors.<br />(http://www.live-graph.org)</em><br />
044     *    4. All advertising materials distributed in form of HTML pages or any other technology
045     *    permitting active hyper-links that mention features or use of this software or any
046     *    derived software must display the acknowledgment specified in condition 3 of this
047     *    agreement, and in addition, include a visible and working hyper-link to the LiveGraph
048     *    homepage (http://www.live-graph.org).
049     * </p>
050     * <p style="font-size:smaller;">THIS SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY
051     *    OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
052     *    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT SHALL
053     *    THE AUTHORS, CONTRIBUTORS OR COPYRIGHT  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
054     *    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  FROM, OUT OF OR
055     *    IN CONNECTION WITH THE SOFTWARE OR THE USE OR  OTHER DEALINGS IN THE SOFTWARE.
056     * </p>
057     * 
058     * @author Greg Paperin (<a href="http://www.paperin.org" target="_blank">http://www.paperin.org</a>)
059     * @version {@value org.LiveGraph.LiveGraph#version}
060     */
061    public class DataStreamWriterFactory {
062    
063    /**
064     * Data file extension used if no other specified.
065     */
066    public static final String defaultFileExtension = "lgdat";
067    
068    
069    /**
070     * Creates a new {@link DataStreamWriter} object to write to the specified {@code PipedInputStream}.
071     * Use this method to create writers to use with the memory stream mode.
072     * @param pipedInputStream The input stream to which the data will be directed.
073     * @return A new data stream writer connected to the specified piped stream.
074     * @throws IOException If an error occurs when creating the piped streams (e.g. when the
075     * {@code PipedInputStream} is already connected or in other cases).
076     */
077    public static DataStreamWriter createDataWriter(PipedInputStream pipedInputStream) throws IOException {
078            PipedOutputStream outs = new PipedOutputStream(pipedInputStream);
079            DataStreamWriter outw = new DataStreamWriter(outs);
080            return outw;
081    }
082    
083    /**
084     * Creates a new {@link DataStreamWriter} object for a unique file whose name is created
085     * on the basis of the specified base name and directory as well as an optional
086     * timestamp and an optional counter to make the file name unique.<br />
087     * This method is a convenience shorthand for
088     * {@code createDataWriter(dataFileDir, dataFileBaseName, defaultFileExtension)}, where
089     * {@code DataStreamWriterFactory.defaultFileExtension} = {@value #defaultFileExtension}.
090     * See the {@linkplain #createDataWriter(String, String, String) three parameters version}
091     * for details.
092     * 
093     * @param dataFileDir The directory where the data file will be created.
094     * @param dataFileBaseName The base name for the data file.
095     * @return A new {@link DataStreamWriter} for the file created from the specifed values.
096     * @throws DataFileAlreadyExistsException If the specified files exists and canot be
097     * overwritten.
098     * @see #createDataWriter(String, String, String)
099     * @see com.softnetConsult.utils.files.FileTools#findUniqueFile(String, String, String, boolean)
100     */
101    public static DataStreamWriter createDataWriter(String dataFileDir, String dataFileBaseName) {
102            return createDataWriter(dataFileDir, dataFileBaseName, defaultFileExtension);
103    }
104    
105    
106    /**
107     * Creates a new {@link DataStreamWriter} object for a unique file whose name is created
108     * on the basis of the specified base name, extension and directory as well as an optional
109     * timestamp and an optional counter to make the file name unique.<br />
110     * If despite of the unique file name an output stream cannot be open (this may happen
111     * with a small probability in the case of several competing processes) the semantics of
112     * this method are the same as for {@link #createDataWriter(File, boolean)} (with
113     * {@code overwrite = false}).<br />
114     * <br />
115     * This method uses the library method
116     * {@link com.softnetConsult.utils.files.FileTools#findUniqueFile(String, String, String, boolean)}
117     * for generating a unique file path (with {@code alwaysUseTimestamp = false}).
118     * The semantics are as follows:<br /> 
119     * 1) A file name is created on the basis of the specified directory, base name and extension.
120     * (e.g. &quot;{@code directory/simulation.lgdat}&quot;).<br />
121     * 2) If the specified file name contains a substring that defines a timestamp marker
122     * (the string &quot;{@value com.softnetConsult.utils.files.FileTools#FILE_NAME_UNIQUE_MAKER_TAG}&quot;),
123     * the marker is replaced by a timestamp based on the current system time. If the specified file
124     * name does not contain a timestamp marker, no timespamp is inserted.
125     * Executed on 20 Jan 2008 at 18:30:15 the result may look like this:
126     * &quot;{@code directory/simulation.08.01.20-18.30.15.lgdat}&quot;.<br />
127     * 3) If no physical file with the resulting file name exists already, this file name is used.
128     * If a file with the resulting path exists, a counter is inserted just before the extension.
129     * The result may look like this:
130     * &quot;{@code directory/simulation.08.01.20-18.30.15(1).lgdat}&quot;.<br />
131     * 4) The uniqueness counter is increased (starting with 1) until the resulting file name
132     * does not describe a physical file that already exists.<br />
133     * 
134     * @param dataFileDir The directory where the data file will be created.
135     * @param dataFileBaseName The base name for the data file.
136     * @param dataFileExt The extension of the data file.
137     * @return A new {@link DataStreamWriter} for the file created from the specifed values.
138     * @throws DataFileAlreadyExistsException If the specified files exists and canot be
139     * overwritten. 
140     * @see com.softnetConsult.utils.files.FileTools#findUniqueFile(String, String, String, boolean)
141     */
142    public static DataStreamWriter createDataWriter(String dataFileDir,
143                                                                                                    String dataFileBaseName, String dataFileExt) {
144            
145            File file = FileTools.findUniqueFile(dataFileDir, dataFileBaseName, dataFileExt, false);
146            return createDataWriter(file, false);
147    }
148    
149    
150    /**
151     * Creates a new {@link DataStreamWriter} object for the specified file path. <br />
152     * If the file already exists, it may be overwritten if {@code overwrite} is {@code true}.<br />
153     * If a file output stream cannot be opened either becasue the file already exists and
154     * {@code overwrite} is {@code false} or for any other reason, the stream creation will be
155     * attempted again several times with a short random delay that increases after each attempt. <br />
156     * If after several attempts the file stream could not be opened, a
157     * {@code DataFileAlreadyExistsException} will be raised.<br />
158     * This is equivalent to {@code createDataWriter(new File(filePath), overwrite)}.
159     * 
160     * @param filePath Path of the file to which the new {@link DataStreamWriter} is to write its output.
161     * @param overwrite Whether to attempt overwriting the specified file it is exists.
162     * @return A new {@link DataStreamWriter} for the specified file.
163     * @throws NullPointerException If the specified file is {@code null}.
164     * @throws DataFileAlreadyExistsException If the specified files exists and canot be
165     * overwritten.
166     */
167    public static DataStreamWriter createDataWriter(String filePath, boolean overwrite) {
168            
169            if (null == filePath)
170                    throw new NullPointerException("Cannot create a DataStreamWriter for a null file");
171            
172            return createDataWriter(new File(filePath), overwrite);
173    }
174    
175    
176    /**
177     * Creates a new {@link DataStreamWriter} object for the specified file. <br />
178     * If the file already exists, it may be overwritten if {@code overwrite} is {@code true}.<br />
179     * If a file output stream cannot be opened either becasue the file already exists and
180     * {@code overwrite} is {@code false} or for any other reason, the stream creation will be
181     * attempted again several times with a short random delay that increases after each attempt. <br />
182     * If after several attempts the file stream could not be opened, a
183     * {@code DataFileAlreadyExistsException} will be raised.
184     * 
185     * @param file A file to which the new {@link DataStreamWriter} is to write its output.
186     * @param overwrite Whether to attempt overwriting the specified file it is exists.
187     * @return A new {@link DataStreamWriter} to the specified file.
188     * @throws NullPointerException If the specified file is {@code null}.
189     * @throws DataFileAlreadyExistsException If the specified files exists and canot be
190     * overwritten.
191     */
192    public static DataStreamWriter createDataWriter(File file, boolean overwrite) {
193            
194            if (null == file)
195                    throw new NullPointerException("Cannot create a DataStreamWriter for a null file");
196            
197            final int[] MAX_WAIT = new int[] {1000, 1000, 1000, 1500, 1500, 2000, 2000, 2000, 3000, 3000};
198            final int[] MIN_WAIT = new int[] { 100,  100,  100,  500,  500, 1000, 1000, 1000, 1500, 2000};
199            
200            IOException failureReason = null;
201            for (int retry = 0; retry < MAX_WAIT.length; retry++) {
202                    
203                    if (overwrite && file.exists())
204                            file.delete();
205                    
206                    if (!file.exists()) {           
207                            try {
208                                    FileOutputStream outs = new FileOutputStream(file);
209                                    DataStreamWriter writer = new DataStreamWriter(outs);
210                                    return writer;
211                            } catch (IOException e) { }
212                    }
213                    int w = MIN_WAIT[retry] + (int) (Math.random() * (MAX_WAIT[retry] - MIN_WAIT[retry]));
214                    SystemTools.sleep(w);
215            }
216            
217            DataFileAlreadyExistsException dfaee = new DataFileAlreadyExistsException(file);
218            if (null != failureReason)
219                    dfaee.initCause(failureReason);
220            throw dfaee;
221    }
222    
223    } // public class DataStreamWriterFactory