Search in sources :

Example 1 with UML

use of org.opengis.annotation.UML in project sis by apache.

the class AnnotationConsistencyCheck method testImplementationAnnotations.

/**
 * Tests the annotations on every SIS implementations of the interfaces enumerated
 * in the {@link #types} array. More specifically this method tests that:
 *
 * <ul>
 *   <li>All implementation classes have {@link XmlRootElement} and {@link XmlType} annotations.</li>
 *   <li>The name declared in the {@code XmlType} annotations matches the
 *       {@link #getExpectedXmlTypeName expected value}.</li>
 *   <li>The name declared in the {@code XmlRootElement} annotations matches the identifier declared
 *       in the {@link UML} annotation of the GeoAPI interfaces, with {@code "Abstract"} prefix added
 *       if needed.</li>
 *   <li>The namespace declared in the {@code XmlRootElement} annotations is not redundant with
 *       the {@link XmlSchema} annotation in the package.</li>
 * </ul>
 *
 * This method does not check the method annotations, since it is {@link #testMethodAnnotations()} job.
 */
@Test
@DependsOnMethod("testInterfaceAnnotations")
public void testImplementationAnnotations() {
    for (final Class<?> type : types) {
        if (CodeList.class.isAssignableFrom(type)) {
            // Skip code lists, since they are not the purpose of this test.
            continue;
        }
        testingClass = type.getCanonicalName();
        /*
             * Get the implementation class, which is mandatory (otherwise the
             * subclass shall not include the interface in the 'types' array).
             */
        final Class<?> impl = getImplementation(type);
        assertNotNull("No implementation found.", impl);
        assertNotSame("No implementation found.", type, impl);
        testingClass = impl.getCanonicalName();
        /*
             * Compare the XmlRootElement with the UML annotation, if any. The UML annotation
             * is mandatory in the default implementation of the 'testInterfaceAnnotations()'
             * method, but we don't require the UML to be non-null here since this is not the
             * job of this test method. This is because subclasses may choose to override the
             * 'testInterfaceAnnotations()' method.
             */
        final XmlRootElement root = impl.getAnnotation(XmlRootElement.class);
        assertNotNull("Missing @XmlRootElement annotation.", root);
        final UML uml = type.getAnnotation(UML.class);
        // More tests on development branch (removed on trunk because test depends on GeoAPI 3.1)
        /*
             * Check that the namespace is the expected one (according subclass)
             * and is not redundant with the package @XmlSchema annotation.
             */
        assertExpectedNamespace(root.namespace(), impl, uml);
        /*
             * Compare the XmlType annotation with the expected value.
             */
        final XmlType xmlType = impl.getAnnotation(XmlType.class);
        assertNotNull("Missing @XmlType annotation.", xmlType);
    // More tests on development branch (removed on trunk because test depends on GeoAPI 3.1)
    }
}
Also used : XmlRootElement(javax.xml.bind.annotation.XmlRootElement) UML(org.opengis.annotation.UML) XmlType(javax.xml.bind.annotation.XmlType) Test(org.junit.Test) DependsOnMethod(org.apache.sis.test.DependsOnMethod)

Example 2 with UML

use of org.opengis.annotation.UML in project sis by apache.

the class AnnotationConsistencyCheck method testWrapperAnnotations.

/**
 * Tests the annotations on wrappers returned by {@link #getWrapperFor(Class)}.
 * More specifically this method tests that:
 *
 * <ul>
 *   <li>The wrapper have a getter and a setter method declared in the same class.</li>
 *   <li>The getter method is annotated with {@code @XmlElement} or {@code @XmlElementRef}, but not both</li>
 *   <li>{@code @XmlElementRef} is used only in parent classes, not in leaf classes.</li>
 *   <li>The name declared in {@code @XmlElement} matches the {@code @UML} identifier.</li>
 * </ul>
 */
@Test
public void testWrapperAnnotations() {
    for (final Class<?> type : types) {
        testingClass = type.getCanonicalName();
        /*
             * Check the annotation on the wrapper, if there is one. If no wrapper is declared
             * specifically for the current type, check if a wrapper is defined for the parent
             * interface. In such case, the getElement() method is required to be annotated by
             * @XmlElementRef, not @XmlElement, in order to let JAXB infer the name from the
             * actual subclass.
             */
        final WrapperClass wrapper;
        try {
            wrapper = getWrapperInHierarchy(type);
        } catch (ClassNotFoundException e) {
            fail(e.toString());
            continue;
        }
        if (wrapper.type == null) {
            // If the wrapper is intentionally undefined, skip it.
            continue;
        }
        /*
             * Now fetch the getter/setter methods, ensure that they are declared in the same class
             * and verify that exactly one of @XmlElement or @XmlElementRef annotation is declared.
             */
        testingClass = wrapper.type.getCanonicalName();
        final XmlElement element;
        if (type.isEnum()) {
            final Field field;
            try {
                field = wrapper.type.getDeclaredField("value");
            } catch (NoSuchFieldException e) {
                fail(e.toString());
                continue;
            }
            element = field.getAnnotation(XmlElement.class);
        } else {
            final Method getter, setter;
            try {
                getter = wrapper.type.getMethod("getElement", (Class<?>[]) null);
                setter = wrapper.type.getMethod("setElement", getter.getReturnType());
            } catch (NoSuchMethodException e) {
                fail(e.toString());
                continue;
            }
            assertEquals("The setter method must be declared in the same class than the " + "getter method - not in a parent class, to avoid issues with JAXB.", getter.getDeclaringClass(), setter.getDeclaringClass());
            assertEquals("The setter parameter type shall be the same than the getter return type.", getter.getReturnType(), TestUtilities.getSingleton(setter.getParameterTypes()));
            element = getter.getAnnotation(XmlElement.class);
            assertEquals("Expected @XmlElement XOR @XmlElementRef.", (element == null), getter.isAnnotationPresent(XmlElementRef.class) || getter.isAnnotationPresent(XmlElementRefs.class));
        }
        /*
             * If the annotation is @XmlElement, ensure that XmlElement.name() is equals
             * to the UML identifier. Then verify that the namespace is the expected one.
             */
        if (element != null) {
            assertFalse("Expected @XmlElementRef.", wrapper.isInherited);
            final UML uml = type.getAnnotation(UML.class);
            if (uml != null) {
                // 'assertNotNull' is 'testInterfaceAnnotations()' job.
                assertEquals("Wrong @XmlElement.", getExpectedXmlRootElementName(uml), element.name());
            }
            final String namespace = assertExpectedNamespace(element.namespace(), wrapper.type, uml);
            if (!CodeList.class.isAssignableFrom(type)) {
                final String expected = getNamespace(getImplementation(type));
                if (expected != null) {
                    // 'assertNotNull' is 'testImplementationAnnotations()' job.
                    assertEquals("Inconsistent @XmlRootElement namespace.", expected, namespace);
                }
            }
        }
    }
}
Also used : Field(java.lang.reflect.Field) CodeList(org.opengis.util.CodeList) UML(org.opengis.annotation.UML) XmlElement(javax.xml.bind.annotation.XmlElement) Method(java.lang.reflect.Method) DependsOnMethod(org.apache.sis.test.DependsOnMethod) Test(org.junit.Test)

Example 3 with UML

use of org.opengis.annotation.UML in project sis by apache.

the class PropertyAccessor method getGetters.

/**
 * Returns the getters. The returned array should never be modified,
 * since it may be shared among many instances of {@code PropertyAccessor}.
 *
 * @param  type            the metadata interface.
 * @param  implementation  the class of metadata implementations, or {@code type} if none.
 * @param  standardImpl    the implementation specified by the {@link MetadataStandard}, or {@code null} if none.
 * @return the getters declared in the given interface (never {@code null}).
 */
private static Method[] getGetters(final Class<?> type, final Class<?> implementation, final Class<?> standardImpl) {
    /*
         * Indices map is used for choosing what to do in case of name collision.
         */
    Method[] getters = (MetadataStandard.IMPLEMENTATION_CAN_ALTER_API ? implementation : type).getMethods();
    final Map<String, Integer> indices = new HashMap<>(hashMapCapacity(getters.length));
    boolean hasExtraGetter = false;
    int count = 0;
    for (Method candidate : getters) {
        if (Classes.isPossibleGetter(candidate)) {
            final String name = candidate.getName();
            if (name.startsWith(SET)) {
                // Paranoiac check.
                continue;
            }
            /*
                 * The candidate method should be declared in the interface. If not, then we require it to have
                 * a @UML annotation. The later case happen when the Apache SIS implementation contains methods
                 * for a new international standard not yet reflected in the GeoAPI interfaces.
                 */
            if (MetadataStandard.IMPLEMENTATION_CAN_ALTER_API) {
                if (type == implementation) {
                    if (!type.isInterface() && !candidate.isAnnotationPresent(UML.class)) {
                        // @UML considered optional only for interfaces.
                        continue;
                    }
                } else
                    try {
                        candidate = type.getMethod(name, (Class[]) null);
                    } catch (NoSuchMethodException e) {
                        if (!candidate.isAnnotationPresent(UML.class)) {
                            // Not a method from an interface, and no @UML in implementation.
                            continue;
                        }
                    }
            }
            /*
                 * At this point, we are ready to accept the method. Before doing so, check if the method override
                 * an other method defined in a parent class with a covariant return type. The JVM considers such
                 * cases as two different methods, while from a Java developer point of view this is the same method.
                 */
            final Integer pi = indices.put(name, count);
            if (pi != null) {
                final Class<?> pt = getters[pi].getReturnType();
                final Class<?> ct = candidate.getReturnType();
                if (ct.isAssignableFrom(pt)) {
                    // Previous type was more accurate.
                    continue;
                }
                if (pt.isAssignableFrom(ct)) {
                    getters[pi] = candidate;
                    continue;
                }
                throw new ClassCastException(Errors.format(Errors.Keys.IllegalArgumentClass_3, Classes.getShortName(type) + '.' + name, ct, pt));
            }
            getters[count++] = candidate;
            if (!hasExtraGetter) {
                hasExtraGetter = name.equals(EXTRA_GETTER.getName());
            }
        }
    }
    /*
         * Sort the standard methods before to add the extra methods (if any) in order to
         * keep the extra methods last. The code checking for the extra methods require
         * them to be last.
         */
    Arrays.sort(getters, 0, count, new PropertyComparator(implementation, standardImpl));
    if (!hasExtraGetter) {
        if (getters.length == count) {
            getters = Arrays.copyOf(getters, count + 1);
        }
        getters[count++] = EXTRA_GETTER;
    }
    getters = ArraysExt.resize(getters, count);
    return getters;
}
Also used : HashMap(java.util.HashMap) PropertyComparator(org.apache.sis.metadata.PropertyComparator) Method(java.lang.reflect.Method) UML(org.opengis.annotation.UML)

Example 4 with UML

use of org.opengis.annotation.UML in project sis by apache.

the class PropertyComparator method indexOf.

/**
 * Returns the index of the given method, or -1 if the method is not found.
 * If positive, the index returned by this method correspond to a sorting in descending order.
 */
private int indexOf(final Method method) {
    /*
         * Check the cached value computed by previous call to 'indexOf(…)'.
         * Example: "getExtents"
         */
    Integer index = order.get(method);
    if (index == null) {
        /*
             * Check the value computed from @XmlType.propOrder() value.
             * Inferred from the method name, so name is often plural.
             * Example: "extents"
             */
        String name = method.getName();
        name = toPropertyName(name, prefix(name).length());
        index = order.get(name);
        if (index == null) {
            /*
                 * Do not happen, except when we have private methods or deprecated public methods
                 * used as bridge between legacy and more recent standards (e.g. ISO 19115:2003 to
                 * ISO 19115:2014), especially when multiplicity changed between the two standards.
                 * Example: "extent"
                 */
            final UML uml = method.getAnnotation(UML.class);
            if (uml == null || (index = order.get(uml.identifier())) == null) {
                index = -1;
            }
        }
        order.put(method, index);
    }
    return index;
}
Also used : UML(org.opengis.annotation.UML)

Example 5 with UML

use of org.opengis.annotation.UML in project sis by apache.

the class PackageVerifier method verify.

/**
 * Validate a field or a method against the expected schema.
 *
 * @param  property      the field or method to validate.
 * @param  javaName      the field name or method name in Java code.
 * @param  valueType     the field type or the method return type, or element type in case of collection.
 * @param  isCollection  whether the given value type is the element type of a collection.
 */
private void verify(final AnnotatedElement property, final String javaName, final Class<?> valueType, final boolean isCollection) throws SchemaException {
    final XmlElement element = property.getDeclaredAnnotation(XmlElement.class);
    if (element == null) {
        // No @XmlElement annotation - skip this property.
        return;
    }
    String name = element.name();
    if (name.equals(AnnotationConsistencyCheck.DEFAULT)) {
        name = javaName;
    }
    String ns = element.namespace();
    if (ns.equals(AnnotationConsistencyCheck.DEFAULT)) {
        ns = classNS;
    }
    if (namespaceIsUsed.put(ns, Boolean.TRUE) == null) {
        throw new SchemaException(errorInClassMember(javaName).append("Missing @XmlNs for namespace ").append(ns));
    }
    /*
         * Remember that we need an adapter for this property, unless the method or field defines its own adapter.
         * In theory we do not need to report missing adapter since JAXB performs its own check, but we do anyway
         * because JAXB has default adapters for String, Double, Boolean, Date, etc. which do not match the way
         * OGC/ISO marshal those elements.
         */
    if (!property.isAnnotationPresent(XmlJavaTypeAdapter.class) && (valueType != null) && !valueType.getName().startsWith(Modules.CLASSNAME_PREFIX)) {
        Class<?> c = valueType;
        while (adapterIsUsed.replace(c, Boolean.TRUE) == null) {
            final Class<?> parent = c.getSuperclass();
            if (parent != null) {
                c = parent;
            } else {
                final Class<?>[] p = c.getInterfaces();
                if (p.length == 0) {
                    throw new SchemaException(errorInClassMember(javaName).append("Missing @XmlJavaTypeAdapter for ").append(valueType));
                }
                // Take only the first interface, which should be the "main" parent.
                c = p[0];
            }
        }
    }
    /*
         * We do not verify fully the properties in legacy namespaces because we didn't loaded their schemas.
         * However we verify at least that those properties are not declared as required.
         */
    if (LEGACY_NAMESPACES.getOrDefault(ns, Collections.emptySet()).contains(name)) {
        if (!isDeprecatedClass && element.required()) {
            throw new SchemaException(errorInClassMember(javaName).append("Legacy property should not be required."));
        }
    } else {
        /*
             * Property in non-legacy namespaces should not be deprecated. Verify also their namespace
             * and whether the property is required or optional, and whether it should be a collection.
             */
        if (property.isAnnotationPresent(Deprecated.class)) {
            throw new SchemaException(errorInClassMember(javaName).append("Unexpected deprecation status."));
        }
        final SchemaCompliance.Info info = properties.get(name);
        if (info == null) {
            throw new SchemaException(errorInClassMember(javaName).append("Unexpected XML element: ").append(name));
        }
        if (info.namespace != null && !ns.equals(info.namespace)) {
            throw new SchemaException(errorInClassMember(javaName).append("Declared namespace: ").append(ns).append(System.lineSeparator()).append("Expected namespace: ").append(info.namespace));
        }
        if (element.required() != info.isRequired) {
            throw new SchemaException(errorInClassMember(javaName).append("Expected @XmlElement(required = ").append(info.isRequired).append(')'));
        }
        /*
             * Following is a continuation of our check for cardinality, but also the beginning of the check
             * for return value type. The return type should be an interface with a UML annotation; we check
             * that this annotation contains the name of the expected type.
             */
        if (isCollection) {
            if (!info.isCollection) {
                if (// Temporarily disabled because require GeoAPI modifications.
                false)
                    throw new SchemaException(errorInClassMember(javaName).append("Value should be a singleton."));
            }
        } else if (info.isCollection) {
            if (// Temporarily disabled because require GeoAPI modifications.
            false)
                throw new SchemaException(errorInClassMember(javaName).append("Value should be a collection."));
        }
        if (valueType != null) {
            final UML valueUML = valueType.getAnnotation(UML.class);
            if (valueUML != null) {
                String expected = info.typeName;
                String actual = valueUML.identifier();
                expected = TYPE_EQUIVALENCES.getOrDefault(expected, expected);
                actual = TYPE_EQUIVALENCES.getOrDefault(actual, actual);
                if (!expected.equals(actual)) {
                    if (// Temporarily disabled because require GeoAPI modifications.
                    false)
                        throw new SchemaException(errorInClassMember(javaName).append("Declared value type: ").append(actual).append(System.lineSeparator()).append("Expected value type: ").append(expected));
                }
            }
        }
        /*
             * Verify if we have a @XmlNs for the type of the value. This is probably not required, but we
             * do that as a safety. A common namespace added by this check is Metadata Common Classes (MCC).
             */
        final Map<String, SchemaCompliance.Info> valueInfo = schemas.typeDefinition(info.typeName);
        if (valueInfo != null) {
            final String valueNS = valueInfo.get(null).namespace;
            if (namespaceIsUsed.put(valueNS, Boolean.TRUE) == null) {
                throw new SchemaException(errorInClassMember(javaName).append("Missing @XmlNs for property value namespace: ").append(valueNS));
            }
        }
    }
}
Also used : UML(org.opengis.annotation.UML) XmlJavaTypeAdapter(javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter) XmlElement(javax.xml.bind.annotation.XmlElement)

Aggregations

UML (org.opengis.annotation.UML)10 Method (java.lang.reflect.Method)4 DependsOnMethod (org.apache.sis.test.DependsOnMethod)4 Test (org.junit.Test)4 XmlElement (javax.xml.bind.annotation.XmlElement)3 CodeList (org.opengis.util.CodeList)2 Field (java.lang.reflect.Field)1 HashMap (java.util.HashMap)1 XmlRootElement (javax.xml.bind.annotation.XmlRootElement)1 XmlType (javax.xml.bind.annotation.XmlType)1 XmlJavaTypeAdapter (javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter)1 PropertyComparator (org.apache.sis.metadata.PropertyComparator)1 Specification (org.opengis.annotation.Specification)1