de.mrapp.parser.feed.common.adapter.AbstractFeedElementParserAdapter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of javafeedparsercommon Show documentation
Show all versions of javafeedparsercommon Show documentation
Contains common classes of projects, which allow to parse feeds
The newest version!
/*
* 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