Java tutorial
/** * DataCleaner (community edition) * Copyright (C) 2014 Neopost - Customer Information Management * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.datacleaner.descriptors; import java.io.Closeable; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.TreeSet; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; import javax.inject.Named; import org.apache.commons.lang.ArrayUtils; import org.datacleaner.api.Alias; import org.datacleaner.api.Categorized; import org.datacleaner.api.Close; import org.datacleaner.api.ComponentCategory; import org.datacleaner.api.ComponentSuperCategory; import org.datacleaner.api.Configured; import org.datacleaner.api.Description; import org.datacleaner.api.Distributed; import org.datacleaner.api.HasDistributionAdvice; import org.datacleaner.api.Initialize; import org.datacleaner.api.MultiStreamComponent; import org.datacleaner.api.Provided; import org.datacleaner.api.Validate; import org.datacleaner.util.ReflectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A {@link ComponentDescriptor} for simple components. Simple components covers * reference data types (Dictionary, SynonymCatalog, StringPattern) as well as * custom configuration components. * * Simple components support the {@link Configured}, {@link Validate}, * {@link Initialize} and {@link Close} annotations as well as the * {@link Closeable} interface. * * @see Initialize * @see Validate * @see Close * @see Configured * @see Closeable */ class SimpleComponentDescriptor<B> extends AbstractDescriptor<B> implements ComponentDescriptor<B> { private static final long serialVersionUID = 1L; private static final Logger logger = LoggerFactory.getLogger(SimpleComponentDescriptor.class); protected final Set<ConfiguredPropertyDescriptor> _configuredProperties;; protected final Set<ProvidedPropertyDescriptor> _providedProperties; protected final Set<InitializeMethodDescriptor> _initializeMethods; protected final Set<ValidateMethodDescriptor> _validateMethods; protected final Set<CloseMethodDescriptor> _closeMethods; /** * Constructor for inheriting from SimpleComponentDescriptor * * @param beanClass */ public SimpleComponentDescriptor(Class<B> beanClass) { this(beanClass, false); } public SimpleComponentDescriptor(final Class<B> beanClass, final boolean initialize) { super(beanClass); _configuredProperties = new TreeSet<ConfiguredPropertyDescriptor>(); _providedProperties = new TreeSet<ProvidedPropertyDescriptor>(); _validateMethods = new HashSet<ValidateMethodDescriptor>(); _initializeMethods = new HashSet<InitializeMethodDescriptor>(); _closeMethods = new HashSet<CloseMethodDescriptor>(); if (initialize) { visitClass(); } } @Override public String getDisplayName() { final Named named = getAnnotation(Named.class); if (named == null) { return getComponentClass().getSimpleName(); } return named.value(); } @Override public final String getDescription() { final Description description = getAnnotation(Description.class); if (description == null) { return null; } return description.value(); } @Override public final <A extends Annotation> A getAnnotation(Class<A> annotationClass) { return ReflectionUtils.getAnnotation(getComponentClass(), annotationClass); } @Override public final Set<Annotation> getAnnotations() { Annotation[] annotations = getComponentClass().getAnnotations(); return new HashSet<Annotation>(Arrays.asList(annotations)); } @Override public Set<ComponentCategory> getComponentCategories() { Categorized categorized = getAnnotation(Categorized.class); if (categorized == null) { return Collections.emptySet(); } Class<? extends ComponentCategory>[] value = categorized.value(); if (value == null || value.length == 0) { return Collections.emptySet(); } Set<ComponentCategory> result = new HashSet<ComponentCategory>(); for (Class<? extends ComponentCategory> categoryClass : value) { if (categoryClass != ComponentCategory.class) { ComponentCategory category = ReflectionUtils.newInstance(categoryClass); result.add(category); } } return result; } @Override public ComponentSuperCategory getComponentSuperCategory() { final Categorized categorized = getAnnotation(Categorized.class); Class<? extends ComponentSuperCategory> superCategoryClass; if (categorized == null) { superCategoryClass = getDefaultComponentSuperCategoryClass(); } else { superCategoryClass = categorized.superCategory(); if (superCategoryClass == ComponentSuperCategory.class) { superCategoryClass = getDefaultComponentSuperCategoryClass(); } } final ComponentSuperCategory superCategory = ReflectionUtils.newInstance(superCategoryClass); return superCategory; } /** * Defines the {@link ComponentSuperCategory} to return, if no * {@link ComponentSuperCategory} was defined * * @return */ protected Class<? extends ComponentSuperCategory> getDefaultComponentSuperCategoryClass() { return ComponentSuperCategory.class; } @Override public B newInstance() { try { return getComponentClass().newInstance(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new IllegalStateException("Could not construct new instance of " + getComponentClass(), e); } } @Override protected void visitClass() { super.visitClass(); if (ReflectionUtils.isCloseable(getComponentClass())) { try { Method method = getComponentClass().getMethod("close", new Class<?>[0]); CloseMethodDescriptorImpl cmd = new CloseMethodDescriptorImpl(method, this); _closeMethods.add(cmd); } catch (Exception e) { // This should be impossible since all closeable's have a no-arg // close() method logger.error("Unexpected exception while getting close() method from Closeable", e); assert false; } } } @Override protected void visitField(Field field) { final boolean isInject = ReflectionUtils.isAnnotationPresent(field, Inject.class); final boolean isConfigured = ReflectionUtils.isAnnotationPresent(field, Configured.class); final boolean isProvided = ReflectionUtils.isAnnotationPresent(field, Provided.class); if (isConfigured && isProvided) { throw new DescriptorException("The field " + field + " is annotated with both @Configured and @Provided, which are mutually exclusive."); } if (!isConfigured && (isInject || isProvided)) { // provided properties = @Inject or @Provided, and NOT @Configured _providedProperties.add(new ProvidedPropertyDescriptorImpl(field, this)); } else if (isConfigured) { if (!isInject) { logger.debug("No @Inject annotation found for @Configured field: {}", field); } ConfiguredPropertyDescriptor cpd = new ConfiguredPropertyDescriptorImpl(field, this); _configuredProperties.add(cpd); } } @Override protected void visitMethod(Method method) { final boolean isInitialize; { final boolean isInitializeAnnotationPresent = ReflectionUtils.isAnnotationPresent(method, Initialize.class); final boolean isPostConstructAnnotationPresent = ReflectionUtils.isAnnotationPresent(method, PostConstruct.class); // @PostConstruct is a valid substitution for @Initialize isInitialize = isInitializeAnnotationPresent || isPostConstructAnnotationPresent; } final boolean isClose; { final boolean isPreDestroyAnnotationPresent = ReflectionUtils.isAnnotationPresent(method, PreDestroy.class); final boolean isCloseAnnotationPresent = ReflectionUtils.isAnnotationPresent(method, Close.class); // @PreDestroy is a valid substitution for @Close isClose = isCloseAnnotationPresent || isPreDestroyAnnotationPresent; } final boolean isValidate = ReflectionUtils.isAnnotationPresent(method, Validate.class); if (isInitialize) { _initializeMethods.add(new InitializeMethodDescriptorImpl(method, this)); } if (isValidate) { _validateMethods.add(new ValidateMethodDescriptorImpl(method, this)); } if (isClose) { _closeMethods.add(new CloseMethodDescriptorImpl(method, this)); } } public final Set<InitializeMethodDescriptor> getInitializeMethods() { return Collections.unmodifiableSet(_initializeMethods); } public final Set<ConfiguredPropertyDescriptor> getConfiguredProperties() { return Collections.unmodifiableSet(_configuredProperties); } public final Set<CloseMethodDescriptor> getCloseMethods() { return Collections.unmodifiableSet(_closeMethods); } @Override public final Set<ValidateMethodDescriptor> getValidateMethods() { return Collections.unmodifiableSet(_validateMethods); } @Override public final Set<ProvidedPropertyDescriptor> getProvidedProperties() { return Collections.unmodifiableSet(_providedProperties); } @Override public final Set<ProvidedPropertyDescriptor> getProvidedPropertiesByType(Class<?> cls) { Set<ProvidedPropertyDescriptor> result = new HashSet<ProvidedPropertyDescriptor>(); for (ProvidedPropertyDescriptor descriptor : _providedProperties) { if (ReflectionUtils.is(descriptor.getType(), cls)) { result.add(descriptor); } } return result; } @Override public final ConfiguredPropertyDescriptor getConfiguredProperty(String configuredName) { for (ConfiguredPropertyDescriptor configuredDescriptor : _configuredProperties) { if (configuredName.equals(configuredDescriptor.getName())) { return configuredDescriptor; } } for (ConfiguredPropertyDescriptor configuredDescriptor : _configuredProperties) { String[] aliases = configuredDescriptor.getAliases(); if (ArrayUtils.contains(aliases, configuredName)) { return configuredDescriptor; } } return null; } @Override public final Set<ConfiguredPropertyDescriptor> getConfiguredPropertiesByAnnotation( Class<? extends Annotation> annotationClass) { Set<ConfiguredPropertyDescriptor> set = new TreeSet<ConfiguredPropertyDescriptor>(); for (ConfiguredPropertyDescriptor configuredDescriptor : _configuredProperties) { Annotation annotation = configuredDescriptor.getAnnotation(annotationClass); if (annotation != null) { set.add(configuredDescriptor); } } return set; } @Override public final Set<ConfiguredPropertyDescriptor> getConfiguredPropertiesByType(Class<?> type, boolean includeArrays) { Set<ConfiguredPropertyDescriptor> set = new TreeSet<ConfiguredPropertyDescriptor>(); for (ConfiguredPropertyDescriptor configuredDescriptor : _configuredProperties) { final boolean include; if (includeArrays) { include = ReflectionUtils.is(configuredDescriptor.getBaseType(), type); } else { Class<?> baseType = configuredDescriptor.getType(); if (baseType.isArray() == type.isArray()) { include = ReflectionUtils.is(baseType, type); } else { include = false; } } if (include) { set.add(configuredDescriptor); } } return set; } @Override public int compareTo(ComponentDescriptor<?> o) { if (o == null) { return 1; } Class<?> otherComponentClass = o.getComponentClass(); if (otherComponentClass == null) { return 1; } int diff = this.getDisplayName().compareTo(o.getDisplayName()); if (diff == 0) { String thisBeanClassName = this.getComponentClass().toString(); String thatBeanClassName = otherComponentClass.toString(); diff = thisBeanClassName.compareTo(thatBeanClassName); } return diff; } @Override public boolean isDistributable() { final Distributed distributed = getAnnotation(Distributed.class); if (distributed == null) { return ReflectionUtils.is(getComponentClass(), HasDistributionAdvice.class); } return distributed.value(); } @Override public boolean isMultiStreamComponent() { return ReflectionUtils.is(getComponentClass(), MultiStreamComponent.class); } @Override public final String[] getAliases() { Alias alias = getAnnotation(Alias.class); if (alias == null) { return new String[0]; } return alias.value(); } @Override public final Set<ConfiguredPropertyDescriptor> getConfiguredPropertiesForInput() { return getConfiguredPropertiesForInput(true); } @Override public final Set<ConfiguredPropertyDescriptor> getConfiguredPropertiesForInput(boolean includeOptional) { Set<ConfiguredPropertyDescriptor> descriptors = new TreeSet<ConfiguredPropertyDescriptor>( _configuredProperties); for (Iterator<ConfiguredPropertyDescriptor> it = descriptors.iterator(); it.hasNext();) { ConfiguredPropertyDescriptor propertyDescriptor = it.next(); if (!propertyDescriptor.isInputColumn()) { it.remove(); } else if (!includeOptional && !propertyDescriptor.isRequired()) { it.remove(); } } return descriptors; } }