Esiste una libreria Java in grado di "diffondere" due oggetti?


90

Esiste una libreria di utilità Java che è analoga al diff del programma Unix, ma per gli oggetti? Sto cercando qualcosa che possa confrontare due oggetti dello stesso tipo e generare una struttura dati che rappresenti le differenze tra loro (e possa confrontare ricorsivamente le differenze nelle variabili di istanza). Sto Non alla ricerca di un'implementazione Java di un diff testo. Inoltre, non sto cercando aiuto su come usare la riflessione per farlo.

L'applicazione che sto mantenendo ha una fragile implementazione di questa funzionalità che ha avuto alcune scelte di design scadenti e che deve essere riscritta, ma sarebbe ancora meglio se potessimo usare qualcosa di disponibile.

Ecco un esempio del tipo di cosa che sto cercando:

SomeClass a = new SomeClass();
SomeClass b = new SomeClass();

a.setProp1("A");
a.setProp2("X");

b.setProp1("B");
b.setProp2("X");

DiffDataStructure diff = OffTheShelfUtility.diff(a, b);  // magical recursive comparison happens here

Dopo il confronto, l'utilità mi direbbe che "prop1" è diverso tra i due oggetti e "prop2" è lo stesso. Penso che sia più naturale per DiffDataStructure essere un albero, ma non sarò pignolo se il codice è affidabile.


9
controlla questo, code.google.com/p/jettison
denolk


14
Questo non è un duplicato di "Come verificare l'uguaglianza di grafici a oggetti complessi?" Sto cercando una libreria, non consigli su come farlo. Inoltre, mi interessa il delta, non se sono uguali o meno.
Kaypro II

1
Guarda github.com/jdereg/java-util che ha un'utilità GraphComparator. Questa classe genererà un elenco di delta tra i grafici a oggetti. Inoltre, può unire (applicare) un delta a un grafico. Effettivamente è List <Delta> = GraphComparator (rootA, rootB). Così come GraphComparator.applyDelta (rootB, List <Delta>).
John DeRegnaucourt

So che non vuoi usare la riflessione, ma questo è sicuramente il modo più semplice / semplice per implementare qualcosa del genere
RobOhRob

Risposte:


44

Potrebbe essere un po 'tardi, ma mi trovavo nella stessa situazione come te e ho finito per creare la mia libreria proprio per il tuo caso d'uso. Dato che sono stato costretto a trovare una soluzione da solo, ho deciso di rilasciarlo su Github, per risparmiare agli altri il duro lavoro. Puoi trovarlo qui: https://github.com/SQiShER/java-object-diff

--- Modifica ---

Ecco un piccolo esempio di utilizzo basato sul codice OP:

SomeClass a = new SomeClass();
SomeClass b = new SomeClass();

a.setProp1("A");
a.setProp2("X");

b.setProp1("B");
b.setProp2("X");

DiffNode diff = ObjectDifferBuilder.buildDefault().compare(a, b);

assert diff.hasChanges();
assert diff.childCount() == 1;
assert diff.getChild('prop1').getState() == DiffNode.State.CHANGED;

1
Posso indicarti questo post che mostra un caso d'uso di java-object-diff? stackoverflow.com/questions/12104969/… Grazie mille per questa utile libreria!
Matthias Wuttke

Ho dato un'occhiata al tuo post e ho scritto una risposta che si è rivelata troppo lunga per essere pubblicata come commento, quindi ecco il "succo": gist.github.com/3555555
SQiShER

2
Neat. Ho finito per scrivere anche la mia implementazione. Non sono sicuro che avrò la possibilità di esplorare veramente la tua implementazione, ma sono curioso di sapere come gestisci gli elenchi. Ho finito per gestire tutte le differenze di raccolta come differenze di Maps (definendo le chiavi in ​​modi diversi a seconda che si tratti di un elenco, set o mappa; quindi utilizzando le operazioni di set sul keySet). Tuttavia, questo metodo è estremamente sensibile alle modifiche dell'indice di elenco. Non ho risolto il problema perché non ne avevo bisogno per la mia applicazione, ma sono curioso di sapere come lo hai gestito (più simile a un diff di testo, presumo).
Kaypro II

2
Hai menzionato un buon punto. Le collezioni sono davvero difficili da gestire. Soprattutto se supporti la fusione, come faccio io. Ho risolto il problema utilizzando le identità degli oggetti per rilevare se un elemento è stato aggiunto, modificato o rimosso (tramite hashCode, è uguale a e contiene). La cosa buona è che posso trattare tutte le collezioni allo stesso modo. La cosa brutta è che non posso gestire correttamente (ad esempio) ArrayList che contengono lo stesso oggetto più volte. Mi piacerebbe aggiungere il supporto per questo, ma sembra essere piuttosto complicato e fortunatamente nessuno lo ha ancora chiesto. :-)
SQiShER

Daniel, grazie per il tuo commento! Hai ragione, ricostruire l'oggetto originale dai diff è difficile, ma nel mio caso non è troppo importante. Inizialmente, ho memorizzato le versioni precedenti del mio oggetto nel database, come mi consigliate. Il problema era che la maggior parte dei documenti di grandi dimensioni non cambiava, c'erano molti dati duplicati. Questo è il motivo per cui ho iniziato a cercare alternative e ho trovato java-object-diff. Ho anche avuto difficoltà con raccolte / mappe e modificato i miei metodi "uguali" per verificare l'uguaglianza del database primario (e non dell'intero oggetto), questo ha aiutato.
Matthias Wuttke

40

http://javers.org è una libreria che fa esattamente ciò di cui hai bisogno: ha metodi come compare (Object leftGraph, Object rightGraph) che restituisce l'oggetto Diff. Diff contiene un elenco di modifiche (ReferenceChange, ValueChange, PropertyChange) ad es

given:
DummyUser user =  dummyUser("id").withSex(FEMALE).build();
DummyUser user2 = dummyUser("id").withSex(MALE).build();
Javers javers = JaversTestBuilder.newInstance()

when:
Diff diff = javers.compare(user, user2)

then:
diff.changes.size() == 1
ValueChange change = diff.changes[0]
change.leftValue == FEMALE
change.rightValue == MALE

Può gestire i cicli nei grafici.

Inoltre puoi ottenere un'istantanea di qualsiasi oggetto grafico. Javers ha serializzatori e deserializzatori JSON per eseguire snapshot e modifiche in modo da poterli salvare facilmente nel database. Con questa libreria puoi facilmente implementare un modulo per l'auditing.


1
questo non funziona. come si crea un'istanza Javers?
Ryan Vettese

2
Javers ha un'eccellente documentazione che spiega tutto: javers.org
Paweł Szymczyk

2
Ho provato a usarlo ma è solo per Java> = 7. Ragazzi, ci sono ancora persone che lavorano a progetti su Java 6 !!!
jesantana

2
Cosa succede se i due oggetti che confronto hanno un elenco di oggetti internamente e vedrò anche quelle differenze? Qual è il livello di gerarchia che javers potrebbe confrontare?
Deepak

1
Sì, vedrai differenze sugli oggetti interni. Al momento non esiste una soglia per il livello gerarchico. Javers andrà il più in profondità possibile, il limite è la dimensione dello stack.
Paweł Szymczyk

16

Sì, la libreria java-util ha una classe GraphComparator che confronterà due Java Object Graphs. Restituisce la differenza come un elenco di delta. Il GraphComparator consente anche di unire (applicare) anche i delta. Questo codice ha solo dipendenze da JDK, non altre librerie.


13
Sembra una bella biblioteca, dovresti menzionare che sei un collaboratore
Christos

4

Potresti anche dare un'occhiata alla soluzione di Apache. La maggior parte dei progetti lo ha già sul proprio percorso di classe sin dalla sua parte di commons-lang.

Controlla la differenza per campi specifici:
http://commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/apache/commons/lang3/builder/DiffBuilder.html

Controlla la differenza usando la riflessione:
http://commons.apache.org/proper/commons-lang/javadocs/api-3.9/org/apache/commons/lang3/builder/ReflectionDiffBuilder.html


Questa dovrebbe essere la risposta accettata!
Janaka Bandara

3

Tutta la libreria Javers ha supporto solo per Java 7, ero in una situazione in quanto voglio che venga utilizzato per un progetto Java 6, quindi mi è capitato di prendere il sorgente e modificare in modo che funzioni per Java 6 di seguito è il codice github .

https://github.com/sand3sh/javers-forJava6

Collegamento Jar: https://github.com/sand3sh/javers-forJava6/blob/master/build/javers-forjava6.jar

Ho cambiato solo le conversioni cast intrinseche supportate da Java 7 "<>" al supporto Java 6 Non garantisco che tutte le funzionalità funzioneranno poiché ho commentato un po 'di codice non necessario per me funziona per tutti i confronti di oggetti personalizzati.



1

Forse questo aiuterà, a seconda di dove usi questo codice, potrebbe essere utile o problematico. Testato questo codice.

    /**
 * @param firstInstance
 * @param secondInstance
 */
protected static void findMatchingValues(SomeClass firstInstance,
        SomeClass secondInstance) {
    try {
        Class firstClass = firstInstance.getClass();
        Method[] firstClassMethodsArr = firstClass.getMethods();

        Class secondClass = firstInstance.getClass();
        Method[] secondClassMethodsArr = secondClass.getMethods();


        for (int i = 0; i < firstClassMethodsArr.length; i++) {
            Method firstClassMethod = firstClassMethodsArr[i];
            // target getter methods.
            if(firstClassMethod.getName().startsWith("get") 
                    && ((firstClassMethod.getParameterTypes()).length == 0)
                    && (!(firstClassMethod.getName().equals("getClass")))
            ){

                Object firstValue;
                    firstValue = firstClassMethod.invoke(firstInstance, null);

                logger.info(" Value "+firstValue+" Method "+firstClassMethod.getName());

                for (int j = 0; j < secondClassMethodsArr.length; j++) {
                    Method secondClassMethod = secondClassMethodsArr[j];
                    if(secondClassMethod.getName().equals(firstClassMethod.getName())){
                        Object secondValue = secondClassMethod.invoke(secondInstance, null);
                        if(firstValue.equals(secondValue)){
                            logger.info(" Values do match! ");
                        }
                    }
                }
            }
        }
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
}

2
Grazie, ma abbiamo già il codice che percorre il grafico a oggetti utilizzando la riflessione elenca le modifiche. Questa domanda non riguarda come farlo, si tratta di cercare di evitare di reinventare la ruota.
Kaypro II

Reinventare la ruota non è male se la reinvenzione è già avvenuta. (IE: La ruota reinventata era già stata pagata.)
Thomas Eding

1
Sono d'accordo con te, ma in questo caso il codice reinventato è un pasticcio irrimediabile.
Kaypro II

3
Non controllerei Fieldsmetodi. I metodi potrebbero essere qualsiasi cosa, come getRandomNumber().
Bohemian

-3

Un approccio più semplice per dirti rapidamente se due oggetti sono diversi sarebbe quello di utilizzare la libreria apache commons

    BeanComparator lastNameComparator = new BeanComparator("lname");
    logger.info(" Match "+bc.compare(firstInstance, secondInstance));

Questo non funziona per me perché ho bisogno di sapere cosa è cambiato, non solo che c'è stato un cambiamento da qualche parte.
Kaypro II
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.