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