All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.mrapp.parser.feed.common.adapter.AbstractFeedElementParserAdapter Maven / Gradle / Ivy

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