001/* 002 * Configurate 003 * Copyright (C) zml and Configurate contributors 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 implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.spongepowered.configurate.objectmapping.meta; 018 019import io.leangen.geantyref.GenericTypeReflector; 020import org.checkerframework.checker.nullness.qual.Nullable; 021import org.spongepowered.configurate.serialize.SerializationException; 022import org.spongepowered.configurate.util.Types; 023 024import java.lang.annotation.Annotation; 025import java.lang.reflect.InvocationTargetException; 026import java.lang.reflect.Method; 027import java.lang.reflect.Modifier; 028import java.lang.reflect.Type; 029import java.util.ArrayList; 030import java.util.List; 031 032/** 033 * A callback executed after all field serialization has been performed. 034 * 035 * @since 4.2.0 036 */ 037@FunctionalInterface 038public interface PostProcessor { 039 040 /** 041 * Perform post-processing on the fully deserialized instance. 042 * 043 * @param instance the instance to post-process 044 * @throws SerializationException if the underlying operation 045 * detects an error 046 * @since 4.2.0 047 */ 048 void postProcess(Object instance) throws SerializationException; 049 050 /** 051 * A factory to produce object post-processors. 052 * 053 * @since 4.2.0 054 */ 055 @FunctionalInterface 056 interface Factory { 057 058 /** 059 * Return a post-processor if any is applicable to the provided type. 060 * 061 * @param type the type to post-process 062 * @return a potential post-processor 063 * @throws SerializationException if there is a declared post-processor 064 * handled by this factory with an invalid type 065 * @since 4.2.0 066 */ 067 @Nullable PostProcessor createProcessor(Type type) throws SerializationException; 068 069 } 070 071 /** 072 * Discover methods annotated with the designated post-processor annotation 073 * in object-mapped types and their supertypes. 074 * 075 * <p>Annotated methods must be non-static, take no parameters, and can have 076 * no declared thrown exceptions 077 * except for {@link SerializationException}.</p> 078 * 079 * @param annotation the annotation that will mark post-processor methods 080 * @return a factory for annotated methods 081 * @since 4.2.0 082 */ 083 static Factory methodsAnnotated(final Class<? extends Annotation> annotation) { 084 return type -> { 085 List<Method> methods = null; 086 for (final Method method : Types.allDeclaredMethods(GenericTypeReflector.erase(type))) { 087 if (method.isAnnotationPresent(annotation)) { 088 // Validate method 089 final int modifiers = method.getModifiers(); 090 if (Modifier.isAbstract(modifiers)) { 091 continue; 092 } 093 094 if (Modifier.isStatic(modifiers)) { 095 throw new SerializationException( 096 type, 097 "Post-processor method " + method.getName() + "() annotated @" + annotation.getSimpleName() 098 + " must not be static." 099 ); 100 } 101 if (method.getParameterCount() != 0) { 102 throw new SerializationException( 103 type, 104 "Post-processor method " + method.getName() + "() annotated @" + annotation.getSimpleName() 105 + " must not take any parameters." 106 ); 107 } 108 109 for (final Class<?> exception : method.getExceptionTypes()) { 110 if (!SerializationException.class.isAssignableFrom(exception)) { 111 throw new SerializationException( 112 type, 113 "Post-processor method " + method.getName() + "() annotated @" + annotation.getSimpleName() 114 + " must only throw SerializationException or its subtypes, but is declared to throw " 115 + exception.getSimpleName() + "." 116 ); 117 } 118 } 119 method.setAccessible(true); 120 121 // Then add it 122 if (methods == null) { 123 methods = new ArrayList<>(); 124 } 125 methods.add(method); 126 } 127 } 128 129 if (methods != null) { 130 final List<Method> finalMethods = methods; 131 return instance -> { 132 SerializationException aggregateException = null; 133 for (final Method postProcessorMethod : finalMethods) { 134 SerializationException exc = null; 135 try { 136 postProcessorMethod.invoke(instance); 137 } catch (final InvocationTargetException ex) { 138 if (ex.getCause() instanceof SerializationException) { 139 exc = (SerializationException) ex.getCause(); 140 exc.initType(type); 141 } else if (ex.getCause() != null) { 142 exc = new SerializationException( 143 type, 144 "Failure occurred in post-processor method " + postProcessorMethod.getName() + "()", ex.getCause() 145 ); 146 } else { 147 exc = new SerializationException( 148 type, 149 "Unknown error occurred attempting to invoke post-processor method " + postProcessorMethod.getName() + "()", 150 ex 151 ); 152 } 153 } catch (final IllegalAccessException | IllegalArgumentException ex) { 154 exc = new SerializationException( 155 type, "Failed to invoke post-processor method " + postProcessorMethod.getName() + "()", ex 156 ); 157 } 158 159 // Capture all relevant exceptions 160 if (exc != null) { 161 if (aggregateException == null) { 162 aggregateException = exc; 163 } else { 164 aggregateException.addSuppressed(exc); 165 } 166 } 167 } 168 169 // If anybody threw an exception, rethrow 170 if (aggregateException != null) { 171 throw aggregateException; 172 } 173 }; 174 } 175 176 return null; 177 }; 178 } 179 180 /** 181 * Discover methods annotated with the {@link PostProcess} annotation. 182 * 183 * <p>All restrictions from {@link #methodsAnnotated(Class)} apply to these 184 * annotated methods.</p> 185 * 186 * @return a new factory for discovering annotated methods 187 * @see #methodsAnnotated(Class) 188 * @since 4.2.0 189 */ 190 static Factory methodsAnnotatedPostProcess() { 191 return methodsAnnotated(PostProcess.class); 192 } 193 194}