Java Programmering - 11. del

Tags:    java programmering
Skrevet af Bruger #4487 @ 06.04.2011

Indledning


Efter en omgang med at håndtere logiske fejl og kaste Exceptions, skal vi nu til en disciplin, som i tilfælde kan være lidt 'fejl' præget. Vi skal nemlig til at lære om hvordan man håndterer og arbejder med almindelige tekst filer, men også hvordan man skriver objekter til binære filer. Du skal ikke frygte for ordet binært, da det er noget du allerede i din almindelige omgang med computere bruger. En lydfil, eller en billedfil er f.eks. en binær fil, men vi skal lære mere om disse senere i artiklen. Lad os først starte ud med almindelige tekst filer.

Håndtering af tekst filer


Du kender sikkert tekst filer allerede, men hvad er det egentlig? En tekst fil er en fil, som indeholder læsbart tekst, ofte er de gemt i et .txt format, men også andre former er brugte. I vores eksempel vil vi bruge en tekst fil i txt format, da dette er mest anvendeligt. Tekst filer kan altså også åbnes med almindelige skriveredigerings programmer og derved læses af almindelige mennesker. java kan også læse tekst filer, ved hjælp af nogle klasser, som vi selvfølgelig skal lære om og benytte her.

Lad os først begynde med at lave en klasse, som skal være den klasse, som vi bruger, når vi gerne vil skrive indhold/data til vores tekst fil. Lad os for en god ordens skyld kalde klassen noget som relaterer til dens formål, så vi kan f.eks. kalde den 'WriteTextFile'. Det første du skal gøre når du gerne vil skrive til en tekst fil, er først at åbne den fil, som vi gerne vil skrive data/tekst til. Så derfor kan vi lave en mutator metode som vi kunne kalde 'openFile', som har en parameter, nemlig en String der angiver stien og navnet til den tekst fil vi gerne vil åbne. Vores nye klasse ser nu således ud.
Fold kodeboks ind/udJava kode 

Sådan nu har vi en metode som er klar til at få noget indhold, som gør at vi åbner en tekst fil. Bemærk at du behøver ikke at lave tekst filen på forhånd, som skal åbnes, da Java automatisk kan 'oprette' en ny fil, hvis en sådan ikke eksisterer (Det er noget andet når vi skal læse filen). Får at skrive tekst/data til vores tekst fil, findes der nogle forskellige klasser du kan benytte, men en af de mest smarte klasser at benytte her er i mine øjne klassen der hedder FileWriter. Klassen FileWriter har nemlig den fordel at vi nemt og bekvemt kan skrive videre på en fil, når vi har lukket den en gang før. Vi kan altså 'tillægge' tekst/data til en fil, som allerede er blevet skrevet til en gang, uden at overskrive det eksisterende indhold på filen. Lad os først lige importerer vores klasse FileWriter, fra pakken 'java.io'. Herefter laver vi et felt/objekt variabel til at kunne indeholde et objekt af denne nye klasse, så vi altid kan initialisere den i vores metode openFile(), og evt. bruge den i de andre metoder vi laver i vores klasse. Efter at have lavet variablen til at gemme vores FileWriter objekt med, kan vi som sagt initialisere det i openFile() metoden, ved at kalde FileWriter klassens konstruktør, som indeholder 2 parametre (Slå evt. FileWriter klassen op i java biblioteket), hvor den første parameter er en String indeholdende filnavnet/stien til filen, og den næste en boolean, som skal være true, hvis vi skal kunne tillægge data til filen frem for at overskrive den. Vores klasse ser nu således ud:
Fold kodeboks ind/udJava kode 

Nu er metoden 'openFile(String fileName)' snart færdig, da den gør som den skal, nemlig at åbne vores tekst fil, så vi kan skrive data til den. Problemet med koden er bare at vores FileWriter objekts konstruktør har en 'throws' indført i dens signatur. Dette betød i henhold til sidste artikel jo at den kaster en Exception. I vores tilfælde kaster FileWriter altså en IOException, som vi er nødt til at behandle på en eller anden måde. Vi kunne som vi gjorde i forrige artikel indhylde vores initialisering i en try, catch blok, med henblik på at 'catche' denne IOException, men vi kan også kaste vores Exception videre, også bare fange den i vores Main klasse. Måden vi gjorde dette på var at bruge nøgleordet 'throws' i vores metodesignatur, efterfulgt af den exception, som vores metode skal kaste videre, nemlig i dette eksempel en IOException. Vi er ydermere nødsaget til at importere vores IOException fra pakken 'java.io'. Vores klasse ser nu således ud:
Fold kodeboks ind/udJava kode 

Nu har vi sådan set åbnet vores tekst fil, så den er klar til at få skrevet tekst/data ind. Bemærk at vores tekst fil indtil videre er en parameter med et fil navn i metoden openFile(String fileName), men vi vil jo indføre denne parameter, når vi bruger den i vores main metode. Det næste vi skal gøre når filen er åben og klar, er selvfølgelig at skrive noget tekst til den. Derfor skal vi lave en ny mutator metode, som vi ironisk nok kunne kalde writeText(). Da vi ikke benytter et grafisk ui, men et tekst baseret ui i form af kommandoprompten/terminalen, skal vi ligesom i sidste artikel benytte klassen Scanner til at læse brugerens input. Denne klasse fås som sagt i pakken 'java.util', som du skal importere fra først. Vi kan nu lave en lokal variabel i metoden 'writeText()', hvor vi initialiserer vores scanner/læser med et objekt. Husk at scanner klassens konstruktør skal have parameteren 'System.in', så vi kan læse brugerens input fra terminalen. Vores klasse ser nu således ud:
Fold kodeboks ind/udJava kode 

Lad os f.eks. sige at vi gerne vil lave en fil hvor brugeren gemmer oplysninger om personer, med informationer om deres navn, alder og stillingsbetegnelse. Til dette skal vi benytte en 'while' løkke akkurat som i sidste artikel, og vi skal også her have en lokal variabel (kaldet 'running') af typen boolean, som vi kan benytte som betingelse til hvornår løkken skal stoppe. Vi kan herefter i starten af vores while løkke tjekke om brugeren vil skrive til filen, og tjekke svaret med et if statement. Hvis svaret var nej, sætter vi running til false, og afbryder løkken, med ordet 'break;'. Vores kode ser nu således ud:
Fold kodeboks ind/udJava kode 

Nu kan vi så i vores 'else' del af if statementet lave den kode som gør at brugeren kan skrive til filen. Vi gemmer altså brugerens input i nogle dertil indrettede lokale variabler (en for hver af de informationer, som vi ønsker at gemme), og skriver dem til sidst til vores fil, via en metode i vores 'FileWriter' objekt. Klassen FileWriter har en metode som hedder 'write()', og den er egentlig fin, men problemet her er nu at med metoden write, er det ikke helt så nemt at lave linjeskift i vores tekst fil. Dette kan vi heldigvis nemt løse ved at benytte en klasse som hedder PrintWriter. PrintWriter klassens konstruktør kræver en parameter, nemlig et objekt af den 'Writer' klasse som der er brugt, og i vores tilfælde har vi brugt klassen FileWriter (FileWriter er en underklasse til 'Writer' klassen). Vi skal altså lave tre ændringer i vores klasse. Det første vi skal gøre er at importerer PrintWriter klassen fra pakken 'java.io'. Herefter skal vi ændre vores lokale variabel kaldet writer til at kunne indeholde et objekt af PrintWriter klassen. Den sidste og tredje ændring vi skal lave er at ændre statementet i vores openFile(String fileName) fra at være 'writer = new FileWriter(fileName, true);', til nu at være writer = new PrintWriter(new FileWriter(fileName, true));. Vi har altså givet PrintWriters konstruktør det som den krævede, nemlig et objekt af FileWriter klassen. Nu kan vi ellers gå videre i vores writeData() metode. Vi kan nu istedet for metoden write() bruge en metode som hedder format(String s, args...), som er stort set den samme metode som vi blev introduceret for i forrige artikel, men her hed den bare således - System.out.printf(). Metoden format(String s, args...) kan altså bruges akkurat ligesom formaterings statementet, og vi skal derfor også benytte % tegnet når vi vil angive at her skal være vores første argument. I vores tilfælde hvor vi gerne vil gemme tre oplysninger, nemlig personens navn (String), personens alder (Integer) og personens stilling (String) kan vi f.eks. skrive vores formatering således "%s %d %s \n", hvor \n betyder nyt linjeskift. Vores klasse ser nu således ud:
Fold kodeboks ind/udJava kode 

Nu er vores metode til at skrive tekst til vores fil sådan set færdig. Vi kan dog indføre en Exception som vi kan smide til vores main med ordet throws, nemlig vores 'NumberFormatException', da brugeren kan komme til at skrive noget andet i stedet for integer tal til vores age. Vi kan jo nemlig ikke konvertere bogstaver osv om til tal, så derfor kan der forekomme en 'NumberFormatException' fra vores metode parseInt(String s). Herefter skal vi lave en sidste metode til denne klasse, nemlig metoden som lukker vores fil. Det er vigtigt altid at lukke sine filer efter sig, når man ikke bruger dem mere, og det er det som vi skal lave en metode til nu. Vi kunne kalde metoden for closeFile(), og indholdet i den er meget simpel. Vi skal lave et if statement, som tjekker om vores variabel 'writer' indeholder et objekt eller ej. Hvis den indeholder et objekt skal vi kalde metoden close() fra vores variabel writer, og vores fil er hermed lukket. Måden vi tjekker på i if statementet kunne være ved at tjekke om vores variabel 'writer' ikke er lig med værdien 'null'. Null betyder nemlig 'intet objekt/ingenting'. Vores kode ser nu således ud:
Fold kodeboks ind/udJava kode 

Du kan hvis du ønsker det teste din klasse nu, ved at lave en main klasse med en main metode, og herefter i en try, catch blok lave et objekt af writeTextFile klassen og kalde dens metoder herefter. Husk at fange de Exceptions som vi har valgt at smide. Jeg vil ikke teste klassen endnu, da jeg først vil have den næste klasse klar, som er den der skal læse vores tekst fils indhold. Princippet er det samme som når man skal skrive til en tekst fil, nemlig at vi skal altid huske først at åbne filen, herefter 'læse' den, for til sidst at lukke filen igen.

For at læse en fil skal vi benytte klassen Scanner, men denne gang skal vi i stedet for at læse input som brugeren skriver via terminalen, læse tekst fra vores tekst fil. Vi skal derfor også benytte endnu en klasse, som hedder File, og som findes i pakken 'java.io'. Så det første vi gør er altså at importere klasserne Scanner og File. Herefter laver vi en objekt variabel som kan indeholde et objekt af vores Scanner klasse. I vores metode openFile(String fileName) skal vi initialisere vores variabel med et Scanner objekt, hvor vi som parameter til konstruktøren giver et objekt af klassen File, som igen skal have vores fil navn som parameter. Vi kunne herefter vælge at smide en Exception videre med ordet throws i metodens signatur, og den exception vi kunne smide videre var 'FileNotFoundException', men da vi allerede vil benytte IOException fra når vi skriver til vores fil, er det en god ide også at benytte den her. Vores klasse ser nu således ud:
Fold kodeboks ind/udJava kode 

Det er meget simpelt indtil nu, da vi har lavet en metode, som åbner vores fil.

Vi skal nu, akkurat som da vi lavede klassen 'WriteTextFile', have en metode der gør noget med vores tekst fil. I dette tilfælde er det bare ikke at skrive til filen, men at læse den. Vi kan så kalde metoden for 'readText()' og give den en while løkke, som skal køre indtil hele filen er læst. Dette gøres ved at bruge Scanner objektets metode der hedder hasNext(), som betingelse i vores while løkke. Metoden hasNext() returnerer nemlig altid true, så længe at der stadig er tekst/data tilbage som den kan læse, det vil sige at den returnerer falskt når hele filen er læst igennem. Vi kan herefter benytte Scanner objektets metoder kaldet next() og nextInt() til både at læse vores to Strings og selvfølgelig vores ene integer værdi. De funde værdier gemmer vi i nogle lokal variabler, hvorefter vi kan benytte System.out.printf() til at udskrive vores værdier. Brug f.eks. formattet - "%-18s %-6d %s \n". Vi kan herefter i metoden (faktisk før vores while løkke begynder) lave et System.out.printf() statement, med samme format (Eller næsten der hvor der står %-6d skal der udskiftes med %-6s, da overskriften "Age" jo også er en string), hvor værdierne bare er overskrifterne på vores 'kolonner', nemlig 'Name', 'Age' og 'Job'. Vores klasse ser nu således ud:
Fold kodeboks ind/udJava kode 

Vi kan hvis vi vil f.eks. kaste en Exception med ordet throws i metoden readText(), som hedder IllegalStateException, men dette er ikke et direkte krav, da denne Exception ikke vil opstå særlig ofte i forbindelse med vores program. Det vil den ikke fordi at denne Exception køres hvis vores program af en eller anden grund ikke er klar til at kunne læse fra filen endnu. Vi kunne undlade den, men da dette også er en artikel der skal lære dig noget, tager vi den med, for et læringsmæssigt synspunkt. Vi skal altså kaste denne IllegalStateException (Du behøver ikke at importere den da den allerede er importeret automatisk via java.lang pakken, som alle klasser importerer automatisk), og herefter kan vi begynde at lave den metode som skal lukke vores fil. Metoden til at lukke filen skal være næsten identisk med metoden fra 'WriteTextFile' klassen, men denne gang skal den ikke tjekke og lukke fra vores variabel 'writer', men selvfølgelig fra vores variabel 'reader'. Vores klasse 'ReadTextFile' ser nu således ud:
Fold kodeboks ind/udJava kode 

Nu har vi sådan set lavet vores to klasser, hvor af den ene kan skrive til en tekst fil, og den anden kan læse fra den.

Lad os så teste det hele


For at teste skal vi selvfølgelig bruge en klasse med en main metode. I vores main metode skal vi så lave to lokale variabler med et objekt af vores to ny oprettede klasser, og vi skal herefter i en try, catch blok først kalde de tre metoder i vores 'WriteTextFile' klasse, og herefter kan vi kalde de tre metoder i vores 'ReadTextFile' klasse. Husk at kalde dem i den orden som vi også lavede dem, så vi først åbner filen, skriver/læser til den også lukker den igen. Vi skal huske at fange de Exceptions, som vi har valgt at kaste videre, og det var - IOException, NumberFormatException og IllegalStateException - Husk også at importere den som skal importeres (IOException). Vores Main klasse ser nu således ud:
Fold kodeboks ind/udJava kode 


Vores første output kunne så se således ud:



Herefter kunne det være at der kom to nye personer til, som vi gerne ville gemme i vores tekst fil. Vi er derfor nødt til at 'forlænge' vores nuværende tekst fil, men dette har vi allerede taget hånd om, ved at skrive med et FileWriter objekt. Vores output kunne så se således ud, læg mærke til at de to nye personer er tilføjet den allerede eksisterende fil.



Sådan, nu har vi lært at skrive og læse til almindelige tekst filer. Tekst filer kan som sagt kun indeholde almindelig tekst, som altså også kan læses af andre tekst behandlingsprogrammer. Vi kan dog også lave vores egne binære filer, hvor vi i stedet for at gemme almindeligt læsbart tekst, gemmer objekter af klasser. Sådan en proces kaldes også for Objekt Serialisering.

Objekt Serialisering


For at skrive objekter til en fil, skal vi benytte to forskellige klasser. Den første hedder FileOutputStream, og er den som skal skrive en strøm (Eng:. Stream) af data til vores fil. Klassen FileOutputStream har flere konstruktører (Du kan se dem alle i Java biblioteket), men den vi skal benytte i dette eksempel er konstruktøren der ser således ud - FileOutputStream(String name) - Som jo kun skal bruge en String med navnet på vores fil. Den næste klasse vi skal benytte hedder ObjectOutputStream, og denne klasse har 2 konstruktører vi kan benytte. Det vil dog ikke have den store sammenhæng at benytte konstruktøren uden parameter, da vi skal have vores data strøm fra FileOutputStream klassen ind i vores ObjectOutputStream objekt. Vi skal altså som parameter til ObjectOutputStream konstruktøren give et objekt af vores FileOutputStream, for at kunne skrive objekter til filer.

Vi kan nu begynde at lave vores eksempel. Vi skal allerførst lave en simpel klasse, som bliver den klasse som vi gerne vil gemme i vores objekt fil. Vi kunne lave en klasse der hedder 'Person', og har to variabler (name og age), samt nogle get metoder (Accessor metoder). Det eneste nye ved klassen bliver at du skal implementere interfacet Serializable. Interfacet Serializable tvinger dig ikke til at implementerer noget specielt, men du kan nu gøre din klasse 'unik' ved at lave en variabel, som ser nøjagtig sådan her ud - private static final long serialVersionUID = - også efter lighedstegnet skrive en vilkårlig 'long' værdi (long er heltal akkurat ligesom integer). Fordelen ved at klassen er unik, er at når vi gemmer et objekt af klassen i en objekt fil, og bagefter tilføjer måske en ekstra variabel til klassen, så vil vi stadig kunne bruge de gamle objekter fra filen, som var de en del af præcis den klasse. Vi siger nu at vi har serialiseret klassen 'Person', ved at tilføje denne specielle variabel sammed med interfacet 'Serializable'. Vores person klasse ser nu således ud:
Fold kodeboks ind/udJava kode 

Vi har nu en klasse, som er klar til at blive skrevet til vores objekt fil. Vi skal nu bare have lavet to klasser, nemlig en som skriver vores objekt, og en som læser det. Klasserne ligner næsten de to vi lige har lavet til at læse en tekst fil med, men der er nogle få ændringer, som jeg her vil gennemgå. Vi starter selvfølgelig med at skrive til vores fil, og som sagt før skal vi benytte klasserne ObjectOutputStream og FileOutputStream, til at skrive til vores objekt med. Metoden 'openFile(String fileName)' skal selvfølgelig initialisere vores 'writer' variabel med et objekt af klassen ObjectOutputStream, men ellers er den uændret (Husk at throwe en IOException). Vores 'writeText()' metode, kan vi ændre navnet til et mere passende 'writeObject()', og herefter til sidst i metoden skal vi slette den linje, som fortæller at vi skal angive et job. Vi skal jo kun have data om navn og alder. Desuden skal vi herefter slette linjen med - writer.format("%s %d %s \n", name, age, job); - og erstatte det med denne linje - writer.writeObject(new Person(name, age)); - Husk også her at kaste en IOException videre fra metoden. Vores metode 'closeFile()' er uændret, da den lukkes på samme måde. Vores nye klasse som vi kunne kalde 'WriteObjectFile' ser nu således ud:
Fold kodeboks ind/udJava kode 

Så skal vi bare lave klassen som skal læse vores objekt fil. Vi kunne kalde klassen for 'ReadObjectFile' og vi skal nu benytte klasserne FileInputStream og ObjectInputStream. Disse klasser ligner næsten dem vi benyttede lige før, men læg mærke til at denne hedder input, hvor den før hed output. Du skal ændre openFile(String fileName) til at bruge disse klasser i stedet, hvor objektet af FileInputStream skal lægges ind som parameter til objektet af ObjectOutputStream, som så selvfølgelig tilføres til vores reader variabel. Husk at metoden kaster en IOException. Vores metode 'readText()', som vi først kan ændre navnet til 'readObject()', skal først lave en deklaration af en lokal variabel, hvor vi gør klar til at kunne lægge et objekt af klassen Person ind i. Herefter kan vi ændre lidt i vores while løkkes krop, så vi initialiserer vores nyligt deklarerede variabel som kan indeholde et objekt af vores Person klasse, med det objekt som bliver læst af vores reader. Vi skal benytte en metode fra objektet i variablen reader, som hedder readObject(). metoden returnerer et object af typen 'Object', så derfor er vi nødt til at lave det som man kalder en casting. En casting er når man specificerer hvilken type af data, som vi gerne vil have objektet til at være. Da metoden returnerer et objekt af datatypen Object, kan vi altså caste den til stort alt hvad vi vil, fordi at alle objekter er automatisk underklasser til klassen Object. Vi kan derfor nemt specificere at dette i virkeligheden er et objekt af klassen Person, selvom den er returneret som data af typen Object. Casting laves ved at man i starten laver en parentes med den datatype som vi gerne vil caste til (I vores tilfælde klassen Person). Herefter kan vi skrive metoden, som var - reader.readObject(); - Vi har nu gemt det læste objekt i vores lokale variabel og vi kan herefter bruge Person klassen accessor metoder (get metoderne) til at udskrive dataene i et udskrivningsstatement. Vores metode skal ikke kaste undtagelsen IllegalStateException, men derimod undtagelserne - EOFException, IOException og ClassNotFoundException. Undtagelsen EOFException betyder 'End-Of-File Exception' og kastes hver gang vi har nået enden på filen. Dette er grunden til at vi også kan ændre vores betingelse i while løkken til bare at være true, fordi den vil automatisk stoppe og smide en EOFException når filen er læst. Til sidst skal vi har vi vores closeFile() metode, men den behøver vi heller ikke at ændre her. Vores klasse 'ReadObjectFile' ser altsåsåledes ud:
Fold kodeboks ind/udJava kode 

Vi kan nu lave en test i vores Main klasse, hvor vi laver et objekt af vores to klasser som skriver og læser filen. herefter kalder vi deres metoder i rækkefølge inde i en try, catch blok, hvor vi selvfølgelig fanger de relevante Exceptions. Bemærk at hvor vores fil før havde endelsen .txt, fordi det var en tekst fil, så kan vi slev vælge en filendelse til vores objekt fil. Bemærk at du ikke skal vælge endelser som allerede kendes af andre programmer installeret på dit styresystem. Vi kunne f.eks. vælge at kalde vores fil for objekter.ser, endelsen .ser er meget ofte brugt, da man så ved at dette er en fil der er serialiseret med objekter, men vi kunne også finde på vores egen endelse. Jeg vil f.eks. kalde min filendelse for .martin, for sjov skyld. Vores main klasse ser altså således ud:
Fold kodeboks ind/udJava kode 

Vi kunne så få et output som følgende:



Desuden hvis du kigger i mappen hvor dine Java filer ligger, vil du ligge mærke til at der nu findes en fil som hedder noget i stil med objekter.martin, eller hvad du nu kaldte den. Det smarte ved at skrive serialiserede objekter til en fil, er at hvis du har et program, kan du kun gemme dataene så længe som programmet kører. Så snart programmet lukkes mister du alle data/objekter som du fik lavet med programmet. Du kan så med serialiserede objekter skrevet til en fil, gemme de nye objekter hver gang programmet lukkes, og næste gang det åbnes, kan du så nemt læse dine objekter ind til programmet igen, og på den måde undgå at miste de data/objekter fra sidste gang programmet kørte.

Transient


Det hænder nogle gange at de objekter, som man serialiserer og gemmer i en fil, har data med, som man ikke ønsker at have med. Det kunne f.eks. være at man skulle gemme noget midlertidigt data i en variabel, som f.eks. skulle benyttes til nogle midlertidige beregninger. Da dataene er midlertidige og altså ikke noget vi ønsker at gemme, kan vi ved hjælp af nøgleordet transient sikre at dataene ikke bliver skrevet til filen sammen med objektet. Hvis vi f.eks. skulle tilføje en midlertidig variabel til vores Person klasse, som vi kun ville benytte mens programmet kørte, men altså ikke noget vi ønskede at gemme, kan vi skrive variablen således - private transient String temporary; - Vores variabel temporary vil stadig kunne have data gemt så længe programmet er aktivt, men dataene i denne variabel vil nu ikke blive gemt sammen med vores Person objekt når denne skrives til vores fil. Du kan spørge om hvorfor at dette overhovedet er nødvendigt, men når vi ikke tager alle data med, kan vi spare en smule på størrelsen af filen, og desuden giver det heller ingen mening at tage data som f.eks. er midlertidige med i en fil, som skal gemme de vigtige data om objekter til vores program.

Nu har vi lært om hvordan man skriver og læser tekst til og fra almindelige tekst filer, og vi har endda også lært at skrive/læse objekter som er serialiseret til en binær fil. Du kan f.eks. benytte denne teknik til at gemme data fra en kørsel af et program, hvorefter at næste gang du kører programmet så kan du åbne filen, for derefter at 'indlæse' alt teksten/objekterne til dit program igen. Dette er ikke en dårlig metode, men der findes en smartere og også mere brugt måde at gemme data fra sit program på. Det er nemlig ved at gemme sin data i en database. Vi skal selvfølgelig lære om databaseadgang med Java i en anden artikel, men ikke i denne her, da det er et lidt stort emne at tage fat på.

Nu kan du ellers glæde dig til næste artikel, hvor vi selvfølgelig skal lære meget mere om at programmere i Java.

Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.

Del også gerne artiklen med dine Facebook venner:  

Kommentarer (0)

Du skal være logget ind for at skrive en kommentar.
t