PDA

View Full Version : [java]cosa uso?


dnarod
16-11-2006, 01:24
devo testare la stabilita di alcuni algoritmi di ordinamento...non ho voglia di fare mille testa mano e mi è venuto in mente che potrei fare un programmino semplice e caruccio che ordina in base ai campi di un oggetto, ma scelti a runtime...ora, non sono un granche esperto quindi la domanda sara confusa a criptica, ma mi piacerebbe sapere come posso realizzare una cosa simile...con la riflessione? cioe come faccio a: tirare fuori i nomi dei campi di una collezione di oggetti di un certo tipo (non so, che abbia nome cognome e telefono), visualizzarli da qualche parte e dare la possibilita di scegliere dunque il criterio da utilizzare per l ordinamento?

inoltre: se voglio scrivere le procedure una volta sola (che funzionino per ordinamenti di tipi diversi) uso i generici?

utlimissima domanda che non c entra una mazza col resto...se c' è il modo, come si fa a dirigere un generico output su piu di uno stream contemporaneamente (non so system out, su un file e su una jtextarea contemporaneamente)?

tnx in anticipo

mad_hhatter
16-11-2006, 07:50
devo testare la stabilita di alcuni algoritmi di ordinamento...non ho voglia di fare mille testa mano e mi è venuto in mente che potrei fare un programmino semplice e caruccio che ordina in base ai campi di un oggetto, ma scelti a runtime...ora, non sono un granche esperto quindi la domanda sara confusa a criptica, ma mi piacerebbe sapere come posso realizzare una cosa simile...con la riflessione? cioe come faccio a: tirare fuori i nomi dei campi di una collezione di oggetti di un certo tipo (non so, che abbia nome cognome e telefono), visualizzarli da qualche parte e dare la possibilita di scegliere dunque il criterio da utilizzare per l ordinamento?

inoltre: se voglio scrivere le procedure una volta sola (che funzionino per ordinamenti di tipi diversi) uso i generici?

utlimissima domanda che non c entra una mazza col resto...se c' è il modo, come si fa a dirigere un generico output su piu di uno stream contemporaneamente (non so system out, su un file e su una jtextarea contemporaneamente)?

tnx in anticipo


fammi capire... tu vuoi un algoritmo di ordinamento generico, nel senso che deve funzionare di volta in volta con oggetti di tipo diverso, ma per ogni esecuzione gli oggetti avranno proprietà simili, o addirittura, saranno dello stesso tipo... ho capito bene?

se si la cosa migliore è usare le interfacce.

tu dichiari una interfaccia Ordinabile (magari con un nome più carino :) ) e fai in modo che il tuo algoritmo richieda di lavorare con oggetti di tipo Ordinabile.
E con ciò il tuo algoritmo è a posto.

ora passiamo agli oggetti. ogni classe dovrà implementare l'interfaccia Ordinabile e dovrai fornire metodi di ordinamento (ad esempio equals() e compareTo() ) per tutti i campi rispetto ai quali potresti richiedere di eseguire l'ordinamento.

mi pare che così il gioco sia fatto... se non ho trascurato qualche dettaglio e se ho capito le te necessità

dnarod
16-11-2006, 08:24
nono è piu semplice ancora...gli oggetti sono sempre tutti dello stesso tipo (un oggetto con due stringhe e un intero giusto cosi per metterci qualcosa) e devo testare la stabilita di due o 3 algoritmi di ordinamento chiamandoli su un array di tali oggetti per farlo vedere bene...ma voglio che a runtime sia il prof a decidere come ordinare (se per nome per cognome o per telefono) e sono indeciso sulla realizzazione...se uso le interfaccia (tra l altro potrei usare comparable senza sbattermi troppo) non risolvo il problema che non so come fargli pescare i campi dell oggetto, listare le possibilita (stamparli ma quello è il meno), e di li dare la possibilita di scegliere come ordinare, ma senza che debba definire una procedura per ogni criterio adottato per l ordinamento (nel mio caso dovrei riscrivere 3 volte)...cioe non so come passare (alla chiamata del metodo di ordinamento) il "modo" in cui deve ordinare...mi è venuto in mente che, a oggetto creato (array di oggetti del tipo che deve ordinare), potrei tirare fuori i campi di quell oggetto e linkarne i nomi alla chiamata dell algoritmo, tipo ordina("modalita"), ma oltre ad essere macchinosa e inelegante sono quasi certo che non sia il modo giusto di procedere (inoltre non risolverei comunque il problema dello scrivere piu volte il metodo per i tipi diversi dei campi dell oggetto...

pensavo fosse una cosa semplice ma evidentemente sono un niubbazzo io...

PGI-Bis
16-11-2006, 12:39
Usa Comparator. Cioè, più di un Comparator. Per l'algoritmo di ordinamento ti basta sapere quando qualcosa sia maggiore minore o uguale a qualcos'altro. Se adatti il tuo procedimento di ordinamento ad accettare non un elemento da ordinare ma qualcosa che ti dica quando due elementi sono uguali o quando uno sia maggiore-minore dell'altro, sei a cavallo. Fai scegliere all'utente il campo del record da ordinare e, in base a quello, scegli uno tra più Comparator (quello che dati due Record restituisce un valore sulla base del confronto di una coppia di campi di quei due record).

Ti faccio un esempino per essere più chiaro.

import java.util.*;
import java.text.*;

public class Main {

public static void main(String[] args) {
Main main = new Main();
/* Carica i dati */
ArrayList<Record> dati = main.caricaDati();
for(Record r : dati) {
System.out.println(r);
}

/* Fa scegliere all'utente un campo di
ordinamento */
System.out.println("Ordina per:");
System.out.println("[1] età");
System.out.println("[2] nome");
System.out.println("[3] cognome");
System.out.println("1, 2 o 3");
Scanner input = new Scanner(System.in);
int choice = input.nextInt();

/* In base alla scelta dell'utente carica il
comparatore adatto */
Comparator<Record> comparatore = null;
if(choice == 1) {
comparatore = new ComparatoreEtà();
System.out.println("Ordino per età...");
} else if(choice == 2) {
comparatore = new ComparatoreNome();
System.out.println("Ordino per nome...");
} else if(choice == 3) {
comparatore = new ComparatoreCognome();
System.out.println("Ordino per cognome...");
}

/* Applica il comparatore scelto */
if(comparatore != null) {
Collections.sort(dati, comparatore); //ordinamento precotto
for(Record r : dati) {
System.out.println(r);
}
}
System.out.println("Fine programma.");
}

public ArrayList<Record> caricaDati() {
ArrayList<Record> dati = new ArrayList<Record>();
Scanner in = null;
try {
in = new Scanner(getClass().getResourceAsStream("/dati.txt"));
while(in.hasNextLine()) {
dati.add(creaRecord(in.nextLine()));
}
} finally {
if(in != null) in.close();
}
return dati;
}

public Record creaRecord(String linea) {
StringTokenizer tk = new StringTokenizer(linea, " ");
Record r = new Record(
Integer.valueOf(tk.nextToken()),
tk.nextToken(),
tk.nextToken());
return r;
}
}

/** Confronta due Record prendendo in esame il loro campo
età */
class ComparatoreEtà implements Comparator<Record> {
public int compare(Record a, Record b) {
int ea = a.getEtà();
int eb = b.getEtà();
return ea == eb ? 0 : ea > eb ? 1 : -1;
}
}

/** Confronta due Record prendendo in esame il loro campo
nome */
class ComparatoreNome implements Comparator<Record> {
private Collator collator = Collator.getInstance();

public int compare(Record a, Record b) {
return collator.compare(a.getNome(), b.getNome());
}
}

/** Confronta due Record prendendo in esame il loro campo
Cognome */
class ComparatoreCognome implements Comparator<Record> {
private Collator collator = Collator.getInstance();

public int compare(Record a, Record b) {
return collator.compare(a.getCognome(), b.getCognome());
}
}

/** Un'età, un nome e un cognome */
class Record {
private final int età;
private final String nome;
private final String cognome;

public Record(int età, String nome, String cognome) {
this.età = età;
this.nome = nome;
this.cognome = cognome;
}

public int getEtà() {
return età;
}

public String getNome() {
return nome;
}

public String getCognome() {
return cognome;
}

public String toString() {
return nome + " " + cognome + " " + età;
}
}

dnarod
16-11-2006, 13:31
grazie per le dritte, ma quello che voglio fare è leggermente diverso (in realta non so se si puo fare una cosa simile, cioe io l ho pensata ma non è detto che sia fattibile o di facile realizzazione); facendo come mi hai suggerito (cioe poi quello che dovro fare se non voglio sbattermi troppo e che avevo pensato all' inizio) devo assumere che il mio programmillo-fuffa (perche alla fine di quello si tratta, un semplice tester per un esercizio sulla stabilita degli algoritmi di ordinamento) funzioni solo con oggetti di quel tipo (nome cognome eta)...quel che voglio io è che funzioni anche con altri oggetti (tutti dello stesso tipo ovviamente); cioe vorrei capire se è possibile, dato un oggetto qualsiasi, fare quello che hai scritto tu col comparator ma senza scriverlo: cioe una volta ho un oggetto record che ha nome cognome eta e tiro fuori i nomi dei campi e li utilizzo come criteri di ordinamento, un altra volta decido di farlo girare con oggetti di tipo (invento) libro che hanno un titolo un autore e una data di pubblicazione, eccetera...ma ogni volta, automaticamente, mi acchiappo i nomi dei campi, li mostro all utilizzatore (abbastanza semplicino da fare) e poi qui viene il bello, lo scrivere un qualche genere di procedura che possa comparare automaticamente i tipi primari (lasciamo perdere cose derivate strane che non ci voglio sclerare piu di troppo) solo guardando il tipo del campo dell oggetto che sta analizzando in quel momento...ad esempio, gli do in pasto un oggetto scarpa, con un numero e un nome, e lui mi da la possibilita di ordinare per i due criteri, poi dopo lo runno con oggetti di tipo persona (nome cognome eta) e mi da la possibilita di ordinare per quei 3 criteri, eccetera...

l unica cosa che mi manca è capire come rendere questo passaggio...perche i nomi dei campi di un oggetto e i relativi tipi son facili da tirare fuori, ma fare un comparatore chiamiamolo "generico" in base al tipo di campo, non so proprio come fare (magari è facile, ma non mi viene in mente)....

scusate se assillo per ste stupidaggini (cmq mi avete gia dato ottimi consigli!)

PGI-Bis
16-11-2006, 14:27
Tutti i linguaggi di programmazione orientati agli oggetti consentono di scrivere un programma senza sapere un bel niente degli oggetti [aggiunto: con cui si sta cercando di dialogare]

Quello che segue è un Comparator che dati due bean che possiedano un metodo pubblico di nome compareTo che restituisce un intero (a prescindere dal fatto che siano o non siano dei Comparable) invoca il metodo del primo riferimento usando il secondo come argomento.

Nota che, a differenza di prima, Main non sa niente di niente. Non c'è Record nel metodo main nè altro che possa valere ad identificare il bersaglio dell'ordinamento.

import java.util.*;
import java.text.*;
import java.lang.reflect.*;

public class Main {

public static void main(String[] args) {
Main main = new Main();
/* Carica i dati */
ArrayList<?> dati = main.caricaDati();
for(Object r : dati) {
System.out.println(r);
}

/* Fa scegliere all'utente un campo di
ordinamento */
System.out.println("Digitare il nome del campo accessibile da comparare");
Scanner in = new Scanner(System.in);
String fieldName = in.nextLine();
Camaleomparatore comparatore = new Camaleomparatore(fieldName);
Collections.sort(dati, comparatore);
System.out.println("Ordinamento per " + fieldName);
for(Object r : dati) {
System.out.println(r);
}
System.out.println("Fine programma.");
}

public ArrayList<Record> caricaDati() {
ArrayList<Record> dati = new ArrayList<Record>();
Scanner in = null;
try {
in = new Scanner(getClass().getResourceAsStream("/dati.txt"));
while(in.hasNextLine()) {
dati.add(creaRecord(in.nextLine()));
}
} finally {
if(in != null) in.close();
}
return dati;
}

public Record creaRecord(String linea) {
StringTokenizer tk = new StringTokenizer(linea, " ");
Record r = new Record(
Integer.valueOf(tk.nextToken()),
tk.nextToken(),
tk.nextToken());
return r;
}
}

class Camaleomparatore implements Comparator<Object> {
private final String accessorName;

public Camaleomparatore(String nomeCampo) {
accessorName =
"get" +
Character.toUpperCase(nomeCampo.charAt(0)) +
nomeCampo.substring(1, nomeCampo.length());
}

public int compare(Object a, Object b) {
try {
Class<?>[] noArgs = null;
Class<?> ca = a.getClass();
Class<?> cb = b.getClass();
Method ma = ca.getMethod(accessorName, noArgs);
Method mb = cb.getMethod(accessorName, noArgs);
Object[] params = null;
Object fa = ma.invoke(a, params);
Object fb = mb.invoke(b, params);
Method compare = fa.getClass().getMethod("compareTo", Object.class);
Object result = compare.invoke(fa, new Object[] { fb });
return (Integer)result;
} catch(Exception ex) {
throw new RuntimeException(ex);
}
}
}

/** Un'età, un nome e un cognome */
class Record {
private final int eta;
private final String nome;
private final String cognome;

public Record(int eta, String nome, String cognome) {
this.eta = eta;
this.nome = nome;
this.cognome = cognome;
}

public int getEta() {
return eta;
}

public String getNome() {
return nome;
}

public String getCognome() {
return cognome;
}

public String toString() {
return nome + " " + cognome + " " + eta;
}
}

Il codice qui sopra funziona se i dati aderiscono ad una convenzione. Dato un campo "ciccio" devono avere un metodo pubblico "getCiccio". Il valore restituito da "getCiccio" deve avere un metodo "compareTo" che accetta un argomento compatibile in assegnamento con il tipo del campo "ciccio".

Vale anche per i dati primitivi perchè l'invocazione riflessiva di un metodo usa le classi involucro per circondare int, double, boolean eccetera e tutte queste classi involucro hanno un metodo compareTo(WrapperDelloStessoTipo).

Il codice qui sopra tratta ogni errore come un evento catastrofico ma è ovviamente possibili rilassare un po' le certezze su cui è fondato.

dnarod
16-11-2006, 22:04
very tnx!

dnarod
17-11-2006, 09:32
ok, era decisamente quello che avevo in mente...grazie, alla fine, come pensavo ci andavano di mezzo generici+riflessione...ora non mi resta che aggiustare un po bene, fare un interfaccetta minima ma godibile e capire come buttare un output su piu stream contemporaneamente senza ragionare troppo (se e possibile farlo senza ragionare troppo)...tra l altro ti ringrazio per il tutorialone su gridbadlayout, quasi quasi provo :)

mad_hhatter
17-11-2006, 09:51
così per curiosità, cosa sono i generici e le riflessioni?

PGI-Bis
17-11-2006, 12:20
Definirei la riflessione come la capacità di un oggetto di accedere ad una sua proprietà attraverso sè stesso.

Fa coppia con l'introspezione che è la capacità di un oggetto di determinare autonomamente le proprietà che possiede.

Così se prendi un oggetto di cui non sai nulla:

oggetto //riferimento di un tipo ignoto

per l'introspezione hai comunque la possibilità di scoprirne vita morte e miracoli: quali campi possiede, qual'è il tipo di quei campi, quali metodi possiede, com'è fatto il ramo ereditario a cui appartiene eccetera. Con la riflessione hai la possibilità di operare sui metodi e sui campi di quell'oggetto. L'intera questione è oltremodo interessante perchè se è vero che il linguaggio di programmazione Java è polimorfo (esistono più tipi ed ogni espressione ha un tipo noto al momento della compilazione) è anche vero che attraverso l'introspezione e la riflessione il suo comportamento diventa assimilabile a quello di un linguaggio amorfo (dove le espressioni non hanno un tipo, a la Smalltalk). Scomodo ma amorfo :D.

Generico è il termine con cui ci si riferisce ad una classe, un'interfaccia o un metodo che dichiara di operare su un tipo totalmente o parzialmente ignoto al momento della compilazione.

Parametrico è il termine con cui ci si riferisce ad una classe, un'interfaccia o un metodo generico nel momento in cui il tipo su cui è opera è svelato tramite quella che è chiamata dichiarazione di parametro.

Così

public class Classe<P> {}

è un tipo generico la cui dichiarazione indica l'operatività su un tipo ignoto a cui si fa riferimento, nel corpo della classe, attraverso l'etichetta "P", mentre in:

Classe<String> x = new Classe<String>();

il tipo del riferimento "x" è il tipo parametrico Classe<String> e la parola String racchiusa tra le parentesi angolari "< >" è detta dichiarazione di parametro.

Trovi un esauriente tutorial sull'argomento qui:

http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf

mad_hhatter
17-11-2006, 19:09
grazie mille... quindi con "generico" si intende proprio quello che nella doc di java 5 viene chiamato "generics". La prima volta che ho visualizzato la doc di java 5 pensavo che la scrittura Class<P> fosse un errore del browser :D (non ridete, vi prego), poi ho scoperto la sua potenza e comodità!

Non ho esperienza della riflessione e della introspezione, ma ne intuisco la potenza!

tornando alla soluzione del problema del topic, nel post in cui hai inserito il codice dicevi che gli oggetti devono seguire la convenzione che se hanno un campo field devono avere anche un metodo pubblico getField(). volevo sapere se è possibile formalizzare questo requisito in modo che il compilatore possa controllare se la convenzione viene rispettata oppure se la cosa è lasciata al programmatore

PGI-Bis
17-11-2006, 19:18
Non è possibile farlo controllare al compilatore. E' possibile farlo attraverso APT, il processore di annotazioni, ma non so quanto sarebbe sensato.

In verità l'uso della riflessione è la formalizzazione in Java della mancata conoscenza di una o più caratteristiche di un oggetto. Se fosse non possibile ma semplicemente opportuno far controllare al compilatore che le informazioni di cui si dichiara "l'ignoranza" esistano allora, necessariamete, tali informazioni dovrebbero essere disponibili all'atto della compilazione e, quindi, già in possesso di chi abbia scritto il programma.

Discorso diverso è all'atto dell'esecuzione. Lì puoi sempre controllare, tramite l'introspezione, se un caratteristica ipotizzata durante la costruzione del programma sia o non sia effettivamente presente.

dnarod
18-11-2006, 14:43
sto producendo una tonnellata di codice per fare una cosa che pensavo mi avrebbe portato via meno tempo...partendo dal tuo suggerimento devo modificare in modo opportuno:
-i metodi di ordinamento non sono di libreria e non sono su arrayList, ma sono su array e sono definiti da me (per semplicita lo faccio solo con i basilari, selection insertion e merge sort)

-la mia idea è che il programma chieda all utente solo il nome di una classe (tipo "MiaClasse") e il nome di un file di testo in cui ha messo i dati (tipo stringa double stringa su ogni riga e separati da un qualche delimitatore, come mi hai giustamente suggerito)...dopodiche io creo un array di oggetti di tipo corrispondente al nome che ha fornito l utente; esempio: se ha scritto "Persona" e poi "dati.txt" io produrro un array Persona[] di lunghezza adeguata e quello sara il mio punto di partenza per l ordinamento

e fino a qui, a parte una possibile bruttissima implementazione (oltre che inelegante, trattandosi nel mio caso di un programmatore poco esperto) il concetto dovrebbe reggere...l unica cosa che ho mollato fuori da tutto, credo che sia aime anche la piu importante...come creo gli oggetti da mettere all interno dell array mentre leggo il file?

mi spiego, non voglio fare un programma allucinante, non mi interessa nemmeno che sia a prova di errore o cose simili, solo che se, come vorrei, penso a piu classi da poter "testare", magari una classe ha 3 campi, l altra 5 eccetera...nel file di testo con i dati (o megli oancora se faccio l input da tastiera, ma non lo faro perche senno uno diventa vecchio) ci si aggiusta facilmente, ma nel programma che deve rendersi conto di questo? cioe, a un certo punto, ho un array dove devo tericamente mettere un oggetto di classe xxx che ha i campi x, y e z...posso trovare il modo di tirarmi fuori il costruttore che prende i 3 parametri (penso sia macchinoso perche deve anche analizzare la stringa) o devo definire dei metodi modificatori e, dopo aver creato l oggetto, modificare i campi per ogni token della stirnga (che imho è forse piu semplice da realizzare), o ancora qualche possibile modo?

disclaimer: mi rendo conto che probabilmente tutto il casino che sto facendo è pressoche inutile, ma visto che prima o poi volevo farmi un giro dentro la riflessione di java, e visto che ho avuto st esercizio (mi è sembrato un buon pretesto), ho pensato di perderci un po di tempo....grazie in anticipo a chi si vuol prendere il mal di testa di helparmi in un programma inutile come questo :)

dnarod
18-11-2006, 15:22
anzi, invece di spiegare cosi sommariamente vedo di pulire un po e postare il codice con qualche commento illuminante (cosi si capira ancora meglio l assoluta inutilita dell impresa :) )

PGI-Bis
18-11-2006, 15:33
Sarà anche inutile ma è interessante. E non è così inutile: tutto il J2EE si fonda su meccanismi che dall'ignoto passano al noto attraverso delle convenzioni non espresse attraverso il codice.

Supponiamo che basti il nome ad identificare la classe.

Tizio dice "Persona", "dati.txt".

La prima cosa da stabilire è cosa serva a creare una Persona. E qui inizi stabilendo un prima convenzione. Ad esempio puoi dire: ciò che consente di creare una persona è un insieme di valori da assegnare ad ognuno dei suoi campi. Vale a dire che se Persona ha i campi nome e cognome a te servono un valore per il nome ed un valore per il cognome. Come fai a sapere quali campi possieda Persona. Introspezione.

Class<?> classeDati = Class.forName(nomePropostoDall'utente);
Field[] campi = classeDati.getDeclaredFields();

L'array "campi" contiene ora tutti i campi, pubblici o privati che siano, dichiarati nella classe corrispondente al nome fornito.

Se persona è:

class Persona {
private String nome, cognome;
}

allora campi avrà due componenti, un java.lang.reflect.Field per "nome" e un java.lang.reflect.Field per "cognome", entrambi di tipo String. Il contratto di getDeclaredFields non specifica l'ordine in cui i campi siano restituiti. Dunque l'ordine non è una costante, quindi non puoi usarlo per ricostruire l'oggetto Persona.
Il che ci porta ad un bel "e se". E se il file di testo con i dati fosse fatto così...

nome=Pippo,cognome=Rossi
nome=Gianni,cognome=Verdi

Be', l'ordine diventa superfluo. Il che ci suggerisce una bozza di un'idea di un quasi algortimo. Chiamiamo "base dati" il file di testo, "record" un gruppo di informazioni in questa base dati corrispondente ad un singolo dato complesso, "campo" una coppia nome-valore contenuta in un record e "destinatario" l'istanza dell'oggetto di tipo ignoto compatibile con la struttura di un record nella base dati.

dato un destinatario
per ogni record nella base dati
per ogni campo nel record
assegna il valore del campo al destinatario

E ancora convenzioni...a carrettate. Se il destinatario ha un costruttore senza argomenti allora possiamo dire che "dato un destinatario" sia semplicemente:

Object instance = classeDati.newInstance();

Se un record occupa una linea nel file di testo allora "per ogni record" diventa "per ogni linea" e se nel file le coppie nome-valore sono separate da una virgola "per ogni campo nel record" diventa semplicemente "per ogni sottostringa prodotta da un separatore ",". Parimenti, se il nome ed il valore in un campo sono separati da un "=" la stringa prima dell'uguale sarà il nome e la stringa dopo l'uguale sarà il valore. Insomma, da base dati a record, da record a campo e da campo a nome e valore è una questione di parzializzazione elementare.


Supponendo, come hai saggiamente suggerito, che ogni tipo di dato sia un readable-writable JavaBean (per ogni campo privato di nome x e tipo T esiste un metodo pubblico getX che restituisce un valore di tipo T ed un metodo pubblico di nome setX che richiede un unico argomento di tipo T, JavaBean è un'altra convenzione di scrittura) assegnare il valore del campo al destinatario diventa:

prendi il nome del campo
usalo per formare il nome di un metodo setter aderente alla convenzione JavaBean
invoca riflessivamente quel metodo usando come parametro il valore del campo.

C'è il problema del valore. Il file di testo contiene...testo. Ma un campo può essere d'altro tipo. Ma è veramente un problema? No altrimenti che programmatori saremmo!.

Per ogni campo, attraverso i Field, sai quale sia il tipo atteso. Sai che il valore di un campo nel database è testo. Tutto quello che ti serve è una categoria d'oggetti in grado di trasformare un testo in un valore Java. Ad esempio:

public interface TextToValueTransformer {
Object getValue(String text);
}

Per quanto hai detto ti bastano otto master TextToValueTransfomer: ...ToString, ...ToByte, ...ToShort, ...ToInteger, ...ToFloat, ...ToDouble. Ad esempio:

public class StringTransformer implements TextToValueTrasformer {
public Object getValue(String text) { return text; }
}

public class IntegerTransfomer implements TextToValueTransformer {
public Object getValue(String text) { return Integer.parseInt(text); }
}

eccetera.

A quel punto ti basta un Registro, che abbia ad esempio la forma:

public class TransformerRegistry {
private Map<Class<?>, TextToValueTransformer> registry =
new HashMap<Class<?>, TextToValueTransformer>();

TextToValueTransformer getTransformer(Class<?> valueClass) {
return registry.get(valueClass);
}
}

naturalmente riempirai registry con i tuoi otto trasformatori associandoli alle classi dei valori che producono.

A questo punto ottenre dal campo del record il valore diventa:

trova nell'array di Field ottenuto per introspezione dalla classe fornita dall'utente, il Field il cui nome (getName()) corrisponda al nome del campo. Da questo Field ottieni il tipo Java del valore:

Class<?> fieldClass = field.getType();

con il tipo del campo ottieni il trasformatore dal registro:

TextToValueTransformer tr = trRegistry.getTrasformer(fieldClass);

e con il trasformatore ottieni il valore Java:

Object fieldValue = tr.getValue(stringa);

Con fieldValue e l'invocazione riflessiva del metodo setter per il campo sul destinatario assegni il valore all'istanza.

Ripetendo per ogni campo e per ogni record ottiene il risultato proposto.

dnarod
18-11-2006, 16:26
accidenti, hai risposto in un amen...sei veramente huge! non ho ancora la certezza matematica di saper fare cio che hai detto, ma il passo dovrebbe essere breve, insomma il piu è fatto e la teoria me l hai abbondantemente spiegata...mo provo! tnx, appena pelo sta gatta te lo dico :)

ps: ma allora non sono proprio un seghino, almeno stavo iniziando a impostare pure io una soluzione tipo quella che hai proposto tu, ma non avevo un adeguata padronanza della sintassi ne il colpo d occhio necessario per realizzarla in poco tempo

PGI-Bis
18-11-2006, 16:38
:D Quando hai un dubbio sulle tue capacità ad affrontare il problema, ricorda le immortali parole del grande maestro yOOda:

"Giudichi tu forse me dall'esperienza? Non dovresti infatti perchè mio alleato è l'orientamento agli oggetti e un potente alleato esso è."

dnarod
19-11-2006, 13:00
accidenti, ho appena fatto lo sgargiante dicendo che non ero proprio una sega, e non riesco a capire al 100% il meccanismo (ergo non produco qualcosa di funzionante)...ero certo di aver capito benissimo, ma arrivato al punto di dover riempire il registro con i trasformatori mi blocco; cioe, prendo la base di dati, la apro, tiro fuori una riga (registro contenente tutti i dati che servono al mio oggetto); poi prendo dall array dei campi della classe che mi interessa quello che matcha il nome del campo scelto dall' utente (e fino a qui è tutto banale); ma poi mi perdo nella mia ignoranza sui generici (che so usare solo a livello molto basilare). una volta che ho il filed anche prendere il tipo è banale...ma quando devo ottenere il trasformatore:

TextToValueTransformer tr = trRegistry.getTrasformer(fieldClass);
Object fieldValue = tr.getValue(stringa);

mi blocco...mi manca uno sputo per capire, ma sembro avere il cervello un po stupido...mi puoi helpare a capire come capire (lol) ?

PGI-Bis
19-11-2006, 16:01
Coltello tra i denti, ampio sorriso e via all'avventura.

Ti faccio un esempio perchè certe cose sembrano magiche finchè uno non le vede. Al che dice: tutto qui, e non si poteva spiegare meglio!

Questo è il file di testo della base dati:

nome=franco,cognome=rossi,eta=125
nome=gianni,cognome=verdi,eta=110
nome=marina,cognome=azzurri,eta=99
nome=giovanna,cognome=blu,eta=56

Questo è il bean i cui dati sono contenuti in quelle righe (una riga un bean).

/**Un esempio di readable/writable JavaBean*/
public class DataBean {
private String nome, cognome;
private int eta;

public void setNome(String nome) {
this.nome = nome;
}

public void setCognome(String cognome) {
this.cognome = cognome;
}

public void setEta(int eta) {
this.eta = eta;
}

public String getNome() {
return nome;
}

public String getCognome() {
return cognome;
}

public int getEta() {
return eta;
}

public int compareTo(DataBean that) {
return -1;
}

public String toString() {
StringBuilder buffer = new StringBuilder();
buffer.append("nome=").append(nome).append(",");
buffer.append("cognome=").append(cognome).append(",");
buffer.append("eta=").append(eta);
return buffer.toString();
}
}

il compareTo è lì a simulacro del compareTo che i tuoi bean hanno e che io sono troppo pigro per rappresentare :D.

Seguono i trasformatori. Prima il tipo:

/** Caratteristiche degli oggetti in grado di trasformare un
valore espresso attraverso caratteri in un valore espresso
attraverso un Object Java */
public interface TextToValueTransformer {
Object getValue(String text);
}

Poi due trasformatori per testo e interi:

/** Trasforma una stringa di testo in uno String */
public class StringTransformer implements TextToValueTransformer {

public Object getValue(String text) {
return text;
}
}

/** Trasforma una stringa di testo in un Integer */
public class IntegerTransformer implements TextToValueTransformer {

public Object getValue(String text) {
return Integer.parseInt(text);
}
}

Il registro è simile a quello che avrai già fatto:

import java.util.*;

/** Registro dei trasformatori di testo in valore Java. Il registro
accoppia un tipo di dato al suo trasformatore. */
public class TransformerRegistry {
private Map<Class<?>, TextToValueTransformer> registry =
new HashMap<Class<?>, TextToValueTransformer>();

public TransformerRegistry() {
registry.put(String.class, new StringTransformer());
registry.put(Integer.class, new IntegerTransformer());
registry.put(int.class, new IntegerTransformer());
}

/** Restituisce il trasformatore accoppiato al tipo c o null
se manchi un'associazione */
public TextToValueTransformer getTransformer(Class<?> c) {
return registry.get(c);
}
}

Nota che il tipo di un campo primitivo è tipo_primitivo.class. Riflessione e introspezione si comportano in modo diverso: per l'introspezione int.class è diverso da Integer.class ma la riflessione usa solo il tipo Integer. Se il tipo runtime del campo è int, usa integer.intValue(), se è Integer usa integer e via.

Poi arriva la base dati, che allego per completezza.

/** Un campo di un Record in un DataBase */
public interface RecordField {
String getFieldName();
String getFieldValue();
}

/** Un Record in un DataBase */
public interface Record {
RecordField getField(String fieldName);
}

import java.util.*;

/** Un DataBase è qualcosa che possiede una collezione di
Record (con buona pace di 75 anni di studi sulle basi dato =))*/
public interface DataBase {
Collection<Record> getRecords();
}


import java.util.*;
import java.net.*;
import java.io.*;

/** Una base dati rappresentata da un file di testo. Il file di testo
deve avere la forma:<b>
[record][interruzione di linea]<br>
[record][interruzione di linea]<br>
[record][interruzione di linea]<br>
...<br>
[record] ha la forma:<br>
[campo][separatore][campo][separatore][campo][separatore]...<br>
[separatore] è il carattere ',' (virgola)<br>
[campo] ha la forma:<br>
[nome][separatore][valore]
[separatore] è il carattere '=' (uguale)<br>
[nome] e [valore] sono sequenze di caratteri<br>
La codifica del testo è quella della piattaforma. ASCII e via =)*/
public class PlainTextDataBase implements DataBase {
private ArrayList<Record> recordList = new ArrayList<Record>();

/** Carica in memoria il contenuto del file di testo
riferito da address */
public PlainTextDataBase(URL address) {
Scanner in = null;
try {
in = new Scanner(address.openStream());
while(in.hasNextLine()) {
parseRecord(in.nextLine());
}
} catch(IOException ex) {
ex.printStackTrace();
} finally {
if(in != null) in.close();
}
}

/** @inheritDoc */
public Collection<Record> getRecords() {
return recordList;
}

/* Trasforma una linea di testo in un RecordObject e lo aggiunge
a RecordList */
private void parseRecord(String recordData) {
RecordObject record = new RecordObject();
StringTokenizer tk = new StringTokenizer(recordData, ",");
while(tk.hasMoreTokens()) {
String fieldData = tk.nextToken();
int splitIndex = fieldData.indexOf("=");
String fieldName = fieldData.substring(0, splitIndex);
String fieldValue = fieldData.substring(
splitIndex + 1, fieldData.length());
record.put(fieldName,
new RecordFieldObject(fieldName, fieldValue));
}
recordList.add(record);
}

/* Definizione dell'oggetto usato da PlainTextDataBase
come RecordField */
private class RecordFieldObject implements RecordField {
private String name, value;

public RecordFieldObject(String name, String value) {
this.name = name;
this.value = value;
}

public String getFieldName() {
return name;
}

public String getFieldValue() {
return value;
}
};

/* Definizione dell'oggetto usato da PlainTextDataBase come
Record */
private class RecordObject implements Record {
private Map<String, RecordField> recordFields =
new HashMap<String, RecordField>();

private void put(String key, RecordField value) {
recordFields.put(key, value);
}

public RecordField getField(String fieldName) {
return recordFields.get(fieldName);
}
};
}

Tutta la manfrina qui sopra legge il file di testo. A 'sto punto manca solo qualcosa che semplifichi un attimo la scrittura riflessiva di un valore di un bean. Io immagino per "semplice" qualcosa che dato un bean, il nome della proprietà ed il suo valore mi consenta di impostare quel valore per quella proprietà in quel javabean con un colpo solo. Poi c'è anche quella faccenda del sapere il tipo di ogni campo del bean. Dal frullatore a me salta fuori questo.

import java.util.*;
import java.beans.*;
import java.lang.reflect.*;

/** Gestisce le proprietà di un bean. */
public class BeanManager {
private Map<String, PropertyDescriptor> propertyMap =
new HashMap<String, PropertyDescriptor>();
private Class<?> beanClass;

/** Crea un BeanManager che opera con le informazioni del tipo
in argomento */
public BeanManager(Class<?> beanClass) throws Exception {
SimpleBeanInfo beanInfo =
(SimpleBeanInfo)Introspector.getBeanInfo(beanClass);
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for(PropertyDescriptor p : pds) {
propertyMap.put(p.getName(), p);
}
//qui ci dirà se esiste o non esiste un bel costruttore
//senza argomenti
beanClass.newInstance();
this.beanClass = beanClass;
}

/** Restituisce un bean ricavato dalla classe indicata in costruzione */
public Object newBean() {
try {
return beanClass.newInstance();
} catch(Exception ex) {
throw new RuntimeException("E come hai fatto a costruirlo!!!??");
}
}


/** Restituisce l'insieme di tutti i nome delle proprietà del bean
gestito da questo BeanManager */
public Collection<String> getPropertyNames() {
return propertyMap.keySet();
}

/** Restituisce il tipo della proprietà nominata in argomento. Ad
esempio se esiste una proprietà "nome" di tipo String questo
metodo restituirà String.class. Restituisce null se non trovi
una proprietà con il nome in argomento */
public Class<?> getPropertyClass(String propertyName) {
PropertyDescriptor pd = propertyMap.get(propertyName);
return pd == null ? null : pd.getPropertyType();
}

/** Invocazione riflessiva del metodo setter per la proprietà propertyName sull'oggetto
bean con il valore propertyValue. Ad esempio, se la proprietà è "nome" questo metodo
invocherà riflessivamente bean.setNome(propertyValue) */
public void setPropertyFor(Object bean, String propertyName, Object propertyValue) {
PropertyDescriptor pd = propertyMap.get(propertyName);
if(pd != null) {
Method setter = pd.getWriteMethod();
if(setter != null) {
try {
setter.invoke(bean, propertyValue);
} catch(Exception ex) {
System.err.println(ex.getMessage());
}
}
}
}
}

A questo punto con un DataBase e un Class<?> possiamo tirar fuori una lista di JavaBean con un cacciavite ad hoc.

import java.util.*;

/** Estra un insieme di bean da un DataBase */
public class BeanExtractor {

/** Restituisce una collezione di bean di tipo BeanClass ricavandone
i dati dal DataBase beanData */
public Collection<Object> extractBeans(Class<?> beanClass, DataBase beanData)
throws Exception
{
TransformerRegistry transformers = new TransformerRegistry();
ArrayList<Object> beans = new ArrayList<Object>();
BeanManager manager = new BeanManager(beanClass);
for(Record record : beanData.getRecords()) {
Object bean = manager.newBean();
fillBean(bean, record, manager, transformers);
beans.add(bean);
}
return beans;
}

/* Dato un Record usa i suoi valori, trasformati dai TextToValueTransformer
del registro transformers, per "riempire" i campi di bean */
private void fillBean(Object bean, Record record, BeanManager manager,
TransformerRegistry transformers)
{
//per ogni proprietà nel bean
for(String propertyName : manager.getPropertyNames()) {
//piglia il campo
RecordField recordField = record.getField(propertyName);
if(recordField != null) {
//se c'è, ottieni il tipo della proprietà del bean
Class<?> beanPropertyType = manager.getPropertyClass(propertyName);
//ottiene il trasformatore dal registro
TextToValueTransformer transformer =
transformers.getTransformer(beanPropertyType);
//se il trasformatore esiste
if(transformer != null) {
Object propertyValue = transformer.getValue(recordField.getFieldValue());
manager.setPropertyFor(bean, propertyName, propertyValue);
}
}
}
}
}

E arriviamo al cacciatore che squarta il lupo: con java Main NomeClasseBean FileDelDataBase otteniamo una lista dei bean contenuti in FileDelDataBase.

import java.lang.reflect.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.beans.*;

public class Main {
public static void main(String[] args) {
Class<?> choosenClass;
DataBase db;
try {
choosenClass = Class.forName(args[0]);
db = new PlainTextDataBase(Main.class.getResource(args[1]));
} catch(Exception ex) {
ex.printStackTrace();
System.err.println("Usage: java Main BeanClassName DataBaseFilePath");
return;
}
BeanExtractor extractor = new BeanExtractor();
Collection<Object> beans;
try {
beans = extractor.extractBeans(choosenClass, db);
} catch(Exception ex) {
ex.printStackTrace();
return;
}
for(Object o : beans) {
System.out.println(o);
}
}

}

Io qui coi JavaBeans non c'ho fatto un bel niente. Tu poi vorrai ordinarli.

dnarod
20-11-2006, 12:37
come sempre sei immenso :)

tnx, appena ho due secondi mi ci butto!

dnarod
22-11-2006, 18:27
va detto che sono riuscito a piu o meno fare quello che avevo in mente (grazie mille!); preso dall' impeto pero, mi sono messo a giocare un po con l' invocazione indiretta dei metodi...vedo di descrivere piu precisamente possibile il problema:

-ho una serie di test noiosi da fare, sempre sui soliti semplici oggetti che contengono piu chiavi rispetto alle quali eseguire odrinamenti (con algoritmi vari)

-cio che mi serve per eseguire un singolo tassello del puzzle che compone il mio megatest è: il nome del metodo da chiamare (nella sua rispettiva classe, ma tanto stanno tutti in una classe statica quindi zero problemi), e il nome del campo-chiave rispetto al quale ordinare

ora, quel che ho pensato io è una cosa di questo genere


public static void call(Object[] o, String methodName, String fieldName)
{
Method method = null;
try
{
method = "ClasseOrdinamento".class.getMethod(methodName, new Class[]
{Object[].class, GenericComparator.class});
}
catch(NoSuchMethodException e){out.println("Metodo inesistente");}
try
{
method.invoke(o, new Object[] {o, new GenericComparator(fieldName)});
}
catch(InvocationTargetException e){out.println("Target illegale");}
catch(IllegalAccessException e){out.println("Accesso illegale");}
}


dove ClasseOrdinamento è la classe dove stanno i medoti da pescare (insomma nulla di complesso)...eppure quando provo a chiamare tipo call(<il mio array di oggetti>, <nome metodo>, <nome campo-chiave>) mi sbatte subito fuori con un bel "Metodo inesistente"....premetto che se provo il tutto solo con array di interi (togliendo solo comparator e cambiando da Object[] a int[], visto che i medoti che lavorano su interi sono vecchie versioni che tengo nella stessa classe) non mi da problemi e fa il suo dovere...

quindi devo ipotizzare che non trovi i metodi nella forma package-ClasseOrdinamento-nomeMetodo perche devo aver pasticciato qualcosa...


...la domanda è, cosa posso aver pasticciato?

ps. non mi odiare :)

PGI-Bis
22-11-2006, 19:05
Il codice che hai incollato è molto bizzarro ma diciamo che rende l'idea.

Posso solo dirti che se java ti dice che quel metodo non c'è... non c'è.

Controlla bene la firma dei metodi che vuoi invocare. Eventualmente, anzichè il laconico "metodo inesistente", fai stampare l'elenco dei metodi pubblici della classe (che ottieni in forma d'array con getMethods) seguito dal nome del metodo richiesto e dai parametri passati a "call".

Non so se sia proprio quello che cercavi o no ma nel codice che riporti l'idea proposta è che il metodo da invocare abbia la forma:

public methodName(Object[] o, GenericComparator g)

è così che sono fatti quei metodi?

Nota che getMethod cerca solo metodi pubblici. Se vuoi invocare riflessivamente un metodo non pubblico devi prima trovarlo con getDeclaredMethod e poi invocarlo.

L'invocazione riflessiva di un metodo non pubblico segue le norme sull'accessibilità dei membri (la storia dei modificatori default, private, protected, public) a meno che il membro acceduto non sia resto sempre accessibile (member.setAccessible(true)), cosa caldamente sconsigliata perchè qualsiasi politica di sicurezza che non si chiami harakiri.policy impedisce l'uso di setAccessible.

dnarod
22-11-2006, 19:56
santissimi numi!! era un errore di "battitura"....grazie per avermi scritto quelle due righe! il metodo è sortqualsiasi(Object[], Comparator)!!!! sbagliavo a passare il parametro formale, GenericComparator.class invece di Comparator.class, mannaggia a chi ha inventato il cut & pest (alla piemontese come diceva sempre una mia profia gloriosa :))

il che pero mi porta a un altro quesito; funzionare funziona, ma, come dici giustamente, il codice è forse un po bizzarro...anche io me ne rendo conto, ma non saprei come abbellirlo (quando passo, come elemento dell' array di parametri attuali, l' array stesso, mi rendo conto che cio è un po strano, ma dato che funziona non mi sono chiesto troppo)

dici che ci sono idee migliori??

PGI-Bis
22-11-2006, 20:07
No va bene. Le bizzarrie a cui mi riferivo sono:

"ClasseOrdinamento".class

Le virgolette non ci vanno altrimenti è un errore (il letterale class partecipa alla formazioni di alcune espressioni primarie se preceduto da un punto e dal nome di un tipo, con le virgolette ottieno un'istanza di String e il compilatore si lamenta).

E c'è questa perlina:

"Target illegale"

:D

L'eccezione InvocationTargetException è rilasciata quando l'esecuzione delle istruzioni contenute nel metodo invocato riflessivamente producano un'eccezione. Insomma, non "Target illegale" ma "L'esecuzione del metodo ha prodotto un'eccezione".

dnarod
22-11-2006, 20:16
aaaa capito, allora ci avevo azzeccato con il mettere l oggetto stesso nei parametri....

"ClasseOrdinamento" (ho specificato ma forse poco e male) è la mia classe di ordinamento che si chiama in un altro modo...ho messo le virgolette per essere piu generale (pure se molto informale ed equivocabile)....

il "target illegale" invece deriva dalla mia scarsa esperienza, mea culpa....pero divertente :)

pensa a zio, pochi mesi fa con mia zia in una stazione della metro in francia: lei fa a lui: "dove siamo ora?" e lui, non sapendo un h di francese e non avendo idea, risponde "...mmmm, siamo a sortie" :))))))))))