/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package libcore.java.io;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.UUID;

public class FileTest extends junit.framework.TestCase {
    private static File createTemporaryDirectory() throws Exception {
        String base = System.getProperty("java.io.tmpdir");
        File directory = new File(base, UUID.randomUUID().toString());
        assertTrue(directory.mkdirs());
        return directory;
    }

    private static String longString(int n) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < n; ++i) {
            result.append('x');
        }
        return result.toString();
    }

    private static File createDeepStructure(File base) throws Exception {
        // ext has a limit of around 256 characters for each path entry.
        // 128 characters should be safe for everything but FAT.
        String longString = longString(128);
        // Keep creating subdirectories until the path length is greater than 1KiB.
        // Ubuntu 8.04's kernel is happy up to about 4KiB.
        File f = base;
        for (int i = 0; f.toString().length() <= 1024; ++i) {
            f = new File(f, longString);
            assertTrue(f.mkdir());
        }
        return f;
    }

    // Rather than test all methods, assume that if createTempFile creates a long path and
    // exists can see it, the code for coping with long paths (shared by all methods) works.
    public void test_longPath() throws Exception {
        File base = createTemporaryDirectory();
        assertTrue(createDeepStructure(base).exists());
    }

    // readlink(2) is a special case,.
    public void test_longReadlink() throws Exception {
        File base = createTemporaryDirectory();
        File target = createDeepStructure(base);
        File source = new File(base, "source");
        assertFalse(source.exists());
        assertTrue(target.exists());
        assertTrue(target.getCanonicalPath().length() > 1024);
        ln_s(target, source);
        assertTrue(source.exists());
        assertEquals(target.getCanonicalPath(), source.getCanonicalPath());
    }

    // TODO: File.list is a special case too, but I haven't fixed it yet, and the new code,
    // like the old code, will die of a native buffer overrun if we exercise it.

    public void test_emptyFilename() throws Exception {
        // The behavior of the empty filename is an odd mixture.
        File f = new File("");
        // Mostly it behaves like an invalid path...
        assertFalse(f.canExecute());
        assertFalse(f.canRead());
        assertFalse(f.canWrite());
        try {
            f.createNewFile();
            fail("expected IOException");
        } catch (IOException expected) {
        }
        assertFalse(f.delete());
        f.deleteOnExit();
        assertFalse(f.exists());
        assertEquals("", f.getName());
        assertEquals(null, f.getParent());
        assertEquals(null, f.getParentFile());
        assertEquals("", f.getPath());
        assertFalse(f.isAbsolute());
        assertFalse(f.isDirectory());
        assertFalse(f.isFile());
        assertFalse(f.isHidden());
        assertEquals(0, f.lastModified());
        assertEquals(0, f.length());
        assertEquals(null, f.list());
        assertEquals(null, f.list(null));
        assertEquals(null, f.listFiles());
        assertEquals(null, f.listFiles((FileFilter) null));
        assertEquals(null, f.listFiles((FilenameFilter) null));
        assertFalse(f.mkdir());
        assertFalse(f.mkdirs());
        assertFalse(f.renameTo(f));
        assertFalse(f.setLastModified(123));
        assertFalse(f.setExecutable(true));
        assertFalse(f.setReadOnly());
        assertFalse(f.setReadable(true));
        assertFalse(f.setWritable(true));
        // ...but sometimes it behaves like "user.dir".
        String cwd = System.getProperty("user.dir");
        assertEquals(new File(cwd), f.getAbsoluteFile());
        assertEquals(cwd, f.getAbsolutePath());
        // TODO: how do we test these without hard-coding assumptions about where our temporary
        // directory is? (In practice, on Android, our temporary directory is accessed through
        // a symbolic link, so the canonical file/path will be different.)
        //assertEquals(new File(cwd), f.getCanonicalFile());
        //assertEquals(cwd, f.getCanonicalPath());
    }

    // http://b/2486943 - between eclair and froyo, we added a call to
    // isAbsolute from the File constructor, potentially breaking subclasses.
    public void test_subclassing() throws Exception {
        class MyFile extends File {
            private String field;
            MyFile(String s) {
                super(s);
                field = "";
            }
            @Override public boolean isAbsolute() {
                field.length();
                return super.isAbsolute();
            }
        }
        new MyFile("");
    }

    // http://b/3047893 - getCanonicalPath wasn't actually resolving symbolic links.
    public void test_getCanonicalPath() throws Exception {
        if (new File("/sdcard").exists()) {
            // This assumes the current Android setup where /sdcard is a symbolic link to
            // /mnt/sdcard.
            File testFile = new File("/sdcard/test1.txt");
            assertEquals("/mnt/sdcard/test1.txt", testFile.getCanonicalPath());
        }

        // This assumes you can create symbolic links in the temporary directory. This isn't
        // true on Android if you're using /sdcard. It will work in /data/local though.
        File base = createTemporaryDirectory();
        File target = new File(base, "target");
        target.createNewFile(); // The RI won't follow a dangling symlink, which seems like a bug!
        File linkName = new File(base, "link");
        ln_s(target, linkName);
        assertEquals(target.getCanonicalPath(), linkName.getCanonicalPath());

        // .../subdir/shorter -> .../target (using a link to ../target).
        File subdir = new File(base, "subdir");
        assertTrue(subdir.mkdir());
        linkName = new File(subdir, "shorter");
        ln_s("../target", linkName.toString());
        assertEquals(target.getCanonicalPath(), linkName.getCanonicalPath());

        // .../l -> .../subdir/longer (using a relative link to subdir/longer).
        linkName = new File(base, "l");
        ln_s("subdir/longer", linkName.toString());
        File longer = new File(base, "subdir/longer");
        longer.createNewFile(); // The RI won't follow a dangling symlink, which seems like a bug!
        assertEquals(longer.getCanonicalPath(), linkName.getCanonicalPath());

        // .../double -> .../target (via a link into subdir and a link back out).
        linkName = new File(base, "double");
        ln_s("subdir/shorter", linkName.toString());
        assertEquals(target.getCanonicalPath(), linkName.getCanonicalPath());
    }

    private static void ln_s(File target, File linkName) throws Exception {
        ln_s(target.toString(), linkName.toString());
    }

    private static void ln_s(String target, String linkName) throws Exception {
        String[] args = new String[] { "ln", "-s", target, linkName };
        // System.err.println("ln -s " + target + " " + linkName);
        Process p = Runtime.getRuntime().exec(args);
        int result = p.waitFor();
        if (result != 0) {
            BufferedReader r = new BufferedReader(new InputStreamReader(p.getErrorStream()));
            String line;
            while ((line = r.readLine()) != null) {
                System.err.println(line);
            }
            fail("ln -s " + target + " " + linkName + " failed. " +
                    "Does that file system support symlinks?");
        }
    }
}
