/******************************************************************************* * Copyright (c) 2012, 2015 VMware, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VMware, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.internal.bestpractices.springiderules; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.eclipse.core.runtime.IProgressMonitor; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.ide.eclipse.beans.core.internal.model.Bean; import org.springframework.ide.eclipse.beans.core.internal.model.BeanReference; import org.springframework.ide.eclipse.beans.core.internal.model.BeansModelUtils; import org.springframework.ide.eclipse.beans.core.internal.model.BeansTypedString; import org.springframework.ide.eclipse.beans.core.model.IBean; import org.springframework.ide.eclipse.beans.core.model.IBeanConstructorArgument; import org.springframework.ide.eclipse.beans.core.model.IBeanProperty; import org.springframework.ide.eclipse.beans.core.model.validation.IBeansValidationContext; import org.springframework.ide.eclipse.beans.core.namespaces.NamespaceUtils; import org.springframework.ide.eclipse.core.model.IModelElement; import org.springframework.ide.eclipse.core.model.validation.IValidationContext; import org.springframework.ide.eclipse.core.model.validation.IValidationRule; import org.springframework.ide.eclipse.core.model.xml.XmlSourceLocation; import org.springframework.ide.eclipse.core.project.IProjectContributorState; /** * This rule checks for cases where it may be possible to simplify the * configuration by using bean inheritance. Using bean inheritance is suggested * when there are more than <code>DEFAULT_MIN_NUM_SIMILAR_BEAN_DEFS</code> beans * with <code>DEFAULT_MIN_NUM_SHARED_PROPERTIES</code> properties in common * where the values of the properties are the same. * @author Wesley Coelho * @author Christian Dupuis * @author Terry Denney * @author Leo Dos Santos */ public class UseBeanInheritance implements IValidationRule<IBean, IBeansValidationContext> { private final static int DEFAULT_MIN_NUM_SIMILAR_BEAN_DEFS = 3; private final static int DEFAULT_MIN_NUM_SHARED_PROPERTIES = 3; public final static String ERROR_ID = "useBeanInheritance"; private int minNumSimilarBeanDefs = DEFAULT_MIN_NUM_SIMILAR_BEAN_DEFS; private int minNumSharedProperties = DEFAULT_MIN_NUM_SHARED_PROPERTIES; public void setMinNumSharedProperties(int minNumSharedProperties) { this.minNumSharedProperties = minNumSharedProperties; } public void setMinNumSimilarBeanDefs(int minNumSimilarBeanDefs) { this.minNumSimilarBeanDefs = minNumSimilarBeanDefs; } /** * Returns <code>true</code> if this rule is able to validate the given * {@link IModelElement} with the specified {@link IValidationContext}. */ public boolean supports(IModelElement element, IValidationContext context) { return element instanceof IBean && isBeanSupported((IBean) element); } /** * Check if there are other beans with similar configuration. */ public void validate(IBean bean, IBeansValidationContext validationContext, IProgressMonitor progressMonitor) { IBean[] allBeans = null; if (validationContext instanceof IProjectContributorState) { AllBeansCache allBeansCache = ((IProjectContributorState) validationContext).get(AllBeansCache.class); if (allBeansCache == null) { allBeansCache = new AllBeansCache(); ((IProjectContributorState) validationContext).hold(allBeansCache); } allBeans = allBeansCache.getAllBeans(validationContext.getRootElement()); } else { Set<IBean> beans = BeansModelUtils.getBeans(validationContext.getRootElement()); allBeans = beans.toArray(new IBean[beans.size()]); } List<IBean> similarBeanList = new ArrayList<IBean>(); for (IBean currBean : allBeans) { if (isSimilar(bean, currBean)) { similarBeanList.add(currBean); } } // Add one to the similar bean count because the current bean counts as // one of the similar ones if (similarBeanList.size() + 1 >= minNumSimilarBeanDefs) { String similarBeanNames = getBeanNamesString(similarBeanList); validationContext .info(bean, ERROR_ID, "Consider using bean inheritance to simplify configuration of the " + bean.getElementName() + " bean. It may be possible to use a parent bean to share configuration with the the following beans: " + similarBeanNames); } } private boolean constructorArgumentsEqual(IBean bean1, IBean bean2) { Set<IBeanConstructorArgument> bean1args = bean1.getConstructorArguments(); Set<IBeanConstructorArgument> bean2args = bean2.getConstructorArguments(); if (bean1args.size() != bean2args.size()) { return false; } for (IBeanConstructorArgument currBean1ConstructorArgument : bean1args) { boolean matchFound = false; for (IBeanConstructorArgument currBean2ConstructorArgument : bean2args) { if (constructorArgumentsEqual(currBean1ConstructorArgument, currBean2ConstructorArgument)) { matchFound = true; break; } } if (!matchFound) { return false; } } return true; } private boolean constructorArgumentsEqual(IBeanConstructorArgument argument1, IBeanConstructorArgument argument2) { if (argument1.getElementName().equals(argument2.getElementName())) { if (propertyValuesEqual(argument1.getValue(), argument2.getValue())) { return true; } } return false; } private String getBeanNamesString(List<IBean> similarBeanList) { String beanNames = ""; for (IBean bean : similarBeanList) { beanNames += bean.getElementName() + " "; } return beanNames; } private boolean initMethodsEqual(IBean bean1, IBean bean2) { if (bean1 instanceof Bean && bean2 instanceof Bean) { AbstractBeanDefinition definition1 = (AbstractBeanDefinition) ((Bean) bean1).getBeanDefinition(); AbstractBeanDefinition definition2 = (AbstractBeanDefinition) ((Bean) bean2).getBeanDefinition(); String initMethod1 = definition1.getInitMethodName(); String initMethod2 = definition2.getInitMethodName(); if (initMethod1 == null) { initMethod1 = ""; } if (initMethod2 == null) { initMethod2 = ""; } if (!initMethod1.equals(initMethod2)) { return false; } } return true; } private boolean isBeanSupported(IBean bean) { if (bean.getElementSourceLocation() instanceof XmlSourceLocation && !NamespaceUtils.DEFAULT_NAMESPACE_URI.equals(((XmlSourceLocation) bean.getElementSourceLocation()) .getNamespaceURI())) { return false; } return true; } /** * Returns true if two beans are similar in the sense that some common * configuration can be factored out into a parent bean configuration. * * Beans are considered similar if they don't have different constructor * arguments or init methods and there are more than * <code>DEFAULT_MIN_NUM_SHARED_PROPERTIES</code> property-value pairs in * common */ private boolean isSimilar(IBean bean1, IBean bean2) { if (!isBeanSupported(bean2)) { return false; } if (!constructorArgumentsEqual(bean1, bean2)) { return false; } if (!initMethodsEqual(bean1, bean2)) { return false; } // Count the number of matching properties int matchingPropertyCount = 0; for (IBeanProperty currProperty1 : bean1.getProperties()) { for (IBeanProperty currProperty2 : bean2.getProperties()) { if (propertiesEqual(currProperty1, currProperty2)) { matchingPropertyCount++; } } } return matchingPropertyCount >= minNumSharedProperties; } /** * Two properties are considered equal if they have they refer to the same * property of the bean (element name) and their values are the same. * @return true if the two properties are the same */ private boolean propertiesEqual(IBeanProperty beanProperty1, IBeanProperty beanProperty2) { if (beanProperty1.getElementName().equals(beanProperty2.getElementName())) { if (propertyValuesEqual(beanProperty1.getValue(), beanProperty2.getValue())) { return true; } } return false; } private boolean propertyValuesEqual(Object value1, Object value2) { if (value1 instanceof BeansTypedString && value2 instanceof BeansTypedString) { BeansTypedString beansTypedString1 = (BeansTypedString) value1; BeansTypedString beansTypedString2 = (BeansTypedString) value2; if (beansTypedString1.getString().equals(beansTypedString2.getString())) { return true; } } else if (value1 instanceof BeanReference && value2 instanceof BeanReference) { BeanReference beanReference1 = (BeanReference) value1; BeanReference beanReference2 = (BeanReference) value2; if (beanReference1.getBeanName().equals(beanReference2.getBeanName())) { return true; } } return false; } }