de.hasait.clap.impl.CLAPClassNode.java Source code

Java tutorial

Introduction

Here is the source code for de.hasait.clap.impl.CLAPClassNode.java

Source

/*
 * Copyright (C) 2013 by Sebastian Hasait (sebastian at hasait dot de)
 *
 * 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 de.hasait.clap.impl;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang3.tuple.Pair;

import de.hasait.clap.CLAP;
import de.hasait.clap.CLAPDecision;
import de.hasait.clap.CLAPDelegate;
import de.hasait.clap.CLAPHelpCategory;
import de.hasait.clap.CLAPKeyword;
import de.hasait.clap.CLAPKeywords;
import de.hasait.clap.CLAPOption;
import de.hasait.clap.CLAPUsageCategory;
import de.hasait.clap.CLAPValue;

/**
 * CLAPNode for handling annotated classes.
 */
public class CLAPClassNode<T> extends AbstractCLAPNodeList implements CLAPValue<T> {

    private static <A extends Annotation> A findAnnotation(final Class<?> pClass, final Class<A> pAnnotationClass) {
        final LinkedList<Class<?>> queue = new LinkedList<Class<?>>();
        queue.add(pClass);
        while (!queue.isEmpty()) {
            final Class<?> clazz = queue.removeFirst();
            if (clazz != null) {
                final A result = clazz.getAnnotation(pAnnotationClass);
                if (result != null) {
                    return result;
                }
                queue.add(clazz.getSuperclass());
                for (final Class<?> interfaze : clazz.getInterfaces()) {
                    queue.add(interfaze);
                }
            }
        }
        return null;
    }

    private static <T extends Annotation> T getAnnotation(final PropertyDescriptor pPropertyDescriptor,
            final Class<T> pAnnotationClass) {
        final Method writeMethod = pPropertyDescriptor.getWriteMethod();
        if (writeMethod != null) {
            final T annotation = writeMethod.getAnnotation(pAnnotationClass);
            if (annotation != null) {
                return annotation;
            }
        }

        final Method readMethod = pPropertyDescriptor.getReadMethod();
        if (readMethod != null) {
            final T annotation = readMethod.getAnnotation(pAnnotationClass);
            if (annotation != null) {
                return annotation;
            }
        }

        return null;
    }

    private final Class<T> _class;

    private final Map<CLAPValue<?>, PropertyDescriptor> _propertyDescriptorByOptionMap;

    private final Set<CLAPKeywordNode> _keywordNodes;

    public CLAPClassNode(final CLAP pCLAP, final Class<T> pClass) {
        super(pCLAP);

        _class = pClass;

        _propertyDescriptorByOptionMap = new HashMap<CLAPValue<?>, PropertyDescriptor>();
        _keywordNodes = new HashSet<CLAPKeywordNode>();

        BeanInfo beanInfo;
        try {
            beanInfo = Introspector.getBeanInfo(pClass);
        } catch (final IntrospectionException e) {
            throw new RuntimeException(e);
        }

        final List<Item> annotations = new ArrayList<Item>();

        final CLAPKeywords classKeywords = findAnnotation(pClass, CLAPKeywords.class);
        if (classKeywords != null) {
            for (final CLAPKeyword classKeyword : classKeywords.value()) {
                annotations.add(new Item(classKeyword.order(), classKeyword, null, null, null));
            }
        }

        final CLAPHelpCategory classHelpCategory = findAnnotation(pClass, CLAPHelpCategory.class);
        if (classHelpCategory != null) {
            setHelpCategory(classHelpCategory.order(), classHelpCategory.titleNLSKey());
        }

        final CLAPUsageCategory classUsageCategory = findAnnotation(pClass, CLAPUsageCategory.class);
        if (classUsageCategory != null) {
            setUsageCategory(classUsageCategory.order(), classUsageCategory.titleNLSKey());
        }

        for (final PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
            final CLAPOption pdOption = getAnnotation(propertyDescriptor, CLAPOption.class);
            final CLAPDelegate pdDelegate = getAnnotation(propertyDescriptor, CLAPDelegate.class);
            final CLAPDecision pdDecision = getAnnotation(propertyDescriptor, CLAPDecision.class);
            final CLAPHelpCategory pdHelpCategory = getAnnotation(propertyDescriptor, CLAPHelpCategory.class);
            final CLAPUsageCategory pdUsageCategory = getAnnotation(propertyDescriptor, CLAPUsageCategory.class);
            if ((pdOption != null ? 1 : 0) + (pdDelegate != null ? 1 : 0) + (pdDecision != null ? 1 : 0) > 1) {
                throw new IllegalArgumentException();
            }
            if (pdOption != null) {
                annotations.add(
                        new Item(pdOption.order(), pdOption, propertyDescriptor, pdHelpCategory, pdUsageCategory));
            }
            if (pdDelegate != null) {
                annotations.add(new Item(pdDelegate.order(), pdDelegate, propertyDescriptor, pdHelpCategory,
                        pdUsageCategory));
            }
            if (pdDecision != null) {
                annotations.add(new Item(pdDecision.order(), pdDecision, propertyDescriptor, pdHelpCategory,
                        pdUsageCategory));
            }
        }

        Collections.sort(annotations, new Comparator<Item>() {

            @Override
            public int compare(final Item pO1, final Item pO2) {
                return Integer.valueOf(pO1._order).compareTo(Integer.valueOf(pO2._order));
            }

        });

        for (final Item entry : annotations) {
            final Object annotation = entry._annotation;

            final AbstractCLAPNode node;
            if (annotation instanceof CLAPKeyword) {
                node = processCLAPKeyword((CLAPKeyword) annotation);
            } else if (annotation instanceof CLAPOption) {
                node = processCLAPOption(entry._propertyDescriptor.getPropertyType(), entry._propertyDescriptor,
                        (CLAPOption) annotation);
            } else if (annotation instanceof CLAPDelegate) {
                node = processCLAPDelegate(entry._propertyDescriptor.getPropertyType(), entry._propertyDescriptor,
                        (CLAPDelegate) annotation);
            } else if (annotation instanceof CLAPDecision) {
                node = processCLAPDecision(entry._propertyDescriptor.getPropertyType(), entry._propertyDescriptor,
                        (CLAPDecision) annotation);
            } else {
                throw new RuntimeException();
            }

            if (entry._helpCategory != null) {
                node.setHelpCategory(entry._helpCategory.order(), entry._helpCategory.titleNLSKey());
            }
            if (entry._usageCategory != null) {
                node.setUsageCategory(entry._usageCategory.order(), entry._usageCategory.titleNLSKey());
            }
        }

    }

    @Override
    public final void fillResult(final CLAPParseContext pContext, final CLAPResultImpl pResult) {
        internalFillResult(pContext, pResult);

        T value;
        try {
            value = _class.newInstance();
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
        boolean anySet = false;
        for (final Entry<CLAPValue<?>, PropertyDescriptor> entry : _propertyDescriptorByOptionMap.entrySet()) {
            final CLAPValue<?> node = entry.getKey();
            if (pResult.getCount(node) > 0) {
                anySet = true;
                final PropertyDescriptor propertyDescriptor = entry.getValue();
                final Object nodeValue = pResult.getValue(node);
                try {
                    propertyDescriptor.getWriteMethod().invoke(value, nodeValue);
                } catch (final Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
        for (final CLAPKeywordNode node : _keywordNodes) {
            if (pContext.getNodeCount(node) > 0) {
                anySet = true;
            }
        }
        if (anySet) {
            pResult.setCount(this, 1);
            pResult.setValue(this, value);
        }
    }

    @Override
    public final CLAPParseContext[] parse(final CLAPParseContext pContext) {
        return internalParse(pContext);
    }

    @Override
    public void printUsage(final Map<CLAPUsageCategoryImpl, StringBuilder> pCategories,
            final CLAPUsageCategoryImpl pCurrentCategory, final StringBuilder pResult) {
        final Pair<CLAPUsageCategoryImpl, StringBuilder> pair = handleUsageCategory(pCategories, pCurrentCategory,
                pResult);
        if (pair != null) {
            final CLAPUsageCategoryImpl currentCategory = pair.getLeft();
            final StringBuilder result = pair.getRight();
            internalPrintUsage(pCategories, currentCategory, result, " "); //$NON-NLS-1$
        }
    }

    @Override
    public final void validate(final CLAPParseContext pContext, final List<String> pErrorMessages) {
        internalValidate(pContext, pErrorMessages);
    }

    private <V> CLAPTypedDecisionNode<V> processCLAPDecision(final Class<V> pPropertyType,
            final PropertyDescriptor pPropertyDescriptor, final CLAPDecision pClapDecision) {
        final CLAPTypedDecisionNode<V> decisionNode = internalAddDecision(pPropertyType);
        final Class<? extends Object>[] branchClasses = pClapDecision.branches();
        for (final Class<? extends Object> branchClassUncasted : branchClasses) {
            if (!pPropertyType.isAssignableFrom(branchClassUncasted)) {
                throw new IllegalArgumentException();
            }
            @SuppressWarnings("unchecked")
            final Class<? extends V> branchClass = (Class<? extends V>) branchClassUncasted;
            decisionNode.addClass(branchClass);
            _propertyDescriptorByOptionMap.put(decisionNode, pPropertyDescriptor);
        }
        return decisionNode;
    }

    private <V> CLAPClassNode<V> processCLAPDelegate(final Class<V> pPropertyType,
            final PropertyDescriptor pPropertyDescriptor, final CLAPDelegate pClapDelegate) {
        final CLAPClassNode<V> classNode = internalAddClass(pPropertyType);
        _propertyDescriptorByOptionMap.put(classNode, pPropertyDescriptor);
        return classNode;
    }

    private CLAPKeywordNode processCLAPKeyword(final CLAPKeyword pClapKeyword) {
        final CLAPKeywordNode keywordNode = internalAddKeyword(pClapKeyword.value());
        _keywordNodes.add(keywordNode);
        return keywordNode;
    }

    private <V> CLAPOptionNode<V> processCLAPOption(final Class<V> pPropertyType,
            final PropertyDescriptor pPropertyDescriptor, final CLAPOption pClapOption) {
        final Character shortKey = pClapOption.shortKey() == ' ' ? null : pClapOption.shortKey();
        final String longKey = pClapOption.longKey().length() == 0 ? null : pClapOption.longKey();
        final boolean required = pClapOption.required();
        final Integer argCount;
        final int argCountA = pClapOption.argCount();
        if (argCountA == CLAPOption.UNLIMITED_ARG_COUNT) {
            argCount = CLAP.UNLIMITED_ARG_COUNT;
        } else if (argCountA == CLAPOption.AUTOMATIC_ARG_COUNT) {
            argCount = null;
        } else {
            argCount = argCountA;
        }
        final Character multiArgSplit = pClapOption.multiArgSplit() == ' ' ? null : pClapOption.multiArgSplit();
        final String descriptionNLSKey = pClapOption.descriptionNLSKey().length() == 0 ? null
                : pClapOption.descriptionNLSKey();
        final String argUsageNLSKey = pClapOption.argUsageNLSKey().length() == 0 ? null
                : pClapOption.argUsageNLSKey();

        final CLAPOptionNode<V> optionNode = internalAddOption(pPropertyType, shortKey, longKey, required, argCount,
                multiArgSplit, descriptionNLSKey, argUsageNLSKey);
        _propertyDescriptorByOptionMap.put(optionNode, pPropertyDescriptor);
        return optionNode;
    }

    private static class Item {

        public final int _order;
        public final Annotation _annotation;
        public final PropertyDescriptor _propertyDescriptor;
        public final CLAPHelpCategory _helpCategory;
        public final CLAPUsageCategory _usageCategory;

        public Item(final int pOrder, final Annotation pAnnotation, final PropertyDescriptor pPropertyDescriptor,
                final CLAPHelpCategory pHelpCategory, final CLAPUsageCategory pUsageCategory) {
            super();
            _order = pOrder;
            _annotation = pAnnotation;
            _propertyDescriptor = pPropertyDescriptor;
            _helpCategory = pHelpCategory;
            _usageCategory = pUsageCategory;
        }

    }

}