001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2020 microBean™. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 014 * implied. See the License for the specific language governing 015 * permissions and limitations under the License. 016 */ 017package org.microbean.settings.converter; 018 019import java.io.IOException; 020import java.io.ObjectInputStream; 021import java.io.ObjectOutputStream; 022 023import java.lang.reflect.Constructor; 024import java.lang.reflect.Executable; 025import java.lang.reflect.Method; 026import java.lang.reflect.Modifier; 027 028import java.util.Objects; 029 030import org.microbean.settings.Converter; 031import org.microbean.settings.Value; 032 033public class ExecutableBasedConverter<T> implements Converter<T> { 034 035 private static final long serialVersionUID = 1L; 036 037 private transient Executable executable; 038 039 public ExecutableBasedConverter(final Method method) { 040 super(); 041 this.executable = Objects.requireNonNull(method); 042 if (!Modifier.isStatic(method.getModifiers())) { 043 throw new IllegalArgumentException("method is not static: " + method); 044 } 045 final Class<?>[] parameterTypes = method.getParameterTypes(); 046 if (parameterTypes == null || parameterTypes.length < 1 || !(CharSequence.class.isAssignableFrom(parameterTypes[0]))) { 047 throw new IllegalArgumentException("method does not take a single CharSequence-assignable parameter: " + method); 048 } 049 } 050 051 public ExecutableBasedConverter(final Constructor<T> constructor) { 052 super(); 053 this.executable = Objects.requireNonNull(constructor); 054 final Class<?>[] parameterTypes = constructor.getParameterTypes(); 055 if (parameterTypes == null || parameterTypes.length < 1 || !(CharSequence.class.isAssignableFrom(parameterTypes[0]))) { 056 throw new IllegalArgumentException("constructor does not take a single CharSequence-assignable parameter: " + constructor); 057 } 058 } 059 060 @Override 061 public final T convert(final Value value) { 062 final T returnValue; 063 if (value == null) { 064 returnValue = null; 065 } else { 066 final String stringValue = value.get(); 067 if (stringValue == null) { 068 returnValue = null; 069 } else { 070 T convertedObject = null; 071 try { 072 if (this.executable instanceof Method) { 073 @SuppressWarnings("unchecked") 074 final T invocationResult = (T)((Method)this.executable).invoke(null, stringValue); 075 convertedObject = invocationResult; 076 } else { 077 assert this.executable instanceof Constructor; 078 @SuppressWarnings("unchecked") 079 final T invocationResult = ((Constructor<T>)this.executable).newInstance(stringValue); 080 convertedObject = invocationResult; 081 } 082 } catch (final ReflectiveOperationException reflectiveOperationException) { 083 throw new IllegalArgumentException(stringValue, reflectiveOperationException); 084 } finally { 085 returnValue = convertedObject; 086 } 087 } 088 } 089 return returnValue; 090 } 091 092 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 093 if (in != null) { 094 in.defaultReadObject(); 095 final boolean constructor = in.readBoolean(); 096 final Class<?> declaringClass = (Class<?>)in.readObject(); 097 assert declaringClass != null; 098 final String methodName; 099 if (constructor) { 100 methodName = null; 101 } else { 102 methodName = in.readUTF(); 103 assert methodName != null; 104 } 105 final Class<?>[] parameterTypes = (Class<?>[])in.readObject(); 106 assert parameterTypes != null; 107 try { 108 if (constructor) { 109 this.executable = declaringClass.getDeclaredConstructor(parameterTypes); 110 } else { 111 this.executable = declaringClass.getMethod(methodName, parameterTypes); 112 } 113 } catch (final ReflectiveOperationException reflectiveOperationException) { 114 throw new IOException(reflectiveOperationException.getMessage(), reflectiveOperationException); 115 } 116 assert this.executable != null; 117 } 118 } 119 120 private void writeObject(final ObjectOutputStream out) throws IOException { 121 if (out != null) { 122 out.defaultWriteObject(); 123 assert this.executable != null; 124 final boolean constructor = this.executable instanceof Constructor; 125 out.writeBoolean(constructor); // true means Constructor 126 out.writeObject(this.executable.getDeclaringClass()); 127 if (!constructor) { 128 out.writeUTF(this.executable.getName()); 129 } 130 out.writeObject(this.executable.getParameterTypes()); 131 } 132 } 133 134}