private void attachSetMethods(ClassMetaData classMetaData) {
   for (Method method : classMetaData.setMethods) {
     PropertyMetaData property = classMetaData.getProperty(method.getName());
     if (property != null) {
       property.addSetMethod(method);
     }
   }
 }
 private void inspectType(Class<?> type, ClassMetaData classMetaData) {
   ClassDetails classDetails = ClassInspector.inspect(type);
   for (Method method : classDetails.getAllMethods()) {
     if (method.getAnnotation(Inject.class) != null) {
       if (!Modifier.isPublic(method.getModifiers())
           && !Modifier.isProtected(method.getModifiers())) {
         throw new UnsupportedOperationException(
             String.format(
                 "Cannot attach @Inject to method %s.%s() as it is not public or protected.",
                 method.getDeclaringClass().getSimpleName(), method.getName()));
       }
       if (Modifier.isStatic(method.getModifiers())) {
         throw new UnsupportedOperationException(
             String.format(
                 "Cannot attach @Inject to method %s.%s() as it is static.",
                 method.getDeclaringClass().getSimpleName(), method.getName()));
       }
     }
   }
   for (PropertyDetails property : classDetails.getProperties()) {
     PropertyMetaData propertyMetaData = classMetaData.property(property.getName());
     for (Method method : property.getGetters()) {
       propertyMetaData.addGetter(method);
     }
     for (Method method : property.getSetters()) {
       propertyMetaData.addSetter(method);
     }
   }
   for (Method method : classDetails.getInstanceMethods()) {
     Class<?>[] parameterTypes = method.getParameterTypes();
     if (parameterTypes.length == 1) {
       classMetaData.addCandidateSetMethod(method);
     }
     if (parameterTypes.length > 0
         && parameterTypes[parameterTypes.length - 1].equals(Action.class)) {
       classMetaData.addActionMethod(method);
     } else if (parameterTypes.length > 0
         && parameterTypes[parameterTypes.length - 1].equals(Closure.class)) {
       classMetaData.addClosureMethod(method);
     }
   }
 }
  private <T> Class<? extends T> generateUnderLock(Class<T> type) {
    Map<Class<?>, Class<?>> cache = GENERATED_CLASSES.get(getClass());
    if (cache == null) {
      // WeakHashMap won't work here. It keeps a strong reference to the mapping value, which is the
      // generated class in this case
      // However, the generated class has a strong reference to the source class (by extending it),
      // so the keys will always be
      // strongly reachable while this Class is strongly reachable. Use weak references for both key
      // and value of the mapping instead.
      cache = new ReferenceMap(AbstractReferenceMap.WEAK, AbstractReferenceMap.WEAK);
      GENERATED_CLASSES.put(getClass(), cache);
    }
    Class<?> generatedClass = cache.get(type);
    if (generatedClass != null) {
      return generatedClass.asSubclass(type);
    }

    if (Modifier.isPrivate(type.getModifiers())) {
      throw new GradleException(
          String.format(
              "Cannot create a proxy class for private class '%s'.", type.getSimpleName()));
    }
    if (Modifier.isAbstract(type.getModifiers())) {
      throw new GradleException(
          String.format(
              "Cannot create a proxy class for abstract class '%s'.", type.getSimpleName()));
    }

    Class<? extends T> subclass;
    try {
      ClassMetaData classMetaData = inspectType(type);

      ClassBuilder<T> builder = start(type, classMetaData);

      builder.startClass();

      if (!DynamicObjectAware.class.isAssignableFrom(type)) {
        if (ExtensionAware.class.isAssignableFrom(type)) {
          throw new UnsupportedOperationException(
              "A type that implements ExtensionAware must currently also implement DynamicObjectAware.");
        }
        builder.mixInDynamicAware();
      }
      if (!GroovyObject.class.isAssignableFrom(type)) {
        builder.mixInGroovyObject();
      }
      builder.addDynamicMethods();
      if (classMetaData.conventionAware && !IConventionAware.class.isAssignableFrom(type)) {
        builder.mixInConventionAware();
      }

      Class noMappingClass = Object.class;
      for (Class<?> c = type; c != null && noMappingClass == Object.class; c = c.getSuperclass()) {
        if (c.getAnnotation(NoConventionMapping.class) != null) {
          noMappingClass = c;
        }
      }

      Set<PropertyMetaData> conventionProperties = new HashSet<PropertyMetaData>();

      for (PropertyMetaData property : classMetaData.properties.values()) {
        if (SKIP_PROPERTIES.contains(property.name)) {
          continue;
        }

        if (property.injector) {
          builder.addInjectorProperty(property);
          for (Method getter : property.getters) {
            builder.applyServiceInjectionToGetter(property, getter);
          }
          for (Method setter : property.setters) {
            builder.applyServiceInjectionToSetter(property, setter);
          }
          continue;
        }

        boolean needsConventionMapping = false;
        if (classMetaData.isExtensible()) {
          for (Method getter : property.getters) {
            if (!Modifier.isFinal(getter.getModifiers())
                && !getter.getDeclaringClass().isAssignableFrom(noMappingClass)) {
              needsConventionMapping = true;
              break;
            }
          }
        }

        if (needsConventionMapping) {
          conventionProperties.add(property);
          builder.addConventionProperty(property);
          for (Method getter : property.getters) {
            builder.applyConventionMappingToGetter(property, getter);
          }
        }

        if (needsConventionMapping) {
          for (Method setter : property.setters) {
            if (!Modifier.isFinal(setter.getModifiers())) {
              builder.applyConventionMappingToSetter(property, setter);
            }
          }
        }
      }

      Set<Method> actionMethods = classMetaData.missingOverloads;
      for (Method method : actionMethods) {
        builder.addActionMethod(method);
      }

      // Adds a set method for each mutable property
      for (PropertyMetaData property : classMetaData.properties.values()) {
        if (property.setters.isEmpty()) {
          continue;
        }
        if (Iterable.class.isAssignableFrom(property.getType())) {
          // Currently not supported
          continue;
        }

        if (property.setMethods.isEmpty()) {
          for (Method setter : property.setters) {
            builder.addSetMethod(property, setter);
          }
        } else if (conventionProperties.contains(property)) {
          for (Method setMethod : property.setMethods) {
            builder.applyConventionMappingToSetMethod(property, setMethod);
          }
        }
      }

      for (Constructor<?> constructor : type.getConstructors()) {
        if (Modifier.isPublic(constructor.getModifiers())) {
          builder.addConstructor(constructor);
        }
      }

      subclass = builder.generate();
    } catch (Throwable e) {
      throw new GradleException(
          String.format("Could not generate a proxy class for class %s.", type.getName()), e);
    }

    cache.put(type, subclass);
    cache.put(subclass, subclass);
    return subclass;
  }