
de.mrapp.parser.feed.common.adapter.AbstractFeedElementParserAdapter Maven / Gradle / Ivy
/*
* JavaFeedParserCommon Copyright 2013-2014 Michael Rapp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package de.mrapp.parser.feed.common.adapter;
import static de.mrapp.parser.feed.common.util.ReflectionUtil.getGenericTypeArguments;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.LinkedList;
import de.mrapp.parser.feed.common.builder.AbstractSettingsParserBuilder;
import de.mrapp.parser.feed.common.datastructure.Element;
import de.mrapp.parser.feed.common.model.FeedElement;
import de.mrapp.parser.feed.common.parser.AbstractFeedElementParser;
/**
* An abstract adapter class, which allows subclasses to remain simple, while
* extending the abstract class {@link AbstractFeedElementParser}. Therefore the
* adapter provides necessary method implementations, which use the Java
* Reflection API to invoke the subclasses' methods and access relevant
* attributes.
*
* As a requirement the subclass must provide methods and attributes conform to
* a specific naming scheme. Disrespecting the naming scheme may cause errors at
* runtime, which are extended from the class {@link ReflectionError}. The
* following naming schemes must be considered when implementing subclasses of
* the adapter:
*
* 1. To retrieve the names of the element's children, which are defined to
* belong to the feed by the appropriate specification, the method
* getDefinedChildNames():String[]
is invoked. The implementation
* of this method is based on determining the fields of the adapter's subclass,
* whose values contain the child names. To allow these field to be discovered,
* their names must end with the suffix 'ELEMENT_NAME', they must have a public,
* static and final modifier and they must define a value of the type
* {@link String}. To retrieve the names of the children, the values of the
* discovered fields are accessed.
*
* 2. To retrieve the names of the element's attributes, which are defined to
* belong to the feed by the appropriate specification, the method
* getDefinedAttributeNames():String[]
is invoked. The
* implementation of this method is based on determining the fields of the
* adapter's subclass, whose values contain the child names. To allow these
* field to be discovered, their names must end with the suffix
* 'ATTRIBUTE_NAME', they must have a public, static and final modifier and they
* must define a value of the type {@link String}. To retrieve the names of the
* attributes, the values of the discovered fields are accessed.
*
*
* 3. To retrieve, whether the element's value is defined to belong to the feed
* by the appropriate specification, or not, the method
* hasDefinedValue():boolean
is invoked. The implementation of this
* method is based on determining, whether a method, which parses the value of
* the element, exists in the subclass of the adapter, or not. Such a method
* must have a private modifier, must not expect any parameters, must have the
* name 'getValue' and must have the return type 'void'. if such a method
* exists, is is assumed, that the element's value is defined to belong to the
* feed.
*
* 4. To instantiate a null object, which represents a null instance of the
* parser's result value, the method getNullObject:ResultType
is
* invoked. The implementation of this method is based on determining the class,
* which represents a null object, which matches to the parser's result type.
* Therefore a class, whose name is equals to result type, plus the suffix
* 'NullObject', must be available in a package named 'nullobject', which is
* located on the same level of the package hierarchy as the parent package of
* the result type. Furthermore, the class must provide a default constructor,
* because no parameters are used to instantiate the null object.
*
* @param
* The type of the parser's result
*
* @author Michael Rapp
*
* @since 1.0.0
*/
public abstract class AbstractFeedElementParserAdapter
extends AbstractFeedElementParser {
/**
* Returns, whether a specific field defines a public string constant, or
* not.
*
* @param field
* The field, which should be checked, as an instance of the
* class {@link Field}
* @return True, if the field defines a public string constant, false
* otherwise
*/
private boolean isPublicStringConstant(final Field field) {
return hasPublicModifier(field) && isConstant(field)
&& hasCompatibleType(field);
}
/**
* Returns, whether a specific field has a public modifier, or not.
*
* @param field
* The field, which should be checked, as an instance of the
* class {@link Field}
* @return True, if the given field has a public modifier, false otherwise
*/
private boolean hasPublicModifier(final Field field) {
return Modifier.isPublic(field.getModifiers());
}
/**
* Returns, whether a specific field defines a constant, or not. To define a
* constant, the field must have a final and a static modifier.
*
* @param field
* The field, which should be checked, as an instance of the
* class {@link Field}
* @return True, if the given field defines a constant, false otherwise
*/
private boolean isConstant(final Field field) {
int modifiers = field.getModifiers();
return Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers);
}
/**
* Returns, whether a specific field defines a compatible type. To be
* compatible, the field must define a constant of the type {@link String}
*
* @param field
* The field, which should be checked, as an instance of the
* class {@link Field}
* @return True, if the given field defines a compatible type, false
* otherwise
*/
private boolean hasCompatibleType(final Field field) {
Class> type = field.getType();
return String.class.isAssignableFrom(type);
}
/**
* Returns, whether a specific field defines a child name, or not. To define
* a child name, the field's name must end with the suffix 'ELEMENT_NAME'.
*
* @param field
* The field, which should be checked, as an instance of the
* class {@link Field}
* @return True, if the given field defines a child name, false otherwise
*/
private boolean definesChildName(final Field field) {
return field.getName().endsWith("ELEMENT_NAME");
}
/**
* Returns, whether a specific field defines an attribute name, or not. To
* define an attribute name, the field's name must end with the suffix
* 'ATTRIBUTE_NAME'.
*
* @param field
* The field, which should be checked, as an instance of the
* class {@link Field}
* @return True, if the given field defines an attribute name, false
* otherwise
*/
private boolean definesAttributeName(final Field field) {
return field.getName().endsWith("ATTRIBUTE_NAME");
}
/**
* Returns the value of a string constant, which is defined by a specific
* field. If an error occurs while accessing the value, an instance of the
* class {@link ConstantAccessError} will be thrown.
*
* @param field
* The field, which defines the string constant, as an instance
* of the class {@link Field}
* @return The value of the string constant, which is defined by the given
* field, as a {@link String}
*/
private String getValueOfStringConstant(final Field field) {
try {
Object value = field.get(this);
return (String) value;
} catch (IllegalArgumentException e) {
throw new ConstantAccessError(
"Error while retrieving child or attribute name", e);
} catch (IllegalAccessException e) {
throw new ConstantAccessError(
"Error while retrieving child or attribute name", e);
} catch (ClassCastException e) {
throw new ConstantAccessError(
"Error while retrieving child or attribute name", e);
}
}
/**
* Creates and returns a string array from a specific collection.
*
* @param collection
* The collection as an instance of the type {@link Collection}
* @return The {@link String} array, which has been created
*/
private String[] collectionToStringArray(final Collection collection) {
String[] array = new String[collection.size()];
collection.toArray(array);
return array;
}
/**
* Returns, whether a specific method parses the value of a feed element, or
* not. Such a method must have a private modifier, must except one
* parameter of the type {@link Element}, its return type must be 'void' and
* its name must be 'parseValue'.
*
* @param method
* The method, which should be checked, as an instance of the
* class {@link Method}
* @return True, if the given method returns the value of a feed element,
* false otherwise
*/
private boolean parsesValue(final Method method) {
return hasPrivateModifier(method) && hasCompatibleName(method)
&& hasCompatibleParameters(method)
&& hasCompatibleReturnType(method);
}
/**
* Returns, whether a specific method has a private modifier, or not.
*
* @param method
* The method, which should be checked, as an instance of the
* class {@link Method}
* @return True, if the given method has a private modifier, false otherwise
*/
private boolean hasPrivateModifier(final Method method) {
return Modifier.isPrivate(method.getModifiers());
}
/**
* Returns, whether a specific method has a compatible name, or not. To be
* compatible, the method's name must be 'parseValue'.
*
* @param method
* The method, which should be checked, as an instance of the
* class {@link Method}
* @return True, if the given method has a compatible name, false otherwise
*/
private boolean hasCompatibleName(final Method method) {
return method.getName().equals("parseValue");
}
/**
* Returns, whether a specific method has compatible parameters, or not. To
* be compatible, the method must expect exactly one parameter of the type
* {@link Element}.
*
* @param method
* The method, which should be checked, as an instance of the
* class {@link Method}
* @return True, if the given method has compatible parameters, false
* otherwise
*/
private boolean hasCompatibleParameters(final Method method) {
Class>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
return Element.class.isAssignableFrom(parameterTypes[0]);
}
return false;
}
/**
* Returns, whether a specific method has a compatible return type. To be
* compatible, the method's return type must be 'void'.
*
* @param method
* The method, which should be checked, as an instance of the
* class {@link Method}
* @return True, if the given method has a compatible return type, false
* otherwise
*/
private boolean hasCompatibleReturnType(final Method method) {
Class> returnType = method.getReturnType();
return returnType.equals(Void.TYPE);
}
/**
* Returns the generic type, which is defined by the implementing subclass
* of this adapter.
*
* @return The generic type as an instance of the class {@link Class}
*/
private Class> getGenericType() {
Class> genericType = getGenericTypeArguments(
AbstractFeedElementParserAdapter.class, getClass()).get(0);
return genericType;
}
/**
* Returns the class name of a null object, which represents a null instance
* of a specific class. The implementing class of the null object must be
* placed inside a package named 'nullobject', which is located on the same
* level as the parent package of the given class. Furthermore, the name of
* the implementing class must be equal to the name of the given class, plus
* the suffix 'NullObject'.
*
* @param type
* The class, which should be represented by the null object, as
* an instance of the class {@link Class}
* @return The fully qualified class name of the null object as a
* {@link String}
*/
private String getNullObjectClassName(final Class> type) {
String typePackageName = type.getPackage().getName();
String typeClassName = type.getSimpleName();
String nullObjectPackageName = typePackageName.substring(0,
typePackageName.lastIndexOf(".") + 1) + "nullobject";
String nullObjectClassName = nullObjectPackageName + "."
+ typeClassName + "NullObject";
return nullObjectClassName;
}
/**
* Instantiates a null object, which is specified by a specific class name.
* The class must define a default constructor, due to no parameters are
* used to instantiate the object. If an error occurs while instantiating
* the object, an instance of the class {@link ClassInstantiationError} will
* be thrown.
*
* @param className
* A fully qualified class name as a {@link String}
* @return The class object, which has been instantiated, casted to the type
* ResultType
*/
@SuppressWarnings("unchecked")
private ResultType instantiateNullObject(final String className) {
try {
Class> nullObjectClass = Class.forName(className);
Object nullObjectInstance = nullObjectClass.newInstance();
return (ResultType) nullObjectInstance;
} catch (ClassNotFoundException e) {
throw new ClassInstantiationError(
"NullObject could not be instantiated", e);
} catch (InstantiationException e) {
throw new ClassInstantiationError(
"NullObject could not be instantiated", e);
} catch (IllegalAccessException e) {
throw new ClassInstantiationError(
"NullObject could not be instantiated", e);
}
}
@Override
protected final String[] getDefinedChildNames() {
Collection childNames = new LinkedList();
Field[] fields = getClass().getDeclaredFields();
for (Field field : fields) {
if (isPublicStringConstant(field) && definesChildName(field)) {
childNames.add(getValueOfStringConstant(field));
}
}
return collectionToStringArray(childNames);
}
@Override
protected final String[] getDefinedAttributeNames() {
Collection attributeNames = new LinkedList();
Field[] fields = getClass().getDeclaredFields();
for (Field field : fields) {
if (isPublicStringConstant(field) && definesAttributeName(field)) {
attributeNames.add(getValueOfStringConstant(field));
}
}
return collectionToStringArray(attributeNames);
}
@Override
protected final boolean hasDefinedValue() {
Method[] methods = getClass().getDeclaredMethods();
for (Method method : methods) {
if (parsesValue(method)) {
return true;
}
}
return false;
}
@Override
protected final ResultType getNullObject() {
Class> genericType = getGenericType();
String nullObjectClassName = getNullObjectClassName(genericType);
return instantiateNullObject(nullObjectClassName);
}
/**
* Creates a new adapter, which allows subclasses to remain simple, while
* extending the abstract class {@link AbstractFeedElementParser}.
*
* @param element
* The element, which should be parsed, as an instance of the
* type {@link Element}
* @param builder
* The builder, which should be used to initialize the parser, as
* an instance of the class {@link AbstractSettingsParserBuilder}
*/
public AbstractFeedElementParserAdapter(final Element element,
final AbstractSettingsParserBuilder, ?> builder) {
super(element, builder);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy