diff options
| author | 2018-12-08 12:05:28 +0100 | |
|---|---|---|
| committer | 2018-12-08 12:05:28 +0100 | |
| commit | e8b2d6d19a8e7ee3ab657d5c37776ba03d43ecb7 (patch) | |
| tree | 228a1974523e23f35dd1216b429ecd5dbb690cd8 | |
| parent | add barebones plugin framework, cleanup (diff) | |
| download | enigma-e8b2d6d19a8e7ee3ab657d5c37776ba03d43ecb7.tar.gz enigma-e8b2d6d19a8e7ee3ab657d5c37776ba03d43ecb7.tar.xz enigma-e8b2d6d19a8e7ee3ab657d5c37776ba03d43ecb7.zip | |
rewrite entry resolution logic in TranslationIndex, hopefully fixing bugs
9 files changed, 82 insertions, 89 deletions
diff --git a/build.gradle b/build.gradle index 8eae880d..54b574b3 100644 --- a/build.gradle +++ b/build.gradle | |||
| @@ -19,7 +19,7 @@ apply plugin: 'com.github.johnrengelman.shadow' | |||
| 19 | apply plugin: 'maven' | 19 | apply plugin: 'maven' |
| 20 | 20 | ||
| 21 | group = 'cuchaz' | 21 | group = 'cuchaz' |
| 22 | version = '0.12.1' | 22 | version = '0.12.2' |
| 23 | 23 | ||
| 24 | def ENV = System.getenv() | 24 | def ENV = System.getenv() |
| 25 | if (ENV.BUILD_NUMBER) { | 25 | if (ENV.BUILD_NUMBER) { |
diff --git a/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java b/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java index b2ddc5fa..db116623 100644 --- a/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java | |||
| @@ -11,6 +11,8 @@ | |||
| 11 | 11 | ||
| 12 | package cuchaz.enigma.analysis; | 12 | package cuchaz.enigma.analysis; |
| 13 | 13 | ||
| 14 | import com.google.common.cache.Cache; | ||
| 15 | import com.google.common.cache.CacheBuilder; | ||
| 14 | import com.google.common.collect.HashMultimap; | 16 | import com.google.common.collect.HashMultimap; |
| 15 | import com.google.common.collect.Lists; | 17 | import com.google.common.collect.Lists; |
| 16 | import com.google.common.collect.Maps; | 18 | import com.google.common.collect.Maps; |
| @@ -19,15 +21,13 @@ import cuchaz.enigma.bytecode.AccessFlags; | |||
| 19 | import cuchaz.enigma.mapping.*; | 21 | import cuchaz.enigma.mapping.*; |
| 20 | import cuchaz.enigma.mapping.entry.*; | 22 | import cuchaz.enigma.mapping.entry.*; |
| 21 | 23 | ||
| 22 | import java.util.Collection; | 24 | import java.util.*; |
| 23 | import java.util.List; | ||
| 24 | import java.util.Map; | ||
| 25 | import java.util.Set; | ||
| 26 | 25 | ||
| 27 | public class TranslationIndex { | 26 | public class TranslationIndex { |
| 28 | 27 | ||
| 29 | private final ReferencedEntryPool entryPool; | 28 | private final ReferencedEntryPool entryPool; |
| 30 | private Map<ClassEntry, ClassEntry> superclasses; | 29 | private Map<ClassEntry, ClassEntry> superclasses; |
| 30 | private Map<Entry, DefEntry> defEntries = new HashMap<>(); | ||
| 31 | private Multimap<ClassEntry, FieldDefEntry> fieldEntries; | 31 | private Multimap<ClassEntry, FieldDefEntry> fieldEntries; |
| 32 | private Multimap<ClassEntry, MethodDefEntry> methodEntries; | 32 | private Multimap<ClassEntry, MethodDefEntry> methodEntries; |
| 33 | private Multimap<ClassEntry, ClassEntry> interfaces; | 33 | private Multimap<ClassEntry, ClassEntry> interfaces; |
| @@ -38,6 +38,14 @@ public class TranslationIndex { | |||
| 38 | this.fieldEntries = HashMultimap.create(); | 38 | this.fieldEntries = HashMultimap.create(); |
| 39 | this.methodEntries = HashMultimap.create(); | 39 | this.methodEntries = HashMultimap.create(); |
| 40 | this.interfaces = HashMultimap.create(); | 40 | this.interfaces = HashMultimap.create(); |
| 41 | |||
| 42 | for (FieldDefEntry entry : fieldEntries.values()) { | ||
| 43 | defEntries.put(entry, entry); | ||
| 44 | } | ||
| 45 | |||
| 46 | for (MethodDefEntry entry : methodEntries.values()) { | ||
| 47 | defEntries.put(entry, entry); | ||
| 48 | } | ||
| 41 | } | 49 | } |
| 42 | 50 | ||
| 43 | public TranslationIndex(TranslationIndex other, Translator translator) { | 51 | public TranslationIndex(TranslationIndex other, Translator translator) { |
| @@ -74,6 +82,14 @@ public class TranslationIndex { | |||
| 74 | translator.getTranslatedMethodDef(mapEntry.getValue()) | 82 | translator.getTranslatedMethodDef(mapEntry.getValue()) |
| 75 | ); | 83 | ); |
| 76 | } | 84 | } |
| 85 | |||
| 86 | for (FieldDefEntry entry : fieldEntries.values()) { | ||
| 87 | defEntries.put(entry, entry); | ||
| 88 | } | ||
| 89 | |||
| 90 | for (MethodDefEntry entry : methodEntries.values()) { | ||
| 91 | defEntries.put(entry, entry); | ||
| 92 | } | ||
| 77 | } | 93 | } |
| 78 | 94 | ||
| 79 | protected ClassDefEntry indexClass(int access, String name, String signature, String superName, String[] interfaces) { | 95 | protected ClassDefEntry indexClass(int access, String name, String signature, String superName, String[] interfaces) { |
| @@ -101,16 +117,27 @@ public class TranslationIndex { | |||
| 101 | 117 | ||
| 102 | protected void indexField(FieldDefEntry fieldEntry) { | 118 | protected void indexField(FieldDefEntry fieldEntry) { |
| 103 | this.fieldEntries.put(fieldEntry.getOwnerClassEntry(), fieldEntry); | 119 | this.fieldEntries.put(fieldEntry.getOwnerClassEntry(), fieldEntry); |
| 120 | this.defEntries.put(fieldEntry, fieldEntry); | ||
| 104 | } | 121 | } |
| 105 | 122 | ||
| 106 | protected void indexMethod(MethodDefEntry methodEntry) { | 123 | protected void indexMethod(MethodDefEntry methodEntry) { |
| 107 | this.methodEntries.put(methodEntry.getOwnerClassEntry(), methodEntry); | 124 | this.methodEntries.put(methodEntry.getOwnerClassEntry(), methodEntry); |
| 125 | this.defEntries.put(methodEntry, methodEntry); | ||
| 108 | } | 126 | } |
| 109 | 127 | ||
| 110 | public void renameClasses(Map<String, String> renames) { | 128 | public void renameClasses(Map<String, String> renames) { |
| 111 | EntryRenamer.renameClassesInMap(renames, this.superclasses); | 129 | EntryRenamer.renameClassesInMap(renames, this.superclasses); |
| 112 | EntryRenamer.renameClassesInMultimap(renames, this.fieldEntries); | 130 | EntryRenamer.renameClassesInMultimap(renames, this.fieldEntries); |
| 113 | EntryRenamer.renameClassesInMultimap(renames, this.methodEntries); | 131 | EntryRenamer.renameClassesInMultimap(renames, this.methodEntries); |
| 132 | |||
| 133 | this.defEntries.clear(); | ||
| 134 | for (FieldDefEntry entry : fieldEntries.values()) { | ||
| 135 | defEntries.put(entry, entry); | ||
| 136 | } | ||
| 137 | |||
| 138 | for (MethodDefEntry entry : methodEntries.values()) { | ||
| 139 | defEntries.put(entry, entry); | ||
| 140 | } | ||
| 114 | } | 141 | } |
| 115 | 142 | ||
| 116 | public ClassEntry getSuperclass(ClassEntry classEntry) { | 143 | public ClassEntry getSuperclass(ClassEntry classEntry) { |
| @@ -171,6 +198,7 @@ public class TranslationIndex { | |||
| 171 | if (entry == null) { | 198 | if (entry == null) { |
| 172 | return false; | 199 | return false; |
| 173 | } | 200 | } |
| 201 | |||
| 174 | if (entry instanceof FieldEntry) { | 202 | if (entry instanceof FieldEntry) { |
| 175 | return fieldExists((FieldEntry) entry); | 203 | return fieldExists((FieldEntry) entry); |
| 176 | } else if (entry instanceof MethodEntry) { | 204 | } else if (entry instanceof MethodEntry) { |
| @@ -190,95 +218,42 @@ public class TranslationIndex { | |||
| 190 | } | 218 | } |
| 191 | 219 | ||
| 192 | public ClassEntry resolveEntryOwner(Entry entry) { | 220 | public ClassEntry resolveEntryOwner(Entry entry) { |
| 193 | return resolveEntryOwner(entry, false); | ||
| 194 | } | ||
| 195 | |||
| 196 | public ClassEntry resolveEntryOwner(Entry entry, boolean checkSuperclassBeforeChild) { | ||
| 197 | if (entry instanceof ClassEntry) { | 221 | if (entry instanceof ClassEntry) { |
| 198 | return (ClassEntry) entry; | 222 | return (ClassEntry) entry; |
| 199 | } | 223 | } |
| 200 | 224 | ||
| 201 | ClassEntry superclassEntry = resolveSuperclass(entry, checkSuperclassBeforeChild); | 225 | if (entryExists(entry)) { |
| 202 | if (superclassEntry != null) { | 226 | return entry.getOwnerClassEntry(); |
| 203 | return superclassEntry; | ||
| 204 | } | ||
| 205 | |||
| 206 | ClassEntry interfaceEntry = resolveInterface(entry); | ||
| 207 | if (interfaceEntry != null) { | ||
| 208 | return interfaceEntry; | ||
| 209 | } | 227 | } |
| 210 | 228 | ||
| 211 | return null; | 229 | DefEntry def = defEntries.get(entry); |
| 212 | } | 230 | if (def != null && (def.getAccess().isPrivate())) { |
| 213 | 231 | return null; | |
| 214 | public ClassEntry resolveSuperclass(Entry entry, boolean checkSuperclassBeforeChild) { | ||
| 215 | |||
| 216 | // Default case | ||
| 217 | if (!checkSuperclassBeforeChild) | ||
| 218 | return resolveSuperclass(entry); | ||
| 219 | |||
| 220 | // Save the original entry | ||
| 221 | Entry originalEntry = entry; | ||
| 222 | |||
| 223 | // Get all possible superclasses and reverse the list | ||
| 224 | List<ClassEntry> superclasses = Lists.reverse(getAncestry(originalEntry.getOwnerClassEntry())); | ||
| 225 | |||
| 226 | boolean existInEntry = false; | ||
| 227 | |||
| 228 | for (ClassEntry classEntry : superclasses) { | ||
| 229 | entry = entry.updateOwnership(classEntry); | ||
| 230 | existInEntry = entryExists(entry); | ||
| 231 | |||
| 232 | // Check for possible entry in interfaces of superclasses | ||
| 233 | ClassEntry interfaceEntry = resolveInterface(entry); | ||
| 234 | if (interfaceEntry != null) | ||
| 235 | return interfaceEntry; | ||
| 236 | if (existInEntry) | ||
| 237 | break; | ||
| 238 | } | 232 | } |
| 239 | 233 | ||
| 240 | // Doesn't exists in superclasses? check the child or return null | 234 | // if we're protected/public/non-static, chances are we're somewhere down |
| 241 | if (!existInEntry) | 235 | LinkedList<ClassEntry> classEntries = new LinkedList<>(); |
| 242 | return !entryExists(originalEntry) ? null : originalEntry.getOwnerClassEntry(); | 236 | classEntries.add(entry.getOwnerClassEntry()); |
| 243 | 237 | while (!classEntries.isEmpty()) { | |
| 244 | return entry.getOwnerClassEntry(); | 238 | ClassEntry c = classEntries.remove(); |
| 245 | } | 239 | Entry cEntry = entry.updateOwnership(c); |
| 246 | 240 | ||
| 247 | public ClassEntry resolveSuperclass(Entry entry) { | 241 | if (entryExists(cEntry)) { |
| 248 | // this entry could refer to a method on a class where the method is not actually implemented | 242 | def = defEntries.get(cEntry); |
| 249 | // travel up the inheritance tree to find the closest implementation | 243 | if (def == null || (!def.getAccess().isPrivate())) { |
| 250 | 244 | return cEntry.getOwnerClassEntry(); | |
| 251 | while (!entryExists(entry)) { | 245 | } |
| 252 | // is there a parent class? | ||
| 253 | ClassEntry superclassEntry = getSuperclass(entry.getOwnerClassEntry()); | ||
| 254 | if (superclassEntry == null) { | ||
| 255 | // this is probably a method from a class in a library | ||
| 256 | // we can't trace the implementation up any higher unless we index the library | ||
| 257 | return null; | ||
| 258 | } | 246 | } |
| 259 | 247 | ||
| 260 | // move up to the parent class | 248 | ClassEntry superC = getSuperclass(c); |
| 261 | entry = entry.updateOwnership(superclassEntry); | 249 | if (superC != null) { |
| 262 | } | 250 | classEntries.add(superC); |
| 263 | return entry.getOwnerClassEntry(); | ||
| 264 | } | ||
| 265 | |||
| 266 | public ClassEntry resolveInterface(Entry entry) { | ||
| 267 | // the interfaces for any class is a forest | ||
| 268 | // so let's look at all the trees | ||
| 269 | |||
| 270 | for (ClassEntry interfaceEntry : this.interfaces.get(entry.getOwnerClassEntry())) { | ||
| 271 | Collection<ClassEntry> subInterface = this.interfaces.get(interfaceEntry); | ||
| 272 | if (subInterface != null && !subInterface.isEmpty()) { | ||
| 273 | ClassEntry result = resolveInterface(entry.updateOwnership(interfaceEntry)); | ||
| 274 | if (result != null) | ||
| 275 | return result; | ||
| 276 | } | 251 | } |
| 277 | ClassEntry resolvedClassEntry = resolveSuperclass(entry.updateOwnership(interfaceEntry)); | 252 | if (entry instanceof MethodEntry) { |
| 278 | if (resolvedClassEntry != null) { | 253 | classEntries.addAll(getInterfaces(c)); |
| 279 | return resolvedClassEntry; | ||
| 280 | } | 254 | } |
| 281 | } | 255 | } |
| 256 | |||
| 282 | return null; | 257 | return null; |
| 283 | } | 258 | } |
| 284 | 259 | ||
diff --git a/src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java b/src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java index 10fb9215..388e7ac3 100644 --- a/src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java +++ b/src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java | |||
| @@ -131,7 +131,7 @@ public class DirectionalTranslator implements Translator { | |||
| 131 | 131 | ||
| 132 | private String translateFieldName(FieldEntry entry) { | 132 | private String translateFieldName(FieldEntry entry) { |
| 133 | // resolve the class entry | 133 | // resolve the class entry |
| 134 | ClassEntry resolvedClassEntry = this.index.resolveEntryOwner(entry, true); | 134 | ClassEntry resolvedClassEntry = this.index.resolveEntryOwner(entry); |
| 135 | if (resolvedClassEntry != null) { | 135 | if (resolvedClassEntry != null) { |
| 136 | // look for the class | 136 | // look for the class |
| 137 | ClassMapping classMapping = findClassMapping(resolvedClassEntry); | 137 | ClassMapping classMapping = findClassMapping(resolvedClassEntry); |
| @@ -175,7 +175,7 @@ public class DirectionalTranslator implements Translator { | |||
| 175 | 175 | ||
| 176 | private String translateMethodName(MethodEntry entry) { | 176 | private String translateMethodName(MethodEntry entry) { |
| 177 | // resolve the class entry | 177 | // resolve the class entry |
| 178 | ClassEntry resolvedOwner = this.index.resolveEntryOwner(entry, true); | 178 | ClassEntry resolvedOwner = this.index.resolveEntryOwner(entry); |
| 179 | if (resolvedOwner != null) { | 179 | if (resolvedOwner != null) { |
| 180 | // look for class | 180 | // look for class |
| 181 | ClassMapping classMapping = findClassMapping(resolvedOwner); | 181 | ClassMapping classMapping = findClassMapping(resolvedOwner); |
diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java b/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java index 72a12c26..8ef4f12b 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java | |||
| @@ -92,9 +92,9 @@ public class MappingsRenamer { | |||
| 92 | deobfName = NameValidator.validateFieldName(deobfName); | 92 | deobfName = NameValidator.validateFieldName(deobfName); |
| 93 | FieldEntry targetEntry = entryPool.getField(obf.getOwnerClassEntry(), deobfName, obf.getDesc()); | 93 | FieldEntry targetEntry = entryPool.getField(obf.getOwnerClassEntry(), deobfName, obf.getDesc()); |
| 94 | ClassEntry definedClass = null; | 94 | ClassEntry definedClass = null; |
| 95 | if (mappings.containsDeobfField(obf.getOwnerClassEntry(), deobfName) || index.containsEntryWithSameName(targetEntry)) | 95 | if (mappings.containsDeobfField(obf.getOwnerClassEntry(), deobfName) || index.containsEntryWithSameName(targetEntry)) { |
| 96 | definedClass = obf.getOwnerClassEntry(); | 96 | definedClass = obf.getOwnerClassEntry(); |
| 97 | else { | 97 | } else { |
| 98 | for (ClassEntry ancestorEntry : this.index.getTranslationIndex().getAncestry(obf.getOwnerClassEntry())) { | 98 | for (ClassEntry ancestorEntry : this.index.getTranslationIndex().getAncestry(obf.getOwnerClassEntry())) { |
| 99 | if (mappings.containsDeobfField(ancestorEntry, deobfName) || index.containsEntryWithSameName(targetEntry.updateOwnership(ancestorEntry))) { | 99 | if (mappings.containsDeobfField(ancestorEntry, deobfName) || index.containsEntryWithSameName(targetEntry.updateOwnership(ancestorEntry))) { |
| 100 | definedClass = ancestorEntry; | 100 | definedClass = ancestorEntry; |
diff --git a/src/main/java/cuchaz/enigma/mapping/entry/ClassDefEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/ClassDefEntry.java index ac1fe2ab..df72e7e9 100644 --- a/src/main/java/cuchaz/enigma/mapping/entry/ClassDefEntry.java +++ b/src/main/java/cuchaz/enigma/mapping/entry/ClassDefEntry.java | |||
| @@ -15,7 +15,7 @@ import com.google.common.base.Preconditions; | |||
| 15 | import cuchaz.enigma.bytecode.AccessFlags; | 15 | import cuchaz.enigma.bytecode.AccessFlags; |
| 16 | import cuchaz.enigma.mapping.Signature; | 16 | import cuchaz.enigma.mapping.Signature; |
| 17 | 17 | ||
| 18 | public class ClassDefEntry extends ClassEntry { | 18 | public class ClassDefEntry extends ClassEntry implements DefEntry { |
| 19 | private final AccessFlags access; | 19 | private final AccessFlags access; |
| 20 | private final Signature signature; | 20 | private final Signature signature; |
| 21 | 21 | ||
| @@ -31,6 +31,7 @@ public class ClassDefEntry extends ClassEntry { | |||
| 31 | return signature; | 31 | return signature; |
| 32 | } | 32 | } |
| 33 | 33 | ||
| 34 | @Override | ||
| 34 | public AccessFlags getAccess() { | 35 | public AccessFlags getAccess() { |
| 35 | return access; | 36 | return access; |
| 36 | } | 37 | } |
diff --git a/src/main/java/cuchaz/enigma/mapping/entry/DefEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/DefEntry.java new file mode 100644 index 00000000..43ad0274 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/DefEntry.java | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | package cuchaz.enigma.mapping.entry; | ||
| 2 | |||
| 3 | import cuchaz.enigma.bytecode.AccessFlags; | ||
| 4 | |||
| 5 | public interface DefEntry extends Entry { | ||
| 6 | AccessFlags getAccess(); | ||
| 7 | } | ||
diff --git a/src/main/java/cuchaz/enigma/mapping/entry/FieldDefEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/FieldDefEntry.java index d18115bf..223410f5 100644 --- a/src/main/java/cuchaz/enigma/mapping/entry/FieldDefEntry.java +++ b/src/main/java/cuchaz/enigma/mapping/entry/FieldDefEntry.java | |||
| @@ -16,7 +16,7 @@ import cuchaz.enigma.bytecode.AccessFlags; | |||
| 16 | import cuchaz.enigma.mapping.Signature; | 16 | import cuchaz.enigma.mapping.Signature; |
| 17 | import cuchaz.enigma.mapping.TypeDescriptor; | 17 | import cuchaz.enigma.mapping.TypeDescriptor; |
| 18 | 18 | ||
| 19 | public class FieldDefEntry extends FieldEntry { | 19 | public class FieldDefEntry extends FieldEntry implements DefEntry { |
| 20 | private final AccessFlags access; | 20 | private final AccessFlags access; |
| 21 | private final Signature signature; | 21 | private final Signature signature; |
| 22 | 22 | ||
| @@ -28,6 +28,7 @@ public class FieldDefEntry extends FieldEntry { | |||
| 28 | this.signature = signature; | 28 | this.signature = signature; |
| 29 | } | 29 | } |
| 30 | 30 | ||
| 31 | @Override | ||
| 31 | public AccessFlags getAccess() { | 32 | public AccessFlags getAccess() { |
| 32 | return access; | 33 | return access; |
| 33 | } | 34 | } |
diff --git a/src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java index ec3af694..960b08d1 100644 --- a/src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java +++ b/src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java | |||
| @@ -16,7 +16,7 @@ import cuchaz.enigma.bytecode.AccessFlags; | |||
| 16 | import cuchaz.enigma.mapping.MethodDescriptor; | 16 | import cuchaz.enigma.mapping.MethodDescriptor; |
| 17 | import cuchaz.enigma.mapping.Signature; | 17 | import cuchaz.enigma.mapping.Signature; |
| 18 | 18 | ||
| 19 | public class MethodDefEntry extends MethodEntry { | 19 | public class MethodDefEntry extends MethodEntry implements DefEntry { |
| 20 | 20 | ||
| 21 | private final AccessFlags access; | 21 | private final AccessFlags access; |
| 22 | private final Signature signature; | 22 | private final Signature signature; |
| @@ -29,6 +29,7 @@ public class MethodDefEntry extends MethodEntry { | |||
| 29 | this.signature = signature; | 29 | this.signature = signature; |
| 30 | } | 30 | } |
| 31 | 31 | ||
| 32 | @Override | ||
| 32 | public AccessFlags getAccess() { | 33 | public AccessFlags getAccess() { |
| 33 | return access; | 34 | return access; |
| 34 | } | 35 | } |
diff --git a/src/main/java/cuchaz/enigma/utils/Utils.java b/src/main/java/cuchaz/enigma/utils/Utils.java index 8e502d47..bd09c64f 100644 --- a/src/main/java/cuchaz/enigma/utils/Utils.java +++ b/src/main/java/cuchaz/enigma/utils/Utils.java | |||
| @@ -27,7 +27,15 @@ import java.util.List; | |||
| 27 | public class Utils { | 27 | public class Utils { |
| 28 | 28 | ||
| 29 | public static int combineHashesOrdered(Object... objs) { | 29 | public static int combineHashesOrdered(Object... objs) { |
| 30 | return combineHashesOrdered(Arrays.asList(objs)); | 30 | final int prime = 67; |
| 31 | int result = 1; | ||
| 32 | for (Object obj : objs) { | ||
| 33 | result *= prime; | ||
| 34 | if (obj != null) { | ||
| 35 | result += obj.hashCode(); | ||
| 36 | } | ||
| 37 | } | ||
| 38 | return result; | ||
| 31 | } | 39 | } |
| 32 | 40 | ||
| 33 | public static int combineHashesOrdered(List<Object> objs) { | 41 | public static int combineHashesOrdered(List<Object> objs) { |