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