001package org.andromda.core.common;
002
003import java.io.File;
004import java.io.IOException;
005import java.net.URL;
006import org.apache.commons.io.FileUtils;
007import org.apache.commons.lang.StringUtils;
008
009/**
010 * Used for writing resources for the framework. Also keeps histories of
011 * previous resources generated so that we can avoid regenerating if the
012 * generated resources are current.
013 *
014 * @author Chad Brandon
015 * @author Bob Fields
016 */
017public class ResourceWriter
018{
019    /**
020     * The shared instance
021     */
022    private static final ResourceWriter instance = new ResourceWriter();
023
024    /**
025     * Gets the shared ResourceWriter instance. Normally you'll want to retrieve
026     * the instance through this method.
027     *
028     * @return the shared instance.
029     */
030    public static ResourceWriter instance()
031    {
032        return instance;
033    }
034
035    /**
036     * Writes the string to the file specified by the fileLocation argument.
037     *
038     * @param string the string to write to the file
039     * @param file the file to which to write.
040     * @param namespace the current namespace for which this resource is being
041     *        written.
042     * @throws IOException
043     */
044    public void writeStringToFile(
045        final String string,
046        final File file,
047        final String namespace)
048        throws IOException
049    {
050        ExceptionUtils.checkNull(
051            "file",
052            file);
053        this.writeStringToFile(
054            string,
055            file.toString(),
056            namespace,
057            true);
058    }
059
060    /**
061     * Writes the string to the file specified by the fileLocation argument.
062     *
063     * @param string the string to write to the file
064     * @param fileLocation the location of the file which to write.
065     * @throws IOException
066     */
067    public void writeStringToFile(
068        final String string,
069        final String fileLocation)
070        throws IOException
071    {
072        this.writeStringToFile(
073            string,
074            fileLocation,
075            true);
076    }
077
078    /**
079     * Writes the string to the file specified by the fileLocation argument.
080     *
081     * @param string the string to write to the file
082     * @param file the file which to write.
083     * @throws IOException
084     */
085    public void writeStringToFile(
086        final String string,
087        final File file)
088        throws IOException
089    {
090        this.writeStringToFile(
091            string,
092            file != null ? file.toString() : null,
093            true);
094    }
095
096    /**
097     * Writes the string to the file specified by the fileLocation argument.
098     *
099     * @param string the string to write to the file
100     * @param fileLocation the location of the file which to write.
101     * @param recordHistory whether or not the history of the file should be
102     *        recorded.
103     */
104    private void writeStringToFile(
105        final String string,
106        final String fileLocation,
107        final boolean recordHistory)
108        throws IOException
109    {
110        this.writeStringToFile(
111            string,
112            fileLocation,
113            null,
114            recordHistory);
115    }
116
117    /**
118     * Writes the string to the file specified by the fileLocation argument.
119     *
120     * @param string the string to write to the file
121     * @param fileLocation the location of the file which to write.
122     * @param namespace the current namespace for which this resource is being
123     *        written.
124     * @throws IOException
125     */
126    public void writeStringToFile(
127        final String string,
128        final String fileLocation,
129        final String namespace)
130        throws IOException
131    {
132        this.writeStringToFile(
133            string,
134            fileLocation,
135            namespace,
136            true);
137    }
138
139    /**
140     * Writes the string to the file specified by the fileLocation argument.
141     *
142     * @param string the string to write to the file
143     * @param fileLocation the location of the file which to write.
144     * @param namespace the current namespace for which this resource is being
145     *        written.
146     * @param recordHistory whether or not the history of this file should be
147     *        recorded.
148     * @throws IOException
149     */
150    private void writeStringToFile(
151        String string,
152        final String fileLocation,
153        final String namespace,
154        final boolean recordHistory)
155        throws IOException
156    {
157        if (string == null)
158        {
159            string = "";
160        }
161        ExceptionUtils.checkEmpty(
162            "fileLocation",
163            fileLocation);
164
165        ResourceUtils.makeDirectories(fileLocation);
166        final Merger merger = Merger.instance();
167        if (merger.requiresMerge(namespace))
168        {
169            string = Merger.instance().getMergedString(
170                    string,
171                    namespace);
172        }
173
174        final File file = new File(fileLocation);
175        FileUtils.writeStringToFile(file, string, this.encoding);
176
177        if (recordHistory)
178        {
179            this.recordHistory(file);
180        }
181    }
182
183    /**
184     * Writes the URL contents to a file specified by the fileLocation argument.
185     *
186     * @param url the URL to read
187     * @param fileLocation the location which to write.
188     * @throws IOException
189     */
190    public void writeUrlToFile(
191        final URL url,
192        final String fileLocation)
193        throws IOException
194    {
195        ResourceUtils.writeUrlToFile(url, fileLocation);
196        this.recordHistory(new File(fileLocation));
197    }
198
199    /**
200     * Stores the encoding to be used for output.
201     */
202    private String encoding = null;
203
204    /**
205     * Sets the encoding to which all output written from this class will be
206     * written.
207     *
208     * @param encoding the encoding type (UTF-8, ISO-8859-1, etc).
209     */
210    public void setEncoding(String encoding)
211    {
212        this.encoding = StringUtils.trimToNull(encoding);
213    }
214
215    private StringBuffer history = new StringBuffer();
216
217    /**
218     * Resets the a history file, to write the history {@link #writeHistory()} must be called.
219     *
220     * @param modelUri used to construct the file name from the modelUri where the history is stored
221     */
222    public void resetHistory(final String modelUri)
223    {
224        String modelFile = modelUri.replace(
225                '\\',
226                '/');
227        int lastSlash = modelFile.lastIndexOf('/');
228        if (lastSlash != -1)
229        {
230            modelFile = modelFile.substring(
231                    lastSlash + 1,
232                    modelFile.length());
233        }
234        this.modelFile = modelFile;
235        this.history = new StringBuffer();
236        this.writtenCount = 0;
237    }
238
239    private String modelFile = null;
240
241    /**
242     * Stores the count of the resources written over this instance's history.
243     */
244    private long writtenCount = 0;
245
246    /**
247     * Gets the number of currently written resources over the course of this instance's history.
248     *
249     * @return the number of written resources.
250     */
251    public long getWrittenCount()
252    {
253        return this.writtenCount;
254    }
255
256    /**
257     * The location to which history is written.
258     */
259    //private static final String HISTORY_LOCATION = Constants.TEMPORARY_DIRECTORY + "history/";
260    private String historyDir = null;
261
262    /**
263     * Gets the file history storage location.
264     * @return model generation history storage location
265     */
266    public String getHistoryStorage()
267    {
268        return this.historyDir + '/' + this.modelFile;
269    }
270
271    /**
272     * Sets the file history storage location.
273     * @param historyDirIn the history file storage location
274     */
275    public void setHistoryStorage(String historyDirIn)
276    {
277        this.historyDir = historyDirIn;
278    }
279
280    /**
281     * Writes the output history to disk.
282     *
283     * @throws IOException
284     */
285    public void writeHistory()
286        throws IOException
287    {
288        writeStringToFile(
289            history.toString(),
290            getHistoryStorage(),
291            false);
292    }
293
294    /**
295     * Writes the string to the file specified by the fileLocation argument.
296     *
297     * @param file the file to which to record the history to
298     */
299    private void recordHistory(File file)
300    {
301        // Resource files may be merged multiple times to the temp directory, before the final output file is written
302        if (this.history != null && !file.getName().endsWith(".vsl"))
303        {
304            this.history.append(file).append(',');
305        }
306        this.writtenCount++;
307    }
308
309    /**
310     * Checks to see if the history is before the given <code>time</code>.
311     *
312     * @param time the time in milliseconds to check against.
313     * @return true/false
314     */
315    public boolean isHistoryBefore(long time)
316    {
317        boolean before = true;
318        try
319        {
320            final File historyFile = new File(getHistoryStorage());
321            if (historyFile.exists() && historyFile.lastModified() >= time)
322            {
323                final String history = ResourceUtils.getContents(new File(getHistoryStorage()).toURI().toURL());
324                final String[] fileNames = history.split(",");
325                long lastModified = 0;
326                for (String fileName : fileNames)
327                {
328                    if (StringUtils.isNotBlank(fileName))
329                    {
330                        File file = new File(fileName.trim());
331
332                        // if we find one file that doesn't exist then
333                        // before is automatically false
334                        if (!file.exists())
335                        {
336                            lastModified = 0;
337                            break;
338                        }
339                        if (file.lastModified() > lastModified)
340                        {
341                            lastModified = file.lastModified();
342                        }
343                    }
344                }
345                before = time > lastModified;
346            }
347        }
348        catch (IOException ex)
349        {
350            before = true;
351        }
352        return before;
353    }
354}