public ResolvedPath getResolvedPluginPath(
     Plugin plugin, String pathId, boolean mergeWithCore, boolean overrideCore, boolean flatten) {
   String key =
       "pluginPath#"
           + plugin.toShortString()
           + "#"
           + pathId
           + "#"
           + mergeWithCore
           + "#"
           + overrideCore
           + "#"
           + flatten;
   ResolvedPath path = (ResolvedPath) pathCache.get(key);
   if (path == null) {
     path = _getResolvedPluginPath(plugin, pathId, mergeWithCore, overrideCore, flatten);
     pathCache.put(key, path);
   } else {
     log.debug("Cache hit for: " + key);
   }
   return path;
 }
  public ResolvedPath _getResolvedPluginPath(
      Plugin plugin, String pathId, boolean mergeWithCore, boolean overrideCore, boolean flatten) {
    // Create a mock artifact for the resolver as a way to add user specified path specs and
    // overrides
    RepoArtifact artifact = new RepoArtifact();
    RepoDependency dependency = new RepoDependency();
    RepoArtifactId pluginId = plugin.getArtifact().getId();
    dependency.setId(pluginId);
    artifact.addDependency(dependency);

    String id = "plugin";
    artifact.addPath(new RepoPath(id, "Plugin path", true, true));

    // Add dependencies
    if (plugin.getDependency() != null) {
      for (Iterator j = plugin.getDependency().getPathSpecs().iterator(); j.hasNext(); ) {
        RepoPathSpec pluginPathSpec = (RepoPathSpec) j.next();

        if (pluginPathSpec.getFrom().equals(pathId)) {
          // Add user specifications. Ignore the mandatory flag and to paths as they
          // are not relevant. However, allow descend to be false in case the writer of the
          // plugin added bogus dependencies.
          dependency.addPathSpec(
              new RepoPathSpec(
                  pathId,
                  id,
                  pluginPathSpec.getOptions(),
                  (pluginPathSpec.isDescend() == null) ? Boolean.TRUE : pluginPathSpec.isDescend(),
                  Boolean.TRUE));
        }
      }
    }

    if (dependency.getPathSpecs().size() == 0) {
      // Add default ... user hasn't specified anything
      dependency.addPathSpec(new RepoPathSpec(pathId, id, null, Boolean.TRUE, Boolean.TRUE));
    }

    // Add core overrides if applicable
    if (overrideCore) {
      for (Iterator j = coreOverrides.iterator(); j.hasNext(); ) {
        RepoOverride override = (RepoOverride) j.next();
        artifact.addOverride(override);
      }
    }

    // Add overrides
    for (Iterator i = overrides.iterator(); i.hasNext(); ) {
      ws.quokka.core.model.Override override = (ws.quokka.core.model.Override) i.next();
      Set paths = override.matchingPluginPaths(plugin.getArtifact().getId());

      if (paths.contains(pathId) || ((paths.size() == 1) && paths.contains("*"))) {
        // Create a copy of the override, moving matching plugin paths to be standard paths
        RepoOverride copy =
            new RepoOverride(
                Collections.singleton("*"),
                override.getGroup(),
                override.getName(),
                override.getType(),
                override.getVersion(),
                override.getWithVersion(),
                override.getWithPathSpecs());
        artifact.addOverride(copy);
      }
    }

    // Remove the plugin itself from the path
    // TODO: Look into a better way of doing this, perhaps modifying Resolver so it has the option
    // of not adding the root in first place.
    ResolvedPath path = pathResolver.resolvePath(id, artifact);
    path.setId("Plugin path '" + pathId + "' from " + pluginId.toShortString());

    List artifacts = new ArrayList();

    for (Iterator i = path.getArtifacts().iterator(); i.hasNext(); ) {
      artifact = (RepoArtifact) i.next();

      if (!artifact.getId().equals(pluginId)) {
        artifacts.add(artifact);
      }

      if (pluginId.equals(artifact.getId().getAnnotations().get("declaredBy"))) {
        artifact.getId().getAnnotations().remove("declaredBy");
      }
    }

    path = new ResolvedPath(path.getId(), artifacts);
    path = handleMergeAndFlatten(mergeWithCore, flatten, path);

    return path;
  }
  public List _resolvePathGroup(Target target, String pathGroupId) {
    Plugin plugin = target.getPlugin();
    PathGroup pathGroup = target.getPathGroup(pathGroupId);

    if (pathGroup == null) {
      if (plugin.getDeclaringPlugin() != null) {
        String declaringTargetName =
            plugin.getDeclaringPlugin().getNameSpace() + ":" + parseImplements(target)[2];
        pathGroup =
            plugin.getDeclaringPlugin().getTarget(declaringTargetName).getPathGroup(pathGroupId);

        // TODO: Check the path group only refers to project paths?
      }

      Assert.isTrue(
          pathGroup != null,
          "Target '"
              + target.getName()
              + "' has requested path group '"
              + pathGroupId
              + "' that does not exist");
    }

    if (log.isDebugEnabled()) {
      log.debug(
          "Resolving path: target="
              + target.getName()
              + ", pathGroup="
              + pathGroupId
              + ", pathGroupElements="
              + pathGroup.getPaths());
    }

    List paths = new ArrayList();

    // Note: merging with core is turned off here so that the proper path tree is maintained
    // However, core overrides are still applied by separating that into a different flag
    resolvePath(
        pathGroup.getPaths(),
        paths,
        false,
        pathGroup.getMergeWithCore().booleanValue(),
        target,
        false);

    if (log.isDebugEnabled()) {
      log.debug("Resolved the following paths for path group '" + pathGroup + "'");
      for (Iterator i = paths.iterator(); i.hasNext(); ) {
        ResolvedPath path = (ResolvedPath) i.next();
        log.debug(pathResolver.formatPath(path, false));
      }
    }

    ResolvedPath path = pathResolver.merge(paths);

    if (pathGroup.getMergeWithCore().booleanValue()) {
      path = mergeWithCore(path);
    }

    //        System.out.println(pathResolver.formatPath(path, false));
    StringBuffer sb = new StringBuffer();

    for (Iterator i = path.getArtifacts().iterator(); i.hasNext(); ) {
      RepoArtifact artifact = (RepoArtifact) i.next();
      sb.append(artifact.getId().toShortString()).append(";");
    }

    if (log.isDebugEnabled()) {
      log.debug(
          "Resolved path: target="
              + target.getName()
              + ", pathGroup="
              + pathGroupId
              + ", path="
              + sb.toString());
    }

    return path.getArtifacts();
  }
  private void addTarget(Map resolved, Target target) {
    //        System.out.println("Adding " + target.getName() + " from " + parents);
    if (target.getPrefix() != null) {
      target.setDefaultProperties(expandPrefix(target.getPrefix(), target.getDefaultProperties()));
    }

    Target existing = (Target) resolved.get(target.getName());

    if (existing != null) {
      RepoArtifactId targetId = target.getPlugin().getArtifact().getId();
      RepoArtifactId existingId = existing.getPlugin().getArtifact().getId();
      Assert.isTrue(
          targetId.equals(existingId),
          target.getLocator(),
          "Multiple targets are defined with the name '"
              + target.getName()
              + "'. Declared in "
              + targetId
              + " and "
              + existingId);

      return;
    }

    resolved.put(target.getName(), target);
    registerTypes(target);
    registerProjectPaths(target);
    buildResources.putAll(target.getPlugin().getBuildResources());

    // PluginDependency declares the target
    addDependentTargets(resolved, target);

    if (target.getImplementsPlugin() != null) {
      // PluginDependency implements the target declared in another plugin
      String[] implementsPlugin = parseImplements(target);
      RepoArtifactId declaringPluginId =
          fastFindMatchingDependency(
              target,
              new RepoArtifactId(
                  implementsPlugin[0], implementsPlugin[1], "plugin", (Version) null));

      Plugin declaringPlugin = getPluginInstance(declaringPluginId);
      String declaringTargetName = declaringPlugin.getNameSpace() + ":" + implementsPlugin[2];

      // Get the declaring plugin and find the matching target
      Target declaringTarget = (Target) resolved.get(declaringTargetName);

      if (declaringTarget == null) {
        declaringTarget = declaringPlugin.getTarget(declaringTargetName);
        Assert.isTrue(
            declaringTarget != null,
            target.getLocator(),
            "'"
                + declaringTargetName
                + "' is not defined in '"
                + declaringPluginId.toShortString()
                + "'");
        addTarget(resolved, declaringTarget);
      }

      Assert.isTrue(
          declaringTarget.isAbstract(),
          target.getLocator(),
          "Target is attempting to implement a non-abstract target: target="
              + target.getName()
              + ", implements="
              + declaringTarget.getName());
      target.getPlugin().setDeclaringPlugin(declaringTarget.getPlugin());

      if (!declaringTarget.isImplemented()) {
        declaringTarget.setImplemented(true);
        declaringTarget.clearDependencies();
      }

      declaringTarget.addDependency(target.getName());

      // Add the declaring targets dependencies to ensure the implementation is executed before them
      for (Iterator i = declaringTarget.getOriginalDependencies().iterator(); i.hasNext(); ) {
        String dependency = (String) i.next();
        target.addDependency(dependency);
      }
    }
  }
  private void resolveTargets(Map resolved, PluginDependency pluginDependency) {
    List dependencyTargets = new ArrayList(pluginDependency.getTargets());
    Plugin plugin = getPluginInstance(pluginDependency.getId());
    plugin.setDependency(pluginDependency);

    for (Iterator i = plugin.getTargets().iterator(); i.hasNext(); ) {
      Target target = (Target) i.next();

      // If the target is based on a template internally, merge with the template
      if (target.getTemplateName() != null) {
        String template = target.getTemplateName();
        template =
            (template.indexOf(":") != -1)
                ? template
                : (target.getPlugin().getNameSpace() + ":" + template);
        target.merge(plugin.getTarget(template));
      }

      // Process explicit target definitions
      boolean added = false;

      for (Iterator j = dependencyTargets.iterator(); j.hasNext(); ) {
        PluginDependencyTarget dependencyTarget = (PluginDependencyTarget) j.next();
        Target targetInstance = null;

        if (dependencyTarget.getTemplate() == null) {
          // Enabling a target
          String name = dependencyTarget.getName();
          name =
              (name.indexOf(":") != -1) ? name : (target.getPlugin().getNameSpace() + ":" + name);

          if (target.getName().equals(name)) {
            Assert.isTrue(
                !target.isTemplate(),
                dependencyTarget.getLocator(),
                "The named target '" + name + "' is a template");

            String prefix = dependencyTarget.getPrefix();
            Assert.isTrue(
                (prefix == null) || prefix.equals(target.getPrefix()),
                dependencyTarget.getLocator(),
                "The prefix '"
                    + prefix
                    + "' should match the target prefix '"
                    + target.getPrefix()
                    + "' if specified");

            if (dependencyTarget.getAlias() != null) {
              target.setAlias(dependencyTarget.getAlias());
            }

            targetInstance = target;
          }
        } else {
          // Instantiation of a template
          String template = dependencyTarget.getTemplate();
          template =
              (template.indexOf(":") != -1)
                  ? template
                  : (target.getPlugin().getNameSpace() + ":" + template);

          if (target.getName().equals(template)) {
            Assert.isTrue(
                target.isTemplate(),
                dependencyTarget.getLocator(),
                "The named target '" + template + "' is not a template");
            targetInstance = (Target) target.clone();
            targetInstance.setPrefix(dependencyTarget.getPrefix());
            targetInstance.setTemplateName(target.getName());
            targetInstance.setName(dependencyTarget.getName());
          }
        }

        if (targetInstance != null) {
          added = true;

          for (Iterator k = dependencyTarget.getDependencies().iterator(); k.hasNext(); ) {
            String dependency = (String) k.next();
            targetInstance.addDependency(dependency);
          }

          // Record the plugin dependency target that introduced the target
          // This will be used later for dependency-of processing
          targetInstance.setPluginDependencyTarget(dependencyTarget);

          addTarget(resolved, targetInstance);
          j.remove();
        }
      }

      // Use defaults
      if (!added && pluginDependency.isUseDefaults() && target.isEnabledByDefault()) {
        addTarget(resolved, target);
      }
    }

    if (dependencyTargets.size() != 0) {
      List names = new ArrayList();

      for (Iterator i = dependencyTargets.iterator(); i.hasNext(); ) {
        PluginDependencyTarget target = (PluginDependencyTarget) i.next();
        names.add(target.getName());
      }

      Assert.isTrue(
          false,
          pluginDependency.getLocator(),
          "The following targets are not defined in plugin '"
              + plugin.getArtifact().getId().toShortString()
              + "': "
              + Strings.join(names.iterator(), ","));
    }
  }
  public Runnable createTargetInstance(Target target, Properties localProperties, Logger logger) {
    //        System.out.println("DefaultProjectModel.createTargetInstance");
    AntClassLoader loader = null;
    DefaultResources resources;

    try {
      org.apache.tools.ant.types.Path classPath = toAntPath(resolvePathGroup(target, "classpath"));

      // Allow additional classes to added to the plugin classpath. This is primarily designed to
      // allow the additional of instrumented testing classes and libraries for code coverage of
      // integration
      // tests with Cobertura
      String key = "quokka.classpath." + target.getPlugin().getArtifact().getId().getGroup();

      if (log.isDebugEnabled()) {
        log.debug("Searching for additional classpath with key: " + key);
      }

      String additionalPath = System.getProperty(key);

      if ((additionalPath != null) && !additionalPath.trim().equals("")) {
        org.apache.tools.ant.types.Path existing = classPath;
        classPath = new org.apache.tools.ant.types.Path(antProject, additionalPath);
        log.verbose("Prefixing classpath with: " + classPath);
        classPath.append(existing); // Make sure additions override existing
      }

      if ("true".equals(antProject.getProperty("quokka.project.debugclassloaders"))) {
        loader =
            new QuokkaLoader(target, antProject.getClass().getClassLoader(), antProject, classPath);
      } else {
        loader = antProject.createClassLoader(classPath);
      }

      loader.setParent(antProject.getCoreLoader());
      loader.setParentFirst(true);
      loader.setIsolated(false);
      loader.setThreadContextLoader();

      // Initialise this plugin
      Plugin plugin = target.getPlugin();
      loader.forceLoadClass(plugin.getClassName());

      Class pluginClass = Class.forName(plugin.getClassName(), true, loader);
      ws.quokka.core.plugin_spi.Plugin actualPlugin =
          (ws.quokka.core.plugin_spi.Plugin) pluginClass.newInstance();

      if (actualPlugin instanceof MetadataAware) {
        ((MetadataAware) actualPlugin).setMetadata(getMetadata());
      }

      if (actualPlugin instanceof ResourcesAware) {
        resources = new DefaultResources(this, target, antProject, logger);
        ((ResourcesAware) actualPlugin).setResources(resources);
      }

      if (actualPlugin instanceof RepositoryAware) {
        ((RepositoryAware) actualPlugin).setRepository(getRepository());
      }

      if (actualPlugin instanceof RepositoryFactoryAware) {
        ((RepositoryFactoryAware) actualPlugin)
            .setRepositoryFactory(
                (RepositoryFactory) antProject.getReference(ProjectHelper.REPOSITORY_FACTORY));
      }

      if (actualPlugin instanceof ResolverAware) {
        ((ResolverAware) actualPlugin).setResolver(pathResolver);
      }

      if (actualPlugin instanceof ModelFactoryAware) {
        ((ModelFactoryAware) actualPlugin).setModelFactory(getModelFactory());
      }

      actualPlugin.initialise();

      return actualPlugin.getTarget(
          (target.getTemplateName() != null) ? target.getTemplateName() : target.getName());
    } catch (Exception e) {
      throw new BuildException(e);
    } finally {
      if (loader != null) {
        loader.resetThreadContextLoader();
        loader.cleanup();
      }
    }
  }