From 7e4e58cddbde76f0aeb6240b1232f39a835c6157 Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Fri, 15 Jul 2011 13:07:12 -0400 Subject: [PATCH] INT-1969 adding type-difference-weight algorithm for more nuanced decision making with regard to class and/or interface hierarchies for the PayloadTypeRouter --- .../integration/router/PayloadTypeRouter.java | 131 ++++++++++-------- 1 file changed, 72 insertions(+), 59 deletions(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/PayloadTypeRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/PayloadTypeRouter.java index 71388b3c01..7b774277fb 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/PayloadTypeRouter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/PayloadTypeRouter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,9 +22,7 @@ import java.util.List; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; -import org.springframework.integration.MessageHandlingException; import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; /** * A Message Router that resolves the {@link MessageChannel} based on the @@ -35,6 +33,8 @@ import org.springframework.util.StringUtils; */ public class PayloadTypeRouter extends AbstractMessageRouter { + private static final String ARRAY_SUFFIX = "[]"; + /** * Selects the most appropriate channel name matching channel identifiers which are the * fully qualified class names encountered while traversing the payload type hierarchy. @@ -51,68 +51,81 @@ public class PayloadTypeRouter extends AbstractMessageRouter { } private String getChannelName(Message message) { - Class type = message.getPayload().getClass(); - boolean array = type.isArray(); - if (array){ - type = type.getComponentType(); - } - while (type != null && !CollectionUtils.isEmpty(this.channelIdentifierMap)) { - - String mappingName = null; - if (array){ - mappingName = type.getName()+"[]"; - } - else { - mappingName = type.getName(); - } - String channelName = this.channelIdentifierMap.get(mappingName); - if (StringUtils.hasText(channelName)) { - return channelName; - } - // next, check for interfaces (super inluded) of this type - - channelName = this.introspectInterfaces(type, message); - - if (channelName != null) { - return channelName; - } - - // finally, continue up the hierarchy - type = type.getSuperclass(); - } - return null; - } - - private String introspectInterfaces(Class type, Message message){ - Class[] interfaces = type.getInterfaces(); - List matchedInterfaces = new ArrayList(); - - this.doIntrospect(interfaces, matchedInterfaces); - - if (matchedInterfaces.isEmpty()){ + if (CollectionUtils.isEmpty(this.channelIdentifierMap)) { return null; } - else { - if (matchedInterfaces.size() > 1){ - throw new MessageHandlingException(message, - "Unresolvable ambiguity while attempting to find closest match for [" + type.getName() + - "]. Candidate types " + matchedInterfaces + " have equal weight."); - } - else { - return matchedInterfaces.get(0); - } + Class type = message.getPayload().getClass(); + boolean isArray = type.isArray(); + if (isArray) { + type = type.getComponentType(); } + return this.findClosestMatch(type, isArray); } - - private void doIntrospect(Class[] interfaces, List matchedInterfaces){ - for (Class extendedInterface : interfaces) { - String currentChannelName = this.channelIdentifierMap.get(extendedInterface.getName()); - if (StringUtils.hasText(currentChannelName)) { - matchedInterfaces.add(extendedInterface.getName()); + + private String findClosestMatch(Class type, boolean isArray) { + int minTypeDiffWeight = Integer.MAX_VALUE; + String closestMatch = null; + boolean ambiguityAtClosestMatchLevel = false; + for (String candidate : this.channelIdentifierMap.keySet()) { + if (isArray) { + if (!candidate.endsWith(ARRAY_SUFFIX)) { + continue; + } + // trim the suffix + candidate = candidate.substring(0, candidate.length() - ARRAY_SUFFIX.length()); + } + else if (candidate.endsWith(ARRAY_SUFFIX)) { + continue; + } + int typeDiffWeight = determineTypeDifferenceWeight(candidate, type, 0); + if (typeDiffWeight < minTypeDiffWeight) { + minTypeDiffWeight = typeDiffWeight; + closestMatch = (isArray) ? candidate + ARRAY_SUFFIX : candidate; + ambiguityAtClosestMatchLevel = false; + } + else if (typeDiffWeight == minTypeDiffWeight && typeDiffWeight != Integer.MAX_VALUE) { + ambiguityAtClosestMatchLevel = true; } - Class[] extendedInterfaces = extendedInterface.getInterfaces(); - this.doIntrospect(extendedInterfaces, matchedInterfaces); } + if (ambiguityAtClosestMatchLevel) { + throw new IllegalStateException( + "Unresolvable ambiguity while attempting to find closest match for [" + type.getName() + "]."); + } + if (closestMatch == null) { + return null; + } + return this.channelIdentifierMap.get(closestMatch); + } + + private int determineTypeDifferenceWeight(String candidate, Class type, int level) { + if (type.getName().equals(candidate)) { + return level; + } + List matchingIntefaces = new ArrayList(); + for (Class iface : type.getInterfaces()) { + if (iface.getName().equals(candidate)) { + matchingIntefaces.add(candidate); + } + if (matchingIntefaces.size() > 1) { + throw new IllegalStateException("Found more than one matching interface at the same level of " + + "the hierarchy while attempting to find closest match for [" + type.getName() + "]."); + } + if (matchingIntefaces.size() == 1) { + return level + 1; + } + // check interface hierarchy + for (Class superInterface : iface.getInterfaces()) { + int weight = this.determineTypeDifferenceWeight(candidate, superInterface, level + 2); + if (weight < Integer.MAX_VALUE) { + return weight; + } + } + } + if (type.getSuperclass() == null) { + // exhausted hierarchy and found no match + return Integer.MAX_VALUE; + } + return this.determineTypeDifferenceWeight(candidate, type.getSuperclass(), level + 2); } }