class Inject {

	public void run() throws InjectionException
	{
		Map<ClassFile, java.lang.Class> implemented = new HashMap<>();

		// inject interfaces first, so the validateTypeIsConvertibleTo
		// check below works
		for (ClassFile cf : deobfuscated.getClasses())
		{
			Annotations an = cf.getAnnotations();

			if (an == null || an.size() == 0)
			{
				continue;
			}

			String obfuscatedName = DeobAnnotations.getObfuscatedName(an);
			if (obfuscatedName == null)
			{
				obfuscatedName = cf.getName();
			}

			ClassFile other = vanilla.findClass(obfuscatedName);
			assert other != null : "unable to find vanilla class from obfuscated name: " + obfuscatedName;

			java.lang.Class implementingClass = injectInterface(cf, other);
			// it can not implement an interface but still have exported static fields, which are
			// moved to client

			implemented.put(cf, implementingClass);
		}

		// requires interfaces to be injected
		mixinInjector.inject();
		construct.inject(implemented);

		for (ClassFile cf : deobfuscated.getClasses())
		{
			java.lang.Class implementingClass = implemented.get(cf);
			Annotations an = cf.getAnnotations();

			if (an == null || an.size() == 0)
			{
				continue;
			}

			String obfuscatedName = DeobAnnotations.getObfuscatedName(an);
			if (obfuscatedName == null)
			{
				obfuscatedName = cf.getName();
			}

			ClassFile other = vanilla.findClass(obfuscatedName);
			assert other != null : "unable to find vanilla class from obfuscated name: " + obfuscatedName;

			for (Field f : cf.getFields())
			{
				an = f.getAnnotations();

				if (an == null || an.find(DeobAnnotations.EXPORT) == null)
				{
					continue; // not an exported field
				}

				Annotation exportAnnotation = an.find(DeobAnnotations.EXPORT);
				String exportedName = exportAnnotation.getElement().getString();

				obfuscatedName = DeobAnnotations.getObfuscatedName(an);

				Annotation getterAnnotation = an.find(DeobAnnotations.OBFUSCATED_GETTER);
				Number getter = null;
				if (getterAnnotation != null)
				{
					getter = (Number) getterAnnotation.getElement().getValue();
				}
				// the ob jar is the same as the vanilla so this field must exist in this class.

				Type obType = getFieldType(f);
				Field otherf = other.findField(obfuscatedName, obType);
				assert otherf != null;

				assert f.isStatic() == otherf.isStatic();

				ClassFile targetClass = f.isStatic() ? vanilla.findClass("client") : other; // target class for getter
				java.lang.Class targetApiClass = f.isStatic() ? CLIENT_CLASS : implementingClass; // target api class for getter
				if (targetApiClass == null)
				{
					assert !f.isStatic();

					// non static field exported on non exported interface
					logger.debug("Non static exported field {} on non exported interface", exportedName);
					continue;
				}

				java.lang.reflect.Method apiMethod = findImportMethodOnApi(targetApiClass, exportedName, true);
				if (apiMethod != null)
				{
					Number setter = null;
					if (getter != null)
					{
						setter = DMath.modInverse(getter); // inverse getter to get the setter
					}

					setters.injectSetter(targetClass, targetApiClass, otherf, exportedName, setter);
				}

				apiMethod = findImportMethodOnApi(targetApiClass, exportedName, false);
				if (apiMethod == null)
				{
					logger.debug("Unable to find import method on api class {} with imported name {}, not injecting getter", targetApiClass, exportedName);
					continue;
				}

				// check that otherf is converable to apiMethod's
				// return type
				Type fieldType = otherf.getType();
				Type returnType = classToType(apiMethod.getReturnType());
				if (!validateTypeIsConvertibleTo(fieldType, returnType))
				{
					throw new InjectionException("Type " + fieldType + " is not convertable to " + returnType + " for getter " + apiMethod);
				}

				getters.injectGetter(targetClass, apiMethod, otherf, getter);
			}

			for (Method m : cf.getMethods())
			{
				hookMethod.process(m);
				invokes.process(m, other, implementingClass);
			}
		}
		
		logger.info("Injected {} getters, {} settters, {} invokers",
			getters.getInjectedGetters(),
			setters.getInjectedSetters(), invokes.getInjectedInvokers());

		drawAfterWidgets.inject();
		scriptVM.inject();
	}

	public  Inject(ClassGroup deobfuscated, ClassGroup vanilla);

}