github.com/coveo/gotemplate@v2.7.7+incompatible/collections/implementation/base_helper.go (about)

     1  package implementation
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  
     7  	"github.com/coveo/gotemplate/errors"
     8  )
     9  
    10  type helperBase = BaseHelper
    11  type helperList = ListHelper
    12  type helperDict = DictHelper
    13  
    14  var must = errors.Must
    15  
    16  // BaseHelper implements basic functionalities required for both IGenericList & IDictionary
    17  type BaseHelper struct {
    18  	ConvertList    func(list baseIList) baseIList
    19  	ConvertDict    func(dict baseIDict) baseIDict
    20  	NeedConversion func(object interface{}, strict bool) bool
    21  }
    22  
    23  // AsList converts object to IGenericList object. It panics if conversion is impossible.
    24  func (bh BaseHelper) AsList(object interface{}) baseIList {
    25  	return must(bh.TryAsList(object)).(baseIList)
    26  }
    27  
    28  // AsDictionary converts object to IDictionary object. It panics if conversion is impossible.
    29  func (bh BaseHelper) AsDictionary(object interface{}) baseIDict {
    30  	return must(bh.TryAsDictionary(object)).(baseIDict)
    31  }
    32  
    33  // Convert tries to convert the supplied object into IDictionary or IGenericList.
    34  // Returns the supplied object if not conversion occurred.
    35  func (bh BaseHelper) Convert(object interface{}) interface{} {
    36  	object, _ = bh.TryConvert(object)
    37  	return object
    38  }
    39  
    40  // CreateList creates a new IGenericList with optional size/capacity arguments.
    41  func (bh BaseHelper) CreateList(args ...int) baseIList {
    42  	var size, capacity int
    43  	switch len(args) {
    44  	case 0:
    45  	case 1:
    46  		size = args[0]
    47  	case 2:
    48  		size, capacity = args[0], args[1]
    49  	default:
    50  		panic(fmt.Errorf("CreateList only accept 2 arguments, size and capacity"))
    51  	}
    52  	if capacity < size {
    53  		capacity = size
    54  	}
    55  	return bh.ConvertList(make(baseList, size, capacity))
    56  }
    57  
    58  // CreateDictionary creates a new IDictionary with optional capacity arguments.
    59  func (bh BaseHelper) CreateDictionary(args ...int) baseIDict {
    60  	var capacity int
    61  	switch len(args) {
    62  	case 0:
    63  	case 1:
    64  		capacity = args[0]
    65  	default:
    66  		panic(fmt.Errorf("CreateList only accept 1 argument for size"))
    67  	}
    68  	return bh.ConvertDict(make(baseDict, capacity))
    69  }
    70  
    71  // TryAsDictionary tries to convert any object to IDictionary object.
    72  func (bh BaseHelper) TryAsDictionary(object interface{}) (baseIDict, error) {
    73  	return bh.tryAsDictionary(object, false)
    74  }
    75  
    76  // TryAsDictionaryStrict tries to convert any object to IDictionary object.
    77  func (bh BaseHelper) TryAsDictionaryStrict(object interface{}) (baseIDict, error) {
    78  	return bh.tryAsDictionary(object, true)
    79  }
    80  
    81  func (bh BaseHelper) tryAsDictionary(object interface{}, strict bool) (baseIDict, error) {
    82  	if object != nil && reflect.TypeOf(object).Kind() == reflect.Ptr {
    83  		object = reflect.ValueOf(object).Elem().Interface()
    84  	}
    85  
    86  	var result baseIDict
    87  	if dict, ok := object.(baseIDict); ok {
    88  		// The object is already a IDictionary
    89  		result = dict
    90  	} else if object == nil {
    91  		result = bh.CreateDictionary()
    92  	} else {
    93  		target := reflect.TypeOf(baseDict{})
    94  		objectType := reflect.TypeOf(object)
    95  		if objectType.ConvertibleTo(target) {
    96  			result = bh.ConvertDict(reflect.ValueOf(object).Convert(target).Interface().(baseIDict))
    97  		} else {
    98  			switch objectType.Kind() {
    99  			case reflect.Map:
   100  				result = bh.CreateDictionary()
   101  				value := reflect.ValueOf(object)
   102  				keys := value.MapKeys()
   103  				for i := range keys {
   104  					result.Set(fmt.Sprint(keys[i]), value.MapIndex(keys[i]).Interface())
   105  				}
   106  			default:
   107  				return nil, fmt.Errorf("Object cannot be converted to dictionary: %T", object)
   108  			}
   109  		}
   110  	}
   111  
   112  	if bh.NeedConversion(result, strict) {
   113  		newDict := bh.CreateDictionary()
   114  		for key, val := range result.AsMap() {
   115  			// We loop on the key/values to ensure that all values are converted to the
   116  			// desired type.
   117  			newDict.Set(key, val)
   118  		}
   119  		result = newDict
   120  	}
   121  
   122  	return result, nil
   123  }
   124  
   125  // TryAsList tries to convert any object to IGenericList object.
   126  func (bh BaseHelper) TryAsList(object interface{}) (baseIList, error) {
   127  	return bh.tryAsList(object, false)
   128  }
   129  
   130  // TryAsListStrict tries to convert any object to IGenericList object.
   131  func (bh BaseHelper) TryAsListStrict(object interface{}) (baseIList, error) {
   132  	return bh.tryAsList(object, true)
   133  }
   134  
   135  func (bh BaseHelper) tryAsList(object interface{}, strict bool) (baseIList, error) {
   136  	if object != nil && reflect.TypeOf(object).Kind() == reflect.Ptr {
   137  		object = reflect.ValueOf(object).Elem().Interface()
   138  	}
   139  
   140  	var result baseIList
   141  	if list, ok := object.(baseIList); ok {
   142  		// The object is already a IGenericList
   143  		result = list
   144  	} else if object == nil {
   145  		result = bh.CreateList()
   146  	} else {
   147  		target := reflect.TypeOf(baseList{})
   148  		objectType := reflect.TypeOf(object)
   149  		if objectType.ConvertibleTo(target) {
   150  			result = bh.ConvertList(reflect.ValueOf(object).Convert(target).Interface().(baseIList))
   151  		} else {
   152  			switch objectType.Kind() {
   153  			case reflect.Slice, reflect.Array:
   154  				value := reflect.ValueOf(object)
   155  				result = bh.CreateList(value.Len())
   156  				for i := 0; i < result.Len(); i++ {
   157  					result.Set(i, value.Index(i).Interface())
   158  				}
   159  			default:
   160  				return nil, fmt.Errorf("Object cannot be converted to generic list: %T", object)
   161  			}
   162  		}
   163  	}
   164  	if bh.NeedConversion(result, false) {
   165  		newList := bh.CreateList(result.Len())
   166  		for i, val := range result.AsArray() {
   167  			newList.Set(i, val)
   168  		}
   169  		result = newList
   170  	}
   171  
   172  	return result, nil
   173  }
   174  
   175  // TryConvert tries to convert any object to IGenericList or IDictionary object.
   176  // Returns true if a conversion occurred.
   177  func (bh BaseHelper) TryConvert(object interface{}) (interface{}, bool) {
   178  	if object != nil {
   179  		if o, err := bh.TryAsDictionary(object); err == nil {
   180  			return o, true
   181  		} else if o, err := bh.TryAsList(object); err == nil {
   182  			return o, true
   183  		}
   184  	}
   185  	return object, false
   186  }
   187  
   188  // NeedConversion determine if the object need deep conversion.
   189  //    strict indicates that the type must be converted to the desired type
   190  //    even if the object implements the Dictionary or List interface.
   191  func NeedConversion(object interface{}, strict bool, typeName string) bool {
   192  	if object == nil {
   193  		return false
   194  	}
   195  	objectType := reflect.TypeOf(object)
   196  	switch objectType.Kind() {
   197  	case reflect.Map:
   198  		if dict, ok := object.(baseIDict); !ok || strict && dict.TypeName().Str() != typeName {
   199  			return true
   200  		}
   201  
   202  		value := reflect.ValueOf(object)
   203  		keys := value.MapKeys()
   204  		for i := range keys {
   205  			if NeedConversion(value.MapIndex(keys[i]).Interface(), strict, typeName) {
   206  				return true
   207  			}
   208  		}
   209  	case reflect.Slice, reflect.Array:
   210  		if list, ok := object.(baseIList); !ok || strict && list.TypeName().Str() != typeName {
   211  			return true
   212  		}
   213  		value := reflect.ValueOf(object)
   214  		for i := 0; i < value.Len(); i++ {
   215  			if NeedConversion(value.Index(i).Interface(), strict, typeName) {
   216  				return true
   217  			}
   218  		}
   219  	}
   220  	return false
   221  }
   222  
   223  var needConversionImpl = NeedConversion