content: /**
   * ﻿Copyright (C) 2007 - 2019 52°North Initiative for Geospatial Open Source
   * Software GmbH
   *
   * 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 org.n52.wps.algorithm.annotation;
  
  import java.lang.reflect.Field;
  import java.lang.reflect.Method;
  import java.lang.reflect.Type;
  import java.lang.reflect.WildcardType;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import junit.framework.TestCase;
  import org.n52.test.mock.MockEnum;
  import org.n52.wps.algorithm.annotation.AnnotationBinding.InputBinding;
  import org.n52.wps.algorithm.annotation.AnnotationBinding.InputFieldBinding;
  import org.n52.wps.algorithm.annotation.AnnotationBinding.InputMethodBinding;
  import org.n52.wps.algorithm.annotation.AnnotationBinding.OutputBinding;
  import org.n52.wps.algorithm.annotation.AnnotationBinding.OutputFieldBinding;
  import org.n52.wps.algorithm.annotation.AnnotationBinding.OutputMethodBinding;
  
  /**
   *
   * @author tkunicki
   */
  public class AnnotatedMemberDescriptorTest extends TestCase {
      
      // START - TEST DATA AS CLASS FIELDS AND METHODS
      
      // represent almost all cases: literal or complex data, input or output. with expections noted below
      public String stringField;
      public List<String> stringListField;
      public List<? extends String> stringExtendListField;
      public List<? super String> stringSuperListField;
      public List unboundListField;  // effectively List<? extends Object>
      
      // no polymorphism in test methods, will break use of methodMap
      public void setString(String stringParameter) {
          this.stringField = stringParameter;
      }
      public String getString() {
          return stringField;
      }
      public void setStringList(List<String> stringListParameter) {
          this.stringListField = stringListParameter;
      }
      public List<String> getStringList() {
          return this.stringListField;
      }
      public void setStringExtendList(List<? extends String> stringExtendListParameter) {
          this.stringExtendListField = stringExtendListParameter;
      }
      public List<? extends String> getStringExtendList() {
          return this.stringExtendListField;
      }
      public void setStringSuperList(List<? super String> stringSuperListParameter) {
          this.stringSuperListField = stringSuperListParameter;
      }
      public List<? super String> getStringSuperList() {
          return this.stringSuperListField;
      }
      public void setUnboundList(List unboundListParameter) {
          this.unboundListField = unboundListParameter;
      }
      public List getUnboundList() {
          return this.unboundListField;
      }
      
      // special case: enumerations for *inputs* have data type of String *unless* List for outputs
      public MockEnum enumField;
      public List<MockEnum> enumListField;
      /* NOT CURRENTLY SUPPORTED, need to be able to infer concrete type by reflection
      public List<? extends MockEnum> enumExtendsListField;
      public List<? super MockEnum> enumSuperListField;
      */
      
      // no polymorphism in test methods, will break use of methodMap
      public MockEnum getEnum() {
          return enumField; 
      }
      public void setEnum(MockEnum enumParameter) {
          this.enumField = enumParameter; 
      }
      public List<MockEnum> getEnumList() {
          return enumListField; 
      }
      public void setEnumList(List<MockEnum> enumListParameter) {
          this.enumListField = enumListParameter; 
      }
      /* NOT CURRENTLY SUPPORTED, need to be able to infer concrete type by reflection
      public List<? extends MockEnum> getEnumExtendList() {
          return enumExtendsListField; 
      }
      public void setEnumExtendList(List<? extends MockEnum> enumExtendsListParameter) {
          this.enumExtendsListField = enumExtendsListParameter; 
      }
      public List<? super MockEnum> getEnumSuperList() {
          return enumSuperListField; 
      }
      public void setEnumSuperList(List<? super MockEnum> enumSuperListParameter) {
          this.enumSuperListField = enumSuperListParameter; 
      }
      */
      
      // END - TEST DATA AS CLASS FIELDS AND METHODS
      
      Map<String, Method> methodMap;
      
      public AnnotatedMemberDescriptorTest(String testName) {
          super(testName);
          methodMap = new HashMap<String, Method>();
          for (Method method : AnnotationMemberDescriptorSample.class.getDeclaredMethods()) {
              methodMap.put(method.getName(), method);
          }
          methodMap = Collections.unmodifiableMap(methodMap);
      }
      
      private Method getSampleMethod(String name) throws NoSuchMethodException {
          Method method = methodMap.get(name);
          if (method == null) {
              throw new NoSuchMethodException(name);
          }
          return method;
      }
      
      private Field getSampleField(String name) throws NoSuchFieldException {
          return AnnotationMemberDescriptorSample.class.getDeclaredField(name);
      }
      
      @Override
      protected void setUp() throws Exception {
          super.setUp();
      }
      
      @Override
      protected void tearDown() throws Exception {
          super.tearDown();
      }
  
      public void testStringFieldAsInput() throws NoSuchFieldException {
          Field field = getSampleField("stringField");
          InputFieldBinding memberDescriptor = new InputFieldBinding(field);
          
          assertEquals(field, memberDescriptor.getMember());
          assertEquals(field.getGenericType(), memberDescriptor.getMemberType());
          
          validateInputMember(memberDescriptor);
      }
      
      public void testStringFieldAsOutput() throws NoSuchFieldException {
          Field field = getSampleField("stringField");
          OutputFieldBinding memberDescriptor = new OutputFieldBinding(field);
          
          assertEquals(field, memberDescriptor.getMember());
          assertEquals(field.getGenericType(), memberDescriptor.getMemberType());
          
          validateOutputMember(memberDescriptor);
      }
      
      public void testStringSetter() throws NoSuchMethodException {
          Method method = getSampleMethod("setString");
          InputMethodBinding memberDescriptor = new InputMethodBinding(method);
          
          assertEquals(method, memberDescriptor.getMember());
          assertEquals(method.getGenericParameterTypes()[0], memberDescriptor.getMemberType());
          
          validateInputMember(memberDescriptor);
      }
      
      public void testStringGetter() throws NoSuchMethodException {
          Method method = getSampleMethod("getString");
          OutputMethodBinding memberDescriptor = new OutputMethodBinding(method);
          
          assertEquals(method, memberDescriptor.getMember());
          assertEquals(method.getGenericReturnType(), memberDescriptor.getMemberType());
          
          validateOutputMember(memberDescriptor);
      }
      
      private void validateInputMember(InputBinding memberDescriptor) {
          // data type matches member for inputs type *unless* member type is List
          assertEquals(memberDescriptor.getMemberType(), memberDescriptor.getType());
          // payload type matches data type, special handling reserved for enumerations
          assertEquals(memberDescriptor.getType(), memberDescriptor.getPayloadType());
          assertEquals(false, memberDescriptor.isMemberTypeList());
          assertEquals(false, memberDescriptor.isTypeEnum());
      }
      
      private void validateOutputMember(OutputBinding memberDescriptor) {
          // data type matches member type for outputs
          assertEquals(memberDescriptor.getMemberType(), memberDescriptor.getType());
          // data type matches payload type, special handling reserved for enumerations
          assertEquals(memberDescriptor.getType(), memberDescriptor.getPayloadType());
          assertEquals(false, memberDescriptor.isTypeEnum());
      }
      
      public void testStringListFieldAsInput() throws NoSuchFieldException {
          Field field = getSampleField("stringListField");
          InputFieldBinding memberDescriptor = new InputFieldBinding(field);
          
          assertEquals(field, memberDescriptor.getMember());
          assertEquals(field.getGenericType(), memberDescriptor.getMemberType());
          
          validateInputListMember(memberDescriptor);
      }
      
      public void testStringListFieldAsOutput() throws NoSuchFieldException {
          Field field = getSampleField("stringListField");
          OutputFieldBinding memberDescriptor = new OutputFieldBinding(field);
          
          assertEquals(field, memberDescriptor.getMember());
          assertEquals(field.getGenericType(), memberDescriptor.getMemberType());
          
          validateOutputListMember(memberDescriptor);
      }
      
      public void testStringListSetter() throws NoSuchMethodException {
          Method method = getSampleMethod("setStringList");
          InputMethodBinding memberDescriptor = new InputMethodBinding(method);
          
          assertEquals(method, memberDescriptor.getMember());
          assertEquals(method.getGenericParameterTypes()[0], memberDescriptor.getMemberType());
          
          validateInputListMember(memberDescriptor);
      }
      
      public void testStringListGetter() throws NoSuchMethodException {
          Method method = getSampleMethod("getStringList");
          OutputMethodBinding memberDescriptor = new OutputMethodBinding(method);
          
          assertEquals(method, memberDescriptor.getMember());
          assertEquals(method.getGenericReturnType(), memberDescriptor.getMemberType());
          
          validateOutputListMember(memberDescriptor);
      }
      
      private void validateInputListMember(InputBinding memberDescriptor) {
          // we extract the parameterized type of the list for inputs.  since member
          //  type is List<String> we expect String
          assertEquals(String.class, memberDescriptor.getType());
          // payload type matches data type, special handling reserved for enumerations
          assertEquals(memberDescriptor.getType(), memberDescriptor.getPayloadType());
          assertEquals(true, memberDescriptor.isMemberTypeList());
          assertEquals(false, memberDescriptor.isTypeEnum());
      }
      
      private void validateOutputListMember(OutputBinding memberDescriptor) {
          // no special handling for outputs of member type List, member type matches data type
          assertEquals(memberDescriptor.getMemberType(), memberDescriptor.getType());
          // no special handling for outputs of member type List, member type matches data type
          assertEquals(memberDescriptor.getMemberType(), memberDescriptor.getPayloadType());
          assertEquals(false, memberDescriptor.isTypeEnum());
      }
      
      public void testStringExtendListFieldAsInput() throws NoSuchFieldException {
          Field field = getSampleField("stringExtendListField");
          InputFieldBinding memberDescriptor = new InputFieldBinding(field);
          
          assertEquals(field, memberDescriptor.getMember());
          assertEquals(field.getGenericType(), memberDescriptor.getMemberType());
          
          validateExtendListInputMember(memberDescriptor);
      }
      
      public void testStringExtendListFieldAsOutput() throws NoSuchFieldException {
          Field field = getSampleField("stringExtendListField");
          OutputFieldBinding memberDescriptor = new OutputFieldBinding(field);
          
          assertEquals(field, memberDescriptor.getMember());
          assertEquals(field.getGenericType(), memberDescriptor.getMemberType());
          
          validateExtendListOutputMember(memberDescriptor);
      }
      
      public void testStringExtendListSetter() throws NoSuchMethodException {
          Method method = getSampleMethod("setStringExtendList");
          InputMethodBinding memberDescriptor = new InputMethodBinding(method);
          
          assertEquals(method, memberDescriptor.getMember());
          assertEquals(method.getGenericParameterTypes()[0], memberDescriptor.getMemberType());
          
          validateExtendListInputMember(memberDescriptor);
      }
      
      public void testStringExtendListGetter() throws NoSuchMethodException {
          Method method = getSampleMethod("getStringExtendList");
          OutputMethodBinding memberDescriptor = new OutputMethodBinding(method);
          
          assertEquals(method, memberDescriptor.getMember());
          assertEquals(method.getGenericReturnType(), memberDescriptor.getMemberType());
          
          validateExtendListOutputMember(memberDescriptor);
      }
      
      private void validateExtendListInputMember(InputBinding memberDescriptor) {
          // we extract the parameterized type of the list for inputs.  since member 
          //  type is List<? extends String> we expect a WildcardType of <? extends String>
          //  we need this information later to make sure we can safely assign an
          //  instance to the list with type safety (fail early behavior)
          Type type = memberDescriptor.getType();
          assertTrue(type instanceof WildcardType);
          WildcardType typeWildcard = (WildcardType)type;
          assertEquals(0, typeWildcard.getLowerBounds().length);
          assertEquals(1, typeWildcard.getUpperBounds().length);
          assertEquals(String.class, typeWildcard.getUpperBounds()[0]);
          // we extract the parameterized type of the list for inputs
          assertEquals(memberDescriptor.getType(), memberDescriptor.getPayloadType());
          assertEquals(true, memberDescriptor.isMemberTypeList());
          assertEquals(false, memberDescriptor.isTypeEnum());
      }
      
      private void validateExtendListOutputMember(OutputBinding memberDescriptor) {
          // no special handling for outputs of member type List, member type matches data type
          assertEquals(memberDescriptor.getMemberType(), memberDescriptor.getType());
          // no special handling for outputs of member type List, member type matches data type
          assertEquals(memberDescriptor.getMemberType(), memberDescriptor.getPayloadType());
          assertEquals(false, memberDescriptor.isTypeEnum());
      }
      
      public void testStringSuperListFieldAsInput() throws NoSuchFieldException {
          Field field = getSampleField("stringSuperListField");
          InputFieldBinding memberDescriptor = new InputFieldBinding(field);
          
          assertEquals(field, memberDescriptor.getMember());
          assertEquals(field.getGenericType(), memberDescriptor.getMemberType());
          
          validateSuperListInputMember(memberDescriptor);
      }
      
      public void testStringSuperListFieldAsOutput() throws NoSuchFieldException {
          Field field = getSampleField("stringSuperListField");
          OutputFieldBinding memberDescriptor = new OutputFieldBinding(field);
          
          assertEquals(field, memberDescriptor.getMember());
          assertEquals(field.getGenericType(), memberDescriptor.getMemberType());
          
          validateSuperListOutputMember(memberDescriptor);
      }
      
      public void testStringSuperListSetter() throws NoSuchMethodException {
          Method method = getSampleMethod("setStringSuperList");
          InputMethodBinding memberDescriptor = new InputMethodBinding(method);
          
          assertEquals(method, memberDescriptor.getMember());
          assertEquals(method.getGenericParameterTypes()[0], memberDescriptor.getMemberType());
          
          validateSuperListInputMember(memberDescriptor);
      }
      
      public void testStringSuperListGetter() throws NoSuchMethodException {
          Method method = getSampleMethod("getStringSuperList");
          OutputMethodBinding memberDescriptor = new OutputMethodBinding(method);
          
          assertEquals(method, memberDescriptor.getMember());
          assertEquals(method.getGenericReturnType(), memberDescriptor.getMemberType());
          
          validateSuperListOutputMember(memberDescriptor);
      }
      
      private void validateSuperListInputMember(InputBinding memberDescriptor) {
          // we extract the parameterized type of the list for inputs.  since member 
          //  type is List<? super String> we expect a WildcardType of <? super String>
          //  we need this information later to make sure we can safely assign an
          //  instance to the list with type safety (fail early behavior)
          Type type = memberDescriptor.getType();
          assertTrue(type instanceof WildcardType);
          WildcardType typeWildcard = (WildcardType)type;
          assertEquals(1, typeWildcard.getLowerBounds().length);
          assertEquals(String.class, typeWildcard.getLowerBounds()[0]);
          assertEquals(1, typeWildcard.getUpperBounds().length);
          assertEquals(Object.class, typeWildcard.getUpperBounds()[0]);
          // we extract the parameterized type of the list for inputs
          assertEquals(memberDescriptor.getType(), memberDescriptor.getPayloadType());
          assertEquals(true, memberDescriptor.isMemberTypeList());
          assertEquals(false, memberDescriptor.isTypeEnum());
      }
      
      private void validateSuperListOutputMember(OutputBinding memberDescriptor) {
          // no special handling for outputs of member type List, member type matches data type
          assertEquals(memberDescriptor.getMemberType(), memberDescriptor.getType());
          // no special handling for outputs of member type List, member type matches data type
          assertEquals(memberDescriptor.getMemberType(), memberDescriptor.getPayloadType());
          assertEquals(false, memberDescriptor.isTypeEnum());
      }
      
      public void testUnboundListFieldAsInput() throws NoSuchFieldException {
          Field field = getSampleField("unboundListField");
          InputFieldBinding memberDescriptor = new InputFieldBinding(field);
          
          assertEquals(field, memberDescriptor.getMember());
          assertEquals(field.getGenericType(), memberDescriptor.getMemberType());
          
          validateUnboundListInputMember(memberDescriptor);
      }
      
      public void testUnboundListFieldAsOutput() throws NoSuchFieldException {
          Field field = getSampleField("unboundListField");
          OutputFieldBinding memberDescriptor = new OutputFieldBinding(field);
          
          assertEquals(field, memberDescriptor.getMember());
          assertEquals(field.getGenericType(), memberDescriptor.getMemberType());
          
          validateUnboundListOutputMember(memberDescriptor);
      }
      
      public void testUnboundListSetter() throws NoSuchMethodException {
          Method method = getSampleMethod("setUnboundList");
          InputMethodBinding memberDescriptor = new InputMethodBinding(method);
          
          assertEquals(method, memberDescriptor.getMember());
          assertEquals(method.getGenericParameterTypes()[0], memberDescriptor.getMemberType());
          
          validateUnboundListInputMember(memberDescriptor);
      }
      
      public void testUnboundListGetter() throws NoSuchMethodException {
          Method method = getSampleMethod("getUnboundList");
          OutputMethodBinding memberDescriptor = new OutputMethodBinding(method);
          
          assertEquals(method, memberDescriptor.getMember());
          assertEquals(method.getGenericReturnType(), memberDescriptor.getMemberType());
          
          validateUnboundListOutputMember(memberDescriptor);
      }
      
      private void validateUnboundListInputMember(InputBinding memberDescriptor) {
  //        // we extract the parameterized type of the list for inputs.  since member 
  //        //  type is List<? super String> we expect a WildcardType of <? super String>
  //        //  we need this information later to make sure we can safely assign an
  //        //  instance to the list with type safety (fail early behavior)
  //        Type type = memberDescriptor.getType();
  //        assertTrue(type instanceof WildcardType);
  //        WildcardType typeWildcard = (WildcardType)type;
  //        assertEquals(0, typeWildcard.getLowerBounds().length);
  //        assertEquals(1, typeWildcard.getUpperBounds().length);
  //        assertEquals(Object.class, typeWildcard.getUpperBounds()[0]);
  //        // we extract the parameterized type of the list for inputs
  //        assertEquals(memberDescriptor.getType(), memberDescriptor.getPayloadType());
  //        assertEquals(true, memberDescriptor.isMemberTypeList());
  //        assertEquals(false, memberDescriptor.isTypeEnum());
           // we extract the parameterized type of the list for inputs.  since member 
          //  type is List<? extends Object> we expect a WildcardType of <? extends Object>
          //  we need this information later to make sure we can safely assign an
          //  instance to the list with type safety (fail early behavior)
          Type type = memberDescriptor.getType();
          assertTrue(type instanceof WildcardType);
          WildcardType typeWildcard = (WildcardType)type;
          assertEquals(0, typeWildcard.getLowerBounds().length);
          assertEquals(1, typeWildcard.getUpperBounds().length);
          assertEquals(Object.class, typeWildcard.getUpperBounds()[0]);
          // we extract the parameterized type of the list for inputs
          assertEquals(memberDescriptor.getType(), memberDescriptor.getPayloadType());
          assertEquals(true, memberDescriptor.isMemberTypeList());
          assertEquals(false, memberDescriptor.isTypeEnum());
      }
      
      private void validateUnboundListOutputMember(OutputBinding memberDescriptor) {
          // no special handling for outputs of member type List, member type matches data type
          assertEquals(memberDescriptor.getMemberType(), memberDescriptor.getType());
          // no special handling for outputs of member type List, member type matches data type
          assertEquals(memberDescriptor.getMemberType(), memberDescriptor.getPayloadType());
          assertEquals(false, memberDescriptor.isTypeEnum());
      }
      
      public void testEnumFieldAsInput() throws NoSuchFieldException {
          Field field = getSampleField("enumField");
          InputFieldBinding memberDescriptor = new InputFieldBinding(field);
          
          assertEquals(field, memberDescriptor.getMember());
          assertEquals(field.getGenericType(), memberDescriptor.getMemberType());
          
          validateEnumInputMember(memberDescriptor);
      }
      
      public void testEnumFieldAsOutput() throws NoSuchFieldException {
          Field field = getSampleField("enumField");
          OutputFieldBinding memberDescriptor = new OutputFieldBinding(field);
          
          assertEquals(field, memberDescriptor.getMember());
          assertEquals(field.getGenericType(), memberDescriptor.getMemberType());
          
          validateEnumOutputMember(memberDescriptor);
      }
      
      public void testEnumSetter() throws NoSuchMethodException {
          Method method = getSampleMethod("setEnum");
          InputMethodBinding memberDescriptor = new InputMethodBinding(method);
          
          assertEquals(method, memberDescriptor.getMember());
          assertEquals(method.getGenericParameterTypes()[0], memberDescriptor.getMemberType());
          
          validateEnumInputMember(memberDescriptor);
      }
      
      public void testEnumGetter() throws NoSuchMethodException {
          Method method = getSampleMethod("getEnum");
          OutputMethodBinding memberDescriptor = new OutputMethodBinding(method);
          
          assertEquals(method, memberDescriptor.getMember());
          assertEquals(method.getGenericReturnType(), memberDescriptor.getMemberType());
          
          validateEnumOutputMember(memberDescriptor);
      }
      
      private void validateEnumInputMember(InputBinding memberDescriptor) {
          assertEquals(MockEnum.class, memberDescriptor.getType());
          // for all instances of Class<? extends Enum> the payload type is Class<String>
          //   as these will be bound with LiteralStringBinding
          assertEquals(String.class, memberDescriptor.getPayloadType());
          assertEquals(false, memberDescriptor.isMemberTypeList());
          assertEquals(true, memberDescriptor.isTypeEnum());
      }
      
      private void validateEnumOutputMember(OutputBinding memberDescriptor) {
          assertEquals(MockEnum.class, memberDescriptor.getType());
          // for all instances of Class<? extends Enum> the payload type is Class<String>
          //   as these will be bound with LiteralStringBinding
          assertEquals(String.class, memberDescriptor.getPayloadType());
          assertEquals(true, memberDescriptor.isTypeEnum());
      }
      
      public void testEnumListFieldAsInput() throws NoSuchFieldException {
          Field field = getSampleField("enumListField");
          InputFieldBinding memberDescriptor = new InputFieldBinding(field);
          
          assertEquals(field, memberDescriptor.getMember());
          assertEquals(field.getGenericType(), memberDescriptor.getMemberType());
          
          validateEnumListInputMember(memberDescriptor);
      }
      
      public void testEnumListFieldAsOutput() throws NoSuchFieldException {
          Field field = getSampleField("enumListField");
          OutputFieldBinding memberDescriptor = new OutputFieldBinding(field);
          
          assertEquals(field, memberDescriptor.getMember());
          assertEquals(field.getGenericType(), memberDescriptor.getMemberType());
          
          validateEnumListOutputMember(memberDescriptor);
      }
      
      public void testEnumListSetter() throws NoSuchFieldException, NoSuchMethodException {
          Method method = getSampleMethod("setEnumList");
          InputMethodBinding memberDescriptor = new InputMethodBinding(method);
          
          assertEquals(method, memberDescriptor.getMember());
          assertEquals(method.getGenericParameterTypes()[0], memberDescriptor.getMemberType());
          
          validateEnumListInputMember(memberDescriptor);
      }
      
      public void testEnumListGetter() throws NoSuchFieldException, NoSuchMethodException {
          Method method = getSampleMethod("getEnumList");
          OutputMethodBinding memberDescriptor = new OutputMethodBinding(method);
          
          assertEquals(method, memberDescriptor.getMember());
          assertEquals(method.getGenericReturnType(), memberDescriptor.getMemberType());
          
          validateEnumListOutputMember(memberDescriptor);
      }
      
      
      private void validateEnumListInputMember(InputBinding memberDescriptor) {
          assertEquals(MockEnum.class, memberDescriptor.getType());
          assertEquals(String.class, memberDescriptor.getPayloadType());
          assertEquals(true, memberDescriptor.isMemberTypeList());
          assertEquals(true, memberDescriptor.isTypeEnum());
      }
      
      private void validateEnumListOutputMember(OutputBinding memberDescriptor) {
          // no special handling for outputs of member type List, member type matches data type
          assertEquals(memberDescriptor.getMemberType(), memberDescriptor.getType());
          // no special handling for outputs of member type List, member type matches data type
          assertEquals(memberDescriptor.getMemberType(), memberDescriptor.getPayloadType());
          assertEquals(false, memberDescriptor.isTypeEnum());
      }
   
  }
  