Programmieren einer Agenda mit einer Liste (als Applet und .txt zum Speichern)

  • Themenstarter Themenstarter ANFÄNGER1267
  • Beginndatum Beginndatum
A

ANFÄNGER1267

Gast
Hallo zusammen,
ich möchte ein Agenda-Applet programmieren. Als Grundlage habe ich eine vorgegebene Liste, die als Input Objekte hat. Desweiteren habe ich eine String-Liste, die nur mit Strings "gefüttert" wird.
Als Datenbank habe ich eine txt-Datei. Das Auslesen der Datei (in der init() ) funktioniert einwandfrei, aber das abspeichern meiner Liste funktioniert leider garnicht.
Das Abspeichern soll über ein ActionEvent geschehen (JButton).

Hier der Code meiner Agenda-Class

Java:
import java.awt.*;
import javax.swing.*;
import java.util.*;
import java.awt.Color.*;
import java.awt.event.*;
import java.io.*;

/**
 * Tragen Sie hier eine Beschreibung der Applet-Klasse agenda ein.
 *
 * @author (Ihr Name) 
 * @version (eine Versionsnummer oder ein Datum)
 */
public class agenda extends JApplet
{
  private List liste;
  private Stringliste stringliste;
  private JPanel listPanel;
  private JPanel befehlsleiste;
  private JPanel einAusgabeBereich;
  private JPanel obererBereich;
  
  private JButton isEmpty;
  private JButton insert;
  private JButton hasAccess;
  private JButton append;
  private JButton setObject;
  private JButton getObject;
  private JButton getNumber;
  private JButton next;
  private JButton toFirst;
  private JButton toLast;
  private JButton remove;
  private JButton insertionSort;
  private JButton insort;
  private JButton umkehren;
  
  private JTextField einAusgabe;
  private JLabel einAusgabeText;
  
  private JMenuBar Menu;
  private JMenu Datei;
  private JMenuItem Beenden;
  private JScrollPane scrollPane;
  
  private JButton speichern;
  private FileWriter fw; 
  private BufferedWriter bw; 


  
  
    /**
     * Wird vom Browser oder Applet-Viewer aufgerufen, um dieses Applet darüber 
     * zu informieren, dass es in das System geladen wurde. Wird stets vor dem
     * ersten Aufruf der start-Methode aufgerufen.
     */
    public void init()
    {

        // Workaround für einen Sicherheitskonflikt mit einigen Browsern wie
        // einige Versionen von Netscape & Internet Explorer, die keinen Zugriff
        // auf die AWT-Systemevent-Queue erlauben, wie ihn JApplets beim Starten
        // vornehmen. Ist für Ihren Browser möglicherweise unnötig.
        JRootPane rootPane = this.getRootPane();    
        rootPane.putClientProperty("defeatSystemEventQueueCheck", Boolean.TRUE);
    
        // tragen Sie hier die Initialisierungen ein, die Ihr Applet benötigt

   
     this.setLayout(new BorderLayout());
     this.setSize(500,500);
     this.setVisible(true);
   
     Menu = new JMenuBar();
     Datei = new JMenu();
     Beenden = new JMenuItem();
     
  //   Menu.add(Datei);
     
     Datei.add(Beenden);
     Datei.setText("Datei");
     
     Beenden.setText("Beenden");
     Beenden.addActionListener(new ActionListener(){
         public void actionPerformed(ActionEvent e){
          System.exit(0); 
            }});
     
     obererBereich=new JPanel(new BorderLayout());
     obererBereich.add(Menu, BorderLayout.NORTH);
     
     liste=new List();
     stringliste=new Stringliste();
     
     listPanel=new JPanel();
     listPanel.setLayout(new BoxLayout(listPanel, BoxLayout.Y_AXIS));
     this.add(listPanel, BorderLayout.CENTER);
     listPanel.setVisible(true);
     
     einAusgabeBereich=new JPanel(new FlowLayout());
     
     einAusgabe= new JTextField();
     einAusgabe.setPreferredSize(new Dimension(100,20));
     
     einAusgabeText=new JLabel("Ein-/Ausgabe:");
     einAusgabeBereich.add(einAusgabeText);
     einAusgabeBereich.add(einAusgabe);
     scrollPane = new JScrollPane();
     scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
     scrollPane.setPreferredSize(new Dimension(400, 500));
     this.add(scrollPane, BorderLayout.EAST);
     this.add(einAusgabeBereich, BorderLayout.SOUTH);
     scrollPane.getViewport().add(listPanel);
     speichern=new JButton("Speichern");
     

     einAusgabeBereich.add(speichern, BorderLayout.SOUTH);
     einAusgabeBereich.setVisible(true);
     repaint();
     
     
     befehlsleiste=new JPanel(new GridLayout());
     befehlsleiste.setPreferredSize(new Dimension(500,50));
     isEmpty = new JButton("IsEmpty");
     isEmpty.addActionListener(new ActionListener() {
     public void actionPerformed(ActionEvent e) {
         String ausgabetext;
         if(liste.isEmpty()){
             ausgabetext="true";
            }
            else{
                ausgabetext="false";
            }
            einAusgabe.setText(ausgabetext);
            einAusgabeBereich.setVisible(true);
            repaint();
        } }
        );
        
    //    befehlsleiste.add(isEmpty);
        
        hasAccess=new JButton("hasAccess");
        hasAccess.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                String ausgabetext;
                if(liste.hasAccess()){
                    ausgabetext="true";
                }
                else{
                    ausgabetext="false";
                }
                
                einAusgabe.setText(ausgabetext);
                einAusgabeBereich.setVisible(true);
                repaint();
               }  }
        );
     //   befehlsleiste.add(hasAccess);
        
        next = new JButton("next");
        next.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if(liste.hasAccess()){
                    ((JLabel) liste.getObject()).setBackground(Color.LIGHT_GRAY);
                    ((JLabel) liste.getObject()).setOpaque(true);
                    ((JLabel) liste.getObject()).setVisible(true);
                    }
                    liste.next();
                    stringliste.next();
                if(liste.hasAccess()){
                    ((JLabel) liste.getObject()).setBackground(Color.RED);
                    ((JLabel) liste.getObject()).setOpaque(true);
                    ((JLabel) liste.getObject()).setVisible(true);    
                    }
                    einAusgabeBereich.setVisible(true);
                    repaint();
                
            }}
            );
        befehlsleiste.add(next);
        setObject = new JButton("setObject");
        setObject.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){
                if(liste.hasAccess()){
                    ((JLabel) liste.getObject()).setText(einAusgabe.getText());
                    ((JLabel) stringliste.getObject()).setText(einAusgabe.getText());
                }
                einAusgabeBereich.setVisible(true);
                repaint();
            }  }
            );
         befehlsleiste.add(setObject);
         
         getObject = new JButton("getObject");
         getObject.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent e){
                if(liste.hasAccess()){
                   einAusgabe.setText( ((JLabel) liste.getObject()).getText() );
                }
                einAusgabeBereich.setVisible(true);
                repaint();
            }  }
            );         
     //    befehlsleiste.add(getObject);
         
         toFirst = new JButton("toFirst");
         toFirst.addActionListener(new ActionListener(){
             public void actionPerformed(ActionEvent e){
                 if(liste.hasAccess()){
                     ((JLabel) liste.getObject()).setBackground(Color.LIGHT_GRAY);
                     ((JLabel) liste.getObject()).setOpaque(true);
                     ((JLabel) liste.getObject()).setVisible(true);
                    }
                    liste.toFirst();
                    stringliste.toFirst();
                  if(liste.hasAccess()){
                      ((JLabel) liste.getObject()).setBackground(Color.RED);
                      ((JLabel) liste.getObject()).setOpaque(true);
                      ((JLabel) liste.getObject()).setVisible(true);
                    }
                    einAusgabeBereich.setVisible(true);
                    repaint();
                }
        }
                );
                
                befehlsleiste.add(toFirst);
            
            toLast=new JButton("toLast");
            toLast.addActionListener(new ActionListener(){
             public void actionPerformed(ActionEvent e){
                 if(liste.hasAccess()){
                     ((JLabel) liste.getObject()).setBackground(Color.LIGHT_GRAY);
                     ((JLabel) liste.getObject()).setOpaque(true);
                     ((JLabel) liste.getObject()).setVisible(true);
                    }
                    liste.toLast();
                    stringliste.toLast();
                  if(liste.hasAccess()){
                      ((JLabel) liste.getObject()).setBackground(Color.RED);
                      ((JLabel) liste.getObject()).setOpaque(true);
                      ((JLabel) liste.getObject()).setVisible(true);
                    }
                    einAusgabeBereich.setVisible(true);
                    repaint();
                }
        }
                );
            befehlsleiste.add(toLast);
            
            append = new JButton("append");
            append.addActionListener(new ActionListener(){
                public void actionPerformed(ActionEvent e){
                    JLabel tempLabel = new JLabel();
                    tempLabel.setText(einAusgabe.getText());
                    liste.append(tempLabel);
                    stringliste.append(tempLabel.getText());
                    tempLabel.setBackground(Color.LIGHT_GRAY);
                    tempLabel.setOpaque(true);
                    tempLabel.setVisible(true);
                    listPanel.add(tempLabel);
                    listPanel.setVisible(true);
                    einAusgabeBereich.setVisible(true);
                    repaint();
                    validate();
                }}
                );
                befehlsleiste.add(append);
                
        
       
       
                           
      

            
            
            remove = new JButton("remove");
            remove.addActionListener(new ActionListener(){
                public void actionPerformed(ActionEvent e){
                    if(liste.hasAccess()){
                        listPanel.remove((JLabel) liste.getObject());
                        liste.remove();
                        stringliste.remove();
                        listPanel.repaint();
                        einAusgabeBereich.setVisible(true);
                        validate();

                    }
                }   }
                );
              befehlsleiste.add(remove);
            

              
              obererBereich.add(befehlsleiste, BorderLayout.CENTER);
              befehlsleiste.setVisible(true);
            
            this.add(obererBereich, BorderLayout.NORTH);
    
            this.setVisible(true);
            try{
            FileReader fr = new FileReader("inhalt.txt");
            BufferedReader br = new BufferedReader(fr);
            String zeile;
            
            while((zeile=br.readLine())!=null )
            {
            append(zeile);
            
            }
            br.close();
            fr.close();
        }catch(IOException b){}
            
    }
             public void append(String s)
             {
                    JLabel tempLabel = new JLabel();
                    tempLabel.setText(s);
                    liste.append(tempLabel);
                    stringliste.append(tempLabel.getText());
                    tempLabel.setBackground(Color.LIGHT_GRAY);
                    tempLabel.setOpaque(true);
                    tempLabel.setVisible(true);
                    listPanel.add(tempLabel);
                    listPanel.setVisible(true);
                    einAusgabeBereich.setVisible(true);
                    repaint();
                    validate();
                
                }
    /**
     * Wird vom Browser oder Applet-Viewer aufgerufen, um dieses Applet darüber 
     * zu informieren, dass es seine Ausführung beginnen soll. Wird nach der
     * init-Methode aufgerufen, und jedes Mal, wenn das Applet auf einer Webseite
     * wieder besucht wird.
     */
    public void start()
    {
     speichern.addActionListener(new ActionListener() {
     public void actionPerformed(ActionEvent e) {
     
          speichern();
        }
    }
    );        

    }

    /** 
     * Wird vom Browser oder Applet-Viewer aufgerufen, um dieses Applet darüber 
     * zu informieren, dass es seine Ausführung beenden soll. Wird aufgerufen,
     * wenn die Webseite, die dieses Applet enthält, durch eine andere ersetzt
     * wurde, und auch unmittelbar bevor das Applet zerstört wird. Wenn es keine
     * Ressourcen gibt, die Sie freigeben müssen (wie etwa Threads, die Sie 
     * anhalten wollen), können Sie diese Methode entfernen.
     */
    public void stop()
    {
         
    }

    /**
     * Wird vom Browser oder Applet-Viewer aufgerufen, um dieses Applet darüber 
     * zu informieren, dass es zurückgefordert wird und dass es jegliche 
     * Ressourcen zerstören soll, die es angelegt hat. Die stop-Methode wird
     * stets vor der destroy-Methode aufgerufen.
     */
    public void destroy()
    {
             
    }


    /**
     * Liefert Informationen über dieses Applet.
     * Ein Applet sollte diese Methode überschreiben, um einen String zu liefern,
     * der Informationen über Autor, Version und Copyright des Applets enthält.
     *
     * @return Informationen über dieses Applet in Form eines Strings
     */
    public String getAppletInfo()
    {
        // ersetzten Sie dies mit Ihren eigenen Informationen
        return "Titel:   \nAutor:   \nEine einfache Beispielbeschreibung des Applets. ";
    }


    /**
     * Liefert Informationen über die Parameter, die von diesem Applet verstanden
     * werden. Sie sollten ein Array von Strings zurückgeben mit Details, getrennt
     * nach Parametern.
     * Jedes Element des Arrays sollte eine Menge von drei Strings sein, die den 
     * Namen, den Typ und eine Beschreibung des Parameters darstellen.
     *
     * @return  Parameterinformation zu diesem Applet in Form eines String[][] Arrays 
     */
    public String[][] getParameterInfo()
    {
        // Parameterinformationen zu diesem Applet liefern
        String paramInfo[][] = {
             {"ersterParameter", "1-10", "Beschreibung des ersten Parameters"},
             {"zweiterParameter", "boolean", "Beschreibung des zweiten Parameters"}
        };
        return paramInfo;
    }

   public void speichern()
   {
    stringliste.toFirst();
    liste.toFirst();
          try{
          fw=new FileWriter("inhalt.txt");
          bw=new BufferedWriter(fw);
          while(stringliste.hasAccess())
          {
            String s=stringliste.getString();
            bw.write(s);
            liste.next();
           }
            bw.close();
            fw.close();
        }catch(IOException a){}
    
    }
   
}

Hier die List-Class. Die StringListe erbt von der List-Class und verwendet die gleichen Methoden.

Java:
/**
 * <p>Materialien zu den zentralen
 * Abiturpruefungen im Fach Informatik ab 2012 in 
 * Nordrhein-Westfalen.</p>
 * <p>Klasse List</p>
 * <p>Objekte der Klasse List verwalten beliebig viele, 
 * linear angeordnete Objekte. Auf hoechstens ein Listenobjekt, 
 * aktuelles Objekt genannt, kann jeweils zugegriffen werden. 
 * Wenn eine Liste leer ist, vollstaendig durchlaufen wurde oder 
 * das aktuelle Objekt am Ende der Liste geloescht wurde, gibt es 
 * kein aktuelles Objekt. Das erste oder das letzte Objekt einer 
 * Liste koennen durch einen Auftrag zum aktuellen Objekt gemacht werden. 
 * Au�erdem kann das dem aktuellen Objekt folgende Listenobjekt 
 * zum neuen aktuellen Objekt werden. Das aktuelle Objekt kann gelesen, 
 * veraendert oder geloescht werden. Ausserdem kann vor dem aktuellen 
 * Objekt ein Listenobjekt eingef�gt werden.
</p>
 * 
 * <p>NW-Arbeitsgruppe: Materialentwicklung zum Zentralabitur 
 * im Fach Informatik</p>
 * 
 * @version 2010-10-22
 */
 

public class List

{ 
   protected Node first, tail, current;

    // Node
   protected class Node { 
        private Object contentObj;
        private Node nextNode;

        public Node(Object pContent) { 
            contentObj = pContent;
            nextNode = null;
        }

        public void setContent(Object pContent) { 
            contentObj = pContent; 
        }

        public Object content() { 
            return contentObj; 
        }

        public void setNext(Node pNext) { 
            nextNode = pNext; 
        }

        public Node getNext() { 
            return nextNode; 
        }

    } // Ende der Klasse Node
 
    /**
     * Eine leere Liste wird erzeugt.
     */
    public List () {
        tail = new Node(null); // Dummy
        first=tail;
        tail.setNext(tail);
        /* Der next-Zeiger des hinteren Dummy-Elementes
         * zeigt auf das vorangehende Element.
         */
        current=first;
    }
  
    /**
     * Die Anfrage liefert den Wert true, wenn die Liste 
     * keine Objekte enthaelt, sonst liefert sie den Wert false.
     * @return true, wenn die Liste leer ist, sonst false
     */
    public boolean isEmpty() { 
        return first==tail; 
    }
 
    /**
     * Die Anfrage liefert den Wert true, wenn es ein
     * aktuelles Objekt gibt, sonst 
     * liefert sie den Wert false.
     * @return true, falls Zugriff moeglich, sonst false
     */
    public boolean hasAccess() { 
        return (!this.isEmpty()) && (current!=tail);
    }
  
    /**
     * Falls die Liste nicht leer ist, es ein aktuelles 
     * Objekt gibt und dieses nicht das letzte Objekt der 
     * Liste ist, wird das dem aktuellen Objekt in der Liste 
     * folgende Objekt zum aktuellen Objekt, andernfalls gibt 
     * es nach Ausf�hrung des Auftrags kein aktuelles Objekt, 
     * d.h. hasAccess() liefert den Wert false.
     */
    public void next() { 
        if (this.hasAccess())
            current = current.getNext();
    }
 
    /**
     * Falls die Liste nicht leer ist, wird das erste 
     * Objekt der Liste aktuelles Objekt. 
     * Ist die Liste leer, geschieht nichts.
     */
    public void toFirst() {
        if (!this.isEmpty())  
            current = first; 
    }
 
    /**
     * Falls die Liste nicht leer ist, wird das 
     * letzte Objekt der Liste aktuelles Objekt. 
     * Ist die Liste leer, geschieht nichts.
     */
    public void toLast() {
        if (!this.isEmpty())  
            current = tail.getNext(); 
    }

    /**
     * Falls es ein aktuelles Objekt gibt 
     * (hasAccess() == true), wird das aktuelle Objekt 
     * zurueckgegeben, andernfalls (hasAccess()== false) 
     * gibt die Anfrage den Wert null zurueck.
     * @return Inhaltsobjekt
     */
    public Object getObject() {   
        if (this.hasAccess())
            return current.content();
        else 
            return null;
    }
 
    /**
     * Falls es ein aktuelles Objekt gibt (hasAccess() == true)
     * und pObject ungleich null ist, wird das aktuelle Objekt 
     * durch pObject ersetzt. Sonst bleibt die Liste unveraendert.
     * @param pObject Inhaltsobjekt
     */
    public void setObject(Object pContent) { 
        if (pContent!=null && this.hasAccess() )
            current.setContent(pContent); 
    }

    /**
     * Ein neues Objekt pObject wird am Ende der Liste eingefuegt. 
     * Das aktuelle Objekt bleibt unveraendert. Wenn die Liste 
     * leer ist, wird das Objekt pObject in die Liste eingefuegt 
     * und es gibt weiterhin kein aktuelles Objekt 
     * (hasAccess() == false). Falls pObject gleich null ist, 
     * bleibt die Liste unveraendert. 
     *@param pObject Inhaltsobject
     */    
    public void append (Object pObject) { 
        if (pObject != null) {  
            Node lNewNode,lPos0;
            lPos0=current;
            lNewNode=new Node(pObject);
            lNewNode.setNext(tail);
            if (this.isEmpty())
                first=lNewNode;
            else {  
                Node lPrevious=tail.getNext();
                lPrevious.setNext(lNewNode);
            }  
            tail.setNext(lNewNode);
            current=lPos0;
        }
    }  

    /**
     *Falls es ein aktuelles Objekt gibt (hasAccess() == true),
     *wird ein neues Objekt vor dem aktuellen Objekt in die 
     *Liste eingefuegt. Das aktuelle Objekt bleibt unveraendert. 
     *Wenn die Liste leer ist, wird pObject in die Liste eingefuegt
     *und es gibt weiterhin kein aktuelles Objekt 
     *(hasAccess() == false). Falls es kein aktuelles Objekt gibt 
     *(hasAccess() == false) und die Liste nicht leer ist oder 
     *pObject gleich null ist, bleibt die Liste unveraendert.
     *@param pObject Inhaltsobjekt
     */
    public void insert(Object pObject) {
        if (pObject != null) {
            Node lNewNode,lFront,lPos,lPos0;
            if (this.isEmpty())
                this.append(pObject);
            else 
            if (this.hasAccess() ) {
                lPos0=current;
                lPos=current;
                lNewNode=new Node(pObject);
                lNewNode.setNext(current);
                if (lPos == first )
                    first=lNewNode;
                else {
                    this.toFirst();
                    lFront=current;
                    while (this.hasAccess() & !(current == lPos)) {
                        lFront = current;
                        this.next();
                    }
                    lFront.setNext(lNewNode);  
                }          
                current=lPos0;
            }
        }
    }

    /**
     * Die Liste pList wird an die Liste angehaengt. Anschliessend 
     * wird pList eine leere Liste. Das aktuelle Objekt bleibt unveraendert. 
     * Falls pList null oder eine leere Liste ist, bleibt die Liste 
     * unveraendert.
     * @param pList Liste
     */
    public void concat(List pList) { 
        Node lCurrent1,lCurrent2,lPos0;
        if (pList != null && !pList.isEmpty() ) {
            lPos0=current;  
            current=tail.getNext();
            lCurrent1=current;
            pList.toFirst();
            current=pList.current;
            lCurrent2=pList.current;
            lCurrent1.setNext(lCurrent2);
            if (lPos0!=tail)
                current=lPos0;
            else 
                current=pList.tail;
            tail=pList.tail;
            // pList wird zur leeren Liste
            pList.tail = new Node(null); // Dummy
            pList.first=pList.tail;
            pList.tail.setNext(tail);
            pList.current=pList.tail;
        }
    }  

    /**
     * Falls es ein aktuelles Objekt gibt (hasAccess() == true),
     * wird das aktuelle Objekt geloescht und das Objekt hinter 
     * dem gelaeschten Objekt wird zum aktuellen Objekt. Wird das 
     * Objekt, das am Ende der Liste steht, geloescht, gibt es kein 
     * aktuelles Objekt mehr (hasAccess() == false). Wenn die Liste 
     * leer ist oder es kein aktuelles Objekt gibt (hasAccess() == false),
     * bleibt die Liste unveraendert.
     */
    public void remove() { 
        Node lPos, lFront;
        if (this.hasAccess() ) {
            if (current==first ) {
                first=current.getNext();
                if (current.getNext()==tail)
                    tail.setNext(first);
                current=first;  
            }
            else {
                lPos=current;
                this.toFirst();
                lFront=current;
                while (this.hasAccess() & !(current==lPos)) {
                    lFront=current;
                    this.next();
                }
                lFront.setNext(lPos.getNext());
                current=lFront.getNext();
                if (current==tail)
                    tail.setNext(lFront);
            }  
        }
    }
    
    public Node getcurrent()
    {
     return current;
    }

}
 
"Funktioniert nicht" heißt? Fehlermeldung?

Eine eigene Klasse List nennen sollte man nicht machen, da es den Namen schon im java.awt und java.util package gibt. Grade wenn man sowas macht:
Java:
import java.awt.*;
[...]
import java.util.*;
[...]
private List liste;
[...]
Welche List-Klasse wird da jetzt verwendet? Die aus dem awt package, die aus dem util package oder womöglich doch die eigene?
 
Die eigene List-Klasse habe ich doch gepostet 🙂
Diese funktioniert auch wunderbar. Das Problem ist, dass das speichern dieser Liste einfach nicht ausgeführt wird.
Es gibt keinen Fehler, sondern es passiert einfach nichts. Das interessante dabei ist, dass wenn ich in der Methode einen Befehl einfüge, dass der ieinen Hintergrund farbig machen soll, dass dies geschieht. Folgerlich wird die Methode ausgeführt, doch das Speichern übersprungen. Gibt es da bestimmte Gründe für ?

Also die Datei dürfte beim Speichern nicht mehr anders geöffnet sein, da ich die Reader wieder mit close() geschlossen habe.
 
Java:
}catch(IOException a){}
Und da lässte dir vllt mal die Exception ausgeben.
 
Applets dürfen nichts auf dem Client speichern, es sei denn, sie sind signiert worden.
Wenn dein Applet also eine AccessControlException wirft, solltest du mal die FAQ besuchen und dein Applet mit dem dort zu findenden Tool signieren.
 
Der Code des Applets wird zwar auf dem Server zur Verfügung gestellt. Das Applet läuft aber in der VM des Clients. Eine Kommunikation mit dem Server ist über die üblichen Netzwerktechnologien möglich. So spontan fallen mir da Sockets, RMI, WebServices... ein.
 

Zurück
Oben