001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.lang3.builder;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.List;
022
023import org.apache.commons.lang3.ArrayUtils;
024
025/**
026 * <p>
027 * Assists in implementing {@link Diffable#diff(Object)} methods.
028 * </p>
029 * 
030 * <p>
031 * To use this class, write code as follows:
032 * </p>
033 * 
034 * <pre>
035 * public class Person implements Diffable&lt;Person&gt; {
036 *   String name;
037 *   int age;
038 *   boolean smoker;
039 *   
040 *   ...
041 *   
042 *   public DiffResult diff(Person obj) {
043 *     // No need for null check, as NullPointerException correct if obj is null
044 *     return new DiffBuilder(this, obj, ToStringStyle.SHORT_PREFIX_STYLE)
045 *       .append("name", this.name, obj.name)
046 *       .append("age", this.age, obj.age)
047 *       .append("smoker", this.smoker, obj.smoker)
048 *       .build();
049 *   }
050 * }
051 * </pre>
052 * 
053 * <p>
054 * The {@code ToStringStyle} passed to the constructor is embedded in the
055 * returned {@code DiffResult} and influences the style of the
056 * {@code DiffResult.toString()} method. This style choice can be overridden by
057 * calling {@link DiffResult#toString(ToStringStyle)}.
058 * </p>
059 * 
060 * @since 3.3
061 * @version $Id: DiffBuilder.java 1565245 2014-02-06 13:39:50Z sebb $
062 * @see Diffable
063 * @see Diff
064 * @see DiffResult
065 * @see ToStringStyle
066 */
067public class DiffBuilder implements Builder<DiffResult> {
068
069    private final List<Diff<?>> diffs;
070    private final boolean objectsTriviallyEqual;
071    private final Object left;
072    private final Object right;
073    private final ToStringStyle style;
074
075    /**
076     * <p>
077     * Constructs a builder for the specified objects with the specified style.
078     * </p>
079     * 
080     * <p>
081     * If {@code lhs == rhs} or {@code lhs.equals(rhs)} then the builder will
082     * not evaluate any calls to {@code append(...)} and will return an empty
083     * {@link DiffResult} when {@link #build()} is executed.
084     * </p>
085     * 
086     * @param lhs
087     *            {@code this} object
088     * @param rhs
089     *            the object to diff against
090     * @param style
091     *            the style will use when outputting the objects, {@code null}
092     *            uses the default
093     * @throws IllegalArgumentException
094     *             if {@code lhs} or {@code rhs} is {@code null}
095     */
096    public DiffBuilder(final Object lhs, final Object rhs,
097            final ToStringStyle style) {
098        if (lhs == null) {
099            throw new IllegalArgumentException("lhs cannot be null");
100        }
101        if (rhs == null) {
102            throw new IllegalArgumentException("rhs cannot be null");
103        }
104
105        this.diffs = new ArrayList<Diff<?>>();
106        this.left = lhs;
107        this.right = rhs;
108        this.style = style;
109
110        // Don't compare any fields if objects equal
111        this.objectsTriviallyEqual = (lhs == rhs || lhs.equals(rhs));
112    }
113
114    /**
115     * <p>
116     * Test if two {@code boolean}s are equal.
117     * </p>
118     * 
119     * @param fieldName
120     *            the field name
121     * @param lhs
122     *            the left hand {@code boolean}
123     * @param rhs
124     *            the right hand {@code boolean}
125     * @return this
126     * @throws IllegalArgumentException
127     *             if field name is {@code null}
128     */
129    public DiffBuilder append(final String fieldName, final boolean lhs,
130            final boolean rhs) {
131        if (fieldName == null) {
132            throw new IllegalArgumentException("Field name cannot be null");
133        }
134
135        if (objectsTriviallyEqual) {
136            return this;
137        }
138        if (lhs != rhs) {
139            diffs.add(new Diff<Boolean>(fieldName) {
140                private static final long serialVersionUID = 1L;
141
142                @Override
143                public Boolean getLeft() {
144                    return Boolean.valueOf(lhs);
145                }
146
147                @Override
148                public Boolean getRight() {
149                    return Boolean.valueOf(rhs);
150                }
151            });
152        }
153        return this;
154    }
155
156    /**
157     * <p>
158     * Test if two {@code boolean[]}s are equal.
159     * </p>
160     * 
161     * @param fieldName
162     *            the field name
163     * @param lhs
164     *            the left hand {@code boolean[]}
165     * @param rhs
166     *            the right hand {@code boolean[]}
167     * @return this
168     * @throws IllegalArgumentException
169     *             if field name is {@code null}
170     */
171    public DiffBuilder append(final String fieldName, final boolean[] lhs,
172            final boolean[] rhs) {
173        if (fieldName == null) {
174            throw new IllegalArgumentException("Field name cannot be null");
175        }
176        if (objectsTriviallyEqual) {
177            return this;
178        }
179        if (!Arrays.equals(lhs, rhs)) {
180            diffs.add(new Diff<Boolean[]>(fieldName) {
181                private static final long serialVersionUID = 1L;
182
183                @Override
184                public Boolean[] getLeft() {
185                    return ArrayUtils.toObject(lhs);
186                }
187
188                @Override
189                public Boolean[] getRight() {
190                    return ArrayUtils.toObject(rhs);
191                }
192            });
193        }
194        return this;
195    }
196
197    /**
198     * <p>
199     * Test if two {@code byte}s are equal.
200     * </p>
201     * 
202     * @param fieldName
203     *            the field name
204     * @param lhs
205     *            the left hand {@code byte}
206     * @param rhs
207     *            the right hand {@code byte}
208     * @return this
209     * @throws IllegalArgumentException
210     *             if field name is {@code null}
211     */
212    public DiffBuilder append(final String fieldName, final byte lhs,
213            final byte rhs) {
214        if (fieldName == null) {
215            throw new IllegalArgumentException("Field name cannot be null");
216        }
217        if (objectsTriviallyEqual) {
218            return this;
219        }
220        if (lhs != rhs) {
221            diffs.add(new Diff<Byte>(fieldName) {
222                private static final long serialVersionUID = 1L;
223
224                @Override
225                public Byte getLeft() {
226                    return Byte.valueOf(lhs);
227                }
228
229                @Override
230                public Byte getRight() {
231                    return Byte.valueOf(rhs);
232                }
233            });
234        }
235        return this;
236    }
237
238    /**
239     * <p>
240     * Test if two {@code byte[]}s are equal.
241     * </p>
242     * 
243     * @param fieldName
244     *            the field name
245     * @param lhs
246     *            the left hand {@code byte[]}
247     * @param rhs
248     *            the right hand {@code byte[]}
249     * @return this
250     * @throws IllegalArgumentException
251     *             if field name is {@code null}
252     */
253    public DiffBuilder append(final String fieldName, final byte[] lhs,
254            final byte[] rhs) {
255        if (fieldName == null) {
256            throw new IllegalArgumentException("Field name cannot be null");
257        }
258
259        if (objectsTriviallyEqual) {
260            return this;
261        }
262        if (!Arrays.equals(lhs, rhs)) {
263            diffs.add(new Diff<Byte[]>(fieldName) {
264                private static final long serialVersionUID = 1L;
265
266                @Override
267                public Byte[] getLeft() {
268                    return ArrayUtils.toObject(lhs);
269                }
270
271                @Override
272                public Byte[] getRight() {
273                    return ArrayUtils.toObject(rhs);
274                }
275            });
276        }
277        return this;
278    }
279
280    /**
281     * <p>
282     * Test if two {@code char}s are equal.
283     * </p>
284     * 
285     * @param fieldName
286     *            the field name
287     * @param lhs
288     *            the left hand {@code char}
289     * @param rhs
290     *            the right hand {@code char}
291     * @return this
292     * @throws IllegalArgumentException
293     *             if field name is {@code null}
294     */
295    public DiffBuilder append(final String fieldName, final char lhs,
296            final char rhs) {
297        if (fieldName == null) {
298            throw new IllegalArgumentException("Field name cannot be null");
299        }
300
301        if (objectsTriviallyEqual) {
302            return this;
303        }
304        if (lhs != rhs) {
305            diffs.add(new Diff<Character>(fieldName) {
306                private static final long serialVersionUID = 1L;
307
308                @Override
309                public Character getLeft() {
310                    return Character.valueOf(lhs);
311                }
312
313                @Override
314                public Character getRight() {
315                    return Character.valueOf(rhs);
316                }
317            });
318        }
319        return this;
320    }
321
322    /**
323     * <p>
324     * Test if two {@code char[]}s are equal.
325     * </p>
326     * 
327     * @param fieldName
328     *            the field name
329     * @param lhs
330     *            the left hand {@code char[]}
331     * @param rhs
332     *            the right hand {@code char[]}
333     * @return this
334     * @throws IllegalArgumentException
335     *             if field name is {@code null}
336     */
337    public DiffBuilder append(final String fieldName, final char[] lhs,
338            final char[] rhs) {
339        if (fieldName == null) {
340            throw new IllegalArgumentException("Field name cannot be null");
341        }
342
343        if (objectsTriviallyEqual) {
344            return this;
345        }
346        if (!Arrays.equals(lhs, rhs)) {
347            diffs.add(new Diff<Character[]>(fieldName) {
348                private static final long serialVersionUID = 1L;
349
350                @Override
351                public Character[] getLeft() {
352                    return ArrayUtils.toObject(lhs);
353                }
354
355                @Override
356                public Character[] getRight() {
357                    return ArrayUtils.toObject(rhs);
358                }
359            });
360        }
361        return this;
362    }
363
364    /**
365     * <p>
366     * Test if two {@code double}s are equal.
367     * </p>
368     * 
369     * @param fieldName
370     *            the field name
371     * @param lhs
372     *            the left hand {@code double}
373     * @param rhs
374     *            the right hand {@code double}
375     * @return this
376     * @throws IllegalArgumentException
377     *             if field name is {@code null}
378     */
379    public DiffBuilder append(final String fieldName, final double lhs,
380            final double rhs) {
381        if (fieldName == null) {
382            throw new IllegalArgumentException("Field name cannot be null");
383        }
384
385        if (objectsTriviallyEqual) {
386            return this;
387        }
388        if (Double.doubleToLongBits(lhs) != Double.doubleToLongBits(rhs)) {
389            diffs.add(new Diff<Double>(fieldName) {
390                private static final long serialVersionUID = 1L;
391
392                @Override
393                public Double getLeft() {
394                    return Double.valueOf(lhs);
395                }
396
397                @Override
398                public Double getRight() {
399                    return Double.valueOf(rhs);
400                }
401            });
402        }
403        return this;
404    }
405
406    /**
407     * <p>
408     * Test if two {@code double[]}s are equal.
409     * </p>
410     * 
411     * @param fieldName
412     *            the field name
413     * @param lhs
414     *            the left hand {@code double[]}
415     * @param rhs
416     *            the right hand {@code double[]}
417     * @return this
418     * @throws IllegalArgumentException
419     *             if field name is {@code null}
420     */
421    public DiffBuilder append(final String fieldName, final double[] lhs,
422            final double[] rhs) {
423        if (fieldName == null) {
424            throw new IllegalArgumentException("Field name cannot be null");
425        }
426
427        if (objectsTriviallyEqual) {
428            return this;
429        }
430        if (!Arrays.equals(lhs, rhs)) {
431            diffs.add(new Diff<Double[]>(fieldName) {
432                private static final long serialVersionUID = 1L;
433
434                @Override
435                public Double[] getLeft() {
436                    return ArrayUtils.toObject(lhs);
437                }
438
439                @Override
440                public Double[] getRight() {
441                    return ArrayUtils.toObject(rhs);
442                }
443            });
444        }
445        return this;
446    }
447
448    /**
449     * <p>
450     * Test if two {@code float}s are equal.
451     * </p>
452     * 
453     * @param fieldName
454     *            the field name
455     * @param lhs
456     *            the left hand {@code float}
457     * @param rhs
458     *            the right hand {@code float}
459     * @return this
460     * @throws IllegalArgumentException
461     *             if field name is {@code null}
462     */
463    public DiffBuilder append(final String fieldName, final float lhs,
464            final float rhs) {
465        if (fieldName == null) {
466            throw new IllegalArgumentException("Field name cannot be null");
467        }
468
469        if (objectsTriviallyEqual) {
470            return this;
471        }
472        if (Float.floatToIntBits(lhs) != Float.floatToIntBits(rhs)) {
473            diffs.add(new Diff<Float>(fieldName) {
474                private static final long serialVersionUID = 1L;
475
476                @Override
477                public Float getLeft() {
478                    return Float.valueOf(lhs);
479                }
480
481                @Override
482                public Float getRight() {
483                    return Float.valueOf(rhs);
484                }
485            });
486        }
487        return this;
488    }
489
490    /**
491     * <p>
492     * Test if two {@code float[]}s are equal.
493     * </p>
494     * 
495     * @param fieldName
496     *            the field name
497     * @param lhs
498     *            the left hand {@code float[]}
499     * @param rhs
500     *            the right hand {@code float[]}
501     * @return this
502     * @throws IllegalArgumentException
503     *             if field name is {@code null}
504     */
505    public DiffBuilder append(final String fieldName, final float[] lhs,
506            final float[] rhs) {
507        if (fieldName == null) {
508            throw new IllegalArgumentException("Field name cannot be null");
509        }
510
511        if (objectsTriviallyEqual) {
512            return this;
513        }
514        if (!Arrays.equals(lhs, rhs)) {
515            diffs.add(new Diff<Float[]>(fieldName) {
516                private static final long serialVersionUID = 1L;
517
518                @Override
519                public Float[] getLeft() {
520                    return ArrayUtils.toObject(lhs);
521                }
522
523                @Override
524                public Float[] getRight() {
525                    return ArrayUtils.toObject(rhs);
526                }
527            });
528        }
529        return this;
530    }
531
532    /**
533     * <p>
534     * Test if two {@code int}s are equal.
535     * </p>
536     * 
537     * @param fieldName
538     *            the field name
539     * @param lhs
540     *            the left hand {@code int}
541     * @param rhs
542     *            the right hand {@code int}
543     * @return this
544     * @throws IllegalArgumentException
545     *             if field name is {@code null}
546     */
547    public DiffBuilder append(final String fieldName, final int lhs,
548            final int rhs) {
549        if (fieldName == null) {
550            throw new IllegalArgumentException("Field name cannot be null");
551        }
552
553        if (objectsTriviallyEqual) {
554            return this;
555        }
556        if (lhs != rhs) {
557            diffs.add(new Diff<Integer>(fieldName) {
558                private static final long serialVersionUID = 1L;
559
560                @Override
561                public Integer getLeft() {
562                    return Integer.valueOf(lhs);
563                }
564
565                @Override
566                public Integer getRight() {
567                    return Integer.valueOf(rhs);
568                }
569            });
570        }
571        return this;
572    }
573
574    /**
575     * <p>
576     * Test if two {@code int[]}s are equal.
577     * </p>
578     * 
579     * @param fieldName
580     *            the field name
581     * @param lhs
582     *            the left hand {@code int[]}
583     * @param rhs
584     *            the right hand {@code int[]}
585     * @return this
586     * @throws IllegalArgumentException
587     *             if field name is {@code null}
588     */
589    public DiffBuilder append(final String fieldName, final int[] lhs,
590            final int[] rhs) {
591        if (fieldName == null) {
592            throw new IllegalArgumentException("Field name cannot be null");
593        }
594
595        if (objectsTriviallyEqual) {
596            return this;
597        }
598        if (!Arrays.equals(lhs, rhs)) {
599            diffs.add(new Diff<Integer[]>(fieldName) {
600                private static final long serialVersionUID = 1L;
601
602                @Override
603                public Integer[] getLeft() {
604                    return ArrayUtils.toObject(lhs);
605                }
606
607                @Override
608                public Integer[] getRight() {
609                    return ArrayUtils.toObject(rhs);
610                }
611            });
612        }
613        return this;
614    }
615
616    /**
617     * <p>
618     * Test if two {@code long}s are equal.
619     * </p>
620     * 
621     * @param fieldName
622     *            the field name
623     * @param lhs
624     *            the left hand {@code long}
625     * @param rhs
626     *            the right hand {@code long}
627     * @return this
628     * @throws IllegalArgumentException
629     *             if field name is {@code null}
630     */
631    public DiffBuilder append(final String fieldName, final long lhs,
632            final long rhs) {
633        if (fieldName == null) {
634            throw new IllegalArgumentException("Field name cannot be null");
635        }
636
637        if (objectsTriviallyEqual) {
638            return this;
639        }
640        if (lhs != rhs) {
641            diffs.add(new Diff<Long>(fieldName) {
642                private static final long serialVersionUID = 1L;
643
644                @Override
645                public Long getLeft() {
646                    return Long.valueOf(lhs);
647                }
648
649                @Override
650                public Long getRight() {
651                    return Long.valueOf(rhs);
652                }
653            });
654        }
655        return this;
656    }
657
658    /**
659     * <p>
660     * Test if two {@code long[]}s are equal.
661     * </p>
662     * 
663     * @param fieldName
664     *            the field name
665     * @param lhs
666     *            the left hand {@code long[]}
667     * @param rhs
668     *            the right hand {@code long[]}
669     * @return this
670     * @throws IllegalArgumentException
671     *             if field name is {@code null}
672     */
673    public DiffBuilder append(final String fieldName, final long[] lhs,
674            final long[] rhs) {
675        if (fieldName == null) {
676            throw new IllegalArgumentException("Field name cannot be null");
677        }
678
679        if (objectsTriviallyEqual) {
680            return this;
681        }
682        if (!Arrays.equals(lhs, rhs)) {
683            diffs.add(new Diff<Long[]>(fieldName) {
684                private static final long serialVersionUID = 1L;
685
686                @Override
687                public Long[] getLeft() {
688                    return ArrayUtils.toObject(lhs);
689                }
690
691                @Override
692                public Long[] getRight() {
693                    return ArrayUtils.toObject(rhs);
694                }
695            });
696        }
697        return this;
698    }
699
700    /**
701     * <p>
702     * Test if two {@code short}s are equal.
703     * </p>
704     * 
705     * @param fieldName
706     *            the field name
707     * @param lhs
708     *            the left hand {@code short}
709     * @param rhs
710     *            the right hand {@code short}
711     * @return this
712     * @throws IllegalArgumentException
713     *             if field name is {@code null}
714     */
715    public DiffBuilder append(final String fieldName, final short lhs,
716            final short rhs) {
717        if (fieldName == null) {
718            throw new IllegalArgumentException("Field name cannot be null");
719        }
720
721        if (objectsTriviallyEqual) {
722            return this;
723        }
724        if (lhs != rhs) {
725            diffs.add(new Diff<Short>(fieldName) {
726                private static final long serialVersionUID = 1L;
727
728                @Override
729                public Short getLeft() {
730                    return Short.valueOf(lhs);
731                }
732
733                @Override
734                public Short getRight() {
735                    return Short.valueOf(rhs);
736                }
737            });
738        }
739        return this;
740    }
741
742    /**
743     * <p>
744     * Test if two {@code short[]}s are equal.
745     * </p>
746     * 
747     * @param fieldName
748     *            the field name
749     * @param lhs
750     *            the left hand {@code short[]}
751     * @param rhs
752     *            the right hand {@code short[]}
753     * @return this
754     * @throws IllegalArgumentException
755     *             if field name is {@code null}
756     */
757    public DiffBuilder append(final String fieldName, final short[] lhs,
758            final short[] rhs) {
759        if (fieldName == null) {
760            throw new IllegalArgumentException("Field name cannot be null");
761        }
762
763        if (objectsTriviallyEqual) {
764            return this;
765        }
766        if (!Arrays.equals(lhs, rhs)) {
767            diffs.add(new Diff<Short[]>(fieldName) {
768                private static final long serialVersionUID = 1L;
769
770                @Override
771                public Short[] getLeft() {
772                    return ArrayUtils.toObject(lhs);
773                }
774
775                @Override
776                public Short[] getRight() {
777                    return ArrayUtils.toObject(rhs);
778                }
779            });
780        }
781        return this;
782    }
783
784    /**
785     * <p>
786     * Test if two {@code Objects}s are equal.
787     * </p>
788     * 
789     * @param fieldName
790     *            the field name
791     * @param lhs
792     *            the left hand {@code Object}
793     * @param rhs
794     *            the right hand {@code Object}
795     * @return this
796     */
797    public DiffBuilder append(final String fieldName, final Object lhs,
798            final Object rhs) {
799
800        if (objectsTriviallyEqual) {
801            return this;
802        }
803        if (lhs == rhs) {
804            return this;
805        }
806
807        Object objectToTest;
808        if (lhs != null) {
809            objectToTest = lhs;
810        } else {
811            // rhs cannot be null, as lhs != rhs
812            objectToTest = rhs;
813        }
814
815        if (objectToTest.getClass().isArray()) {
816            if (objectToTest instanceof boolean[]) {
817                return append(fieldName, (boolean[]) lhs, (boolean[]) rhs);
818            }
819            if (objectToTest instanceof byte[]) {
820                return append(fieldName, (byte[]) lhs, (byte[]) rhs);
821            }
822            if (objectToTest instanceof char[]) {
823                return append(fieldName, (char[]) lhs, (char[]) rhs);
824            }
825            if (objectToTest instanceof double[]) {
826                return append(fieldName, (double[]) lhs, (double[]) rhs);
827            }
828            if (objectToTest instanceof float[]) {
829                return append(fieldName, (float[]) lhs, (float[]) rhs);
830            }
831            if (objectToTest instanceof int[]) {
832                return append(fieldName, (int[]) lhs, (int[]) rhs);
833            }
834            if (objectToTest instanceof long[]) {
835                return append(fieldName, (long[]) lhs, (long[]) rhs);
836            }
837            if (objectToTest instanceof short[]) {
838                return append(fieldName, (short[]) lhs, (short[]) rhs);
839            }
840
841            return append(fieldName, (Object[]) lhs, (Object[]) rhs);
842        }
843
844        // Not array type
845        diffs.add(new Diff<Object>(fieldName) {
846            private static final long serialVersionUID = 1L;
847
848            @Override
849            public Object getLeft() {
850                return lhs;
851            }
852
853            @Override
854            public Object getRight() {
855                return rhs;
856            }
857        });
858
859        return this;
860    }
861
862    /**
863     * <p>
864     * Test if two {@code Object[]}s are equal.
865     * </p>
866     * 
867     * @param fieldName
868     *            the field name
869     * @param lhs
870     *            the left hand {@code Object[]}
871     * @param rhs
872     *            the right hand {@code Object[]}
873     * @return this
874     */
875    public DiffBuilder append(final String fieldName, final Object[] lhs,
876            final Object[] rhs) {
877        if (objectsTriviallyEqual) {
878            return this;
879        }
880
881        if (!Arrays.equals(lhs, rhs)) {
882            diffs.add(new Diff<Object[]>(fieldName) {
883                private static final long serialVersionUID = 1L;
884
885                @Override
886                public Object[] getLeft() {
887                    return lhs;
888                }
889
890                @Override
891                public Object[] getRight() {
892                    return rhs;
893                }
894            });
895        }
896
897        return this;
898    }
899
900    /**
901     * <p>
902     * Builds a {@link DiffResult} based on the differences appended to this
903     * builder.
904     * </p>
905     * 
906     * @return a {@code DiffResult} containing the differences between the two
907     *         objects.
908     */
909    @Override
910    public DiffResult build() {
911        return new DiffResult(left, right, diffs, style);
912    }
913
914}