/**
     * Computes the cumulative set of ids used in a given layout. We can't just depth-first-search
     * the graph and check the set of ids encountered along the way, because we need to detect when
     * multiple includes contribute the same ids. For example, if a file is included more than once,
     * that would result in duplicates.
     */
    private Set<String> getIds(Layout layout, Deque<Layout> stack, Set<Layout> seen) {
      seen.add(layout);

      Set<String> layoutIds = layout.getIds();
      List<Layout> includes = layout.getIncludes();
      if (includes != null) {
        Set<String> ids = new HashSet<String>();
        if (layoutIds != null) {
          ids.addAll(layoutIds);
        }

        stack.push(layout);

        Multimap<String, Set<String>> nameToIds = ArrayListMultimap.create(includes.size(), 4);

        for (Layout included : includes) {
          if (seen.contains(included)) {
            continue;
          }
          Set<String> includedIds = getIds(included, stack, seen);
          if (includedIds != null) {
            String layoutName = included.getLayoutName();

            idCheck:
            for (String id : includedIds) {
              if (ids.contains(id)) {
                Collection<Set<String>> idSets = nameToIds.get(layoutName);
                if (idSets != null) {
                  for (Set<String> siblingIds : idSets) {
                    if (siblingIds.contains(id)) {
                      // The id reference was added by a sibling,
                      // so no need to complain (again)
                      continue idCheck;
                    }
                  }
                }

                // Duplicate! Record location request for new phase.
                if (mLocations == null) {
                  mErrors = new ArrayList<Occurrence>();
                  mLocations = ArrayListMultimap.create();
                  mContext
                      .getDriver()
                      .requestRepeat(DuplicateIdDetector.this, Scope.ALL_RESOURCES_SCOPE);
                }

                Map<Layout, Occurrence> occurrences = new HashMap<Layout, Occurrence>();
                findId(layout, id, new ArrayDeque<Layout>(), occurrences, new HashSet<Layout>());
                assert occurrences.size() >= 2;

                // Stash a request to find the given include
                Collection<Occurrence> values = occurrences.values();
                List<Occurrence> sorted = new ArrayList<Occurrence>(values);
                Collections.sort(sorted);
                String msg =
                    String.format(
                        "Duplicate id %1$s, defined or included multiple " + "times in %2$s: %3$s",
                        id, layout.getDisplayName(), sorted.toString());

                // Store location request for the <include> tag
                Occurrence primary = new Occurrence(layout.getFile(), msg, null);
                Multimap<String, Occurrence> m = ArrayListMultimap.create();
                m.put(layoutName, primary);
                mLocations.put(layout.getFile(), m);
                mErrors.add(primary);

                Occurrence prev = primary;

                // Now store all the included occurrences of the id
                for (Occurrence occurrence : values) {
                  if (occurrence.file.equals(layout.getFile())) {
                    occurrence.message = "Defined here";
                  } else {
                    occurrence.message =
                        String.format("Defined here, included via %1$s", occurrence.includePath);
                  }

                  m = ArrayListMultimap.create();
                  m.put(id, occurrence);
                  mLocations.put(occurrence.file, m);

                  // Link locations together
                  prev.next = occurrence;
                  prev = occurrence;
                }
              }
              ids.add(id);
            }

            // Store these ids such that on a conflict, we can tell when
            // an id was added by a single variation of this file
            nameToIds.put(layoutName, includedIds);
          }
        }
        Layout visited = stack.pop();
        assert visited == layout;
        return ids;
      } else {
        return layoutIds;
      }
    }