org.joda.beans.MetaBean Maven / Gradle / Ivy
/*
* Copyright 2001-present Stephen Colebourne
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.joda.beans;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
/**
* A meta-bean, defining those aspects of a bean which are not specific
* to a particular instance, such as the type and set of meta-properties.
*
* This interface can be thought of as the equivalent of {@link Class} but for beans.
* In most cases the meta-bean will be code generated and the concrete class will have additional methods.
*/
public interface MetaBean {
/**
* Obtains a meta-bean from a {@code Class}.
*
* This will return a meta-bean if it has been registered, or if the class
* implements {@link DynamicBean} and has a no-args constructor.
* Note that the common case where the meta-bean is registered by a static initializer is handled.
*
* @param cls the class to get the meta-bean for, not null
* @return the meta-bean associated with the class, not null
* @throws IllegalArgumentException if unable to obtain the meta-bean
*/
public static MetaBean of(Class> cls) {
return MetaBeans.lookup(cls);
}
/**
* Registers a meta-bean.
*
* This should be done for all beans in a static factory where possible.
* If the meta-bean is dynamic, this method should not be called.
*
* @param metaBean the meta-bean, not null
* @throws IllegalArgumentException if unable to register
*/
public static void register(MetaBean metaBean) {
MetaBeans.register(metaBean);
}
//-----------------------------------------------------------------------
/**
* Checks whether this bean is buildable or not.
*
* A buildable bean can be constructed using {@link #builder()}.
* If this method returns true then {@code builder()} must return a valid builder.
* If this method returns false then {@code builder()} must throw {@link UnsupportedOperationException}.
*
* @return true if this bean is buildable
*/
public abstract boolean isBuildable();
/**
* Creates a bean builder that can be used to create an instance of this bean.
*
* The builder is used in two main ways.
* The first is to allow immutable beans to be constructed.
* The second is to enable automated tools like serialization/deserialization.
*
* The builder can be thought of as a {@code Map} of {@link MetaProperty} to value.
* Note that the implementation is not necessarily an actual map.
*
* @return the bean builder, not null
* @throws UnsupportedOperationException if the bean cannot be created
*/
public abstract BeanBuilder extends Bean> builder();
//-----------------------------------------------------------------------
/**
* Gets the bean name, which is normally the fully qualified class name of the bean.
*
* This is primarily used for human-readable output.
*
* @return the name of the bean, not empty
*/
public default String beanName() {
return beanType().getName();
}
/**
* Get the type of the bean, represented as a {@code Class}.
*
* A {@code MetaBean} can be thought of as the equivalent of {@link Class} but for beans.
* This method allows the actual {@code Class} instance of the bean to be obtained.
*
* @return the type of the bean, not null
*/
public abstract Class extends Bean> beanType();
//-----------------------------------------------------------------------
/**
* Counts the number of properties.
*
* Each meta-bean manages a single bean with a known set of properties.
* This method returns the count of properties.
*
* @return the number of properties
*/
public default int metaPropertyCount() {
return metaPropertyMap().size();
}
/**
* Checks if a property exists.
*
* Each meta-bean manages a single bean with a known set of properties.
* This method checks whether there is a property with the specified name.
*
* @param propertyName the property name to check, null returns false
* @return true if the property exists
*/
public default boolean metaPropertyExists(String propertyName) {
return metaPropertyMap().containsKey(propertyName);
}
/**
* Gets a meta-property by name.
*
* Each meta-bean manages a single bean with a known set of properties.
* This method returns the property with the specified name.
*
* The base interface throws an exception if the name is not recognised.
* By contrast, the {@code DynamicMetaBean} subinterface creates the property on demand.
*
* @param the property type, optional, enabling auto-casting
* @param propertyName the property name to retrieve, not null
* @return the meta property, not null
* @throws NoSuchElementException if the property name is invalid
*/
@SuppressWarnings("unchecked")
public default MetaProperty metaProperty(String propertyName) {
MetaProperty> mp = metaPropertyMap().get(propertyName);
if (mp == null) {
throw new NoSuchElementException("Unknown property: " + propertyName);
}
return (MetaProperty) mp;
}
/**
* Gets an iterator of meta-properties.
*
* This method returns an {@code Iterable}, which is simpler than a {@code Map}.
* As a result, implementations may be able to optimise, and so this method should be
* preferred to {@link #metaPropertyMap()} where a choice is possible.
*
* @return the unmodifiable map of meta property objects, not null
*/
public default Iterable> metaPropertyIterable() {
return metaPropertyMap().values();
}
/**
* Gets the map of meta-properties, keyed by property name.
*
* Where possible, use {@link #metaPropertyIterable()} instead as it typically has better performance.
*
* @return the unmodifiable map of meta property objects, not null
*/
public abstract Map> metaPropertyMap();
/**
* Gets the annotations associated with this bean.
*
* The annotations are queried from the bean.
* This is typically accomplished by querying the annotations of an underlying
* {@link Class} however any strategy is permitted.
*
* If the implementation has a mutable set of annotations, then the result of
* this method must stream over those annotations in existence when this method
* is called to avoid concurrency issues.
*
* The default implementation uses the annotations from {@link #beanType()}.
*
* @return the annotations, unmodifiable, not null
*/
public default List annotations() {
return Collections.unmodifiableList(Arrays.asList(beanType().getAnnotations()));
}
/**
* Gets an annotation from the bean.
*
* The annotations are queried from the bean.
* This is typically accomplished by querying the annotations of an underlying
* {@link Class} however any strategy is permitted.
*
* @param the annotation type
* @param annotationClass the annotation class to find, not null
* @return the annotation, not null
* @throws NoSuchElementException if the annotation is not specified
*/
@SuppressWarnings("unchecked")
public default A annotation(Class annotationClass) {
List annotations = annotations();
for (Annotation annotation : annotations) {
if (annotationClass.isInstance(annotation)) {
return (A) annotation;
}
}
throw new NoSuchElementException("Unknown annotation: " + annotationClass.getName());
}
/**
* Finds an optional annotation from the property.
*
* The annotations are queried from the property.
* This is typically accomplished by querying the annotations of the underlying
* instance variable however any strategy is permitted.
*
* @param the annotation type
* @param annotationClass the annotation class to find, not null
* @return the annotation, empty if not found
* @since 2.11.0
*/
@SuppressWarnings("unchecked")
public default Optional annotationOpt(Class annotationClass) {
List annotations = annotations();
for (Annotation annotation : annotations) {
if (annotationClass.isInstance(annotation)) {
return Optional.of((A) annotation);
}
}
return Optional.empty();
}
}