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)
}
}
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);
}
}
}
}
}
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;
}
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;
}
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));
}
}
}
}
Aggregations