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: <Class Name> 60 * <member name> : <value> 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 }