*Markus: File umbenennen scheitert

Hallo,

bei einem einer Methode übergebenen File will ich bei fehlender
Extension diese hinzufügen. Dabei versuchte ich verschiedene Variationen
von getAbsolutePath, o.ä. bishin zu getName(). Testhalber lasse ich mir
sogar die Pfade ausgeben:

  
 private File checkAndSetExtension(File filename)   {  
  String dateiname = filename.getName().trim();  
        if (dateiname.lastIndexOf('.') != -1)   {  
         if (dateiname.substring(dateiname.lastIndexOf('.')).toLowerCase() != ".tel")  {  
            System.out.println("1: " + file.getAbsolutePath().toString());  
            if (filename.renameTo(new File(file.getAbsolutePath().toString(), file.getAbsolutePath().toString() + ".tel")) == false)  
             throw new SecurityException();  
         }  
        }  
        else   {  
       System.out.println("2: " + file.getAbsolutePath().toString());  
        if (filename.renameTo(new File(file.getAbsolutePath().toString(), file.getAbsolutePath().toString() + ".tel")) == false)  
         throw new SecurityException();  
        }  
  
  return filename;  
 }  

Das Problem ist folgendes:
Werfe ich eine SecurityException, kann die Datei NIE gespeichert
werden, selbst dann nicht, wenn die Extension richtig ist, was mich
weiters zu der Annahme führt, dass folgende Zeile falsche
Ergebnisse liefert:

if (dateiname.substring(dateiname.lastIndexOf('.')).toLowerCase() != ".tel")

Ich ließ mir diesen Substring ausgeben, aber er IST definitiv ".tel",
wodurch ich erstens nicht verstehe, warum das Programm in diese
Verzweigung einsteigt und zweitens woran es scheitert, dass die Datei
nicht gepspeichert wird, denn lasse ich den Wurf der Exception bei beiden
Fällen einfach weg, kann die Datei plötzlich gespeichert werden. Nur
funktioniert hier eben das Hinzufügen der Extension ".tel" nicht. Die
Datei würde so gespeichert werden, wie der Dateiname auch übergeben
wurde. Durch die Exception wollte ich eben überprüfen, warum es nicht
unbenannt werden kann, aber durch den Wurf der Exception treten nur noch
mehr Fragen auf.
Ich benutze übrigens Gentoo Linux und in meinem Home-Verzeichnis, das
file.getAbsolutePath().toString() übrigens auch richtig ausgibt, kann ich
definitiv schreiben. Andernfalls würde die Datei ja ohne Exception auch
nicht gespeichert werden.
Weiß jemand Rat? Ich bin mit meinen Weisheiten am Ende.

Markus

  1. Hallo,

    bei einem einer Methode übergebenen File will ich bei fehlender
    Extension diese hinzufügen. Dabei versuchte ich verschiedene Variationen
    von getAbsolutePath, o.ä. bishin zu getName(). Testhalber lasse ich mir
    sogar die Pfade ausgeben:

    Keine Ahnung was du genau brauchst. Aber getAbsolutePath() ist üblicherweise
    ausreichend.

    private File checkAndSetExtension(File filename)   {
      String dateiname = filename.getName().trim();
            if (dateiname.lastIndexOf('.') != -1)   {
             if (dateiname.substring(dateiname.lastIndexOf('.')).toLowerCase() != ".tel")  {

    ______________________________________________________________________________^^

    Gaaaanz schlechte Idee. Um Strings zu vergleichen sollte immer
    String.equals(String) verwendet werden, außer man weiß ganz genau was
    man da tut.
    Bitte lies mal ein Buch oder eine Online-Resource deiner Wahl zum Thema
    Strings und deren Verwendung. Das sind Grundlagen, die auf keinen Fall
    fehlen dürfen!

    throw new SecurityException();

    Wieso wirfst du eine SecurityException? Das ist eine Exception des
    Security Manager von Java, der z.B. darauf achtet, daß bestimmte
    Operationen im Applet-Kontext oder Server-Kontext nicht ausgeführt
    werden dürfen.

    Nimm hier lieber IOException oder eine eigene Exception.

    System.out.println("2: " + file.getAbsolutePath().toString());

    Das toString() ist unnötig.

    Gruß
    Slyh

    1. Hallo,

      Keine Ahnung was du genau brauchst. Aber getAbsolutePath() ist üblicherweise
      ausreichend.

      Ok, da hast du recht.

      Gaaaanz schlechte Idee. Um Strings zu vergleichen sollte immer
      String.equals(String) verwendet werden, außer man weiß ganz genau was
      man da tut.

      Ja, Schlampigkeitsfehler. Sollte natürlich nicht vorkommen.

      Wieso wirfst du eine SecurityException? Das ist eine Exception des
      Security Manager von Java, der z.B. darauf achtet, daß bestimmte
      Operationen im Applet-Kontext oder Server-Kontext nicht ausgeführt
      werden dürfen.

      Gut, denn ich wusste nicht, was mit Security Manager eigentlich gemein ist. Ich dachte natürlich, dass es einen tieferen Grund haben muss, genau diese Exception zu werfen, wie es auch einen tiefen Grund, den ich nicht verstehe, haben muss, dass bei renameTo kein String übergeben wird, sondern ein "File"-Objekt.

      Nimm hier lieber IOException oder eine eigene Exception.

      Ok, ich habe es nun umgeschrieben. Dennoch ändert sich das fehlerhafte Verhalten des Programms nicht. Es zeigt genau die gleichen Symptome, wie vorher:

        
       private File checkAndSetExtension(File filename) throws IOException   {  
        String dateiname = filename.getName().trim();  
              if (dateiname.lastIndexOf('.') != -1)   {  
               if (dateiname.substring(dateiname.lastIndexOf('.')).toLowerCase().equals(".tel"))  {  
                  System.out.println("1: " + file.getAbsolutePath());  
                  if (filename.renameTo(new File(file.getAbsolutePath(), file.getAbsolutePath() + ".tel")) == false)  
                   throw new IOException();  
               }  
              }  
              else   {  
             System.out.println("2: " + file.getAbsolutePath());  
              if (filename.renameTo(new File(file.getAbsolutePath(), file.getAbsolutePath() + ".tel")) == false)  
               throw new IOException();  
              }  
        
        return filename;  
       }  
      
      

      Mit equals steigt das Programm ebenfalls selbst bei richtiger Extension in die Verzweigung ein. Jetzt verstehe ich bald gar nichts mehr.

      Markus

      1. Nachtrag:

        Bei equals muss ich natürlich auf NICHT-Gleichheit überprüfen. Zumindest steigt das Programm bei richtiger Extension nicht in die Verzweigung ein, aber das automatische Anfügen funktioniert dennoch noch immer nicht, wie ich bereits beschrieben habe.

      2. Hallo,

        bevor wir im Detail weitermachen, eine Frage:

        Wo kommt die Variable "file" her und gehört die wirklich hier rein?

        Gruß
        Slyh

  2. Hi.

    Ich vermute mal stark, dass der Fehler tatsächlich hier liegt...

    Ansonsten habe ich die Funktion auch mal etwas 'aufgeräumt':

      
    private File checkAndSetExtension(File file, String ext, boolean ignoreCase)  
        throws FileNotFoundException, IOException  
    {  
        if(!file.isFile())  
        {  
            throw new FileNotFoundException();  
        }  
        String name = file.getName();  
        if(ignoreCase)  
        {  
            ext = ext.toLowerCase();  
            name = name.toLowerCase();  
        }  
        if(name.endsWith("." + ext))  
        {  
            return file;  
        }  
        int i = name.lastIndexOf('.');  
        if(file.renameTo(new File(file.getParentFile(), i < 0 ?  
            name + "." + ext :  
            name.substring(0, i + 1) + ext)))  
        {  
            return file;  
        }  
        else throw new IOException("Failed to rename " + name);  
    }  
    
    

    Christoph

    1. Hallo,

      alles das File betreffend kommt aus dieser Methode:

        
         if (e.getSource() == telefondateiSpeichern)   {  
          if (filechooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION)   {  
                      DateiSpeicherung datenspeichern = new DateiSpeicherung(filechooser.getSelectedFile());  
                      try  {  
                       datenspeichern.datenSpeichern(pdfleser.getListe());  
                       writeInfomeldung(GREEN, "Die Datei " + filechooser.getSelectedFile() + " wurde erfolgreich gespeichert.");  
                      }  
                      catch (FileNotFoundException fnfe)  {  
                       writeInfomeldung(RED, fnfe + ": Die Datei wurde nicht gefunden!");  
                      }  
                      catch (IOException ioe)   {  
                       writeInfomeldung(RED, ioe + ": Eingabe-Ausgabe-Fehler!");  
                      }  
          }  
         }  
      
      

      Hier die komplette Klasse Datenspeicherung:

        
      public class DateiSpeicherung  {  
        
       private File file;  
        
       public DateiSpeicherung(File filename)   {  
        file = filename;  
       }  
        
       private File checkAndSetExtension(File filename) throws IOException   {  
        String dateiname = filename.getName().trim();  
              if (dateiname.lastIndexOf('.') != -1)   {  
               if (!dateiname.substring(dateiname.lastIndexOf('.')).toLowerCase().equals(".tel"))  {  
                  System.out.println("1: " + file.getAbsolutePath());  
                  if (filename.renameTo(new File(file.getAbsolutePath(), file.getAbsolutePath() + ".tel")) == false)  
                   throw new IOException();  
               }  
              }  
              else   {  
             System.out.println("2: " + file.getAbsolutePath());  
              if (filename.renameTo(new File(file.getAbsolutePath(), file.getAbsolutePath() + ".tel")) == false)  
               throw new IOException();  
              }  
        
        return filename;  
       }  
        
       public void datenSpeichern(HashMap<String, String> telefondaten) throws FileNotFoundException, IOException     {  
        ObjectOutputStream outputstream;  
        
        file = checkAndSetExtension(file);  
        
        outputstream = new ObjectOutputStream(new FileOutputStream(file));  
        outputstream.writeObject(telefondaten);  
        outputstream.close();  
       }  
      }  
      
      

      Markus

      1. Hallo.

        Hast du mal versucht, deine Methode einfach durch meine Version zu ersetzen - ich habe jetzt nicht die Lust, das selbst zu testen...

        Bei dir verschleiert die Verwendung von filename und file, dass beide ja Referenzen auf dasselbe Objekt sind - d.h., den Rückgabewert der Funktion kann man sich ebenso wie die Zuweisung file = checkAndSet... sparen (habe ich beim schreiben 'meiner' Methode auch nicht bedacht)

        Außerdem versuchst du mit

        new File(file.getAbsolutePath(), file.getAbsolutePath() + ".tel")

        der durch file adressierten Datei ein Kindelement hinzuzufügen - was aber nur bei Verzeichnissen Sinn ergeben sollte:

          
        new File(file.getParentFile(), file.getName() + ".tel")  
        
        

        Was ich für die sauberste Lösung halte: 'Meine' Methode irgendwo öffentlich zugänglich ablegen (z.B. einfach als public static void checkAndSetExtension(File file, String ext, boolean ignoreCase))), falls man die Funktionalität noch an anderer Stelle benötigt, und der Klasse DateiSpeicherung ein

          
        private void checkAndSetExtension()  
        {  
            checkAndSetExtension(file, "tel", true);  
        }
        

        spendieren.

          
        file = checkAndSetExtension(file);  
        
        

        aus datenSpeichern() wird dann einfach

          
        checkAndSetExtension();  
        
        

        PS: Ich bin davon ausgegangen, dass bei bereits vorhandener Dateieindung diese ersetzt werden soll - falls nicht, müsste man das noch ändern...

        Christoph

        1. Hallo,

          Bei dir verschleiert die Verwendung von filename und file, dass beide ja Referenzen auf dasselbe Objekt sind

          Du hast recht. Durch das viele Herumprobieren habe ich so einiges verwurschtelt.

          Was ich für die sauberste Lösung halte: 'Meine' Methode irgendwo öffentlich zugänglich ablegen

          Nein, denn ich habe sie absichtlich private gesetzt. Andere Klassen, z.B. das GUI, hat mit derartigen Methoden nichts zu tun. Diese Methode soll maximal in der Speicherebene exisiteren, die bei mir eben diese eine Klasse ist.

      2. Hallo,

        mir ist nicht ganz klar, warum Du überhaupt File.renameTo() verwendest. Mit renameTo() wird diese Datei nämlich sofort im Filesystem umbenannt.

        Wenn in Deinem JFileChooser eine _vohandene_ Datei ausgewählt wurde, die _nicht_ den Typ .tel hat, soll dann wirklich _diese_ _vorhandene_ Datei überschrieben werden? Oder willst Du nur den Typ .tel immer dann anhängen, wenn jemand einen Namen in den JFileChooser eintippt, es also diese Datei noch gar nicht gibt. In diesem Fall liefert JFileChooser.getSelectedFile() zwar eine Datei, aber eine, die noch gar nicht in Filesystem existiert. Das Umbenennen scheitert dann natürlich.

        Du müsstest also im Fall, dass die _gewählte_, also schon vorhandene Datei eine .tel-Datei ist, diese verwenden und wenn nicht, dann einfach ein neues File-Objekt mit new File(file.getAbsolutePath().concat(".tel")) erzeugen und dann _dieses_ weiter verwenden.

        viele Grüße

        Axel

        1. Hallo,

          mir ist nicht ganz klar, warum Du überhaupt File.renameTo() verwendest. Mit renameTo() wird diese Datei nämlich sofort im Filesystem umbenannt.

          Ja, genau das war auch das Problem. Die Datei existierte nämlich dort noch nicht.

          Du müsstest also im Fall, dass die _gewählte_, also schon vorhandene Datei eine .tel-Datei ist, diese verwenden und wenn nicht, dann einfach ein neues File-Objekt mit new File(file.getAbsolutePath().concat(".tel")) erzeugen und dann _dieses_ weiter verwenden.

          Ja, das scheint die einzige Möglichkeit zu sein. Jedenfalls funktioniert es jetzt so:

            
           private File checkAndSetExtension() throws IOException   {  
            if (!file.getName().trim().toLowerCase().endsWith(".tel"))  
             file = new File(file.getAbsolutePath().concat(".tel"));  
            return file;  
           }  
          
          

          Danke für deinen Hinweis. Übrigens verstehe ich nicht ganz, was sich die Autoren bei der File-Klasse gedacht haben. Extra ein neues Objekt anlegen zu müssen ist schon eher ein Mit-Kanonen-auf-Spatzen-Schießen.
          Ich dachte die renameTo-Methode funktioniert so, dass _in der Instanz des Objekts_ irgend eine Variable, die den Dateipfad beinhaltet, einfach geändert wird, aber offensichtlich gingen die Gedankengänge der Autoren dabei in eine andere Richtung.

          Markus

          1. Hallo,

            Danke für deinen Hinweis. Übrigens verstehe ich nicht ganz, was sich die Autoren bei der File-Klasse gedacht haben. Extra ein neues Objekt anlegen zu müssen ist schon eher ein Mit-Kanonen-auf-Spatzen-Schießen.

            Diese Einschätzung liegt aber jetzt an Deinen konkreten Erfordernissen, bzw. sogar an Deinen _scheinbaren_ Erfordernissen ;-) Im Prinzip ist es schon logisch, dass man ein neues File erzeugt, wenn man in ein neues File schreiben möchte. Du könntest eher überlegen, ob der JFileChooser wirklich ein oder mehrere File-Objekte liefern sollte oder nicht eher Pfad-Strings. Bei genauerem Abwägen der Vor- und Nachteile wirst Du aber sehen, dass dies schon sinnvoll ist, zumal man ja ohne Probleme vom File an den Pfad kommt.

            Ich dachte die renameTo-Methode funktioniert so, dass _in der Instanz des Objekts_ irgend eine Variable, die den Dateipfad beinhaltet, einfach geändert wird, aber offensichtlich gingen die Gedankengänge der Autoren dabei in eine andere Richtung.

            Ja, bspw.:
            In eine Datei soll geschrieben werden. Es wird aber festgestellt, dass diese Datei bereits vorhanden ist. Deshalb sorgt man über das File-Objekt per renameTo() dafür, dass die Datei zunächst in [name].bak umbenannt wird. Das File-Objekt steht danach zum Schreiben in die jetzt freie Dateiressource zur Verfügung.

            viele Grüße

            Axel

    2. Kleine Korrektur:

        
      void checkAndSetExtension(File file, String ext, boolean ignoreCase)  
          throws FileNotFoundException, IOException  
          // Rückgabewert nicht erforderlich, da kein neues File-Objekt erzeugt wird  
      {  
          if(!file.isFile())  
              throw new FileNotFoundException();  
          String name = file.getName();  
          if(ignoreCase)  
          {  
              ext = ext.toLowerCase();  
              name = name.toLowerCase();  
          }  
          if(name.endsWith("." + ext))  
              return;  
          int i = name.lastIndexOf('.');  
          if(!file.renameTo(new File(file.getParentFile(), i < 0 ?  
              name + "." + ext :  
              name.substring(0, i + 1) + ext)))  
          {  
              throw new IOException("Failed to rename " + name);  
          }  
      }  
      
      
      1. Hallo,

        // Rückgabewert nicht erforderlich, da kein neues File-Objekt erzeugt wird

        Hab ich auch erst gedacht. Es ist aber offenbar so, daß durch das renameTo()
        das File-Objekt, auf dem die Operation ausgeführt wird, nicht geändert
        wird. Es zeigt also nachwievor auf die ursprüngliche Datei. Es ist also
        durchaus sinnvoll, das neue File-Objekt zurückzugeben.

        In deinem  ersten Beispiel gibst du das ursprüngliche File-Objekt zurück,
        was insofern falsch ist, als daß es nicht auf die "neue" Datei zeigt,
        sondern noch immer auf die alte, die es jetzt aber gar nicht mehr gibt.

        Gruß
        Slyh