Qual é a melhor maneira de encontrar o diretório inicial dos usuários em Java?

279

A dificuldade é que deve ser multiplataforma. Windows 2000, XP, Vista, OSX, Linux, outras variantes unix. Estou procurando um trecho de código que possa fazer isso em todas as plataformas e uma maneira de detectar a plataforma.

Agora, você deve estar ciente do bug 4787931 que user.homenão funciona corretamente; portanto, não me forneça respostas em livros didáticos, posso encontrá-las nos manuais.

Bruno Ranschaert
fonte
1
Você tentou as soluções alternativas mencionadas no bug? Há muitas sugestões.
Joachim Sauer
1
o bug 4787931 para versões java até 1.4.2 aparece novamente como bug 6519127 para java 1.6. O problema não está desaparecendo e ainda está listado como de baixa prioridade.
GregA100k 01/09/09
16
Nota: o bug 4787391 está marcado como corrigido no Java 8
Steven R. Loomis

Respostas:

364

O bug que você mencionou (bug 4787391) foi corrigido no Java 8. Mesmo se você estiver usando uma versão mais antiga do Java, a System.getProperty("user.home")abordagem provavelmente ainda é a melhor. A user.homeabordagem parece funcionar em um número muito grande de casos. Uma solução 100% à prova de balas no Windows é difícil, porque o Windows tem um conceito inconstante do significado do diretório inicial.

Se user.homenão for bom o suficiente para você, sugiro escolher uma definição de home directorypara Windows e usá-la, obtendo a variável de ambiente apropriada System.getenv(String).

DJClayworth
fonte
135

Na verdade, com o Java 8, o caminho certo é usar:

System.getProperty("user.home");

O bug JDK-6519127 foi corrigido e a seção "Incompatibilidades entre JDK 8 e JDK 7" das notas de versão afirma:

Área: Libs Principais / java.lang

Sinopse

As etapas usadas para determinar o diretório inicial do usuário no Windows foram alteradas para seguir a abordagem recomendada pela Microsoft. Essa alteração pode ser observada em edições mais antigas do Windows ou em que configurações do registro ou variáveis ​​de ambiente estão definidas para outros diretórios. Natureza da incompatibilidade

behavioral RFE

6519127

Apesar da pergunta ser antiga, deixo isso para referência futura.

Paulo Fidalgo
fonte
35
System.getProperty("user.home");

Veja o JavaDoc .

Joachim Sauer
fonte
11
Não, não é uma resposta correta, é a mesma que acima. Sim, eu não só li os JavaDocs, mas também tentei em todas as plataformas antes de fazer esta pergunta! A resposta não é tão simples.
de Bruno Ranschaert
3
Isso pode dar errado no Windows, onde ele vai apenas tomar o pai do diretório desktop, que pode estar em qualquer lugar ...
Chronial
29

O conceito de um diretório HOME parece ser um pouco vago no que diz respeito ao Windows. Se as variáveis ​​de ambiente (HOMEDRIVE / HOMEPATH / USERPROFILE) não forem suficientes, pode ser necessário recorrer ao uso de funções nativas via JNI ou JNA . SHGetFolderPath permite recuperar pastas especiais, como Meus Documentos (CSIDL_PERSONAL) ou Configurações Locais \ Dados de Aplicativos (CSIDL_LOCAL_APPDATA).

Código JNA de amostra:

public class PrintAppDataDir {

    public static void main(String[] args) {
        if (com.sun.jna.Platform.isWindows()) {
            HWND hwndOwner = null;
            int nFolder = Shell32.CSIDL_LOCAL_APPDATA;
            HANDLE hToken = null;
            int dwFlags = Shell32.SHGFP_TYPE_CURRENT;
            char[] pszPath = new char[Shell32.MAX_PATH];
            int hResult = Shell32.INSTANCE.SHGetFolderPath(hwndOwner, nFolder,
                    hToken, dwFlags, pszPath);
            if (Shell32.S_OK == hResult) {
                String path = new String(pszPath);
                int len = path.indexOf('\0');
                path = path.substring(0, len);
                System.out.println(path);
            } else {
                System.err.println("Error: " + hResult);
            }
        }
    }

    private static Map<String, Object> OPTIONS = new HashMap<String, Object>();
    static {
        OPTIONS.put(Library.OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
        OPTIONS.put(Library.OPTION_FUNCTION_MAPPER,
                W32APIFunctionMapper.UNICODE);
    }

    static class HANDLE extends PointerType implements NativeMapped {
    }

    static class HWND extends HANDLE {
    }

    static interface Shell32 extends Library {

        public static final int MAX_PATH = 260;
        public static final int CSIDL_LOCAL_APPDATA = 0x001c;
        public static final int SHGFP_TYPE_CURRENT = 0;
        public static final int SHGFP_TYPE_DEFAULT = 1;
        public static final int S_OK = 0;

        static Shell32 INSTANCE = (Shell32) Native.loadLibrary("shell32",
                Shell32.class, OPTIONS);

        /**
         * see http://msdn.microsoft.com/en-us/library/bb762181(VS.85).aspx
         * 
         * HRESULT SHGetFolderPath( HWND hwndOwner, int nFolder, HANDLE hToken,
         * DWORD dwFlags, LPTSTR pszPath);
         */
        public int SHGetFolderPath(HWND hwndOwner, int nFolder, HANDLE hToken,
                int dwFlags, char[] pszPath);

    }

}
McDowell
fonte
Para sua informação, a pasta que corresponde ao diretório inicial do usuário é CSIDL_PROFILE. Consulte msdn.microsoft.com/en-us/library/bb762494(VS.85).aspx .
26516 Matt Solnit
Sim, esta é uma versão elaborada para o caso do Windows.
de Bruno Ranschaert
2
Nas versões recentes do JNA (mais precisamente na plataforma jna), existe uma classe Shell32Util que encapsula a API do Windows correspondente de uma maneira muito agradável. Em particular, o uso de Shell32Util.getKnownFolderPath (...) em combinação com uma das constantes da classe KnownFolders deve ser apropriado. A função mais antiga da API getFolderPath está obsoleta desde o Windows Vista.
Sebastian Marsching
17

Outros responderam a pergunta antes de mim, mas um programa útil para imprimir todas as propriedades disponíveis é:

for (Map.Entry<?,?> e : System.getProperties().entrySet()) {
    System.out.println(String.format("%s = %s", e.getKey(), e.getValue())); 
}
oxbow_lakes
fonte
Eu não dependeria disso, porque nem todas as propriedades são padronizadas. Em vez disso, verifique o JavaDoc for System.getProperties () para descobrir quais propriedades são garantidas.
Joachim Sauer
6
Isso pode ser verdade, mas ainda assim é bastante útil para um novato! Eu não tenho certeza se merece 2 votos negativos :-(
oxbow_lakes
6

Enquanto procurava a versão Scala, tudo o que pude encontrar foi o código JNA de McDowell acima. Incluo minha porta Scala aqui, pois atualmente não há lugar mais apropriado.

import com.sun.jna.platform.win32._
object jna {
    def getHome: java.io.File = {
        if (!com.sun.jna.Platform.isWindows()) {
            new java.io.File(System.getProperty("user.home"))
        }
        else {
            val pszPath: Array[Char] = new Array[Char](WinDef.MAX_PATH)
            new java.io.File(Shell32.INSTANCE.SHGetSpecialFolderPath(null, pszPath, ShlObj.CSIDL_MYDOCUMENTS, false) match {
                case true => new String(pszPath.takeWhile(c => c != '\0'))
                case _    => System.getProperty("user.home")
            })
        }
    }
}

Assim como na versão Java, você precisará adicionar o Java Native Access , incluindo os dois arquivos jar, às suas bibliotecas referenciadas.

É bom ver que o JNA agora torna isso muito mais fácil do que quando o código original foi publicado.

Peter
fonte
2

Eu usaria o algoritmo detalhado no relatório de erros usando System.getenv (String) e utilizaria a propriedade user.dir se nenhuma das variáveis ​​de ambiente indicasse um diretório existente válido. Isso deve funcionar em várias plataformas.

Acho que, no Windows, o que você realmente procura é o diretório "documentos" nocional do usuário.

Lawrence Dol
fonte
2

Alternativa seria usar o Apache CommonsIO em FileUtils.getUserDirectory()vez deSystem.getProperty("user.home") . Você obterá o mesmo resultado e não há chance de introduzir um erro de digitação ao especificar a propriedade do sistema.

Há uma grande chance de você já ter a biblioteca Apache CommonsIO em seu projeto. Não o introduza se planeja usá-lo apenas para obter o diretório inicial do usuário.

mladzo
fonte
0

Se você deseja algo que funcione bem no Windows, existe um pacote chamado WinFoldersJava que encerra a chamada nativa para obter os diretórios 'especiais' no Windows. Nós o usamos com freqüência e funciona bem.

Neil Benn
fonte