torek, 5. april 2011

Javin naštevni tip – Enum

Redko kaj napišem pa čeprav imam kar nekaj idej – lenoba pač. Nekatere ideje so že vsaj delno realizirane a do objave tukaj bi bilo potrebno vložiti še nekaj dela. Tokrat bom pisal o Javinem naštevnem tipu in njegovi uporabi.

Naštevni tip v Javi je dosti močnejši kot pa v ostalih programskih jezikih (C, C++, C# …). Precej so podobni razredom. Lahko imajo konstruktor, metode, spremenljivke in implementirajo vmesnike. Pred časom sem jih uporabil za izdelavo grafičnega vmesnika za vnos virov(referenc) pri člankih ipd. Cilj je bil narediti karseda enostavno strukturo teh virov, ki bo enostavna za vzdrževanje in pregledna. Za začetek je potrebno definirat grafične gradnike, ki se bodo prikazali na zaslonu. Vnosna polja, izbira datuma, kombinirano polje ipd.

To naredimo z naštevnim tipom, ki mu definiramo abstraktno metodo, ki vrača Swing komponento. Pri vsakem definiranem tipu to metodo prepišemo, da nam vrača ustrezno komponento. Definiramo si vse različne tipe komponent in njihove izpeljanke, ki jih bomo kasneje rabili.
package javaenumblog.sourcecontrol;

import java.awt.Component;
import javax.swing.JComboBox;
import javax.swing.JTextArea;
import javax.swing.JTextField;

//define all kind of types/components with contents and variations

public enum Type {

    TEXTFIELD {

        @Override
        public Component getComponent() {
            return new JTextField(30);
        }
    },INITIAL
    {

        public Component getComponent()
        {
            return new JTextField(5);
        }
    },
    AUTHORS {

        @Override
        public Component getComponent() {
            JTextArea textArea = new JTextArea(1, 40);
            textArea.setLineWrap(true);
            return textArea;
        }
    },
    EDUCATION {

        @Override
        public Component getComponent() {
            return new JComboBox(new Object[]{
                        "B.Sc", "M.Sc.", "PHD"
                    });
        }
    };

    public abstract Component getComponent();
}
V naslednjem koraku definiramo vse različne tipe in njihove nazive. Kasneje jih bomo uporabili za sestavljanje vseh različnih vrst referenc. Definiramo si dve spremenljivki in pripadajoči metodi, ki vračata njuno vrednost. Konstruktor pa nam predpisuje ustrezno obliko naštevnega tipa. Za lepšo kodo statično uvozimo imensko področje. To nam omogoča direktno delo s tipi brez kvalifikatorja.
package javaenumblog.sourcecontrol;
//staticly imported enums may be used without qualification
import static javaenumblog.sourcecontrol.Type.*;
//define fields (title and type)
public enum SourceTypes {

    articleTitle("Article title:", TEXTFIELD),
    authors("Authors:", AUTHORS),
    publicationTitle("Publication title:", TEXTFIELD),
    year("Year:", INITIAL),
    initials("Initials:", INITIAL),
    obtainedUrl("Page URI:", TEXTFIELD),
    education("Education:", EDUCATION),;
    protected String label;
    protected Type type;

    SourceTypes(String label, Type type) {
        this.label = label;
        this.type = type;
    }

    public String getLabel() {
        return label;
    }

    public Type getType() {
        return type;
    }
}
V naslednjem koraku pa že definiramo vse različne oblike referenc na vire. Sestavimo jih iz polj, ki smo jih določili zgoraj. Vsaki referenci pa dodamo tudi naziv. To naredimo na podoben način kot smo zgoraj le da tukaj potrebujemo polje vseh potrebnih vnosnih polj. Prepišemo pa tudi metodo toString() tako, da nam bo vračala samo oznako. Pravtako statično uvozimo naštevni tip za čistejšo kodo.
package javaenumblog.sourcecontrol;

import static javaenumblog.sourcecontrol.SourceTypes.*;

//construct sources you need from sourceTypes
public enum Sources {

    WEBARTICLE("Web magazine - article", new SourceTypes[]{
authors, articleTitle, publicationTitle, year, obtainedUrl
}), ARTICLE("demo article", new SourceTypes[]{
initials, education, articleTitle
});
//and more...
    protected String label;
    protected SourceTypes[] sourceTypes;

    private Sources(String title, SourceTypes[] sourceTypes) {
        this.label = title;
        this.sourceTypes = sourceTypes;
    }

    public SourceTypes[] getSourceTypes() {
        return sourceTypes;
    }

    public String getLabel() {
        return label;
    }

    @Override
    public String toString() {
        return label;
    }
}
Sedaj je potrebno še napisati razred, ki bo izbrane komponente razporedil na grafični vmesnik in kasneje shranil ustrezne podatke k pripadajoči komponenti. Logiko poskušamo karseda ločiti od grafičnega vmesnika zato v konstruktor dobimo samo referenco na JPanel komponento. Na njo bomo izrisali vmesnik za vnos. V metodi za generiranje vmesnika pa se sprehodimo skozi vse gradnike, ki jih zahteva referenca in jih izrisujemo na panel v dvojicah (naziv, komponenta). Ker jih dodajamo kar absolutno moramo vsakemu določiti tudi meje preden jih dodamo. Ne smemo pozabiti tudi na ustrezne odmike. Da pa lahko za vsako polje vemo k kateri komponenti spada pa jih sproti dodajamo še v povezan seznam (LinkedHashMap) – vrstni red je pomemben! Seveda pa moramo napisati še metodo, ki nam bo nove vrednosti tudi prebrala iz ustreznih komponent. Tukaj se sprehodimo čez naš seznam dvojic (naziv, komponenta) in glede na tip komponente preberemo njeno vrednost. To naredimo kar na takšen način, ki sem ga že opisal TUKAJ. V tem primeru lastnosti in vrednosti izpišemo kar na standardni izhod.
package javaenumblog.sourcecontrol;
import java.awt.Component;
import java.util.LinkedHashMap;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
public class GUIGenerator {
    private JPanel panel = null;
    private LinkedHashMap<String, Component> mapComponent = new LinkedHashMap<String, Component>();
    private enum Classes {
        JTextField, JTextArea, JComboBox
    };

    public GUIGenerator(JPanel panel) {
        this.panel = panel;
    }

    public void generateInput(Sources types) {
        mapComponent.clear();
        if (types != null) {
            panel.removeAll();
            int Y = 20;
            for (SourceTypes sourceTypes : types.getSourceTypes()) {
                String labelTag = sourceTypes.getLabel();
                JLabel label = new JLabel(labelTag);
                label.setBounds(20, Y, label.getPreferredSize().width, label.getPreferredSize().height);
                Component component = sourceTypes.getType().getComponent();
                panel.add(label);
                component.setBounds(200, Y, component.getPreferredSize().width, component.getPreferredSize().height);
                panel.add(component);
                mapComponent.put(sourceTypes.name(), component);
                Y += 40;
            }
            panel.repaint();
            panel.revalidate();
        }
    }

    public void generateOutput() {
        for (String label : mapComponent.keySet()) {
            Component component = mapComponent.get(label);
            String sout = "Property: " + label + " ; value: ";
            String className = component.getClass().getSimpleName();
            switch (Classes.valueOf(className)) {
                case JTextField:
                    sout += ((JTextField) component).getText();
                    break;
                case JTextArea:
                    sout += ((JTextArea) component).getText();
                    break;
                case JComboBox:
                    sout += ((JComboBox) component).getSelectedItem();
                    break;
            }
            System.out.println(sout);
        }
    }
}
V zadnjem koraku pa še sestavimo enostaven grafični vmesnik, ki bo glede na izbiro v kombiniranem polju izrisal pripadajoč grafični vmesnik. Kombinirano polje vežemo na seznam, ki ga napolnimo iz naštevnega tipa. Naredimo nov objekt generatorja in mu dodamo panel. Ob spremembi izbire v kombiniranem polju preberemo za katero vrsto reference gre in izrišemo ustrezen vmesnik. Pri izpisu pa samo pokličemo metodo, ki smo jo prej napisali.



Pri zagonu programa nas pričaka podoben zaslon kot spodaj. V kombiniranem polju sta na voljo dve različni referenci, ki generirata različna vnosna polja.


S klikom na gumb Print pa se na standardni izhod izpišejo nazivi in vrednosti kot smo določili pri posamezni vrsti reference.