1/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
2 *
3 * This program and the accompanying materials are made available under
4 * the terms of the Common Public License v1.0 which accompanies this distribution,
5 * and is available at http://www.eclipse.org/legal/cpl-v10.html
6 *
7 * $Id: Files.java,v 1.1.1.1.2.1 2004/07/09 01:28:37 vlad_r Exp $
8 */
9package com.vladium.util;
10
11import java.io.BufferedReader;
12import java.io.File;
13import java.io.FileReader;
14import java.io.IOException;
15import java.util.ArrayList;
16import java.util.HashSet;
17import java.util.LinkedList;
18import java.util.List;
19import java.util.Set;
20import java.util.StringTokenizer;
21
22// ----------------------------------------------------------------------------
23/**
24 * @author Vlad Roubtsov, (C) 2003
25 */
26public
27abstract class Files
28{
29    // public: ................................................................
30
31    /**
32     * No duplicate elimination.
33     *
34     * @param atfile
35     */
36    public static String [] readFileList (final File atfile)
37        throws IOException
38    {
39        if (atfile == null) throw new IllegalArgumentException ("null input: atfile");
40
41        List _result = null;
42
43        BufferedReader in = null;
44        try
45        {
46            in = new BufferedReader (new FileReader (atfile), 8 * 1024); // uses default encoding
47            _result = new LinkedList ();
48
49            for (String line; (line = in.readLine ()) != null; )
50            {
51                line = line.trim ();
52                if ((line.length () == 0) || (line.charAt (0) == '#')) continue;
53
54                _result.add (line);
55            }
56        }
57        finally
58        {
59            if (in != null) try { in.close (); } catch (Exception ignore) {}
60        }
61
62        if ((_result == null) || _result.isEmpty ())
63            return IConstants.EMPTY_STRING_ARRAY;
64        else
65        {
66            final String [] result = new String [_result.size ()];
67            _result.toArray (result);
68
69            return result;
70        }
71    }
72
73    /**
74     * Converts an array of path segments to an array of Files. The order
75     * of files follows the original order of path segments, except "duplicate"
76     * entries are removed. The definition of duplicates depends on 'canonical':
77     * <ul>
78     *  <li> if 'canonical'=true, the pathnames are canonicalized via {@link #canonicalizePathname}
79     *  before they are compared for equality
80     *  <li> if 'canonical'=false, the pathnames are compared as case-sensitive strings
81     * </ul>
82     *
83     * Note that duplicate removal in classpaths affects ClassLoader.getResources().
84     * The first mode above makes the most sense, however the last one is what
85     * Sun's java.net.URLClassLoader appears to do. Hence the last mode might be
86     * necessary for reproducing its behavior in Sun-compatible JVMs.
87     */
88    public static File [] pathToFiles (final String [] path, final boolean canonical)
89    {
90        if (path == null) throw new IllegalArgumentException ("null input: path");
91        if (path.length == 0) return IConstants.EMPTY_FILE_ARRAY;
92
93        final List /* Files */ _result = new ArrayList (path.length);
94        final Set /* String */ pathnames = new HashSet (path.length);
95
96        final String separators = ",".concat (File.pathSeparator);
97
98        for (int i = 0; i < path.length; ++ i)
99        {
100            String segment = path [i];
101            if (segment == null) throw new IllegalArgumentException ("null input: path[" + i + "]");
102
103            final StringTokenizer tokenizer = new StringTokenizer (segment, separators);
104            while (tokenizer.hasMoreTokens ())
105            {
106                String pathname = tokenizer.nextToken ();
107
108                if (canonical) pathname = canonicalizePathname (pathname);
109
110                if (pathnames.add (pathname))
111                {
112                    _result.add (new File (pathname));
113                }
114            }
115        }
116
117        final File [] result = new File [_result.size ()];
118        _result.toArray (result);
119
120        return result;
121    }
122
123    /**
124     * Converts 'pathname' into the canonical OS form. This wrapper function
125     * will return the absolute form of 'pathname' if File.getCanonicalPath() fails.
126     */
127    public static String canonicalizePathname (final String pathname)
128    {
129        if (pathname == null) throw new IllegalArgumentException ("null input: pathname");
130
131        try
132        {
133            return new File (pathname).getCanonicalPath ();
134        }
135        catch (Exception e)
136        {
137            return new File (pathname).getAbsolutePath ();
138        }
139    }
140
141    public static File canonicalizeFile (final File file)
142    {
143        if (file == null) throw new IllegalArgumentException ("null input: file");
144
145        try
146        {
147            return file.getCanonicalFile ();
148        }
149        catch (Exception e)
150        {
151            return file.getAbsoluteFile ();
152        }
153    }
154
155    /**
156     * Invariant: (getFileName (file) + getFileExtension (file)).equals (file.getName ()).
157     *
158     * @param file File input file descriptor [must be non-null]
159     *
160     * @return String file name without the extension [excluding '.' separator]
161     * [if 'file' does not appear to have an extension, the full name is returned].
162     *
163     * @throws IllegalArgumentException if 'file' is null
164     */
165    public static String getFileName (final File file)
166    {
167        if (file == null) throw new IllegalArgumentException ("null input: file");
168
169        final String name = file.getName ();
170        int lastDot = name.lastIndexOf ('.');
171        if (lastDot < 0) return name;
172
173        return name.substring (0, lastDot);
174    }
175
176    /**
177     * Invariant: (getFileName (file) + getFileExtension (file)).equals (file.getName ()).
178     *
179     * @param file File input file descriptor [must be non-null]
180     *
181     * @return String extension [including '.' separator] or "" if 'file' does not
182     * appear to have an extension.
183     *
184     * @throws IllegalArgumentException if 'file' is null
185     */
186    public static String getFileExtension (final File file)
187    {
188        if (file == null) throw new IllegalArgumentException ("null input: file");
189
190        final String name = file.getName ();
191        int lastDot = name.lastIndexOf ('.');
192        if (lastDot < 0) return "";
193
194        return name.substring (lastDot);
195    }
196
197    /**
198     *
199     * @param dir [null is ignored]
200     * @param file [absolute overrides 'dir']
201     * @return
202     */
203    public static File newFile (final File dir, final File file)
204    {
205        if (file == null) throw new IllegalArgumentException ("null input: file");
206
207        if ((dir == null) || file.isAbsolute ()) return file;
208
209        return new File (dir, file.getPath ());
210    }
211
212    /**
213     *
214     * @param dir [null is ignored]
215     * @param file [absolute overrides 'dir']
216     * @return
217     */
218    public static File newFile (final File dir, final String file)
219    {
220        if (file == null) throw new IllegalArgumentException ("null input: file");
221
222        final File fileFile  = new File (file);
223        if ((dir == null) || fileFile.isAbsolute ()) return fileFile;
224
225        return new File (dir, file);
226    }
227
228    /**
229     *
230     * @param dir [null is ignored]
231     * @param file [absolute overrides 'dir']
232     * @return
233     */
234    public static File newFile (final String dir, final String file)
235    {
236        if (file == null) throw new IllegalArgumentException ("null input: file");
237
238        final File fileFile  = new File (file);
239        if ((dir == null) || fileFile.isAbsolute ()) return fileFile;
240
241        return new File (dir, file);
242    }
243
244    /**
245     * Renames 'source' to 'target' [intermediate directories are created if necessary]. If
246     * 'target' exists and 'overwrite' is false, the method is a no-op. No exceptions are
247     * thrown except for when input is invalid. If the operation fails half-way it can leave
248     * some file system artifacts behind.
249     *
250     * @return true iff the renaming was actually performed.
251     *
252     * @param source file descriptor [file must exist]
253     * @param target target file descriptor [an existing target may get deleted
254     * if 'overwrite' is true]
255     * @param overwrite if 'true', forces an existing target to be deleted
256     *
257     * @throws IllegalArgumentException if 'source' is null or file does not exist
258     * @throws IllegalArgumentException if 'target' is null
259     */
260    public static boolean renameFile (final File source, final File target, final boolean overwrite)
261    {
262        if ((source == null) || ! source.exists ())
263            throw new IllegalArgumentException ("invalid input source: [" + source + "]");
264        if (target == null)
265            throw new IllegalArgumentException ("null input: target");
266
267        final boolean targetExists;
268        if (! (targetExists = target.exists ()) || overwrite)
269        {
270            if (targetExists)
271            {
272                // need to delete the target first or the rename will fail:
273                target.delete (); // not checking the result here: let the rename fail later
274            }
275            else
276            {
277                // note that File.renameTo() does not create intermediate directories on demand:
278                final File targetDir = target.getParentFile ();
279                if ((targetDir != null) && ! targetDir.equals (source.getParentFile ()))
280                    targetDir.mkdirs (); // TODO: clean this up on failure?
281            }
282
283            // note: this can fail for a number of reasons, including the target
284            // being on a different drive/file system:
285            return source.renameTo (target);
286        }
287
288        return false;
289    }
290
291    /**
292     * A slightly stricter version of File.createTempFile() in J2SDK 1.3: it requires
293     * that the caller provide an existing parent directory for the temp file.
294     * This defers to File.createTempFile (prefix, extension, parentDir) after
295     * normalizing 'extension'.<P>
296     *
297     * MT-safety: if several threads use this API concurrently, the temp files
298     * created are guaranteed to get created without any collisions and correspond
299     * to files that did not exist before. However, if such a temp file is deleted
300     * at a later point, this method may reuse its file name. These MT-safety
301     * guarantees do not hold if files are created in the same directory outside
302     * of this method.
303     *
304     * @param parentDir parent dir for the temp file [may not be null and must exist]
305     * @param prefix prefix pattern for the temp file name [only the first 3
306     * chars are guaranteed to be used]
307     * @param extension pattern for the temp file name [null is equivalient to
308     * ".tmp"; this is always normalized to start with "."; only the first 3
309     * non-"." chars are guaranteed to be used]
310     *
311     * @return writeable temp file descriptor [incorporates 'parentDir' in its pathname]
312     * @throws IOException if a temp file could not be created
313     */
314    public static File createTempFile (final File parentDir, final String prefix, String extension)
315        throws IOException
316    {
317        if ((parentDir == null) || ! parentDir.exists ())
318            throw new IllegalArgumentException ("invalid parent directory: [" + parentDir + "]");
319        if ((prefix == null) || (prefix.length () < 3))
320            throw new IllegalArgumentException ("null or less than 3 chars long: " + prefix);
321
322        if (extension == null) extension = ".tmp";
323        else if (extension.charAt (0) != '.') extension = ".".concat (extension);
324
325        return File.createTempFile (prefix, extension, parentDir);
326    }
327
328    // protected: .............................................................
329
330    // package: ...............................................................
331
332    // private: ...............................................................
333
334
335    private Files () {} // prevent subclassing
336
337} // end of class
338// ----------------------------------------------------------------------------