/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.runtime.core.transformer;
import org.mule.runtime.api.metadata.DataType;
import org.mule.runtime.core.api.transformer.Converter;
import org.mule.runtime.core.api.transformer.Transformer;
import java.util.List;
import org.apache.commons.beanutils.MethodUtils;
/**
* Given a {@link org.mule.runtime.core.api.transformer.Transformer} instance, an input class and output class this object will
* create a weighting for a transformer. This weighthing can be used compare one transformer with another, which can be useful for
* choosing a transformer to use given the input class and required output class.
*/
public class TransformerWeighting implements Comparable {
private Transformer transformer;
private int inputWeighting;
private int outputWeighting;
private Class inputClass;
private Class outputClass;
public TransformerWeighting(Class inputClass, Class outputClass, Transformer transformer) {
this.inputClass = inputClass;
this.outputClass = outputClass;
this.transformer = transformer;
init();
}
private void init() {
inputWeighting = Integer.MAX_VALUE;
List<DataType> sourceTypes = transformer.getSourceDataTypes();
for (DataType type : sourceTypes) {
int weighting = getWeighting(-1, inputClass, type.getType());
if (weighting < inputWeighting && weighting != -1) {
inputWeighting = weighting;
}
}
outputWeighting = getWeighting(-1, outputClass, transformer.getReturnDataType().getType());
inputWeighting = (inputWeighting == Integer.MAX_VALUE ? -1 : inputWeighting);
outputWeighting = (outputWeighting == Integer.MAX_VALUE ? -1 : outputWeighting);
}
/**
* This is a very basic algorithm for creating a match rating for two classes. An offset weighting can also be passed in. Where
* w is weighting, if the classes are not assignable but the src is a primitive type, the w for the corresponding object type
* will be returned, otherwise return -1 if the classes match exactly and dest is Object then w+3 is returned if the classes
* match exactly and dest is not Object then w+1 is returned if the classes are assignable and there is a direct equality to an
* interface on the class, w+2 is returned, If there a super class, that will get matched using the above criteria but using w =
* w + 1 If there is no match -1 is returned
*
* @param weighting an offset weighting, by default -1 should be used
* @param src the src class being matched
* @param dest the destination class to match to
* @return a weighting where 0 would be an exact match, -1 would be no match and a positive integer that defines how close the
* match is
*/
protected int getWeighting(int weighting, Class src, Class dest) {
if (!dest.isAssignableFrom(src)) {
if (src.isPrimitive()) {
return getWeighting(weighting, MethodUtils.getPrimitiveWrapper(src), dest);
}
return -1;
}
if (dest.equals(src)) {
if (dest.equals(Object.class)) {
return weighting + 3;
} else {
return weighting + 1;
}
}
if (dest.isInterface() && src.getInterfaces().length > 0) {
for (int i = 0; i < src.getInterfaces().length; i++) {
Class aClass = src.getInterfaces()[i];
if (dest.equals(aClass)) {
return weighting + 2;
}
}
}
if (src.getSuperclass() != null) {
return getWeighting(weighting + 1, src.getSuperclass(), dest);
}
return -1;
}
public Class getInputClass() {
return inputClass;
}
public int getInputWeighting() {
return inputWeighting;
}
public Class getOutputClass() {
return outputClass;
}
public int getOutputWeighting() {
return outputWeighting;
}
public Transformer getTransformer() {
return transformer;
}
public boolean isExactMatch() {
return inputWeighting == 0 && outputWeighting == 0;
}
public boolean isNotMatch() {
return inputWeighting == -1 || outputWeighting == -1;
}
@Override
public int compareTo(Object o) {
TransformerWeighting weighting = (TransformerWeighting) o;
if (weighting.getInputWeighting() == getInputWeighting() && weighting.getOutputWeighting() == getOutputWeighting()) {
// We only check the weighting if we have an exact match
// These transformers should always implement Converter, but jic we check here
if (weighting.getTransformer() instanceof Converter && this.getTransformer() instanceof Converter) {
int x = ((Converter) weighting.getTransformer()).getPriorityWeighting();
int y = ((Converter) this.getTransformer()).getPriorityWeighting();
if (x > y) {
return -1;
}
if (x < y) {
return 1;
}
return 0;
} else {
return 0;
}
} else {
if (isNotMatch()) {
return -1;
} else if (weighting.isNotMatch() && !isNotMatch()) {
return 1;
} else if (weighting.isExactMatch() && !isExactMatch()) {
return -1;
} else if (weighting.getInputWeighting() < getInputWeighting() && weighting.getOutputWeighting() < getOutputWeighting()) {
return -1;
}
// If the outputWeighting is closer to 0 its a better match
else if (weighting.getInputWeighting() == getInputWeighting() && weighting.getOutputWeighting() < getOutputWeighting()) {
return -1;
}
else if (weighting.getInputWeighting() < getInputWeighting() && weighting.getOutputWeighting() == getOutputWeighting()) {
return -1;
}
return 1;
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TransformerWeighting that = (TransformerWeighting) o;
if (inputClass != null ? !inputClass.equals(that.inputClass) : that.inputClass != null) {
return false;
}
if (outputClass != null ? !outputClass.equals(that.outputClass) : that.outputClass != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result;
result = (transformer != null ? transformer.hashCode() : 0);
result = 31 * result + inputWeighting;
result = 31 * result + outputWeighting;
result = 31 * result + (inputClass != null ? inputClass.hashCode() : 0);
result = 31 * result + (outputClass != null ? outputClass.hashCode() : 0);
return result;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("TransformerWeighting");
sb.append("{inputClass=").append(inputClass);
sb.append(", inputWeighting=").append(inputWeighting);
sb.append(", outputClass=").append(outputClass);
sb.append(", outputWeighting=").append(outputWeighting);
sb.append(", transformer=").append(transformer.getName());
sb.append('}');
return sb.toString();
}
}