001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.util.file;
018
019import java.io.BufferedInputStream;
020import java.io.BufferedOutputStream;
021import java.io.FileInputStream;
022import java.io.FileNotFoundException;
023import java.io.FileOutputStream;
024import java.io.FileWriter;
025import java.io.IOException;
026import java.io.InputStream;
027import java.io.ObjectInputStream;
028import java.io.ObjectOutputStream;
029import java.io.OutputStream;
030import java.io.Serializable;
031import java.net.URI;
032import java.time.Instant;
033import org.apache.wicket.util.io.Streams;
034import org.apache.wicket.util.watch.IModifiable;
035
036
037/**
038 * Simple extension of File that adds an implementation of IModifiable for files. This allows the
039 * ModificationWatcher class to watch files for modification. The IModifiable.lastModifiedTime()
040 * method also returns a Time object with a more convenient API than either Date or a value in
041 * milliseconds.
042 * 
043 * @author Jonathan Locke
044 */
045public class File extends java.io.File implements IModifiable
046{
047        private static final long serialVersionUID = 1L;
048
049        /**
050         * Constructor.
051         * 
052         * @param parent
053         *            parent
054         * @param child
055         *            child
056         */
057        public File(final File parent, final String child)
058        {
059                super(parent, child);
060        }
061
062        /**
063         * Construct.
064         * 
065         * @param parent
066         *            parent
067         * @param child
068         *            child
069         */
070        public File(final java.io.File parent, final String child)
071        {
072                super(parent, child);
073        }
074
075        /**
076         * Construct.
077         * 
078         * @param file
079         *            File from java.io package
080         */
081        public File(final java.io.File file)
082        {
083                super(file.getAbsolutePath());
084        }
085
086        /**
087         * Constructor.
088         * 
089         * @param pathname
090         *            path name
091         */
092        public File(final String pathname)
093        {
094                super(pathname);
095        }
096
097        /**
098         * Constructor.
099         * 
100         * @param parent
101         *            parent
102         * @param child
103         *            child
104         */
105        public File(final String parent, final String child)
106        {
107                super(parent, child);
108        }
109
110        /**
111         * Constructor.
112         * 
113         * @param uri
114         *            file uri
115         */
116        public File(final URI uri)
117        {
118                super(uri);
119        }
120
121        /**
122         * @param name
123         *            Name of child file
124         * @return Child file object
125         */
126        public File file(final String name)
127        {
128                return new File(this, name);
129        }
130
131        /**
132         * @return File extension (whatever is after the last '.' in the file name)
133         */
134        public String getExtension()
135        {
136                final int lastDot = getName().lastIndexOf('.');
137                if (lastDot >= 0)
138                {
139                        return getName().substring(lastDot + 1);
140                }
141                return null;
142        }
143
144        /**
145         * @return Parent folder
146         */
147        public Folder getParentFolder()
148        {
149                return new Folder(getParent());
150        }
151
152        /**
153         * @return Input stream that reads this file
154         * @throws FileNotFoundException
155         *             Thrown if the file cannot be found
156         */
157        public InputStream inputStream() throws FileNotFoundException
158        {
159                return new BufferedInputStream(new FileInputStream(this));
160        }
161
162        /**
163         * Returns a Time object representing the most recent time this file was modified.
164         *
165         * @return This file's lastModified() value as a Time object or <code>null</code> if
166         * that information is not available
167         */
168        @Override
169        public Instant lastModifiedTime()
170        {
171                final long time = lastModified();
172                
173                if(time == 0)
174                {
175                        return null;
176                }
177                return Instant.ofEpochMilli(time);
178        }
179
180        /**
181         * Creates a buffered output stream that writes to this file. If the parent folder does not yet
182         * exist, creates all necessary folders in the path.
183         * 
184         * @return Output stream that writes to this file
185         * @throws FileNotFoundException
186         *             Thrown if the file cannot be found
187         */
188        public OutputStream outputStream() throws FileNotFoundException
189        {
190                final Folder parent = getParentFolder();
191                if (!parent.exists())
192                {
193                        if (!parent.mkdirs())
194                        {
195                                throw new FileNotFoundException("Couldn't create path " + parent);
196                        }
197                }
198                return new BufferedOutputStream(new FileOutputStream(this));
199        }
200
201        /**
202         * @return String read from this file
203         * @throws IOException
204         */
205        public String readString() throws IOException
206        {
207                final InputStream in = new FileInputStream(this);
208                try
209                {
210                        return Streams.readString(in);
211                }
212                finally
213                {
214                        in.close();
215                }
216        }
217
218        /**
219         * @return Object read from serialization file
220         * @throws IOException
221         * @throws ClassNotFoundException
222         */
223        public Object readObject() throws IOException, ClassNotFoundException
224        {
225                return new ObjectInputStream(inputStream()).readObject();
226        }
227
228        /**
229         * @param object
230         *            Object to write to this file
231         * @throws IOException
232         */
233        public void writeObject(final Serializable object) throws IOException
234        {
235                new ObjectOutputStream(outputStream()).writeObject(object);
236        }
237
238        /**
239         * @return True if the file was removed
240         * @see java.io.File#delete()
241         */
242        public boolean remove()
243        {
244                return Files.remove(this);
245        }
246
247        /**
248         * Force contents of file to physical storage
249         * 
250         * @throws IOException
251         */
252        public void sync() throws IOException
253        {
254                final FileInputStream in = new FileInputStream(this);
255                try
256                {
257                        in.getFD().sync();
258                }
259                finally
260                {
261                        in.close();
262                }
263        }
264
265        /**
266         * @return This file in double quotes (useful for passing to commands and tools that have issues
267         *         with spaces in filenames)
268         */
269        public String toQuotedString()
270        {
271                return "\"" + toString() + "\"";
272        }
273
274        /**
275         * Writes the given file to this one
276         * 
277         * @param file
278         *            The file to copy
279         * @return number of bytes written
280         * @throws IOException
281         */
282        public int write(final File file) throws IOException
283        {
284                final InputStream in = new BufferedInputStream(new FileInputStream(file));
285                try
286                {
287                        return write(in);
288                }
289                finally
290                {
291                        in.close();
292                }
293        }
294
295        /**
296         * Writes the given input stream to this file
297         * 
298         * @param input
299         *            The input
300         * @return Number of bytes written
301         * @throws IOException
302         */
303        public int write(final InputStream input) throws IOException
304        {
305                return Files.writeTo(this, input);
306        }
307
308        /**
309         * Write the given string to this file
310         * 
311         * @param string
312         *            The string to write
313         * @throws IOException
314         */
315        public void write(final String string) throws IOException
316        {
317                final FileWriter out = new FileWriter(this);
318                try
319                {
320                        out.write(string);
321                }
322                finally
323                {
324                        out.close();
325                }
326        }
327}