1 /*
   2  * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
   3  *
   4  * Redistribution and use in source and binary forms, with or without
   5  * modification, are permitted provided that the following conditions
   6  * are met:
   7  *
   8  *   - Redistributions of source code must retain the above copyright
   9  *     notice, this list of conditions and the following disclaimer.
  10  *
  11  *   - Redistributions in binary form must reproduce the above copyright
  12  *     notice, this list of conditions and the following disclaimer in the
  13  *     documentation and/or other materials provided with the distribution.
  14  *
  15  *   - Neither the name of Oracle nor the names of its
  16  *     contributors may be used to endorse or promote products derived
  17  *     from this software without specific prior written permission.
  18  *
  19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  20  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  21  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  24  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  26  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  27  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  28  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30  */
  31 
  32 import java.io.IOException;
  33 import java.lang.reflect.Field;
  34 import java.util.Arrays;
  35 import java.util.HashSet;
  36 import java.util.Set;
  37 
  38 /**
  39  * The example illustrates how to use the default method for mixin.
  40  *
  41  * @author Taras Ledkov
  42  * @see MixClass
  43  * @see Debuggable
  44  * @see Equalable
  45  * @see Hashable
  46  */
  47 public class MixIn {
  48 
  49     /**
  50      * Implement this interface for a class that must be in debug print
  51      */
  52     public interface Debuggable {
  53 
  54         /**
  55          * Print the class name and all data members to the type string. 
  56          * Reflection is used for it.
  57          *
  58          * @return the string formatted like the following: <pre>
  59          * State of the: &lt;Class Name&gt;
  60          * &lt;member name&gt; : &lt;value&gt;
  61          * ...
  62          * </pre>
  63          */
  64         default String toDebugString() {
  65             StringBuilder sb = new StringBuilder();
  66             sb.append("State of the: ").append(
  67                     this.getClass().getSimpleName()).append("\n");
  68             for (Class cls = this.getClass();
  69                     cls != null;
  70                     cls = cls.getSuperclass()) {
  71                 for (Field f : cls.getDeclaredFields()) {
  72                     try {
  73                         f.setAccessible(true);
  74                         sb.append(f.getName()).append(" : ").
  75                                 append(f.get(this)).append("\n");
  76                     } catch (IllegalAccessException e) {
  77                     }
  78                 }
  79             }
  80             return sb.toString();
  81         }
  82     }
  83 
  84     /**
  85      * This interface implements the method {@link #equalsReflect} that can be used
  86      * for simple overriding of the Object.equal method.
  87      */
  88     public interface Equalable {
  89 
  90         /**
  91          * Indicates whether some other object is equal to this one. Uses
  92          * reflection to access data members. Two objects are equal if all the
  93          * members (except members from #excludeFields list) are equal.
  94          *
  95          * @param obj object to compare
  96          * @param excludeFields the values of these fields don't affect 
  97          * the return value
  98          * @return {@code true} if this object is the same as the obj argument;
  99          * {@code false} otherwise.
 100          */
 101         default boolean equalsReflect(Object obj, String... excludeFields) {
 102             if (this == obj) {
 103                 return true;
 104             }
 105             if (obj == null || getClass() != obj.getClass()) {
 106                 return false;
 107             }
 108             Set<String> excludes = new HashSet<>(Arrays.asList(excludeFields));
 109             for (Class cls = this.getClass();
 110                     cls != null;
 111                     cls = cls.getSuperclass()) {
 112                 for (Field f : cls.getDeclaredFields()) {
 113                     if (excludes.contains(f.getName())) {
 114                         break;
 115                     }
 116                     try {
 117                         f.setAccessible(true);
 118                         Object thisFieldVal = f.get(this);
 119                         Object oFieldVal = f.get(obj);
 120                         if (thisFieldVal != null) {
 121                             if (!thisFieldVal.equals(oFieldVal)) {
 122                                 return false;
 123                             }
 124                         } else if (oFieldVal != null) {
 125                             return false;
 126                         }
 127                     } catch (IllegalAccessException e) {
 128                     }
 129                 }
 130             }
 131             return true;
 132         }
 133     }
 134 
 135     /**
 136      * This interface implements the method {@link #hashCodeReflect} 
 137      * that can be used for simple overriding of the Object.hashCode method.
 138      */
 139     public interface Hashable {
 140 
 141         /**
 142          * Returns a hash code value for the object. This method uses reflection
 143          * to access an object's data members. Hash code is calculated on
 144          * members hash codes.
 145          *
 146          * @param excludeFields the values of these fields don't affect 
 147          * the return value
 148          *
 149          * @return a hash code value for this object.
 150          */
 151         default int hashCodeReflect(String... excludeFields) {
 152             int result = 0;
 153             Set<String> excludes = new HashSet<>(Arrays.asList(excludeFields));
 154             for (Class cls = this.getClass();
 155                     cls != null;
 156                     cls = cls.getSuperclass()) {
 157                 for (Field f : cls.getDeclaredFields()) {
 158                     if (excludes.contains(f.getName())) {
 159                         break;
 160                     }
 161                     try {
 162                         f.setAccessible(true);
 163                         Object val = f.get(this);
 164                         result = 31 * result + val.hashCode();
 165                     } catch (IllegalAccessException e) {
 166                     }
 167                 }
 168             }
 169             return result;
 170         }
 171     }
 172 
 173     /**
 174      * Sample class to demonstrate mixin. This class inherits the behavior 
 175      * of the {@link Debuggable}, {@link Equalable}, {@link Hashable}
 176      */
 177     public static class MixClass implements Debuggable, Equalable, Hashable {
 178 
 179         /**
 180          * Need to illustrate debug print, compare and hash code calculation
 181          */
 182         private final int n = 28;
 183         /**
 184          * Need to illustrate debug print, compare and hash code calculation
 185          */
 186         private final String strHello = "Hello world";
 187 
 188         /**
 189          * Use the default method from {@link Equalable} to check equals
 190          *
 191          * @param o object to compare
 192          */
 193         @Override
 194         public boolean equals(Object o) {
 195             return equalsReflect(o);
 196         }
 197 
 198         /**
 199          * Use the default method from {@link Hashable} to calculate 
 200          * the hash code
 201          */
 202         @Override
 203         public int hashCode() {
 204             return hashCodeReflect();
 205         }
 206     }
 207 
 208     /**
 209      * Illustrate the behavior of the MixClass
 210      *
 211      * @param args command-line arguments
 212      * @throws java.io.IOException internal demo error
 213      */
 214     public static void main(final String[] args) throws IOException {
 215         MixClass a = new MixClass();
 216         MixClass b = new MixClass();
 217         System.out.println(a.toDebugString());
 218         System.out.println(a.equals(b));
 219     }
 220 }