public void execute(PersistentCache cache) {
      File jarFile = jarFile(cache);
      LOGGER.debug("Generating worker process classes to {}.", jarFile);

      URL currentClasspath = getClass().getProtectionDomain().getCodeSource().getLocation();
      JarJarTask task = new JarJarTask();
      task.setDestFile(jarFile);

      // TODO - calculate this list of classes dynamically
      final List<Resource> classResources = new ArrayList<Resource>();
      List<Class<?>> renamedClasses =
          Arrays.asList(
              GradleWorkerMain.class,
              BootstrapSecurityManager.class,
              EncodedStream.EncodedInput.class,
              FilteringClassLoader.class,
              FilteringClassLoader.Spec.class,
              ClassLoaderHierarchy.class,
              ClassLoaderVisitor.class,
              ClassLoaderSpec.class,
              JavaReflectionUtil.class,
              JavaMethod.class,
              GradleException.class,
              NoSuchPropertyException.class,
              NoSuchMethodException.class,
              UncheckedException.class,
              PropertyAccessor.class,
              PropertyMutator.class,
              Factory.class,
              Spec.class);
      List<Class<?>> classes = new ArrayList<Class<?>>();
      classes.addAll(renamedClasses);
      for (Class<?> aClass : classes) {
        final String fileName = aClass.getName().replace('.', '/') + ".class";

        // Prefer the class from the same classpath as the current class. This is for the case where
        // we're running in a test under an older
        // version of Gradle, whose worker classes will be visible to us.
        // TODO - remove this once we have upgraded to a wrapper with these changes in it
        Enumeration<URL> resources;
        try {
          resources = WorkerProcessClassPathProvider.class.getClassLoader().getResources(fileName);
        } catch (IOException e) {
          throw new UncheckedIOException(e);
        }
        URL resource = null;
        while (resources.hasMoreElements()) {
          URL url = resources.nextElement();
          resource = url;
          if (url.toString().startsWith(currentClasspath.toString())) {
            break;
          }
        }
        URLResource urlResource =
            new URLResource(resource) {
              @Override
              public synchronized String getName() {
                return fileName;
              }
            };
        classResources.add(urlResource);
      }

      task.add(
          new ResourceCollection() {
            public Iterator iterator() {
              return classResources.iterator();
            }

            public int size() {
              return classResources.size();
            }

            public boolean isFilesystemOnly() {
              return true;
            }
          });

      // Don't rename references to this class
      Rule rule = new Rule();
      rule.setPattern(SystemApplicationClassLoaderWorker.class.getName());
      rule.setResult(SystemApplicationClassLoaderWorker.class.getName());
      task.addConfiguredRule(rule);

      // Rename everything else
      rule = new Rule();
      rule.setPattern("org.gradle.**");
      rule.setResult("jarjar.@0");
      task.addConfiguredRule(rule);

      AntUtil.execute(task);
    }
/**
 * Generates a subclass of the target class to mix-in some DSL behaviour.
 *
 * <ul>
 *   <li>For each property, a convention mapping is applied. These properties may have a setter
 *       method.
 *   <li>For each property whose getter is annotated with {@code Inject}, a service instance will be
 *       injected instead. These properties may have a setter method.
 *   <li>For each mutable property as set method is generated.
 *   <li>For each method whose last parameter is an {@link org.gradle.api.Action}, an override is
 *       generated that accepts a {@link groovy.lang.Closure} instead.
 *   <li>Coercion from string to enum property is mixed in.
 *   <li>{@link groovy.lang.GroovyObject} is mixed in to the class.
 * </ul>
 */
public abstract class AbstractClassGenerator implements ClassGenerator {
  private static final Map<Class<?>, Map<Class<?>, Class<?>>> GENERATED_CLASSES =
      new HashMap<Class<?>, Map<Class<?>, Class<?>>>();
  private static final Lock CACHE_LOCK = new ReentrantLock();
  private static final Collection<String> SKIP_PROPERTIES =
      Arrays.asList(
          "class", "metaClass", "conventionMapping", "convention", "asDynamicObject", "extensions");

  public <T> T newInstance(Class<T> type, Object... parameters) {
    return DirectInstantiator.instantiate(generate(type), parameters);
  }

  public <T> Class<? extends T> generate(Class<T> type) {
    try {
      CACHE_LOCK.lock();
      return generateUnderLock(type);
    } finally {
      CACHE_LOCK.unlock();
    }
  }

  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;
  }

  protected abstract <T> ClassBuilder<T> start(Class<T> type, ClassMetaData classMetaData);

  private ClassMetaData inspectType(Class<?> type) {
    boolean isConventionAware = type.getAnnotation(NoConventionMapping.class) == null;
    boolean extensible = JavaReflectionUtil.getAnnotation(type, NonExtensible.class) == null;

    ClassMetaData classMetaData = new ClassMetaData(extensible, isConventionAware);
    inspectType(type, classMetaData);
    attachSetMethods(classMetaData);
    findMissingClosureOverloads(classMetaData);
    classMetaData.complete();
    return classMetaData;
  }

  private void findMissingClosureOverloads(ClassMetaData classMetaData) {
    for (Method method : classMetaData.actionMethods) {
      Method overload =
          findClosureOverload(method, classMetaData.closureMethods.get(method.getName()));
      if (overload == null) {
        classMetaData.actionMethodRequiresOverload(method);
      }
    }
  }

  private Method findClosureOverload(Method method, Collection<Method> candidates) {
    for (Method candidate : candidates) {
      if (candidate.getParameterTypes().length != method.getParameterTypes().length) {
        continue;
      }
      boolean matches = true;
      for (int i = 0; matches && i < candidate.getParameterTypes().length - 1; i++) {
        if (!candidate.getParameterTypes()[i].equals(method.getParameterTypes()[i])) {
          matches = false;
        }
      }
      if (matches) {
        return candidate;
      }
    }
    return null;
  }

  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);
      }
    }
  }

  protected static class ClassMetaData {
    private final Map<String, PropertyMetaData> properties =
        new LinkedHashMap<String, PropertyMetaData>();
    private final Set<Method> missingOverloads = new LinkedHashSet<Method>();
    private final boolean extensible;
    private final boolean conventionAware;
    private List<Method> actionMethods = new ArrayList<Method>();
    private SetMultimap<String, Method> closureMethods = LinkedHashMultimap.create();
    private List<Method> setMethods = new ArrayList<Method>();

    public ClassMetaData(boolean extensible, boolean conventionAware) {
      this.extensible = extensible;
      this.conventionAware = conventionAware;
    }

    @Nullable
    public PropertyMetaData getProperty(String name) {
      return properties.get(name);
    }

    public PropertyMetaData property(String name) {
      PropertyMetaData property = properties.get(name);
      if (property == null) {
        property = new PropertyMetaData(name);
        properties.put(name, property);
      }
      return property;
    }

    public void addActionMethod(Method method) {
      actionMethods.add(method);
    }

    public void addClosureMethod(Method method) {
      closureMethods.put(method.getName(), method);
    }

    public void addCandidateSetMethod(Method method) {
      setMethods.add(method);
    }

    public void complete() {
      setMethods = null;
      actionMethods = null;
      closureMethods = null;
    }

    public void actionMethodRequiresOverload(Method method) {
      missingOverloads.add(method);
    }

    public boolean providesDynamicObjectImplementation() {
      PropertyMetaData property = properties.get("asDynamicObject");
      return property != null && !property.getters.isEmpty();
    }

    public boolean isExtensible() {
      return extensible;
    }

    public boolean isConventionAware() {
      return conventionAware;
    }
  }

  protected static class PropertyMetaData {
    final String name;
    final List<Method> getters = new ArrayList<Method>();
    final List<Method> setters = new ArrayList<Method>();
    final List<Method> setMethods = new ArrayList<Method>();
    boolean injector;

    private PropertyMetaData(String name) {
      this.name = name;
    }

    @Override
    public String toString() {
      return String.format("[property %s]", name);
    }

    public String getName() {
      return name;
    }

    public Class<?> getType() {
      if (!getters.isEmpty()) {
        return getters.get(0).getReturnType();
      }
      return setters.get(0).getParameterTypes()[0];
    }

    public void addGetter(Method method) {
      if (getters.add(method) && method.getAnnotation(Inject.class) != null) {
        injector = true;
      }
    }

    public void addSetter(Method method) {
      setters.add(method);
    }

    public void addSetMethod(Method method) {
      setMethods.add(method);
    }
  }

  protected interface ClassBuilder<T> {
    void startClass();

    void addConstructor(Constructor<?> constructor) throws Exception;

    void mixInDynamicAware() throws Exception;

    void mixInConventionAware() throws Exception;

    void mixInGroovyObject() throws Exception;

    void addDynamicMethods() throws Exception;

    void addInjectorProperty(PropertyMetaData property);

    void applyServiceInjectionToGetter(PropertyMetaData property, Method getter) throws Exception;

    void applyServiceInjectionToSetter(PropertyMetaData property, Method setter) throws Exception;

    void addConventionProperty(PropertyMetaData property) throws Exception;

    void applyConventionMappingToGetter(PropertyMetaData property, Method getter) throws Exception;

    void applyConventionMappingToSetter(PropertyMetaData property, Method setter) throws Exception;

    void applyConventionMappingToSetMethod(PropertyMetaData property, Method metaMethod)
        throws Exception;

    void addSetMethod(PropertyMetaData propertyMetaData, Method setter) throws Exception;

    void addActionMethod(Method method) throws Exception;

    Class<? extends T> generate() throws Exception;
  }
}