001/* 002 * Copyright 2022-2025 Revetware LLC. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.soklet.converter; 018 019import javax.annotation.Nonnull; 020import javax.annotation.Nullable; 021import javax.annotation.concurrent.ThreadSafe; 022import java.lang.reflect.ParameterizedType; 023import java.lang.reflect.Type; 024import java.util.Arrays; 025import java.util.Collections; 026import java.util.List; 027import java.util.Optional; 028 029import static com.soklet.core.Utilities.trimAggressivelyToNull; 030import static java.lang.String.format; 031import static java.util.Objects.requireNonNull; 032 033/** 034 * Convenience superclass which provides default implementations of {@link ValueConverter} methods. 035 * 036 * @author <a href="https://www.revetkn.com">Mark Allen</a> 037 */ 038@ThreadSafe 039public abstract class AbstractValueConverter<F, T> implements ValueConverter<F, T> { 040 @Nonnull 041 private final Type fromType; 042 @Nonnull 043 private final Type toType; 044 045 /** 046 * Supports subclasses that have both 'from' and 'to' generic types. 047 */ 048 public AbstractValueConverter() { 049 List<Type> genericTypes = genericTypesForClass(getClass()); 050 051 Type fromType = null; 052 Type toType = null; 053 054 if (genericTypes.size() == 2) { 055 fromType = genericTypes.get(0); 056 toType = genericTypes.get(1); 057 } 058 059 if (fromType == null || toType == null) 060 throw new IllegalStateException(format("Unable to extract generic %s type information from %s", 061 ValueConverter.class.getSimpleName(), this)); 062 063 this.fromType = fromType; 064 this.toType = toType; 065 } 066 067 /** 068 * Supports subclasses that have only a 'to' generic type, like {@link FromStringValueConverter}. 069 * 070 * @param fromType an explicitly-provided 'from' type 071 */ 072 public AbstractValueConverter(@Nonnull Type fromType) { 073 requireNonNull(fromType); 074 075 List<Type> genericTypes = genericTypesForClass(getClass()); 076 077 Type toType = null; 078 079 if (genericTypes.size() == 1) 080 toType = genericTypes.get(0); 081 082 if (toType == null) 083 throw new IllegalStateException(format("Unable to extract generic %s type information from %s", 084 ValueConverter.class.getSimpleName(), this)); 085 086 this.fromType = fromType; 087 this.toType = toType; 088 } 089 090 @Nonnull 091 @Override 092 @SuppressWarnings("unchecked") 093 public final Optional<T> convert(@Nullable F from) throws ValueConversionException { 094 // Special handling for String types 095 if (from instanceof String && shouldTrimFromValues()) 096 from = (F) trimAggressivelyToNull((String) from); 097 098 try { 099 return performConversion(from); 100 } catch (ValueConversionException e) { 101 throw e; 102 } catch (Exception e) { 103 throw new ValueConversionException(format("Unable to convert value '%s' of type %s to an instance of %s", from, 104 getFromType(), getToType()), e, getFromType(), from, getToType()); 105 } 106 } 107 108 @Nonnull 109 protected Boolean shouldTrimFromValues() { 110 return true; 111 } 112 113 /** 114 * Subclasses must implement this method to convert a 'from' instance to a 'to' instance. 115 * 116 * @param from the instance we are converting from 117 * @return an instance that was converted to 118 * @throws Exception if an error occured during conversion 119 */ 120 @Nonnull 121 public abstract Optional<T> performConversion(@Nullable F from) throws Exception; 122 123 @Override 124 @Nonnull 125 public Type getFromType() { 126 return this.fromType; 127 } 128 129 @Override 130 @Nonnull 131 public Type getToType() { 132 return this.toType; 133 } 134 135 @Override 136 @Nonnull 137 public String toString() { 138 return format("%s{fromType=%s, toType=%s}", getClass().getSimpleName(), getFromType(), getToType()); 139 } 140 141 @Nonnull 142 static List<Type> genericTypesForClass(@Nullable Class<?> valueConverterClass) { 143 if (valueConverterClass == null) 144 return List.of(); 145 146 // TODO: this only works for simple cases (direct subclass or direct use of ValueConverter interface) and doesn't do full error handling. 147 List<Type> genericInterfaces = Arrays.asList(valueConverterClass.getGenericInterfaces()); 148 149 // If not direct use of interface, try superclass (no error handling done yet) 150 if (genericInterfaces.size() == 0) 151 genericInterfaces = Collections.singletonList(valueConverterClass.getGenericSuperclass()); 152 153 // Figure out what the two type arguments are for ValueConverter 154 for (Type genericInterface : genericInterfaces) { 155 if (genericInterface instanceof ParameterizedType) { 156 Object rawType = ((ParameterizedType) genericInterface).getRawType(); 157 158 if (!ValueConverter.class.isAssignableFrom((Class<?>) rawType)) 159 continue; 160 161 Type[] genericTypes = ((ParameterizedType) genericInterface).getActualTypeArguments(); 162 return Arrays.asList(genericTypes); 163 } 164 } 165 166 return List.of(); 167 } 168}