Package uk.co.cwspencer.gdb.messages

Source Code of uk.co.cwspencer.gdb.messages.GdbMiMessageConverter

package uk.co.cwspencer.gdb.messages;

import com.intellij.openapi.diagnostic.Logger;
import uk.co.cwspencer.gdb.gdbmi.GdbMiList;
import uk.co.cwspencer.gdb.gdbmi.GdbMiResult;
import uk.co.cwspencer.gdb.gdbmi.GdbMiResultRecord;
import uk.co.cwspencer.gdb.gdbmi.GdbMiValue;
import uk.co.cwspencer.gdb.messages.annotations.GdbMiConversionRule;
import uk.co.cwspencer.gdb.messages.annotations.GdbMiDoneEvent;
import uk.co.cwspencer.gdb.messages.annotations.GdbMiEvent;
import uk.co.cwspencer.gdb.messages.annotations.GdbMiField;

import java.lang.reflect.*;
import java.util.Arrays;
import java.util.List;

/**
* Class which handles the conversion of GDB/MI messages to Java objects.
*/
public class GdbMiMessageConverter {
    private static final Logger m_log =
            Logger.getInstance("#uk.co.cwspencer.gdb.messages.GdbMiMessageConverter");

    /**
     * Special value that can be returned by value processors to indicate that the normal automatic
     * processing should be applied to the value.
     */
    public static final Object ValueProcessorPassThrough = new Object();

    /**
     * Converts the given GDB/MI result record into a suitable Java object.
     *
     * @param record The GDB result record.
     * @return The new object, or null if it could not be created.
     */
    public static GdbEvent processRecord(GdbMiResultRecord record) {
        return processRecord(record, null);
    }

    /**
     * Converts the given GDB/MI result record into a suitable Java object.
     *
     * @param record      The GDB result record.
     * @param commandType The original command type (excluding any parameters) that was sent which
     *                    caused GDB to send the record. This is used for mapping 'done' events to the appropriate
     *                    type.
     * @return The new object, or null if it could not be created.
     */
    public static GdbEvent processRecord(GdbMiResultRecord record, String commandType) {
        // Iterate through the list of event types
        GdbEvent event = null;
        for (Class<?> clazz : GdbMiEventTypes.classes) {
            // Verify the type has a GdbMiEvent annotation
            GdbMiEvent eventAnnotation = clazz.getAnnotation(GdbMiEvent.class);
            if (eventAnnotation == null) {
                m_log.warn("Class " + clazz.getName() + " is in the GdbMiEventTypes.classes list " +
                        "but does not have a GdbMiEvent annotation");
                continue;
            }

            // Check if this type is appropriate for the record
            if (eventAnnotation.recordType() == record.type) {
                boolean match = false;
                for (String className : eventAnnotation.className()) {
                    if (className.equals(record.className)) {
                        match = true;
                        break;
                    }
                }

                if (match) {
                    // Found a matching event type wrapper
                    List<GdbMiResult> results = record.results;

                    // If it is a 'done' event then search for a more specific event type
                    if (commandType != null && clazz.equals(GdbDoneEvent.class)) {
                        for (Class<?> doneEventClass : GdbMiEventTypes.doneEventTypes) {
                            GdbMiDoneEvent doneEventAnnotation =
                                    doneEventClass.getAnnotation(GdbMiDoneEvent.class);
                            if (doneEventAnnotation == null) {
                                m_log.warn("Class " + doneEventClass.getName() + " is in the " +
                                        "GdbMiEventTypes.doneEventTypes list but does not have a" +
                                        "GdbMiDoneEvent annotation");
                                continue;
                            }

                            if (doneEventAnnotation.command().equals(commandType)) {
                                // Found a match; check if we need to transpose a specific result
                                // onto this class
                                if (!doneEventAnnotation.transpose().isEmpty()) {
                                    List<GdbMiResult> transposedResults = transposeDoneEvent(record,
                                            doneEventAnnotation);
                                    if (transposedResults == null) {
                                        m_log.warn("Class " + doneEventClass.getName() + " is " +
                                                "trying to transpose '" +
                                                doneEventAnnotation.transpose() + "', but the result " +
                                                "does not exist or is not a tuple or list of results");
                                        break;
                                    }

                                    results = transposedResults;
                                }

                                clazz = doneEventClass;
                                break;
                            }
                        }
                    }

                    // Process the object
                    event = (GdbEvent) processObject(clazz, results);
                    break;
                }

            }

        }
        return event;
    }

    /**
     * Extracts data from the result requested by the class.
     *
     * @param record              The result record.
     * @param doneEventAnnotation The annotation on the class.
     * @return The new list of results, or null if it could not be transposed.
     */
    private static List<GdbMiResult> transposeDoneEvent(GdbMiResultRecord record,
                                                        GdbMiDoneEvent doneEventAnnotation) {
        // Search for the requested result
        for (GdbMiResult result : record.results) {
            if (result.variable.equals(doneEventAnnotation.transpose())) {
                // Found it; check it is an appropriate type (it must be a tuple or a list of
                // results)
                if (!(result.value.type == GdbMiValue.Type.Tuple ||
                        (result.value.type == GdbMiValue.Type.List &&
                                (result.value.list.type == GdbMiList.Type.Empty ||
                                        result.value.list.type == GdbMiList.Type.Results)))) {
                    return null;
                }

                return result.value.type ==
                        GdbMiValue.Type.Tuple ? result.value.tuple : result.value.list.results;
            }
        }
        return null;
    }

    /**
     * Converts the given list of GDB/MI results to an object of the given type.
     *
     * @param clazz   The type of object to create.
     * @param results The results from GDB.
     * @return The new object, or null if it could not be created.
     */
    public static Object processObject(Class<?> clazz, List<GdbMiResult> results) {
        try {
            Object object = clazz.newInstance();

            // Populate the fields with data from the result
            Field[] fields = clazz.getFields();
            for (Field field : fields) {
                GdbMiField fieldAnnotation = field.getAnnotation(GdbMiField.class);
                if (fieldAnnotation == null) {
                    continue;
                }

                // Find a result with the requested variable name
                for (GdbMiResult result : results) {
                    if (!fieldAnnotation.name().equals(result.variable)) {
                        continue;
                    }

                    // Found a matching field; convert the value
                    convertField(object, clazz, field, fieldAnnotation, result);
                    break;
                }
            }

            return object;
        } catch (Throwable ex) {
            m_log.warn("Failed to convert GDB/MI message to a Java object", ex);
            return null;
        }
    }

    /**
     * Converts a GdbMiResult into a suitable Java type and puts it in the given field on the given
     * object.
     *
     * @param event           The object to put the value into.
     * @param clazz           The class of the object.
     * @param field           The field on the object to put the value into.
     * @param fieldAnnotation The GdbMiField annotation on the field.
     * @param result          The result to get the data from.
     */
    private static void convertField(Object event, Class<?> clazz, Field field,
                                     GdbMiField fieldAnnotation, GdbMiResult result) throws InvocationTargetException,
            IllegalAccessException {
        // Check the result type is supported by the field
        boolean foundValueType = false;
        for (GdbMiValue.Type valueType : fieldAnnotation.valueType()) {
            if (valueType == result.value.type) {
                foundValueType = true;
                break;
            }
        }
        if (!foundValueType) {
            m_log.warn("Annotation on \"" + field.getName() + "\" requires on of GDB/MI types \"" +
                    Arrays.toString(fieldAnnotation.valueType()) + "\"; got \"" + result.value.type + "\"");
            return;
        }

        if (!fieldAnnotation.valueProcessor().isEmpty()) {
            // Field has a custom value processor
            convertFieldUsingValueProcessor(event, clazz, field, fieldAnnotation, result);
        } else {
            // Field does not have a custom value processor; convert it manually
            convertFieldManually(event, field, result);
        }
    }

    /**
     * Converts a GdbMiResult into a suitable Java type and puts it in the given field on the given
     * object using a custom value processor defined by the field.
     *
     * @param event           The object to put the value into.
     * @param clazz           The class of the object.
     * @param field           The field on the object to put the value into.
     * @param fieldAnnotation The GdbMiField annotation on the field.
     * @param result          The result to get the data from.
     */
    private static void convertFieldUsingValueProcessor(Object event, Class<?> clazz, Field field,
                                                        GdbMiField fieldAnnotation, GdbMiResult result) throws InvocationTargetException,
            IllegalAccessException {
        // Get the value processor function
        Method valueProcessor;
        try {
            String valueProcessorName = fieldAnnotation.valueProcessor();
            int lastDotIndex = valueProcessorName.lastIndexOf('.');
            if (lastDotIndex == -1) {
                // Value processor is a function on the parent class
                valueProcessor = clazz.getMethod(valueProcessorName, GdbMiValue.class);
            } else {
                // Value processor is a fully-qualified name
                String className = valueProcessorName.substring(0, lastDotIndex);
                String methodName = valueProcessorName.substring(lastDotIndex + 1);

                Class<?> valueProcessorClass = Class.forName(className);
                valueProcessor = valueProcessorClass.getMethod(methodName, GdbMiValue.class);
            }
        } catch (NoSuchMethodException ex) {
            m_log.warn("Annotation on " + field.getName() + " has value processor " +
                    fieldAnnotation.valueProcessor() + ", but no such function exists on the class " +
                    "(or it does not take the right arguments)", ex);
            return;
        } catch (ClassNotFoundException ex) {
            m_log.warn("Annotation on " + field.getName() + " has value processor " +
                    fieldAnnotation.valueProcessor() + ", but the referenced class does not exist", ex);
            return;
        }

        // Invoke the method
        Object resultValue = null;
        Object value;
        try {
            value = valueProcessor.invoke(event, result.value);
        } catch (Throwable ex) {
            m_log.warn("Field to invoke value processor for field " + field.getName() + " with " +
                    "value " + resultValue, ex);
            return;
        }

        // We don't need to do anything if the value processor returned null
        if (value == null) {
            return;
        }

        // If the value processor returns the special value ValueProcessorPassThrough then we need
        // to apply the default processing to the value
        if (value == ValueProcessorPassThrough) {
            convertFieldManually(event, field, result);
            return;
        }

        // Check the returned value is of the correct type
        if (!field.getType().isAssignableFrom(value.getClass())) {
            m_log.warn("Field " + field.getName() + " is of type " + field.getType() + ", but " +
                    "the value processor returned " + value + " [type=" + value.getClass() + "]");
            return;
        }

        // Set the value on the field
        try {
            field.set(event, value);
        } catch (IllegalAccessException ex) {
            m_log.warn("Failed to set value on field " + field, ex);
        }
    }

    /**
     * Converts a GdbMiResult into a suitable Java type and puts it in the given field on the given
     * object using built-in value processors.
     *
     * @param event  The object to put the value into.
     * @param field  The field on the object to put the value into.
     * @param result The result to get the data from.
     */
    static void convertFieldManually(Object event, Field field, GdbMiResult result) throws
            InvocationTargetException, IllegalAccessException {
        ParameterizedType genericType = null;
        {
            Type basicGenericType = field.getGenericType();
            if (basicGenericType instanceof ParameterizedType) {
                genericType = (ParameterizedType) basicGenericType;
            }
        }

        Object value = applyConversionRules(field.getType(), genericType, result.value);
        if (value != null) {
            field.set(event, value);
        } else {
            m_log.warn("No conversion rules were available to convert GDB/MI result '" +
                    result + "' for field " + field);
        }
    }

    /**
     * Applies the conversion rules to the given GDB/MI result and returns the converted object.
     *
     * @param targetType        The type of object to be created.
     * @param genericTargetType The generic type of the object to be created. May be null.
     * @param value             The value to get the data from.
     * @return The new object, or null if it could not be created.
     */
    static Object applyConversionRules(Class<?> targetType, ParameterizedType genericTargetType,
                                       GdbMiValue value) throws InvocationTargetException, IllegalAccessException {
        // Apply the conversion rules until we get a match
        Object jValue = null;
        Method[] methods = GdbMiValueConversionRules.class.getMethods();
        for (Method method : methods) {
            // Verify it is a conversion rule
            if (method.getAnnotation(GdbMiConversionRule.class) == null) {
                continue;
            }

            jValue = method.invoke(null, targetType, genericTargetType, value);
            if (jValue != null) {
                break;
            }
        }
        return jValue;
    }
}
TOP

Related Classes of uk.co.cwspencer.gdb.messages.GdbMiMessageConverter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.