/**
   * A private helper routine to make the compiler code more sane.
   *
   * <p>This processes a single definition in a dependency tree. It works as a single step in a
   * breadth first traversal of the tree, accumulating children in the 'deps' set, and updating the
   * compile context with the current definition.
   *
   * <p>Note that once the definition has been retrieved, this code uses the 'canonical' descriptor
   * from the definition, discarding the incoming descriptor.
   *
   * @param descriptor the descriptor that we are currently handling, must not be in the compiling
   *     defs.
   * @param cc the compile context to allow us to accumulate information.
   * @param deps the set of dependencies that we are accumulating.
   * @throws QuickFixException if the definition is not found, or validateDefinition() throws one.
   */
  private <D extends Definition> D getHelper(
      DefDescriptor<D> descriptor, CompileContext cc, Set<DefDescriptor<?>> deps)
      throws QuickFixException {
    CompilingDef<D> cd = cc.getCompiling(descriptor);

    if (cd.def != null) {
      return cd.def;
    }
    try {
      if (!fillCompilingDef(cd, cc.context)) {
        //
        // At this point, we have failed to get the def, so we should throw an
        // error. The first stanza is to provide a more useful error description
        // including the set of components using the missing component.
        //
        if (!cd.parents.isEmpty()) {
          StringBuilder sb = new StringBuilder();
          Location handy = null;
          for (Definition parent : cd.parents) {
            handy = parent.getLocation();
            if (sb.length() != 0) {
              sb.append(", ");
            }
            sb.append(parent.getDescriptor().toString());
          }
          throw new DefinitionNotFoundException(descriptor, handy, sb.toString());
        }
        throw new DefinitionNotFoundException(descriptor);
      }
      //
      // Ok. We have a def. let's figure out what to do with it.
      //
      Set<DefDescriptor<?>> newDeps = Sets.newHashSet();
      cd.def.appendDependencies(newDeps);
      //
      // FIXME: this code will go away with preloads.
      // This pulls in the context preloads. not pretty, but it works.
      //
      if (!cc.addedPreloads && cd.descriptor.getDefType().equals(DefType.APPLICATION)) {
        cc.addedPreloads = true;
        Set<String> preloads = cc.context.getPreloads();
        for (String preload : preloads) {
          if (!preload.contains("_")) {
            DependencyDefImpl.Builder ddb = new DependencyDefImpl.Builder();
            ddb.setResource(preload);
            ddb.setType("APPLICATION,COMPONENT,STYLE,EVENT");
            ddb.build().appendDependencies(newDeps);
          }
        }
      }
      for (DefDescriptor<?> dep : newDeps) {
        if (!defs.containsKey(dep)) {
          CompilingDef<?> depcd = cc.getCompiling(dep);
          depcd.parents.add(cd.def);
        }
      }
      deps.addAll(newDeps);
      cc.dependencies.put(cd.descriptor, cd.def);
      return cd.def;
    } catch (DefinitionNotFoundException dnfe) {
      //
      // In the case that we have a DefinitionNotFoundException for our current descriptor,
      // cache the fact that we didn't find one.
      //
      if (dnfe.getDescriptor().equals(descriptor)) {
        cd.def = null;
        defs.put(descriptor, cd.def);
        if (cd.cacheable) {
          defsCache.put(descriptor, Optional.fromNullable(cd.def));
        }
      }
      throw dnfe;
    }
  }