bild
Skolan för
elektroteknik
och datavetenskap
 

Laboration 5 - Livsträdet

OBS! Nytt 6 maj:
Har lagt till lite info om JTree på bilderna till föreläsning 8. Där finns också ett körbart litet exempel med JTree.

Mål

  • Att kunna tolka och använda given programkod.
  • Att kunna bygga upp en trädstruktur rekursivt från en textfil.
    Det är tillåtet att använda någon XML-parser men instruktionerna kommer att anpassas till att man
    INTE gör det.
  • Att kunna använda den avancerade grafiska bibliotekskomponenten JTree för att visualisera ett träd.
  • Att förstå JTree:s hjälpklasser och att kunna definiera din egen version av klassen som beskriver trädets noder.

 

Boken Object-oriented programming in Java av Martin Kalin har tidigare använts som kursbok på DD2385. I boken finns ett program som grafiskt presenterar filkatalogträd med hjälp av en avancerad swingkomponent, JTree.

Uppgiften i denna labb är att grafiskt presentera en annan trädstruktur. JTree ska användas men också den inramning till JTree som Kalins program har.

Kalins kod har modifierats en del och delats upp på två klasser. Först har vi TreeFrame som definierar trädet och dess inramning: En TreeFrame är en JFrame samt innehåller en knapp, en liten klickruta och det klickbara trädet. TreeFrame är ett körbart program men det träd som skapas hara bara ett rotobjekt, inga grenar.

Den andra klassen, DirTree2, ärver TreeFrame och definierar om de metoder i TreeFrame som skapar själva trädstrukturen så att det är ett filkatalogträd som visas. Några nya hjälpmetoder och någon variabel definieras också i DirTree2.

TreeFrame.java.     DirTree2.java

Spara båda dessa klasser och kompilera och provkör dem. Båda har main-metod. Se till att ni förstår hur de är konstruerade, åtminstone i stora drag.

java TreeFrame    visar ett träd med bara en enda nod, roten.

java DirTree2     visar trädet för den filkatalog du står i. Försök med någon annan katalog, t.ex.

java DirTree2 ~    visar trädet för din hemkatalog under Linux/Unix (kan ta tid om den är stor)

java DirTree2 ../../recept     visar katalogen recept som förutsätts ligga två nivåer uppåt i katalogträdet.

Kolla att klickningar i trädet fungerar och att man kan få veta detaljer om en fil om Show details är markerad.

Ny subklass till TreeFrame

Labbuppgiftens program ska ha en struktur som liknar DirTree2, dvs det ska ärva från TreeFrame och definiera om de metoder som behövs för att beskriva ett nytt träd. De metoder som rör filkatalogträd ska förstås inte vara med i labbuppgiften.

Ett minimalt livsträd

I denna förberedande uppgift ska ett litet enkelt träd visas, nämligen ett livsträd med tre barn som alla är löv. I trädets rot ska det stå Liv och de tre barnen ska vara Växter, Djur och Svampar. Dessa ska senare i sin tur kunna innehålla ordningar, underordningar, familjer, släkten och arter. Gör en ny subklass till TreeFrame. Definiera initTree() att göra följande:
  • Skapa en rotnod med ordet "Liv".
  • Skapa en trädmodell.
  • Tillverka en nod child på samma sätt som rotnoden men med texten "Växter".
  • Addera den till trädmodellen med root som förälder, root.add(child);
    Titta eventuellt i DirTree2 för att se hur man gör.
  • Gör likadant med "Djur" och "Svampar", alltså lägg dem som barn till "Liv".
  • Skapa trädet, JTree-objektet (kan göras direkt efter modellen skapas).
  • Kompilera, kör och kolla att det ser bra ut.
Det går bra att skapa modellobjekt och trädobjekt från en enda rotnod och därefter bygga ut med fler noder.

Ett rekursivt livsträd

I stället för att tillverka noderna "för hand" ska du läsa in livsträdet från filen Liv.xml (finns även som Liv.txt) och bygga upp ett träd. Programmet måste klara godtyckligt antal barn på varje nivå. En liten testversion av filen ser ut som visas här nedanför. Filen finns också i LillaLiv.txt. Om du har problem med svenska bokstäverna, använd följande versioner som är utan åäö: Life.txt, Life.xml och TinyLife.txt
 
<Biosfär namn="Liv"> är allt som fortplantar sej 
<Rike namn="Växter"> kan inte förflytta sej 
</Rike> 
<Rike namn="Djur"> kan förflytta sej 
</Rike>
<Rike namn="Svampar"> är varken djur eller växter 
</Rike>
</Biosfär>
Förutom nodens namn finns i filerna också en nivå (t.ex. Biosfär, Rike) och en förklarande text men för dem finns ingen plats i klassen DefaultMutableTreeNode. Skriv därför en egen subklass t.ex. MyNode med String-variablerna level och text så att alla data från livträdsfilerna får plats.

Skriv en rekursiv metod som läser filen och skapar trädet, t.ex. enligt följande. Observera att detta är ett förslag. Om ni vill låta metoden ha parametrar eller jobba med flera metoder så är det självklart tillåtet. Tipsen nedan behöver inte följas!

root = readNode(); //Läser filen och skapar noden och alla dess barn rekursivt.
Lifetree.gif Metoden returnerar ett objekt av typ MyNode som blir trädets rot. Metoden anropas rekursivt för varje nod i trädet och lägger noden som barn till sin förälder.

När anropet ovan är klart har hela trädet skapats. Den rekursiva readNode() läser och skapar en nod och alla nodens barn.

Man kan ha nytta av att ha en global variabel String rad; som är en buffert mellan programmet och filen. Allra först läses filens första rad till variabeln rad. När raden är behandlad läses nästa filrad till rad o.s.v. Genom att rad är global kommer man åt den från alla anropsnivåer av readNode().

Liksom i DirTree2 ska man kunna ange filnamn i exekveringskommandot

java LifeTree livfil

och om inget namn anges ska programmet ta filen Liv.xml.
 

Detaljupplysningar om livet

I det ursprungliga programmet får man filupplysningar om "Show details" är valt. I ditt program ska man i stället få se den förklarande texten, till exempel

Art: Mås gillar Vaxholmsbåtar.

Fixa så att showDetails(TreePath p) lägger ut denna text i sin JOptionPane.
 

Läsning från textfil

Görs enklast med klassen Scanner:
  static Scanner sc = new Scanner(new File("infil.txt"));
new File(...) kan generera ett FileNotFoundException som måste hanteras med try ... catch men om det gick bra att hitta filen kan man sedan läsa radvis med sc.nextLine(). Gör Scanner-variabeln static så kan den skapas redan i main - metoden! Med sc.hasNextLine() känner man av om det finns fler rader att läsa i filen. Det är bara filöppningen som måste stå i try ... catch. Filgenomgången kan göras utanför.

Krav på programmet

  • Indatafil med godtycklig storlek ska klaras av. Programmet får inte förutsätta att någon nod har ett fast antal barn eller att trädet har ett visst djup.
  • Rekursion är lämpligt att använda när filen läses men det är inte nödvändigt att följa anvisningarna ovan med readNode().
  • Programmet ska kontrollera att starttagg och sluttagg stämmer överens men behöver inte identifiera andra typer av fel i indatafilen.

Filformat m.m.

  • Programmet får förutsätta att indatafilen är formaterad så som exemplen, alltså att filen är radindelad med en "tagg" per rad som exempelfilerna och att det är exakt ett blanktecken mellan nivån och ordet namn
    <Biosfär namn="Liv"> är allt som fortplantar sej
    samt inga blanktecken alls på andra ställen utom i den förklarande texten i slutet av raderna.
  • Det är tillåtet att läsa filen radvis, dvs att låta programmet hämta in en hel rad från filen och därefter bearbeta hela den raden, läsa in ny rad o.s.v. Alternativet är att läsa ett tecken i taget vilket gör uppgiften svårare.
Självklart är det tillåtet och snyggt att klara av indata som inte är så väl formaterade, t.ex. har flera blanktecken mellan orden, blanktecken före och efter tecknen "=", "<" och ">" samt extra radbyten eller utelämnade radbyten. Det är naturligtvis också utmärkt att identifiera andra syntaxfel än felaktig sluttagg men det krävs inte!

Redovisning

Kör programmet med den större infilen och demonstrera alla funktioner. Glöm inte att be om handledarens signatur på kvittensbladet när han/hon är nöjd med er redovisning!

Extrauppgift för högre betyg (liten!)

Ordna så att man förutom grundinformationen Art: Mås gillar Vaxholmsbåtar också får hela kedjan av namn utskriven på formen men allt som är Mås är Fåglar är Djur är Liv. Användbar metod i TreePath är getLastPathComponent(). I DefaultMutableTreeNode och därmed i MyNode finns metoden getParent(). Det finns även andra metoder som löser uppgiften.
Copyright © Sidansvarig: Ann Bengtsson <ann@nada.kth.se>
Uppdaterad 2013-05-06