001/* 002 * Trident - A Multithreaded Server Alternative 003 * Copyright 2014 The TridentSDK Team 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 net.tridentsdk.reflect; 018 019import com.google.common.collect.Iterators; 020import com.google.common.collect.Lists; 021import com.google.common.collect.Maps; 022import com.google.common.collect.PeekingIterator; 023import net.tridentsdk.util.TridentLogger; 024 025import javax.annotation.concurrent.ThreadSafe; 026import java.lang.reflect.Constructor; 027import java.lang.reflect.Field; 028import java.lang.reflect.InvocationTargetException; 029import java.lang.reflect.Modifier; 030import java.util.Arrays; 031import java.util.Iterator; 032import java.util.List; 033import java.util.Map; 034 035/** 036 * Injects the target class, or creates a new instance of an injected class 037 * 038 * @author The TridentSDK Team 039 * @since 0.3-alpha-DP 040 * @param <T> the type to inject for 041 */ 042@ThreadSafe 043public final class Injector<T> { 044 private static final Map<Class<?>, Producer<?>> injectors = Maps.newConcurrentMap(); 045 046 private final Class<T> clazz; 047 048 private Injector(Class<T> clazz) { 049 this.clazz = clazz; 050 } 051 052 /** 053 * Binds a class 054 * 055 * <p>This returns an injector which you MUST use to bind the producer</p> 056 * 057 * @param clazz the class to bind 058 * @param <T> the type of the class 059 * @return the injector which provides binding members 060 */ 061 public static <T> Injector<T> inject(Class<T> clazz) { 062 return new Injector<>(clazz); 063 } 064 065 /** 066 * Binds the provided class to the producer 067 * 068 * <p>The producer creates objects for injectable fields with that particular type</p> 069 * 070 * @param producer the producing class 071 */ 072 public void with(Producer<T> producer) { 073 injectors.put(this.clazz, producer); 074 } 075 076 /** 077 * Binds the provided class to the instance given 078 * 079 * <p>This provides singleton-like functionality for the injector</p> 080 * 081 * @param instance the instance to return for any injection 082 */ 083 public void with(final T instance) { 084 with(new Producer<T>() { 085 @Override 086 public T produce() { 087 return instance; 088 } 089 090 @Override 091 public T produce(Class<?> metadata) { 092 return instance; 093 } 094 }); 095 } 096 097 /** 098 * Injects all static fields in the class 099 * 100 * <p>This does not inject any constructors, only static fields of the provided class to inject</p> 101 * 102 * @param clazz the class with static fields to inject 103 */ 104 public static void staticInject(Class<?> clazz) { 105 for (Field field : findFields(clazz)) { 106 if (!Modifier.isStatic(field.getModifiers())) continue; 107 setField(null, field); 108 } 109 } 110 111 /** 112 * Creates a new object which has injectable fields, using the injectable constructor 113 * 114 * @param clazz the class to instantiate 115 * @param args the parameters, not including the injectable classes, in order of declaration 116 * @param <T> the type to return 117 * @return the new object 118 */ 119 public static <T> T newObject(Class<T> clazz, Object... args) { 120 for (Map.Entry<Constructor, Class<?>[]> entry : findConstructors(clazz).entrySet()) { 121 List<Object> arguments = Lists.newArrayList(); 122 Constructor constructor = entry.getKey(); 123 Class<?>[] constructorParameters = entry.getValue(); 124 PeekingIterator<Object> iterator = Iterators.peekingIterator(Iterators.forArray(args)); 125 126 if (!checkArray(args, constructorParameters)) 127 continue; 128 129 for (Class<?> c : constructorParameters) { 130 Producer<?> producer = injectors.get(c); 131 Inject inject = (Inject) constructor.getAnnotation(Inject.class); 132 133 if (iterator.hasNext()) { 134 if (iterator.peek().getClass() == c) { 135 arguments.add(iterator.next()); 136 continue; 137 } 138 } 139 140 if (producer == null) { 141 if (iterator.hasNext()) { 142 if (iterator.peek().getClass() == c) { 143 arguments.add(iterator.next()); 144 continue; 145 } 146 } 147 148 TridentLogger.get().error(new IllegalArgumentException( 149 "Constructor " + clazz.getName() + "(" + 150 Arrays.toString(constructorParameters) 151 .replaceAll("class ", "") 152 .replaceAll("\\[", "") 153 .replaceAll("\\]", "") + ") " + 154 "does not provide or registered parameter " + c.getName())); 155 return null; 156 } else { 157 if (inject.meta() == Class.class) { 158 arguments.add(producer.produce()); 159 } else { 160 arguments.add(producer.produce(inject.meta())); 161 } 162 } 163 } 164 165 try { 166 T t = (T) constructor.newInstance(arguments.toArray()); 167 for (Field field : findFields(clazz)) { 168 setField(t, field); 169 } 170 return t; 171 } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { 172 TridentLogger.get().error(e); 173 return null; 174 } 175 } 176 177 try { 178 T t = clazz.newInstance(); 179 for (Field field : findFields(clazz)) { 180 setField(t, field); 181 } 182 return t; 183 } catch (InstantiationException | IllegalAccessException e) { 184 TridentLogger.get().error(e); 185 return null; 186 } 187 } 188 189 /** 190 * Injects all fields of an existing object 191 * 192 * @param instance the instance of the object to inject 193 */ 194 public static void injectAll(Object instance) { 195 for (Field field : findFields(instance.getClass())) { 196 setField(instance, field); 197 } 198 } 199 200 private static void setField(Object instance, Field field) { 201 field.setAccessible(true); 202 203 Class<?> type = field.getType(); 204 Producer<?> producer = injectors.get(type); 205 if (producer == null) { 206 TridentLogger.get().error(new IllegalArgumentException("Class " + 207 instance.getClass().getName() + " does not have bound injector for type " + type.getName())); 208 return; 209 } 210 211 Inject inject = field.getAnnotation(Inject.class); 212 try { 213 if (inject.meta() == Class.class) { 214 field.set(instance, producer.produce()); 215 } else { 216 field.set(instance, producer.produce(inject.meta())); 217 } 218 } catch (IllegalAccessException e) { 219 TridentLogger.get().error(e); 220 } 221 } 222 223 private static List<Field> findFields(Class<?> c) { 224 List<Field> fields = Lists.newArrayList(); 225 for (Field field : c.getDeclaredFields()) { 226 if (field.getAnnotation(Inject.class) != null) 227 fields.add(field); 228 } 229 return fields; 230 } 231 232 private static Map<Constructor, Class<?>[]> findConstructors(Class<?> c) { 233 Map<Constructor, Class<?>[]> map = Maps.newHashMap(); 234 for (Constructor constructor : c.getDeclaredConstructors()) { 235 if (constructor.getAnnotation(Inject.class) != null) 236 map.put(constructor, constructor.getParameterTypes()); 237 } 238 239 for (Constructor constructor : c.getConstructors()) { 240 if (map.containsKey(constructor)) continue; 241 if (constructor.getAnnotation(Inject.class) != null) 242 map.put(constructor, constructor.getParameterTypes()); 243 } 244 245 return map; 246 } 247 248 private static boolean checkArray(Object[] args, Class<?>[] params) { 249 Iterator<Class<?>> iterator = Iterators.forArray(params); 250 for (Object o : args) { 251 if (!Iterators.contains(iterator, o.getClass())) 252 return false; 253 } 254 255 return true; 256 } 257}