org.carrot2.util.attribute.AttributeDescriptor.java Source code

Java tutorial

Introduction

Here is the source code for org.carrot2.util.attribute.AttributeDescriptor.java

Source

/*
 * Carrot2 project.
 *
 * Copyright (C) 2002-2010, Dawid Weiss, Stanisaw Osiski.
 * All rights reserved.
 *
 * Refer to the full license file "carrot2.LICENSE"
 * in the root folder of the repository checkout or at:
 * http://www.carrot2.org/carrot2.LICENSE
 */

package org.carrot2.util.attribute;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;

import org.apache.commons.lang.ClassUtils;
import org.carrot2.util.ListUtils;
import org.carrot2.util.attribute.constraint.*;
import org.carrot2.util.attribute.metadata.AttributeMetadata;
import org.simpleframework.xml.*;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.BiMap;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;

/**
 * Provides a full description of an individual attribute, including its {@link #key},
 * {@link #type}, {@link #defaultValue} and {@link #constraints}. Also contains
 * human-readable {@link #metadata} about the attribute such as title, label or
 * description.
 * <p>
 * {@link AttributeDescriptor}s can be obtained from {@link BindableDescriptor}s, which in
 * turn are built by {@link BindableDescriptorBuilder#buildDescriptor(Object)};
 */
@Root(name = "attribute-descriptor")
public class AttributeDescriptor {
    /**
     * Human-readable metadata describing the attribute.
     */
    @Element
    public final AttributeMetadata metadata;

    /**
     * Type of the attribute as defined by {@link Attribute#key()}.
     */
    @org.simpleframework.xml.Attribute
    public final String key;

    /**
     * Type of the attribute. Primitive types are represented by their corresponding
     * wrapper/ box types.
     */
    @org.simpleframework.xml.Attribute
    public final Class<?> type;

    /**
     * Default value of the attribute.
     */
    public final Object defaultValue;

    /**
     * Constraints defined for the attribute. If the attribute has no constraints, this
     * list is empty.
     */
    public final List<Annotation> constraints;

    /**
     * <code>True</code> if the attribute is an {@link Input} attribute.
     */
    public final boolean inputAttribute;

    /**
     * <code>True</code> if the attribute is an {@link Output} attribute.
     */
    public final boolean outputAttribute;

    /**
     * <code>True</code> if the attribute is a {@link Required} attribute.
     */
    @org.simpleframework.xml.Attribute(name = "required")
    public final boolean requiredAttribute;

    /**
     * Field representing the attribute.
     */
    final Field attributeField;

    /**
     * Name of field representing the attribute, for serialization.
     */
    @org.simpleframework.xml.Attribute(name = "field")
    @SuppressWarnings("unused")
    private String attributeFieldString;

    /**
     * Name of the class declaring the attribute, for serialization.
     */
    @org.simpleframework.xml.Attribute(name = "declaring-class")
    @SuppressWarnings("unused")
    private String attributeDeclaringClassString;

    /**
     * Default value as string, for serialization.
     */
    @org.simpleframework.xml.Attribute(name = "default", required = false)
    @SuppressWarnings("unused")
    private String defaultValueString;

    /**
     * Annotation names, for serialization.
     */
    @ElementList(entry = "annotation", required = false)
    private ArrayList<String> annotations;

    /**
     * Instances of this attribute's constraints, for serialization.
     */
    @ElementList(name = "constraints", required = false)
    private ArrayList<Constraint> constraintInstances;

    /**
     * In case of Enum attributes, a list of allowed values, for serialization.
     */
    @Element(name = "allowed-values", required = false)
    private AllowedValues allowedValues;

    /**
     * 
     */
    AttributeDescriptor(Field field, Object defaultValue, List<Annotation> constraints,
            AttributeMetadata metadata) {
        this.attributeField = field;
        this.attributeDeclaringClassString = field.getDeclaringClass().getName();

        this.key = BindableUtils.getKey(field);
        this.type = ClassUtils.primitiveToWrapper(field.getType());
        this.defaultValue = defaultValue;
        this.constraints = constraints;
        this.metadata = metadata;

        this.inputAttribute = field.getAnnotation(Input.class) != null;
        this.outputAttribute = field.getAnnotation(Output.class) != null;
        this.requiredAttribute = field.getAnnotation(Required.class) != null;

        prepareForSerialization();
    }

    /**
     * Returns an annotation specified for the attribute.
     * 
     * @param annotationClass type of annotation to be returned
     * @return annotation of the attribute or <code>null</code> is annotation of the
     *         provided type is not defined for the attribute
     */
    public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
        return attributeField.getAnnotation(annotationClass);
    }

    /**
     * Returns <code>true</code> if the given value is valid for the attribute described
     * by this descriptor (non-<code>null</code> for {@link Required} attributes and
     * fulfilling all other constraints).
     */
    public final boolean isValid(Object value) {
        if (requiredAttribute && value == null) {
            return false;
        }

        if (value == null) {
            value = defaultValue;
        }

        final Annotation[] constraints = this.constraints.toArray(new Annotation[this.constraints.size()]);

        return ConstraintValidator.isMet(value, constraints).length == 0;
    }

    /**
     * Transforms {@link AttributeDescriptor}s into their keys.
     */
    public static final class AttributeDescriptorToKey implements Function<AttributeDescriptor, String> {
        public static final AttributeDescriptorToKey INSANCE = new AttributeDescriptorToKey();

        private AttributeDescriptorToKey() {
        }

        public String apply(AttributeDescriptor d) {
            return d.key;
        }
    }

    @Override
    public String toString() {
        return key + "=" + type;
    }

    /**
     * @see "http://issues.carrot2.org/browse/CARROT-693"
     */
    @SuppressWarnings("unchecked")
    private void prepareForSerialization() {
        attributeFieldString = attributeField.getName();

        // Default value
        if (inputAttribute && defaultValue != null) {
            if (defaultValue instanceof Collection<?>) {
                if (((Collection<?>) defaultValue).isEmpty()) {
                    return;
                }
            }

            if (attributeField.getAnnotation(ImplementingClasses.class) != null) {
                defaultValueString = defaultValue.getClass().getName();
            } else {
                if (defaultValue instanceof Enum<?>) {
                    defaultValueString = ((Enum<?>) defaultValue).name();
                } else {
                    defaultValueString = defaultValue.toString();
                }
            }
        }

        // Annotations as strings
        final Annotation[] annotationInstances = attributeField.getAnnotations();
        annotations = Lists.newArrayListWithExpectedSize(annotationInstances.length);
        for (Annotation annotation : annotationInstances) {
            annotations.add(annotation.annotationType().getSimpleName());
        }

        // Constraints. As ValueHintEnumConstraint is a dummy constraint whose main
        // purpose is to expose the ValueHintEnum annotation in the API, we filter it out
        // here and put the values provided by ValueHintEnum in the allowed values list in
        // the same way as for proper enums.
        constraintInstances = ListUtils.asArrayList(Collections2.filter(
                ConstraintFactory.createConstraints(attributeField.getAnnotations()), new Predicate<Constraint>() {
                    public boolean apply(Constraint constraint) {
                        return !(constraint instanceof ValueHintEnumConstraint);
                    }
                }));
        // Remove empty element from serialized output
        if (constraintInstances.isEmpty()) {
            constraintInstances = null;
        }

        // Allowed values of enum types and strings with ValueHintEnum annotations
        Class<? extends Enum<?>> enumType = null;
        boolean otherValuesAllowed = false;
        if (type.isEnum()) {
            enumType = (Class<? extends Enum<?>>) type;
            otherValuesAllowed = false;
        } else if (CharSequence.class.isAssignableFrom(type)) {
            final ValueHintEnum hint = getAnnotation(ValueHintEnum.class);
            if (hint != null) {
                enumType = hint.values();
                otherValuesAllowed = true;
            }
        }

        if (enumType != null) {
            allowedValues = new AllowedValues(otherValuesAllowed);
            final BiMap<String, String> valueToLabel = ValueHintMappingUtils.getValueToFriendlyName(enumType);

            for (String value : ValueHintMappingUtils.getValidValuesMap(enumType).keySet()) {
                allowedValues.add(value, valueToLabel.get(value));
            }
        }
    }

    @Root(name = "allowed-values")
    @SuppressWarnings("unused")
    private static class AllowedValues {
        @ElementList(name = "allowed-values", entry = "value", required = false, inline = true)
        private ArrayList<AllowedValue> allowedValues;

        @org.simpleframework.xml.Attribute(name = "other-values-allowed")
        private boolean otherValuesAllowed;

        private AllowedValues(boolean otherValuesAllowed) {
            this.allowedValues = Lists.newArrayList();
            this.otherValuesAllowed = otherValuesAllowed;
        }

        void add(String value, String label) {
            allowedValues.add(new AllowedValue(value, label));
        }
    }

    @SuppressWarnings("unused")
    private static class AllowedValue {
        @org.simpleframework.xml.Attribute
        String label;

        @Text
        String value;

        public AllowedValue(String value, String label) {
            this.value = value;
            this.label = label;
        }
    }
}