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}