/** construct deep-copy of other */
 protected Topology(Topology other) {
   dcEndpoints = HashMultimap.create(other.dcEndpoints);
   dcRacks = new HashMap<String, Multimap<String, InetAddress>>();
   for (String dc : other.dcRacks.keySet())
     dcRacks.put(dc, HashMultimap.create(other.dcRacks.get(dc)));
   currentLocations = new HashMap<InetAddress, Pair<String, String>>(other.currentLocations);
 }
  @NotNull
  private static Multimap<FqName, Pair<FunctionDescriptor, PsiMethod>>
      getSuperclassToFunctionsMultimap(
          @NotNull PsiMethodWrapper method,
          @NotNull BindingContext bindingContext,
          @NotNull ClassDescriptor containingClass) {
    Multimap<FqName, Pair<FunctionDescriptor, PsiMethod>> result = HashMultimap.create();

    Name functionName = Name.identifier(method.getName());
    int parameterCount = method.getParameters().size();

    for (JetType supertype : TypeUtils.getAllSupertypes(containingClass.getDefaultType())) {
      ClassifierDescriptor klass = supertype.getConstructor().getDeclarationDescriptor();
      assert klass != null;
      FqName fqName = DescriptorUtils.getFQName(klass).toSafe();

      for (FunctionDescriptor fun :
          klass.getDefaultType().getMemberScope().getFunctions(functionName)) {
        if (fun.getKind().isReal() && fun.getValueParameters().size() == parameterCount) {
          PsiElement declaration = BindingContextUtils.descriptorToDeclaration(bindingContext, fun);
          if (declaration instanceof PsiMethod) {
            result.put(fqName, Pair.create(fun, (PsiMethod) declaration));
          } // else declaration is null or JetNamedFunction: both cases are processed later
        }
      }
    }
    return result;
  }
Example #3
0
  public void checkRedeclarationsInInnerClassNames(@NotNull TopDownAnalysisContext c) {
    for (ClassDescriptorWithResolutionScopes classDescriptor : c.getDeclaredClasses().values()) {
      if (classDescriptor.getKind() == ClassKind.CLASS_OBJECT) {
        // Class objects should be considered during analysing redeclarations in classes
        continue;
      }

      Collection<DeclarationDescriptor> allDescriptors =
          classDescriptor.getScopeForMemberLookup().getOwnDeclaredDescriptors();

      ClassDescriptorWithResolutionScopes classObj = classDescriptor.getClassObjectDescriptor();
      if (classObj != null) {
        Collection<DeclarationDescriptor> classObjDescriptors =
            classObj.getScopeForMemberLookup().getOwnDeclaredDescriptors();
        if (!classObjDescriptors.isEmpty()) {
          allDescriptors = Lists.newArrayList(allDescriptors);
          allDescriptors.addAll(classObjDescriptors);
        }
      }

      Multimap<Name, DeclarationDescriptor> descriptorMap = HashMultimap.create();
      for (DeclarationDescriptor desc : allDescriptors) {
        if (desc instanceof ClassDescriptor || desc instanceof PropertyDescriptor) {
          descriptorMap.put(desc.getName(), desc);
        }
      }

      reportRedeclarations(descriptorMap);
    }
  }
  /**
   * 将原始数据切割后装入ThreadInfo,并以Key=WaitID,Value= ThreadInfo实体放入到Multimap<String, ThreadInfo>集合中
   * ps:Multimap 类似于Map<key,collection>, key:value-> 1:n
   *
   * @param rawDatas
   * @return
   */
  public Multimap<String, ThreadInfo> getThreadInfo(List<String[]> rawDatas) {
    Multimap<String, ThreadInfo> w_IdMap = HashMultimap.create();
    List<ThreadInfo> threadsList = Lists.newArrayList();
    for (String[] rawData : rawDatas) {
      ThreadInfo threadInfo = new ThreadInfo();
      Pattern t_id = Pattern.compile("tid=(0x[\\d\\w]+)");
      Pattern t_name = Pattern.compile("\"([\\d\\D]*)\"");
      Pattern w_Id = Pattern.compile("\\[(0x[\\d\\w]+)\\]");
      Matcher tIdMatcher = t_id.matcher(rawData[0]);
      Matcher nameMatcher = t_name.matcher(rawData[0]);
      Matcher w_IdMatcher = w_Id.matcher(rawData[0]);
      if (tIdMatcher.find()) {
        threadInfo.setThreadId(tIdMatcher.group(1));
      }

      if (nameMatcher.find()) {
        threadInfo.setThreadName(nameMatcher.group(1));
      }

      if (w_IdMatcher.find()) {
        threadInfo.setWaitThreadId(w_IdMatcher.group(1));
      }
      threadInfo.setThreadCondition(rawData[1]);
      w_IdMap.put(threadInfo.getWaitThreadId(), threadInfo);
    }
    return w_IdMap;
  }
Example #5
0
 private synchronized Multimap<Range, InetAddress> getPendingRangesMM(String table) {
   Multimap<Range, InetAddress> map = pendingRanges.get(table);
   if (map == null) {
     map = HashMultimap.create();
     pendingRanges.put(table, map);
   }
   return map;
 }
Example #6
0
  private int repeatInternal(
      @NotNull PseudocodeImpl originalPseudocode,
      @Nullable Label startLabel,
      @Nullable Label finishLabel,
      int labelCount) {
    Integer startIndex =
        startLabel != null
            ? ((PseudocodeLabel) startLabel).getTargetInstructionIndex()
            : Integer.valueOf(0);
    assert startIndex != null;
    Integer finishIndex =
        finishLabel != null
            ? ((PseudocodeLabel) finishLabel).getTargetInstructionIndex()
            : Integer.valueOf(originalPseudocode.mutableInstructionList.size());
    assert finishIndex != null;

    Map<Label, Label> originalToCopy = Maps.newLinkedHashMap();
    Multimap<Instruction, Label> originalLabelsForInstruction = HashMultimap.create();
    for (PseudocodeLabel label : originalPseudocode.labels) {
      Integer index = label.getTargetInstructionIndex();
      if (index == null) continue; // label is not bounded yet
      if (label == startLabel || label == finishLabel) continue;

      if (startIndex <= index && index <= finishIndex) {
        originalToCopy.put(label, label.copy(labelCount++));
        originalLabelsForInstruction.put(getJumpTarget(label), label);
      }
    }
    for (Label label : originalToCopy.values()) {
      labels.add((PseudocodeLabel) label);
    }
    for (int index = startIndex; index < finishIndex; index++) {
      Instruction originalInstruction = originalPseudocode.mutableInstructionList.get(index);
      repeatLabelsBindingForInstruction(
          originalInstruction, originalToCopy, originalLabelsForInstruction);
      Instruction copy = copyInstruction(originalInstruction, originalToCopy);
      addInstruction(copy);
      if (originalInstruction == originalPseudocode.errorInstruction
          && copy instanceof SubroutineExitInstruction) {
        errorInstruction = (SubroutineExitInstruction) copy;
      }
      if (originalInstruction == originalPseudocode.exitInstruction
          && copy instanceof SubroutineExitInstruction) {
        exitInstruction = (SubroutineExitInstruction) copy;
      }
      if (originalInstruction == originalPseudocode.sinkInstruction
          && copy instanceof SubroutineSinkInstruction) {
        sinkInstruction = (SubroutineSinkInstruction) copy;
      }
    }
    if (finishIndex < mutableInstructionList.size()) {
      repeatLabelsBindingForInstruction(
          originalPseudocode.mutableInstructionList.get(finishIndex),
          originalToCopy,
          originalLabelsForInstruction);
    }
    return labelCount;
  }
 private Multimap<Range<Token>, InetAddress> getPendingRangesMM(String keyspaceName) {
   Multimap<Range<Token>, InetAddress> map = pendingRanges.get(keyspaceName);
   if (map == null) {
     map = HashMultimap.create();
     Multimap<Range<Token>, InetAddress> priorMap = pendingRanges.putIfAbsent(keyspaceName, map);
     if (priorMap != null) map = priorMap;
   }
   return map;
 }
 /**
  * expand super types after scanning, for super types that were not scanned. this is helpful in
  * finding the transitive closure without scanning all 3rd party dependencies. it uses {@link
  * ReflectionUtils#getSuperTypes(Class)}.
  *
  * <p>for example, for classes A,B,C where A supertype of B, B supertype of C:
  *
  * <ul>
  *   <li>if scanning C resulted in B (B->C in store), but A was not scanned (although A supertype
  *       of B) - then getSubTypes(A) will not return C
  *   <li>if expanding supertypes, B will be expanded with A (A->B in store) - then getSubTypes(A)
  *       will return C
  * </ul>
  */
 public void expandSuperTypes() {
   if (store.keySet().contains(index(SubTypesScanner.class))) {
     Multimap<String, String> mmap = store.get(index(SubTypesScanner.class));
     Sets.SetView<String> keys = Sets.difference(mmap.keySet(), Sets.newHashSet(mmap.values()));
     Multimap<String, String> expand = HashMultimap.create();
     for (String key : keys) {
       expandSupertypes(expand, key, forName(key));
     }
     mmap.putAll(expand);
   }
 }
  /** Returns documents grouped by partitions. */
  SetMultimap<Object, Document> getDocumentsByPartition(List<Document> documents) {
    final SetMultimap<Object, Document> index = HashMultimap.create();
    for (Document document : documents) {
      final Collection<Object> partitions = document.getField(partitionIdFieldName);
      for (Object partition : partitions) {
        index.put(partition, document);
      }
    }

    return ImmutableSetMultimap.copyOf(index);
  }
 /** @return an endpoint to token multimap representation of tokenToEndpointMap (a copy) */
 public Multimap<InetAddress, Token> getEndpointToTokenMapForReading() {
   lock.readLock().lock();
   try {
     Multimap<InetAddress, Token> cloned = HashMultimap.create();
     for (Map.Entry<Token, InetAddress> entry : tokenToEndpointMap.entrySet())
       cloned.put(entry.getValue(), entry.getKey());
     return cloned;
   } finally {
     lock.readLock().unlock();
   }
 }
    /** Stores current DC/rack assignment for ep */
    protected void addEndpoint(InetAddress ep) {
      IEndpointSnitch snitch = DatabaseDescriptor.getEndpointSnitch();
      String dc = snitch.getDatacenter(ep);
      String rack = snitch.getRack(ep);
      Pair<String, String> current = currentLocations.get(ep);
      if (current != null) {
        if (current.left.equals(dc) && current.right.equals(rack)) return;
        dcRacks.get(current.left).remove(current.right, ep);
        dcEndpoints.remove(current.left, ep);
      }

      dcEndpoints.put(dc, ep);

      if (!dcRacks.containsKey(dc)) dcRacks.put(dc, HashMultimap.<String, InetAddress>create());
      dcRacks.get(dc).put(rack, ep);

      currentLocations.put(ep, Pair.create(dc, rack));
    }
Example #12
0
/**
 * Represents one network of nodes, where each nodes is somehow connected to another within the
 * network.
 *
 * <p>Network contains following node types: - networking nodes - nodes that are a back-bone of a
 * network. These allow to connect multiple nodes in the network. A networking node "conducts" the
 * "signal" of the network to nodes defined in the "connectingOnSides" nodes in its vicinity. - leaf
 * nodes - nodes that are only receiving or producing a signal, and do not themselves "conduct" it
 * to other nodes.
 *
 * <p>A couple of non-obvious facts: 1. The same node (defined as location) cannot be both a
 * networking node and a leaf node in the same network. 2. The same leaf node can be a member of
 * multiple disjunctive networks (different network on each side). 3. A valid network can have no
 * networking nodes at all, and exactly two leaf nodes (neighbouring leaf nodes).
 *
 * @author Marcin Sciesinski <*****@*****.**>
 */
public class SimpleNetwork implements Network {
  private static final boolean SANITY_CHECK = false;
  private SetMultimap<ImmutableBlockLocation, NetworkNode> networkingNodes = HashMultimap.create();
  private SetMultimap<ImmutableBlockLocation, NetworkNode> leafNodes = HashMultimap.create();

  // Distance cache
  private Map<TwoNetworkNodes, Integer> distanceCache = Maps.newHashMap();

  public static SimpleNetwork createDegenerateNetwork(
      NetworkNode networkNode1, NetworkNode networkNode2) {
    if (!areNodesConnecting(networkNode1, networkNode2))
      throw new IllegalArgumentException("These two nodes are not connected");

    SimpleNetwork network = new SimpleNetwork();
    network.leafNodes.put(networkNode1.location, networkNode1);
    network.leafNodes.put(networkNode2.location, networkNode2);
    return network;
  }

  /**
   * Adds a networking node to the network.
   *
   * @param networkNode Definition of the networking node position and connecting sides.
   */
  public void addNetworkingNode(NetworkNode networkNode) {
    if (SANITY_CHECK && !canAddNetworkingNode(networkNode))
      throw new IllegalStateException("Unable to add this node to network");
    networkingNodes.put(networkNode.location, networkNode);
    distanceCache.clear();
  }

  /**
   * Adds a leaf node to the network.
   *
   * @param networkNode Definition of the leaf node position and connecting sides.
   */
  public void addLeafNode(NetworkNode networkNode) {
    if (SANITY_CHECK && (!canAddLeafNode(networkNode) || isEmptyNetwork()))
      throw new IllegalStateException("Unable to add this node to network");
    leafNodes.put(networkNode.location, networkNode);
    distanceCache.clear();
  }

  /**
   * Returns the network size - a number of nodes it spans. If the same node is added twice with
   * different connecting sides, it is counted twice.
   *
   * @return The sum of networking nodes and leaf nodes (count).
   */
  @Override
  public int getNetworkSize() {
    return networkingNodes.size() + leafNodes.size();
  }

  /**
   * Removes a leaf node from the network. If this removal made the network degenerate, it will
   * return <code>true</code>.
   *
   * @param networkingNode Definition of the leaf node position and connecting sides.
   * @return <code>true</code> if the network after the removal is degenerated or empty (no longer
   *     valid).
   */
  public boolean removeLeafNode(NetworkNode networkingNode) {
    // Removal of a leaf node cannot split the network, so it's just safe to remove it
    // We just need to check, if after removal of the node, network becomes degenerated, if so - we
    // need
    // to signal that the network is no longer valid and should be removed.
    final boolean changed = leafNodes.remove(networkingNode.location, networkingNode);
    if (!changed)
      throw new IllegalStateException("Tried to remove a node that is not in the network");

    distanceCache.clear();

    return isDegeneratedNetwork() || isEmptyNetwork();
  }

  public void removeAllLeafNodes() {
    leafNodes.clear();
    distanceCache.clear();
  }

  public void removeAllNetworkingNodes() {
    networkingNodes.clear();
    distanceCache.clear();
  }

  public void removeNetworkingNode(NetworkNode networkNode) {
    if (!networkingNodes.remove(networkNode.location, networkNode))
      throw new IllegalStateException("Tried to remove a node that is not in the network");
    distanceCache.clear();
  }

  public Collection<NetworkNode> getNetworkingNodes() {
    return Collections.unmodifiableCollection(networkingNodes.values());
  }

  public Collection<NetworkNode> getLeafNodes() {
    return Collections.unmodifiableCollection(leafNodes.values());
  }

  public static boolean areNodesConnecting(NetworkNode node1, NetworkNode node2) {
    for (Side side : SideBitFlag.getSides(node1.connectionSides)) {
      final ImmutableBlockLocation possibleConnectedLocation = node1.location.move(side);
      if (node2.location.equals(possibleConnectedLocation)
          && SideBitFlag.hasSide(node2.connectionSides, side.reverse())) return true;
    }
    return false;
  }

  /**
   * If this network can connect to node at the location specified with the specified connecting
   * sides.
   *
   * @param networkNode Definition of the networking node position and connecting sides.
   * @return If the networking node can be added to the network (connects to it).
   */
  public boolean canAddNetworkingNode(NetworkNode networkNode) {
    if (isEmptyNetwork()) return true;
    if (networkingNodes.containsValue(networkNode) || leafNodes.containsValue(networkNode))
      return false;
    return canConnectToNetworkingNode(networkNode);
  }

  public boolean canAddLeafNode(NetworkNode networkNode) {
    if (isEmptyNetwork()) return false;
    if (networkingNodes.containsValue(networkNode) || leafNodes.containsValue(networkNode))
      return false;

    return canConnectToNetworkingNode(networkNode);
  }

  private boolean canConnectToNetworkingNode(NetworkNode networkNode) {
    for (Side connectingOnSide : SideBitFlag.getSides(networkNode.connectionSides)) {
      final ImmutableBlockLocation possibleConnectionLocation =
          networkNode.location.move(connectingOnSide);
      for (NetworkNode possibleConnectedNode : networkingNodes.get(possibleConnectionLocation)) {
        if (SideBitFlag.hasSide(possibleConnectedNode.connectionSides, connectingOnSide.reverse()))
          return true;
      }
    }
    return false;
  }

  @Override
  public boolean hasNetworkingNode(NetworkNode networkNode) {
    return networkingNodes.containsValue(networkNode);
  }

  @Override
  public boolean hasLeafNode(NetworkNode networkNode) {
    return leafNodes.containsValue(networkNode);
  }

  @Override
  public int getDistance(NetworkNode from, NetworkNode to) {
    TwoNetworkNodes nodePair = new TwoNetworkNodes(from, to);
    final Integer cachedDistance = distanceCache.get(nodePair);
    if (cachedDistance != null) return cachedDistance;

    if ((!hasNetworkingNode(from) && !hasLeafNode(from))
        || (!hasNetworkingNode(to) && !hasLeafNode(to)))
      throw new IllegalArgumentException("Cannot test nodes not in network");

    if (from.equals(to)) return 0;

    if (SimpleNetwork.areNodesConnecting(from, to)) return 1;

    // Breadth-first search of the network
    Set<NetworkNode> visitedNodes = Sets.newHashSet();
    visitedNodes.add(from);

    Set<NetworkNode> networkingNodesToTest = Sets.newHashSet();
    listConnectedNotVisitedNetworkingNodes(visitedNodes, from, networkingNodesToTest);
    int distanceSearched = 1;
    while (networkingNodesToTest.size() > 0) {
      distanceSearched++;

      for (NetworkNode nodeToTest : networkingNodesToTest) {
        if (SimpleNetwork.areNodesConnecting(nodeToTest, to)) {
          distanceCache.put(new TwoNetworkNodes(from, to), distanceSearched);
          return distanceSearched;
        }
        visitedNodes.add(nodeToTest);
      }

      Set<NetworkNode> nextNetworkingNodesToTest = Sets.newHashSet();
      for (NetworkNode nodeToTest : networkingNodesToTest)
        listConnectedNotVisitedNetworkingNodes(visitedNodes, nodeToTest, nextNetworkingNodesToTest);

      networkingNodesToTest = nextNetworkingNodesToTest;
    }
    return -1;
  }

  @Override
  public boolean isInDistance(int distance, NetworkNode from, NetworkNode to) {
    if (distance < 0) throw new IllegalArgumentException("distance must be >= 0");

    TwoNetworkNodes nodePair = new TwoNetworkNodes(from, to);
    final Integer cachedDistance = distanceCache.get(nodePair);
    if (cachedDistance != null) return cachedDistance <= distance;

    if ((!hasNetworkingNode(from) && !hasLeafNode(from))
        || (!hasNetworkingNode(to) && !hasLeafNode(to)))
      throw new IllegalArgumentException("Cannot test nodes not in network");

    return isInDistanceInternal(distance, from, to, nodePair);
  }

  private boolean isInDistanceInternal(
      int distance, NetworkNode from, NetworkNode to, TwoNetworkNodes cachePairKey) {
    if (from.equals(to)) return true;

    if (distance == 0) return false;

    if (SimpleNetwork.areNodesConnecting(from, to)) return true;

    // Breadth-first search of the network
    Set<NetworkNode> visitedNodes = Sets.newHashSet();
    visitedNodes.add(from);

    Set<NetworkNode> networkingNodesToTest = Sets.newHashSet();
    listConnectedNotVisitedNetworkingNodes(visitedNodes, from, networkingNodesToTest);
    int distanceSearched = 1;
    while (distanceSearched < distance) {
      distanceSearched++;

      for (NetworkNode nodeToTest : networkingNodesToTest) {
        if (SimpleNetwork.areNodesConnecting(nodeToTest, to)) {
          distanceCache.put(cachePairKey, distanceSearched);
          return true;
        }
        visitedNodes.add(nodeToTest);
      }

      Set<NetworkNode> nextNetworkingNodesToTest = Sets.newHashSet();
      for (NetworkNode nodeToTest : networkingNodesToTest)
        listConnectedNotVisitedNetworkingNodes(visitedNodes, nodeToTest, nextNetworkingNodesToTest);

      networkingNodesToTest = nextNetworkingNodesToTest;
    }

    return false;
  }

  @Override
  public boolean isInDistanceWithSide(int distance, NetworkNode from, NetworkNode to, Side toSide) {
    to = new NetworkNode(to.location.toVector3i(), toSide);
    TwoNetworkNodes nodePair = new TwoNetworkNodes(from, to);
    return isInDistanceInternal(distance, from, to, nodePair);
  }

  @Override
  public byte getLeafSidesInNetwork(NetworkNode networkNode) {
    if (!hasLeafNode(networkNode))
      throw new IllegalArgumentException("Cannot test nodes not in network");

    if (networkingNodes.size() == 0) {
      // Degenerated network
      for (Side connectingOnSide : SideBitFlag.getSides(networkNode.connectionSides)) {
        Vector3i possibleLocation = networkNode.location.toVector3i();
        possibleLocation.add(connectingOnSide.getVector3i());
        for (NetworkNode node : leafNodes.get(new ImmutableBlockLocation(possibleLocation))) {
          if (SideBitFlag.hasSide(node.connectionSides, connectingOnSide.reverse())) {
            return SideBitFlag.getSide(connectingOnSide);
          }
        }
      }

      return 0;
    } else {
      byte result = 0;
      for (Side connectingOnSide : SideBitFlag.getSides(networkNode.connectionSides)) {
        Vector3i possibleLocation = networkNode.location.toVector3i();
        possibleLocation.add(connectingOnSide.getVector3i());
        for (NetworkNode node : networkingNodes.get(new ImmutableBlockLocation(possibleLocation))) {
          if (SideBitFlag.hasSide(node.connectionSides, connectingOnSide.reverse())) {
            result += SideBitFlag.getSide(connectingOnSide);
          }
        }
      }

      return result;
    }
  }

  //    public Map<Vector3i, Byte> getConnectedNodes(Vector3i location, byte connectionSides) {
  //        Map<Vector3i, Byte> result = Maps.newHashMap();
  //        for (Direction connectingOnSide : DirectionsUtil.getDirections(connectionSides)) {
  //            final Vector3i possibleNodeLocation = new Vector3i(location);
  //            possibleNodeLocation.add(connectingOnSide.getVector3i());
  //
  //            final Byte directionsForNodeOnThatSide = networkingNodes.get(possibleNodeLocation);
  //            if (directionsForNodeOnThatSide != null &&
  // DirectionsUtil.hasDirection(directionsForNodeOnThatSide, connectingOnSide.reverse()))
  //                result.put(possibleNodeLocation, directionsForNodeOnThatSide);
  //
  //            for (byte directionsForLeafNodeOnThatSide : leafNodes.get(possibleNodeLocation)) {
  //                if (DirectionsUtil.hasDirection(directionsForLeafNodeOnThatSide,
  // connectingOnSide.reverse()))
  //                    result.put(possibleNodeLocation, directionsForLeafNodeOnThatSide);
  //            }
  //        }
  //        return result;
  //    }

  private void listConnectedNotVisitedNetworkingNodes(
      Set<NetworkNode> visitedNodes, NetworkNode location, Collection<NetworkNode> result) {
    for (Side connectingOnSide : SideBitFlag.getSides(location.connectionSides)) {
      final ImmutableBlockLocation possibleConnectionLocation =
          location.location.move(connectingOnSide);
      for (NetworkNode possibleConnection : networkingNodes.get(possibleConnectionLocation)) {
        if (!visitedNodes.contains(possibleConnection)
            && SideBitFlag.hasSide(possibleConnection.connectionSides, connectingOnSide.reverse()))
          result.add(possibleConnection);
      }
    }
  }

  private boolean isDegeneratedNetwork() {
    return networkingNodes.isEmpty() && leafNodes.size() == 1;
  }

  private boolean isEmptyNetwork() {
    return networkingNodes.isEmpty() && leafNodes.isEmpty();
  }
}
/**
 * My solution to an Amazon interview question - a map-reduce type process examines customer
 * purchases and classifies them based on product categories. For each category (so we don't
 * recommend computers when they are buying grocery!) we create a product associativity undirected
 * graph. The weight of the edge that connects 2 product vertices in the graph is proportional to
 * the number of times they appear together in customer purchases. Thus when a customer purchases a
 * product we find the associativity graph for the product category and then find the product vertex
 * and get all its adjacent vertices sorted by the edge weights we return the top N weighted
 * vertices as the most pertinent recommendations for the category of the purchased product. The
 * Question: 5_STAR
 *
 * <p>How will you design the backend of product recommendations (You may also like these carousal)
 * system on amazon.com
 *
 * <p>Created by haytham.aldokanji on 7/21/16.
 */
public class ProductRecommendationSystem {
  private static final Map<String, Product> productCache = new HashMap<>();
  private static final Map<String, Customer> customerCache = new HashMap<>();
  private static final SetMultimap<String, String> purchasesCache = HashMultimap.create();
  private static final Map<String, ProductAssociativityGraph> productAssociativityGraphMap =
      new HashMap<>();
  private final int maxNumRecommendations;

  public ProductRecommendationSystem(int maxNumRecommendations) {
    this.maxNumRecommendations = maxNumRecommendations;
  }

  public static void main(String[] args) {
    test();
  }

  // batch job (possibly real-time using streaming APIs)
  public static void createProductAssociativityGraphPerCategory() {
    Set<String> customers = purchasesCache.keySet();
    customers.forEach(
        customer -> {
          List<String> customerPurchases = Lists.newArrayList(purchasesCache.get(customer));
          for (int i = 0; i < customerPurchases.size(); i++) {
            for (int j = i + 1; j < customerPurchases.size(); j++) {
              Product product1 = productCache.get(customerPurchases.get(i));
              Product product2 = productCache.get(customerPurchases.get(j));

              if (product1.category.equals(product2.category)) {
                ProductAssociativityGraph graph =
                    productAssociativityGraphMap.getOrDefault(
                        product1.category, ProductAssociativityGraph.create());

                graph.addAssociation(Vertex.create(product1.id), Vertex.create(product2.id), 1);
                productAssociativityGraphMap.putIfAbsent(product1.category, graph);
              }
            }
          }
        });
  }

  private static void test() {
    String customerKey = "customer";
    String productKey = "product";

    IntStream.range(0, 5)
        .forEach(
            index ->
                customerCache.put(
                    customerKey + index, new Customer(customerKey + index, "name" + index)));
    IntStream.range(0, 100)
        .forEach(
            index ->
                productCache.put(
                    productKey + index, new Product(productKey + index, "category" + (index % 5))));

    Random random = new Random();

    Set<String> productIds = new HashSet<>();
    int activeProductSize = productCache.size() / 10;

    for (int i = 0; i < 1000; i++) {
      Customer customer = customerCache.get(customerKey + random.nextInt(customerCache.size()));

      int randomSuffix = random.nextInt(activeProductSize);
      if (randomSuffix < 3) {
        randomSuffix = random.nextInt(productCache.size());
      }
      Product product = productCache.get(productKey + randomSuffix);
      if (!productIds.contains(product.id)) {
        purchasesCache.put(customer.id, product.id);
        productIds.add(product.id);
      }
    }
    createProductAssociativityGraphPerCategory();

    ProductRecommendationSystem recommendationSystem = new ProductRecommendationSystem(7);
    for (int i = 0; i < activeProductSize * 2; i++) {
      Customer customer = customerCache.get(customerKey + random.nextInt(customerCache.size()));
      Product product = productCache.get(productKey + random.nextInt(activeProductSize));

      List<Product> recommendations = recommendationSystem.purchase(customer, product);
      System.out.printf("%s%n", recommendations);
    }
  }

  // purchase product and return relevant product recommendations
  public List<Product> purchase(Customer customer, Product product) {
    purchasesCache.put(customer.id, product.id);

    ProductAssociativityGraph graph = productAssociativityGraphMap.get(product.category);
    Vertex v = Vertex.create(product.id);
    List<Vertex> associations = graph.getProductAssociations(v);

    int recommendSize = Math.min(associations.size(), maxNumRecommendations);
    return associations
        .stream()
        .map(vertex -> productCache.get(vertex.productId))
        .limit(recommendSize)
        .collect(Collectors.toList());
  }

  private static class Product {
    private String id;
    private String category;

    public Product(String id, String category) {
      this.id = id;
      this.category = category;
    }

    @Override
    public String toString() {
      return id + "/" + category;
    }
  }

  private static class Customer {
    private String id;
    private String name;

    public Customer(String id, String name) {
      this.id = id;
      this.name = name;
    }
  }

  private static class ProductAssociativityGraph {
    private final Table<Vertex, Vertex, Edge> graph = HashBasedTable.create();

    public static ProductAssociativityGraph create() {
      return new ProductAssociativityGraph();
    }

    public void addAssociation(Vertex v1, Vertex v2, int edgeWeight) {
      if (containsAssociation(v1, v2)) {
        Edge edge = getAssociationWeight(v1, v2);
        edge.addWeight(edgeWeight);
      } else {
        graph.put(v1, v2, new Edge(edgeWeight));
      }
    }

    public Edge getAssociationWeight(Vertex v1, Vertex v2) {
      Edge edge = graph.get(v1, v2);
      if (edge == null) {
        edge = graph.get(v2, v1);
      }
      return edge;
    }

    public List<Vertex> getProductAssociations(Vertex v) {
      Map<Vertex, Edge> adjacent = Maps.newHashMap(graph.row(v));
      adjacent.putAll(graph.column(v));

      return adjacent
          .entrySet()
          .stream()
          .sorted((e1, e2) -> e2.getValue().weight - e1.getValue().weight)
          .map(Map.Entry::getKey)
          .collect(Collectors.toList());
    }

    public boolean containsAssociation(Vertex v1, Vertex v2) {
      return graph.contains(v1, v2) || graph.contains(v2, v1);
    }

    public void clear() {
      graph.clear();
    }

    @Override
    public String toString() {
      return "ProductAssociativityGraph{" + "graph=" + graph + '}';
    }
  }

  private static class Edge {
    private int weight;

    private Edge(int weight) {
      this.weight = weight;
    }

    public static Edge create(int weight) {
      return new Edge(weight);
    }

    public void addWeight(int amount) {
      this.weight += amount;
    }

    @Override
    public String toString() {
      return String.valueOf(weight);
    }
  }

  private static class Vertex {
    private final String productId;

    public Vertex(String productId) {
      this.productId = productId;
    }

    public static Vertex create(String productId) {
      return new Vertex(productId);
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      Vertex vertex = (Vertex) o;

      return productId.equals(vertex.productId);
    }

    @Override
    public int hashCode() {
      return productId.hashCode();
    }

    @Override
    public String toString() {
      return String.valueOf(productId);
    }
  }
}
Example #14
0
/**
 * This class keeps information on the current locally installed SDK. It tries to lazily load
 * information as much as possible.
 *
 * <p>Packages are accessed by their type and a main query attribute, depending on the package type.
 * There are different versions of {@link #getPkgInfo} which depend on the query attribute.
 *
 * <table border='1' cellpadding='3'>
 * <tr>
 * <th>Type</th>
 * <th>Query parameter</th>
 * <th>Getter</th>
 * </tr>
 *
 * <tr>
 * <td>Tools</td>
 * <td>Unique instance</td>
 * <td>{@code getPkgInfo(PkgType.PKG_TOOLS)} => {@link LocalPkgInfo}</td>
 * </tr>
 *
 * <tr>
 * <td>Platform-Tools</td>
 * <td>Unique instance</td>
 * <td>{@code getPkgInfo(PkgType.PKG_PLATFORM_TOOLS)} => {@link LocalPkgInfo}</td>
 * </tr>
 *
 * <tr>
 * <td>Docs</td>
 * <td>Unique instance</td>
 * <td>{@code getPkgInfo(PkgType.PKG_DOCS)} => {@link LocalPkgInfo}</td>
 * </tr>
 *
 * <tr>
 * <td>Build-Tools</td>
 * <td>{@link FullRevision}</td>
 * <td>{@code getLatestBuildTool()} => {@link BuildToolInfo}, <br/>
 *     or {@code getBuildTool(FullRevision)} => {@link BuildToolInfo}, <br/>
 *     or {@code getPkgInfo(PkgType.PKG_BUILD_TOOLS, FullRevision)} => {@link LocalPkgInfo}, <br/>
 *     or {@code getPkgsInfos(PkgType.PKG_BUILD_TOOLS)} => {@link LocalPkgInfo}[]</td>
 * </tr>
 *
 * <tr>
 * <td>Extras</td>
 * <td>String vendor/path</td>
 * <td>{@code getExtra(String)} => {@link LocalExtraPkgInfo}, <br/>
 *     or {@code getPkgInfo(PkgType.PKG_EXTRAS, String)} => {@link LocalPkgInfo}, <br/>
 *     or {@code getPkgsInfos(PkgType.PKG_EXTRAS)} => {@link LocalPkgInfo}[]</td>
 * </tr>
 *
 * <tr>
 * <td>Sources</td>
 * <td>{@link AndroidVersion}</td>
 * <td>{@code getPkgInfo(PkgType.PKG_SOURCES, AndroidVersion)} => {@link LocalPkgInfo}, <br/>
 *     or {@code getPkgsInfos(PkgType.PKG_SOURCES)} => {@link LocalPkgInfo}[]</td>
 * </tr>
 *
 * <tr>
 * <td>Samples</td>
 * <td>{@link AndroidVersion}</td>
 * <td>{@code getPkgInfo(PkgType.PKG_SAMPLES, AndroidVersion)} => {@link LocalPkgInfo}, <br/>
 *     or {@code getPkgsInfos(PkgType.PKG_SAMPLES)} => {@link LocalPkgInfo}[]</td>
 * </tr>
 *
 * <tr>
 * <td>Platforms</td>
 * <td>{@link AndroidVersion}</td>
 * <td>{@code getPkgInfo(PkgType.PKG_PLATFORMS, AndroidVersion)} => {@link LocalPkgInfo}, <br/>
 *     or {@code getPkgInfo(PkgType.PKG_ADDONS, String)} => {@link LocalPkgInfo}, <br/>
 *     or {@code getPkgsInfos(PkgType.PKG_PLATFORMS)} => {@link LocalPkgInfo}[], <br/>
 *     or {@code getTargetFromHashString(String)} => {@link IAndroidTarget}</td>
 * </tr>
 *
 * <tr>
 * <td>Add-ons</td>
 * <td>{@link AndroidVersion} x String vendor/path</td>
 * <td>{@code getPkgInfo(PkgType.PKG_ADDONS, String)} => {@link LocalPkgInfo}, <br/>
 *     or {@code getPkgsInfos(PkgType.PKG_ADDONS)}    => {@link LocalPkgInfo}[], <br/>
 *     or {@code getTargetFromHashString(String)} => {@link IAndroidTarget}</td>
 * </tr>
 *
 * <tr>
 * <td>System images</td>
 * <td>{@link AndroidVersion} x {@link String} ABI</td>
 * <td>{@code getPkgsInfos(PkgType.PKG_SYS_IMAGES)} => {@link LocalPkgInfo}[]</td>
 * </tr>
 *
 * </table>
 *
 * Apps/libraries that use it are encouraged to keep an existing instance around (using a singleton
 * or similar mechanism).
 *
 * <p>Threading: All accessor methods are synchronized on the same internal lock so it's safe to
 * call them from any thread, even concurrently. <br>
 * A method like {@code getPkgsInfos} returns a copy of its data array, which objects are not
 * altered after creation, so its value is not influenced by the internal state after it returns.
 *
 * <p>Implementation Background:
 *
 * <ul>
 *   <li>The sdk manager has a set of "Package" classes that cover both local and remote SDK
 *       operations.
 *   <li>Goal was to split it in 2 cleanly separated parts: {@link LocalSdk} parses sdk on disk, and
 *       a separate class wraps the downloaded manifest (this is now handled within Studio only)
 *   <li>The local SDK should be a singleton accessible somewhere, so there will be one in ADT (via
 *       the Sdk instance), one in Studio, and one in the command line tool. <br>
 *       Right now there's a bit of mess with some classes creating a temp LocalSdkParser, some
 *       others using an SdkManager instance, and that needs to be sorted out.
 *   <li>As a transition, the SdkManager instance wraps a LocalSdk and uses this. Eventually the
 *       SdkManager.java class will go away (its name is totally misleading, for starters.)
 *   <li>The current LocalSdkParser stays as-is for compatibility purposes and the goal is also to
 *       totally remove it when the SdkManager class goes away.
 * </ul>
 *
 * @version 2 of the {@code SdkManager} class, essentially.
 */
public class LocalSdk {

  /** Location of the SDK. Maybe null. Can be changed. */
  private File mSdkRoot;
  /** File operation object. (Used for overriding in mock testing.) */
  private final IFileOp mFileOp;
  /** List of package information loaded so far. Lazily populated. */
  @GuardedBy(value = "mLocalPackages")
  private final Multimap<PkgType, LocalPkgInfo> mLocalPackages = TreeMultimap.create();
  /** Directories already parsed into {@link #mLocalPackages}. */
  @GuardedBy(value = "mLocalPackages")
  private final Multimap<PkgType, LocalDirInfo> mVisitedDirs = HashMultimap.create();
  /** A legacy build-tool for older platform-tools < 17. */
  private BuildToolInfo mLegacyBuildTools;
  /** Cache of targets from local sdk. See {@link #getTargets()}. */
  @GuardedBy(value = "mLocalPackages")
  private List<IAndroidTarget> mCachedTargets = null;

  private Set<MissingTarget> mCachedMissingTargets = null;

  /** Creates an initial LocalSdk instance with an unknown location. */
  public LocalSdk() {
    mFileOp = new FileOp();
  }

  /**
   * Creates an initial LocalSdk instance for a known SDK location.
   *
   * @param sdkRoot The location of the SDK root folder.
   */
  public LocalSdk(@NonNull File sdkRoot) {
    this();
    setLocation(sdkRoot);
  }

  /**
   * Creates an initial LocalSdk instance with an unknown location. This is designed for unit tests
   * to override the {@link FileOp} being used.
   *
   * @param fileOp The alternate {@link FileOp} to use for all file-based interactions.
   */
  @VisibleForTesting(visibility = Visibility.PRIVATE)
  public LocalSdk(@NonNull IFileOp fileOp) {
    mFileOp = fileOp;
  }

  /*
   * Returns the current IFileOp being used.
   */
  @NonNull
  public IFileOp getFileOp() {
    return mFileOp;
  }

  /**
   * Sets or changes the SDK root location. This also clears any cached information.
   *
   * @param sdkRoot The location of the SDK root folder.
   */
  public void setLocation(@NonNull File sdkRoot) {
    assert sdkRoot != null;
    mSdkRoot = sdkRoot;
    clearLocalPkg(PkgType.PKG_ALL);
  }

  /**
   * Location of the SDK. Maybe null. Can be changed.
   *
   * @return The location of the SDK. Null if not initialized yet.
   */
  @Nullable
  public File getLocation() {
    return mSdkRoot;
  }

  /**
   * Location of the SDK. Maybe null. Can be changed. The getLocation() API replaces this function.
   *
   * @return The location of the SDK. Null if not initialized yet.
   */
  @Deprecated
  @Nullable
  public String getPath() {
    return mSdkRoot != null ? mSdkRoot.getPath() : null;
  }

  /**
   * Clear the tracked visited folders & the cached {@link LocalPkgInfo} for the given filter types.
   *
   * @param filters A set of PkgType constants or {@link PkgType#PKG_ALL} to clear everything.
   */
  public void clearLocalPkg(@NonNull EnumSet<PkgType> filters) {
    mLegacyBuildTools = null;

    synchronized (mLocalPackages) {
      for (PkgType filter : filters) {
        mVisitedDirs.removeAll(filter);
        mLocalPackages.removeAll(filter);
      }

      // Clear the targets if the platforms or addons are being cleared
      if (filters.contains(PkgType.PKG_PLATFORM) || filters.contains(PkgType.PKG_ADDON)) {
        mCachedMissingTargets = null;
        mCachedTargets = null;
      }
    }
  }

  /**
   * Check the tracked visited folders to see if anything has changed for the requested filter
   * types. This does not refresh or reload any package information.
   *
   * @param filters A set of PkgType constants or {@link PkgType#PKG_ALL} to clear everything.
   */
  public boolean hasChanged(@NonNull EnumSet<PkgType> filters) {
    for (PkgType filter : filters) {
      Collection<LocalDirInfo> dirInfos;
      synchronized (mLocalPackages) {
        dirInfos = mVisitedDirs.get(filter);
        for (LocalDirInfo dirInfo : dirInfos) {
          if (dirInfo.hasChanged()) {
            return true;
          }
        }
      }
    }

    return false;
  }

  // --------- Generic querying ---------

  /**
   * Retrieves information on a package identified by an {@link IPkgDesc}.
   *
   * @param descriptor {@link IPkgDesc} describing a package.
   * @return The first package found with the same descriptor or null.
   */
  @Nullable
  public LocalPkgInfo getPkgInfo(@NonNull IPkgDesc descriptor) {

    for (LocalPkgInfo pkg : getPkgsInfos(EnumSet.of(descriptor.getType()))) {
      IPkgDesc d = pkg.getDesc();
      if (d.equals(descriptor)) {
        return pkg;
      }
    }

    return null;
  }

  /**
   * Retrieves information on a package identified by an {@link AndroidVersion}.
   *
   * <p>Note: don't use this for {@link PkgType#PKG_SYS_IMAGE} since there can be more than one ABI
   * and this method only returns a single package per filter type.
   *
   * @param filter {@link PkgType#PKG_PLATFORM}, {@link PkgType#PKG_SAMPLE} or {@link
   *     PkgType#PKG_SOURCE}.
   * @param version The {@link AndroidVersion} specific for this package type.
   * @return An existing package information or null if not found.
   */
  @Nullable
  public LocalPkgInfo getPkgInfo(@NonNull PkgType filter, @NonNull AndroidVersion version) {
    assert filter == PkgType.PKG_PLATFORM
        || filter == PkgType.PKG_SAMPLE
        || filter == PkgType.PKG_SOURCE;

    for (LocalPkgInfo pkg : getPkgsInfos(filter)) {
      IPkgDesc d = pkg.getDesc();
      if (d.hasAndroidVersion() && d.getAndroidVersion().equals(version)) {
        return pkg;
      }
    }

    return null;
  }

  /**
   * Retrieves information on a package identified by its {@link FullRevision}.
   *
   * <p>Note that {@link PkgType#PKG_TOOLS} and {@link PkgType#PKG_PLATFORM_TOOLS} are unique in a
   * local SDK so you'll want to use {@link #getPkgInfo(PkgType)} to retrieve them instead.
   *
   * @param filter {@link PkgType#PKG_BUILD_TOOLS}.
   * @param revision The {@link FullRevision} uniquely identifying this package.
   * @return An existing package information or null if not found.
   */
  @Nullable
  public LocalPkgInfo getPkgInfo(@NonNull PkgType filter, @NonNull FullRevision revision) {

    assert filter == PkgType.PKG_BUILD_TOOLS;

    for (LocalPkgInfo pkg : getPkgsInfos(filter)) {
      IPkgDesc d = pkg.getDesc();
      if (d.hasFullRevision() && d.getFullRevision().equals(revision)) {
        return pkg;
      }
    }
    return null;
  }

  /**
   * Retrieves information on a package identified by its {@link String} path.
   *
   * <p>For add-ons and platforms, the path is the target hash string (see {@link AndroidTargetHash}
   * for helpers methods to generate this string.)
   *
   * @param filter {@link PkgType#PKG_ADDON}, {@link PkgType#PKG_PLATFORM}.
   * @param path The vendor/path uniquely identifying this package.
   * @return An existing package information or null if not found.
   */
  @Nullable
  public LocalPkgInfo getPkgInfo(@NonNull PkgType filter, @NonNull String path) {

    assert filter == PkgType.PKG_ADDON || filter == PkgType.PKG_PLATFORM;

    for (LocalPkgInfo pkg : getPkgsInfos(filter)) {
      IPkgDesc d = pkg.getDesc();
      if (d.hasPath() && path.equals(d.getPath())) {
        return pkg;
      }
    }
    return null;
  }

  /**
   * Retrieves information on a package identified by both vendor and path strings.
   *
   * <p>For add-ons the path is target hash string (see {@link AndroidTargetHash} for helpers
   * methods to generate this string.)
   *
   * @param filter {@link PkgType#PKG_EXTRA}, {@link PkgType#PKG_ADDON}.
   * @param vendor The vendor id of the extra package.
   * @param path The path uniquely identifying this package for its vendor.
   * @return An existing package information or null if not found.
   */
  @Nullable
  public LocalPkgInfo getPkgInfo(
      @NonNull PkgType filter, @NonNull String vendor, @NonNull String path) {

    assert filter == PkgType.PKG_EXTRA || filter == PkgType.PKG_ADDON;

    for (LocalPkgInfo pkg : getPkgsInfos(filter)) {
      IPkgDesc d = pkg.getDesc();
      if (d.hasVendor() && vendor.equals(d.getVendor().getId())) {
        if (d.hasPath() && path.equals(d.getPath())) {
          return pkg;
        }
      }
    }
    return null;
  }

  /**
   * Retrieves information on an extra package identified by its {@link String} vendor/path.
   *
   * @param vendor The vendor id of the extra package.
   * @param path The path uniquely identifying this package for its vendor.
   * @return An existing extra package information or null if not found.
   */
  @Nullable
  public LocalExtraPkgInfo getExtra(@NonNull String vendor, @NonNull String path) {
    return (LocalExtraPkgInfo) getPkgInfo(PkgType.PKG_EXTRA, vendor, path);
  }

  /**
   * For unique local packages. Returns the cached LocalPkgInfo for the requested type. Loads it
   * from disk if not cached.
   *
   * @param filter {@link PkgType#PKG_TOOLS} or {@link PkgType#PKG_PLATFORM_TOOLS} or {@link
   *     PkgType#PKG_DOC}.
   * @return null if the package is not installed.
   */
  @Nullable
  public LocalPkgInfo getPkgInfo(@NonNull PkgType filter) {
    if (filter != PkgType.PKG_TOOLS
        && filter != PkgType.PKG_PLATFORM_TOOLS
        && filter != PkgType.PKG_DOC
        && filter != PkgType.PKG_NDK) {
      assert false;
      return null;
    }

    LocalPkgInfo info = null;
    synchronized (mLocalPackages) {
      Collection<LocalPkgInfo> existing = mLocalPackages.get(filter);
      assert existing.size() <= 1;
      if (!existing.isEmpty()) {
        return existing.iterator().next();
      }

      File uniqueDir = new File(mSdkRoot, filter.getFolderName());

      if (!mVisitedDirs.containsEntry(filter, new LocalDirInfo.MapComparator(uniqueDir))) {
        switch (filter) {
          case PKG_TOOLS:
            info = scanTools(uniqueDir);
            break;
          case PKG_PLATFORM_TOOLS:
            info = scanPlatformTools(uniqueDir);
            break;
          case PKG_DOC:
            info = scanDoc(uniqueDir);
            break;
          case PKG_NDK:
            info = scanNdk(uniqueDir);
          default:
            break;
        }
      }

      // Whether we have found a valid pkg or not, this directory has been visited.
      mVisitedDirs.put(filter, new LocalDirInfo(mFileOp, uniqueDir));

      if (info != null) {
        mLocalPackages.put(filter, info);
      }
    }

    return info;
  }

  /**
   * Retrieve all the info about the requested package type. This is used for the package types that
   * have one or more instances, each with different versions. The resulting array is sorted
   * according to the PkgInfo's sort order.
   *
   * <p>Note: you can use this with {@link PkgType#PKG_TOOLS}, {@link PkgType#PKG_PLATFORM_TOOLS}
   * and {@link PkgType#PKG_DOC} but since there can only be one package of these types, it is more
   * efficient to use {@link #getPkgInfo(PkgType)} to query them.
   *
   * @param filter One of {@link PkgType} constants.
   * @return A list (possibly empty) of matching installed packages. Never returns null.
   */
  @NonNull
  public LocalPkgInfo[] getPkgsInfos(@NonNull PkgType filter) {
    return getPkgsInfos(EnumSet.of(filter));
  }

  /**
   * Retrieve all the info about the requested package types. This is used for the package types
   * that have one or more instances, each with different versions. The resulting array is sorted
   * according to the PkgInfo's sort order.
   *
   * <p>To force the LocalSdk parser to load <b>everything</b>, simply call this method with the
   * {@link PkgType#PKG_ALL} argument to load all the known package types.
   *
   * <p>Note: you can use this with {@link PkgType#PKG_TOOLS}, {@link PkgType#PKG_PLATFORM_TOOLS}
   * and {@link PkgType#PKG_DOC} but since there can only be one package of these types, it is more
   * efficient to use {@link #getPkgInfo(PkgType)} to query them.
   *
   * @param filters One or more of {@link PkgType#PKG_ADDON}, {@link PkgType#PKG_PLATFORM}, {@link
   *     PkgType#PKG_BUILD_TOOLS}, {@link PkgType#PKG_EXTRA}, {@link PkgType#PKG_SOURCE}, {@link
   *     PkgType#PKG_SYS_IMAGE}
   * @return A list (possibly empty) of matching installed packages. Never returns null.
   */
  @NonNull
  public LocalPkgInfo[] getPkgsInfos(@NonNull EnumSet<PkgType> filters) {
    List<LocalPkgInfo> list = Lists.newArrayList();

    for (PkgType filter : filters) {
      if (filter == PkgType.PKG_TOOLS
          || filter == PkgType.PKG_PLATFORM_TOOLS
          || filter == PkgType.PKG_DOC
          || filter == PkgType.PKG_NDK) {
        LocalPkgInfo info = getPkgInfo(filter);
        if (info != null) {
          list.add(info);
        }
      } else {
        synchronized (mLocalPackages) {
          Collection<LocalPkgInfo> existing = mLocalPackages.get(filter);
          assert existing != null; // Multimap returns an empty set if not found

          if (!existing.isEmpty()) {
            list.addAll(existing);
            continue;
          }

          File subDir = new File(mSdkRoot, filter.getFolderName());

          if (!mVisitedDirs.containsEntry(filter, new LocalDirInfo.MapComparator(subDir))) {
            switch (filter) {
              case PKG_BUILD_TOOLS:
                scanBuildTools(subDir, existing);
                break;

              case PKG_PLATFORM:
                scanPlatforms(subDir, existing);
                break;

              case PKG_SYS_IMAGE:
                scanSysImages(subDir, existing, false);
                break;

              case PKG_ADDON_SYS_IMAGE:
                scanSysImages(subDir, existing, true);
                break;

              case PKG_ADDON:
                scanAddons(subDir, existing);
                break;

              case PKG_SAMPLE:
                scanSamples(subDir, existing);
                break;

              case PKG_SOURCE:
                scanSources(subDir, existing);
                break;

              case PKG_EXTRA:
                scanExtras(subDir, existing);
                break;

              case PKG_TOOLS:
              case PKG_PLATFORM_TOOLS:
              case PKG_DOC:
              case PKG_NDK:
                break;
              default:
                throw new IllegalArgumentException("Unsupported pkg type " + filter.toString());
            }
            mVisitedDirs.put(filter, new LocalDirInfo(mFileOp, subDir));
            list.addAll(existing);
          }
        }
      }
    }

    Collections.sort(list);
    return list.toArray(new LocalPkgInfo[list.size()]);
  }

  // ---------- Package-specific querying --------

  /**
   * Returns the {@link BuildToolInfo} for the given revision.
   *
   * @param revision The requested revision.
   * @return A {@link BuildToolInfo}. Can be null if {@code revision} is null or is not part of the
   *     known set returned by {@code getPkgsInfos(PkgType.PKG_BUILD_TOOLS)}.
   */
  @Nullable
  public BuildToolInfo getBuildTool(@Nullable FullRevision revision) {
    LocalPkgInfo pkg = getPkgInfo(PkgType.PKG_BUILD_TOOLS, revision);
    if (pkg instanceof LocalBuildToolPkgInfo) {
      return ((LocalBuildToolPkgInfo) pkg).getBuildToolInfo();
    }
    return null;
  }

  /**
   * Returns the highest build-tool revision known, or null if there are are no build-tools.
   *
   * <p>If no specific build-tool package is installed but the platform-tools is lower than 17, then
   * this creates and returns a "legacy" built-tool package using platform-tools. (We only split
   * build-tools out of platform-tools starting with revision 17, before they were both the same
   * thing.)
   *
   * @return The highest build-tool revision known, or null.
   */
  @Nullable
  public BuildToolInfo getLatestBuildTool() {
    if (mLegacyBuildTools != null) {
      return mLegacyBuildTools;
    }

    LocalPkgInfo[] pkgs = getPkgsInfos(PkgType.PKG_BUILD_TOOLS);

    if (pkgs.length == 0) {
      LocalPkgInfo ptPkg = getPkgInfo(PkgType.PKG_PLATFORM_TOOLS);
      if (ptPkg instanceof LocalPlatformToolPkgInfo
          && ptPkg.getDesc().getFullRevision().compareTo(new FullRevision(17)) < 0) {
        // older SDK, create a compatible build-tools
        mLegacyBuildTools = createLegacyBuildTools((LocalPlatformToolPkgInfo) ptPkg);
        return mLegacyBuildTools;
      }
      return null;
    }

    assert pkgs.length > 0;

    // Note: the pkgs come from a TreeMultimap so they should already be sorted.
    // Just in case, sort them again.
    Arrays.sort(pkgs);

    // LocalBuildToolPkgInfo's comparator sorts on its FullRevision so we just
    // need to take the latest element.
    LocalPkgInfo pkg = pkgs[pkgs.length - 1];
    if (pkg instanceof LocalBuildToolPkgInfo) {
      return ((LocalBuildToolPkgInfo) pkg).getBuildToolInfo();
    }

    return null;
  }

  @NonNull
  private BuildToolInfo createLegacyBuildTools(@NonNull LocalPlatformToolPkgInfo ptInfo) {
    File platformTools = new File(getLocation(), SdkConstants.FD_PLATFORM_TOOLS);
    File platformToolsLib = ptInfo.getLocalDir();
    File platformToolsRs = new File(platformTools, SdkConstants.FN_FRAMEWORK_RENDERSCRIPT);

    return new BuildToolInfo(
        ptInfo.getDesc().getFullRevision(),
        platformTools,
        new File(platformTools, SdkConstants.FN_AAPT),
        new File(platformTools, SdkConstants.FN_AIDL),
        new File(platformTools, SdkConstants.FN_DX),
        new File(platformToolsLib, SdkConstants.FN_DX_JAR),
        new File(platformTools, SdkConstants.FN_RENDERSCRIPT),
        new File(platformToolsRs, SdkConstants.FN_FRAMEWORK_INCLUDE),
        new File(platformToolsRs, SdkConstants.FN_FRAMEWORK_INCLUDE_CLANG),
        null,
        null,
        null,
        null,
        new File(platformTools, SdkConstants.FN_ZIPALIGN));
  }

  /**
   * Returns the targets (platforms & addons) that are available in the SDK. The target list is
   * created on demand the first time then cached. It will not refreshed unless {@link
   * #clearLocalPkg} is called to clear platforms and/or add-ons.
   *
   * <p>The array can be empty but not null.
   */
  @NonNull
  public IAndroidTarget[] getTargets() {
    synchronized (mLocalPackages) {
      if (mCachedTargets == null) {
        List<IAndroidTarget> result = Lists.newArrayList();
        LocalPkgInfo[] pkgsInfos =
            getPkgsInfos(EnumSet.of(PkgType.PKG_PLATFORM, PkgType.PKG_ADDON));
        for (LocalPkgInfo info : pkgsInfos) {
          assert info instanceof LocalPlatformPkgInfo;
          IAndroidTarget target = ((LocalPlatformPkgInfo) info).getAndroidTarget();
          if (target != null) {
            result.add(target);
          }
        }
        mCachedTargets = result;
      }
      return mCachedTargets.toArray(new IAndroidTarget[mCachedTargets.size()]);
    }
  }

  public IAndroidTarget[] getTargets(boolean includeMissing) {
    IAndroidTarget[] result = getTargets();
    if (includeMissing) {
      result = ObjectArrays.concat(result, getMissingTargets(), IAndroidTarget.class);
    }
    return result;
  }

  @NonNull
  public IAndroidTarget[] getMissingTargets() {
    synchronized (mLocalPackages) {
      if (mCachedMissingTargets == null) {
        Map<MissingTarget, MissingTarget> result = Maps.newHashMap();
        Set<ISystemImage> seen = Sets.newHashSet();
        for (IAndroidTarget target : getTargets()) {
          Collections.addAll(seen, target.getSystemImages());
        }
        for (LocalPkgInfo local : getPkgsInfos(PkgType.PKG_ADDON_SYS_IMAGE)) {
          LocalAddonSysImgPkgInfo info = (LocalAddonSysImgPkgInfo) local;
          ISystemImage image = info.getSystemImage();
          if (!seen.contains(image)) {
            addOrphanedSystemImage(image, info.getDesc(), result);
          }
        }
        for (LocalPkgInfo local : getPkgsInfos(PkgType.PKG_SYS_IMAGE)) {
          LocalSysImgPkgInfo info = (LocalSysImgPkgInfo) local;
          ISystemImage image = info.getSystemImage();
          if (!seen.contains(image)) {
            addOrphanedSystemImage(image, info.getDesc(), result);
          }
        }
        mCachedMissingTargets = result.keySet();
      }
      return mCachedMissingTargets.toArray(new IAndroidTarget[mCachedMissingTargets.size()]);
    }
  }

  private static void addOrphanedSystemImage(
      ISystemImage image, IPkgDesc desc, Map<MissingTarget, MissingTarget> targets) {
    IdDisplay vendor = desc.getVendor();
    MissingTarget target =
        new MissingTarget(
            vendor == null ? null : vendor.getDisplay(),
            desc.getTag().getDisplay(),
            desc.getAndroidVersion());
    MissingTarget existing = targets.get(target);
    if (existing == null) {
      existing = target;
      targets.put(target, target);
    }
    existing.addSystemImage(image);
  }

  /**
   * Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}.
   *
   * @param hash the {@link IAndroidTarget} hash string.
   * @return The matching {@link IAndroidTarget} or null.
   */
  @Nullable
  public IAndroidTarget getTargetFromHashString(@Nullable String hash) {
    if (hash != null) {
      IAndroidTarget[] targets = getTargets(true);
      for (IAndroidTarget target : targets) {
        if (target != null && hash.equals(AndroidTargetHash.getTargetHashString(target))) {
          return target;
        }
      }
    }
    return null;
  }

  // -------------

  /** Try to find a tools package at the given location. Returns null if not found. */
  private LocalToolPkgInfo scanTools(File toolFolder) {
    // Can we find some properties?
    Properties props = parseProperties(new File(toolFolder, SdkConstants.FN_SOURCE_PROP));
    FullRevision rev = PackageParserUtils.getPropertyFull(props, PkgProps.PKG_REVISION);
    if (rev == null) {
      return null;
    }

    FullRevision minPlatToolsRev =
        PackageParserUtils.getPropertyFull(props, PkgProps.MIN_PLATFORM_TOOLS_REV);
    if (minPlatToolsRev == null) {
      minPlatToolsRev = FullRevision.NOT_SPECIFIED;
    }

    LocalToolPkgInfo info = new LocalToolPkgInfo(this, toolFolder, props, rev, minPlatToolsRev);

    // We're not going to check that all tools are present. At the very least
    // we should expect to find android and an emulator adapted to the current OS.
    boolean hasEmulator = false;
    boolean hasAndroid = false;
    String android1 = SdkConstants.androidCmdName().replace(".bat", ".exe");
    String android2 = android1.indexOf('.') == -1 ? null : android1.replace(".exe", ".bat");
    File[] files = mFileOp.listFiles(toolFolder);
    for (File file : files) {
      String name = file.getName();
      if (SdkConstants.FN_EMULATOR.equals(name)) {
        hasEmulator = true;
      }
      if (android1.equals(name) || (android2 != null && android2.equals(name))) {
        hasAndroid = true;
      }
    }
    if (!hasAndroid) {
      info.appendLoadError("Missing %1$s", SdkConstants.androidCmdName());
    }
    if (!hasEmulator) {
      info.appendLoadError("Missing %1$s", SdkConstants.FN_EMULATOR);
    }

    return info;
  }

  /** Try to find a platform-tools package at the given location. Returns null if not found. */
  private LocalPlatformToolPkgInfo scanPlatformTools(File ptFolder) {
    // Can we find some properties?
    Properties props = parseProperties(new File(ptFolder, SdkConstants.FN_SOURCE_PROP));
    FullRevision rev = PackageParserUtils.getPropertyFull(props, PkgProps.PKG_REVISION);
    if (rev == null) {
      return null;
    }

    LocalPlatformToolPkgInfo info = new LocalPlatformToolPkgInfo(this, ptFolder, props, rev);
    return info;
  }

  /** Try to find a docs package at the given location. Returns null if not found. */
  private LocalDocPkgInfo scanDoc(File docFolder) {
    // Can we find some properties?
    Properties props = parseProperties(new File(docFolder, SdkConstants.FN_SOURCE_PROP));
    MajorRevision rev = PackageParserUtils.getPropertyMajor(props, PkgProps.PKG_REVISION);
    if (rev == null) {
      return null;
    }

    try {
      AndroidVersion vers = new AndroidVersion(props);
      LocalDocPkgInfo info = new LocalDocPkgInfo(this, docFolder, props, vers, rev);

      // To start with, a doc folder should have an "index.html" to be acceptable.
      // We don't actually check the content of the file.
      if (!mFileOp.isFile(new File(docFolder, "index.html"))) {
        info.appendLoadError("Missing index.html");
      }
      return info;

    } catch (AndroidVersionException e) {
      return null; // skip invalid or missing android version.
    }
  }

  /** Try to find an NDK package at the given location. Returns null if not found. */
  @Nullable
  private LocalNdkPkgInfo scanNdk(@NonNull File ndkFolder) {
    // Can we find some properties?
    Properties props = parseProperties(new File(ndkFolder, SdkConstants.FN_SOURCE_PROP));
    FullRevision rev = PackageParserUtils.getPropertyFull(props, PkgProps.PKG_REVISION);
    if (rev == null) {
      return null;
    }

    return new LocalNdkPkgInfo(this, ndkFolder, props, rev);
  }

  /**
   * Helper used by scanXyz methods below to check whether a directory should be visited. It can be
   * skipped if it's not a directory or if it's already marked as visited in mVisitedDirs for the
   * given package type -- in which case the directory is added to the visited map.
   *
   * @param pkgType The package type being scanned.
   * @param directory The file or directory to check.
   * @return False if directory can/should be skipped. True if directory should be visited, in which
   *     case it's registered in mVisitedDirs.
   */
  private boolean shouldVisitDir(@NonNull PkgType pkgType, @NonNull File directory) {
    if (!mFileOp.isDirectory(directory)) {
      return false;
    }
    synchronized (mLocalPackages) {
      if (mVisitedDirs.containsEntry(pkgType, new LocalDirInfo.MapComparator(directory))) {
        return false;
      }
      mVisitedDirs.put(pkgType, new LocalDirInfo(mFileOp, directory));
    }
    return true;
  }

  private void scanBuildTools(File collectionDir, Collection<LocalPkgInfo> outCollection) {
    // The build-tool root folder contains a list of per-revision folders.
    for (File buildToolDir : mFileOp.listFiles(collectionDir)) {
      if (!shouldVisitDir(PkgType.PKG_BUILD_TOOLS, buildToolDir)) {
        continue;
      }

      Properties props = parseProperties(new File(buildToolDir, SdkConstants.FN_SOURCE_PROP));
      FullRevision rev = PackageParserUtils.getPropertyFull(props, PkgProps.PKG_REVISION);
      if (rev == null) {
        continue; // skip, no revision
      }

      BuildToolInfo btInfo = new BuildToolInfo(rev, buildToolDir);
      LocalBuildToolPkgInfo pkgInfo =
          new LocalBuildToolPkgInfo(this, buildToolDir, props, rev, btInfo);
      outCollection.add(pkgInfo);
    }
  }

  private void scanPlatforms(File collectionDir, Collection<LocalPkgInfo> outCollection) {
    for (File platformDir : mFileOp.listFiles(collectionDir)) {
      if (!shouldVisitDir(PkgType.PKG_PLATFORM, platformDir)) {
        continue;
      }

      Properties props = parseProperties(new File(platformDir, SdkConstants.FN_SOURCE_PROP));
      MajorRevision rev = PackageParserUtils.getPropertyMajor(props, PkgProps.PKG_REVISION);
      if (rev == null) {
        continue; // skip, no revision
      }

      FullRevision minToolsRev = PackageParserUtils.getPropertyFull(props, PkgProps.MIN_TOOLS_REV);
      if (minToolsRev == null) {
        minToolsRev = FullRevision.NOT_SPECIFIED;
      }

      try {
        AndroidVersion vers = new AndroidVersion(props);

        LocalPlatformPkgInfo pkgInfo =
            new LocalPlatformPkgInfo(this, platformDir, props, vers, rev, minToolsRev);
        outCollection.add(pkgInfo);

      } catch (AndroidVersionException e) {
        continue; // skip invalid or missing android version.
      }
    }
  }

  private void scanAddons(File collectionDir, Collection<LocalPkgInfo> outCollection) {
    for (File addonDir : mFileOp.listFiles(collectionDir)) {
      if (!shouldVisitDir(PkgType.PKG_ADDON, addonDir)) {
        continue;
      }

      Properties props = parseProperties(new File(addonDir, SdkConstants.FN_SOURCE_PROP));
      MajorRevision rev = PackageParserUtils.getPropertyMajor(props, PkgProps.PKG_REVISION);
      if (rev == null) {
        continue; // skip, no revision
      }

      try {
        AndroidVersion vers = new AndroidVersion(props);

        // Starting with addon-4.xsd, we have vendor-id and name-id available
        // in the add-on source properties so we'll use that directly.

        String nameId = props.getProperty(PkgProps.ADDON_NAME_ID);
        String nameDisp = props.getProperty(PkgProps.ADDON_NAME_DISPLAY);
        String vendorId = props.getProperty(PkgProps.ADDON_VENDOR_ID);
        String vendorDisp = props.getProperty(PkgProps.ADDON_VENDOR_DISPLAY);

        if (nameId == null) {
          // Support earlier add-ons that only had a name display attribute
          nameDisp = props.getProperty(PkgProps.ADDON_NAME, "Unknown");
          nameId = LocalAddonPkgInfo.sanitizeDisplayToNameId(nameDisp);
        }

        if (nameId != null && nameDisp == null) {
          nameDisp = LocalExtraPkgInfo.getPrettyName(null, nameId);
        }

        if (vendorId != null && vendorDisp == null) {
          vendorDisp = LocalExtraPkgInfo.getPrettyName(null, nameId);
        }

        if (vendorId == null) {
          // Support earlier add-ons that only had a vendor display attribute
          vendorDisp = props.getProperty(PkgProps.ADDON_VENDOR, "Unknown");
          vendorId = LocalAddonPkgInfo.sanitizeDisplayToNameId(vendorDisp);
        }

        LocalAddonPkgInfo pkgInfo =
            new LocalAddonPkgInfo(
                this,
                addonDir,
                props,
                vers,
                rev,
                new IdDisplay(vendorId, vendorDisp),
                new IdDisplay(nameId, nameDisp));
        outCollection.add(pkgInfo);

      } catch (AndroidVersionException e) {
        continue; // skip invalid or missing android version.
      }
    }
  }

  private void scanSysImages(
      File collectionDir, Collection<LocalPkgInfo> outCollection, boolean scanAddons) {
    List<File> propFiles = Lists.newArrayList();
    PkgType type = scanAddons ? PkgType.PKG_ADDON_SYS_IMAGE : PkgType.PKG_SYS_IMAGE;

    // Create a list of folders that contains a source.properties file matching these patterns:
    // sys-img/target/tag/abi
    // sys-img/target/abis
    // sys-img/add-on-target/abi
    // sys-img/target/add-on/abi
    for (File platformDir : mFileOp.listFiles(collectionDir)) {
      if (!shouldVisitDir(type, platformDir)) {
        continue;
      }

      for (File dir1 : mFileOp.listFiles(platformDir)) {
        // dir1 might be either a tag or an abi folder.
        if (!shouldVisitDir(type, dir1)) {
          continue;
        }

        File prop1 = new File(dir1, SdkConstants.FN_SOURCE_PROP);
        if (mFileOp.isFile(prop1)) {
          // dir1 was a legacy abi folder.
          if (!propFiles.contains(prop1)) {
            propFiles.add(prop1);
          }
        } else {
          File[] dir1Files = mFileOp.listFiles(dir1);
          for (File dir2 : dir1Files) {
            // dir2 should be an abi folder in a tag folder.
            if (!shouldVisitDir(type, dir2)) {
              continue;
            }

            File prop2 = new File(dir2, SdkConstants.FN_SOURCE_PROP);
            if (mFileOp.isFile(prop2)) {
              if (!propFiles.contains(prop2)) {
                propFiles.add(prop2);
              }
            }
          }
        }
      }
    }

    for (File propFile : propFiles) {
      Properties props = parseProperties(propFile);
      MajorRevision rev = PackageParserUtils.getPropertyMajor(props, PkgProps.PKG_REVISION);
      if (rev == null) {
        continue; // skip, no revision
      }

      try {
        AndroidVersion vers = new AndroidVersion(props);

        IdDisplay tag = LocalSysImgPkgInfo.extractTagFromProps(props);
        String vendorId = props.getProperty(PkgProps.ADDON_VENDOR_ID, null);
        File abiDir = propFile.getParentFile();

        if (vendorId == null && !scanAddons) {
          LocalSysImgPkgInfo pkgInfo =
              new LocalSysImgPkgInfo(this, abiDir, props, vers, tag, abiDir.getName(), rev);
          outCollection.add(pkgInfo);

        } else if (vendorId != null && scanAddons) {
          String vendorDisp = props.getProperty(PkgProps.ADDON_VENDOR_DISPLAY, vendorId);
          IdDisplay vendor = new IdDisplay(vendorId, vendorDisp);

          LocalAddonSysImgPkgInfo pkgInfo =
              new LocalAddonSysImgPkgInfo(
                  this, abiDir, props, vers, vendor, tag, abiDir.getName(), rev);
          outCollection.add(pkgInfo);
        }

      } catch (AndroidVersionException e) {
        continue; // skip invalid or missing android version.
      }
    }
  }

  private void scanSamples(File collectionDir, Collection<LocalPkgInfo> outCollection) {
    for (File platformDir : mFileOp.listFiles(collectionDir)) {
      if (!shouldVisitDir(PkgType.PKG_SAMPLE, platformDir)) {
        continue;
      }

      Properties props = parseProperties(new File(platformDir, SdkConstants.FN_SOURCE_PROP));
      MajorRevision rev = PackageParserUtils.getPropertyMajor(props, PkgProps.PKG_REVISION);
      if (rev == null) {
        continue; // skip, no revision
      }

      FullRevision minToolsRev = PackageParserUtils.getPropertyFull(props, PkgProps.MIN_TOOLS_REV);
      if (minToolsRev == null) {
        minToolsRev = FullRevision.NOT_SPECIFIED;
      }

      try {
        AndroidVersion vers = new AndroidVersion(props);

        LocalSamplePkgInfo pkgInfo =
            new LocalSamplePkgInfo(this, platformDir, props, vers, rev, minToolsRev);
        outCollection.add(pkgInfo);
      } catch (AndroidVersionException e) {
        continue; // skip invalid or missing android version.
      }
    }
  }

  private void scanSources(File collectionDir, Collection<LocalPkgInfo> outCollection) {
    // The build-tool root folder contains a list of per-revision folders.
    for (File platformDir : mFileOp.listFiles(collectionDir)) {
      if (!shouldVisitDir(PkgType.PKG_SOURCE, platformDir)) {
        continue;
      }

      Properties props = parseProperties(new File(platformDir, SdkConstants.FN_SOURCE_PROP));
      MajorRevision rev = PackageParserUtils.getPropertyMajor(props, PkgProps.PKG_REVISION);
      if (rev == null) {
        continue; // skip, no revision
      }

      try {
        AndroidVersion vers = new AndroidVersion(props);

        LocalSourcePkgInfo pkgInfo = new LocalSourcePkgInfo(this, platformDir, props, vers, rev);
        outCollection.add(pkgInfo);
      } catch (AndroidVersionException e) {
        continue; // skip invalid or missing android version.
      }
    }
  }

  private void scanExtras(File collectionDir, Collection<LocalPkgInfo> outCollection) {
    for (File vendorDir : mFileOp.listFiles(collectionDir)) {
      if (!shouldVisitDir(PkgType.PKG_EXTRA, vendorDir)) {
        continue;
      }

      for (File extraDir : mFileOp.listFiles(vendorDir)) {
        if (!shouldVisitDir(PkgType.PKG_EXTRA, extraDir)) {
          continue;
        }

        Properties props = parseProperties(new File(extraDir, SdkConstants.FN_SOURCE_PROP));
        NoPreviewRevision rev =
            PackageParserUtils.getPropertyNoPreview(props, PkgProps.PKG_REVISION);
        if (rev == null) {
          continue; // skip, no revision
        }

        String oldPaths = PackageParserUtils.getProperty(props, PkgProps.EXTRA_OLD_PATHS, null);

        String vendorId = vendorDir.getName();
        String vendorDisp = props.getProperty(PkgProps.EXTRA_VENDOR_DISPLAY);
        if (vendorDisp == null || vendorDisp.isEmpty()) {
          vendorDisp = vendorId;
        }

        String displayName = props.getProperty(PkgProps.EXTRA_NAME_DISPLAY, null);

        LocalExtraPkgInfo pkgInfo =
            new LocalExtraPkgInfo(
                this,
                extraDir,
                props,
                new IdDisplay(vendorId, vendorDisp),
                extraDir.getName(),
                displayName,
                PkgDescExtra.convertOldPaths(oldPaths),
                rev);
        outCollection.add(pkgInfo);
      }
    }
  }

  /**
   * Parses the given file as properties file if it exists. Returns null if the file does not exist,
   * cannot be parsed or has no properties.
   */
  private Properties parseProperties(File propsFile) {
    InputStream fis = null;
    try {
      if (mFileOp.exists(propsFile)) {
        fis = mFileOp.newFileInputStream(propsFile);

        Properties props = new Properties();
        props.load(fis);

        // To be valid, there must be at least one property in it.
        if (!props.isEmpty()) {
          return props;
        }
      }
    } catch (IOException e) {
      // Ignore
    } finally {
      if (fis != null) {
        try {
          fis.close();
        } catch (IOException e) {
        }
      }
    }
    return null;
  }
}
 protected Topology() {
   dcEndpoints = HashMultimap.create();
   dcRacks = new HashMap<String, Multimap<String, InetAddress>>();
   currentLocations = new HashMap<InetAddress, Pair<String, String>>();
 }
 public void updateNormalTokens(Collection<Token> tokens, InetAddress endpoint) {
   Multimap<InetAddress, Token> endpointTokens = HashMultimap.create();
   for (Token token : tokens) endpointTokens.put(endpoint, token);
   updateNormalTokens(endpointTokens);
 }