| // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file | 
 | // for details. All rights reserved. Use of this source code is governed by a | 
 | // BSD-style license that can be found in the LICENSE file. | 
 | package com.android.tools.r8.naming; | 
 |  | 
 | import com.android.tools.r8.naming.MemberNaming.FieldSignature; | 
 | import com.android.tools.r8.naming.MemberNaming.MethodSignature; | 
 | import com.android.tools.r8.naming.MemberNaming.Signature; | 
 | import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind; | 
 | import com.android.tools.r8.utils.ChainableStringConsumer; | 
 | import com.android.tools.r8.utils.ThrowingConsumer; | 
 | import com.google.common.collect.ImmutableMap; | 
 | import com.google.common.collect.Maps; | 
 | import java.util.ArrayList; | 
 | import java.util.Collection; | 
 | import java.util.Collections; | 
 | import java.util.Comparator; | 
 | import java.util.HashMap; | 
 | import java.util.List; | 
 | import java.util.Map; | 
 | import java.util.Objects; | 
 |  | 
 | /** | 
 |  * Stores name information for a class. | 
 |  * <p> | 
 |  * This includes how the class was renamed and information on the classes members. | 
 |  */ | 
 | public class ClassNamingForNameMapper implements ClassNaming { | 
 |  | 
 |   public static class Builder extends ClassNaming.Builder { | 
 |     private final String originalName; | 
 |     private final String renamedName; | 
 |     private final Map<MethodSignature, MemberNaming> methodMembers = Maps.newHashMap(); | 
 |     private final Map<FieldSignature, MemberNaming> fieldMembers = Maps.newHashMap(); | 
 |     private final Map<String, List<MappedRange>> mappedRangesByName = Maps.newHashMap(); | 
 |     private final Map<String, List<MemberNaming>> mappedNamingsByName = Maps.newHashMap(); | 
 |  | 
 |     private Builder(String renamedName, String originalName) { | 
 |       this.originalName = originalName; | 
 |       this.renamedName = renamedName; | 
 |     } | 
 |  | 
 |     @Override | 
 |     public ClassNaming.Builder addMemberEntry(MemberNaming entry) { | 
 |       if (entry.isMethodNaming()) { | 
 |         methodMembers.put((MethodSignature) entry.getRenamedSignature(), entry); | 
 |       } else { | 
 |         fieldMembers.put((FieldSignature) entry.getRenamedSignature(), entry); | 
 |       } | 
 |       mappedNamingsByName | 
 |           .computeIfAbsent(entry.getRenamedName(), m -> new ArrayList<>()) | 
 |           .add(entry); | 
 |       return this; | 
 |     } | 
 |  | 
 |     @Override | 
 |     public ClassNamingForNameMapper build() { | 
 |       Map<String, MappedRangesOfName> map; | 
 |  | 
 |       if (mappedRangesByName.isEmpty()) { | 
 |         map = Collections.emptyMap(); | 
 |       } else { | 
 |         map = new HashMap<>(mappedRangesByName.size()); | 
 |         for (Map.Entry<String, List<MappedRange>> entry : mappedRangesByName.entrySet()) { | 
 |           map.put(entry.getKey(), new MappedRangesOfName(entry.getValue())); | 
 |         } | 
 |       } | 
 |  | 
 |       return new ClassNamingForNameMapper( | 
 |           renamedName, originalName, methodMembers, fieldMembers, map, mappedNamingsByName); | 
 |     } | 
 |  | 
 |     /** The parameters are forwarded to MappedRange constructor, see explanation there. */ | 
 |     @Override | 
 |     public void addMappedRange( | 
 |         Range minifiedRange, | 
 |         MemberNaming.MethodSignature originalSignature, | 
 |         Object originalRange, | 
 |         String renamedName) { | 
 |       mappedRangesByName | 
 |           .computeIfAbsent(renamedName, k -> new ArrayList<>()) | 
 |           .add(new MappedRange(minifiedRange, originalSignature, originalRange, renamedName)); | 
 |     } | 
 |   } | 
 |  | 
 |   /** List of MappedRanges that belong to the same renamed name. */ | 
 |   public static class MappedRangesOfName { | 
 |     private final List<MappedRange> mappedRanges; | 
 |  | 
 |     public MappedRangesOfName(List<MappedRange> mappedRanges) { | 
 |       this.mappedRanges = mappedRanges; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Return the first MappedRange that contains {@code line}. Return general MappedRange ("a() -> | 
 |      * b") if no concrete mapping found or null if nothing found. | 
 |      */ | 
 |     public MappedRange firstRangeForLine(int line) { | 
 |       MappedRange bestRange = null; | 
 |       for (MappedRange range : mappedRanges) { | 
 |         if (range.minifiedRange == null) { | 
 |           if (bestRange == null) { | 
 |             // This is an "a() -> b" mapping (no concrete line numbers), remember this if there'll | 
 |             // be no better one. | 
 |             bestRange = range; | 
 |           } | 
 |         } else if (range.minifiedRange.contains(line)) { | 
 |           // Concrete minified range found ("x:y:a()[:u[:v]] -> b") | 
 |           return range; | 
 |         } | 
 |       } | 
 |       return bestRange; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Search for a MappedRange where the minified range contains the specified {@code line} and | 
 |      * return that and the subsequent MappedRanges with the same minified range. Return general | 
 |      * MappedRange ("a() -> b") if no concrete mapping found or empty list if nothing found. | 
 |      */ | 
 |     public List<MappedRange> allRangesForLine(int line) { | 
 |       return allRangesForLine(line, true); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Search for a MappedRange where the minified range contains the specified {@code line} and | 
 |      * return that and the subsequent MappedRanges with the same minified range. | 
 |      * | 
 |      * @param line The line number to find the range for | 
 |      * @param takeFirstWithNoLineRange Specify if no range is found, to take a general one that. | 
 |      * @return The list with all ranges for line. | 
 |      */ | 
 |     public List<MappedRange> allRangesForLine(int line, boolean takeFirstWithNoLineRange) { | 
 |       MappedRange noLineRange = null; | 
 |       for (int i = 0; i < mappedRanges.size(); ++i) { | 
 |         MappedRange rangeI = mappedRanges.get(i); | 
 |         if (rangeI.minifiedRange == null) { | 
 |           if (noLineRange == null && takeFirstWithNoLineRange) { | 
 |             // This is an "a() -> b" mapping (no concrete line numbers), remember this if there'll | 
 |             // be no better one. | 
 |             noLineRange = rangeI; | 
 |           } | 
 |         } else if (rangeI.minifiedRange.contains(line)) { | 
 |           // Concrete minified range found ("x:y:a()[:u[:v]] -> b") | 
 |           int j = i + 1; | 
 |           for (; j < mappedRanges.size(); ++j) { | 
 |             if (!Objects.equals(mappedRanges.get(j).minifiedRange, rangeI.minifiedRange)) { | 
 |               break; | 
 |             } | 
 |           } | 
 |           return mappedRanges.subList(i, j); | 
 |         } | 
 |       } | 
 |       return noLineRange == null ? Collections.emptyList() : Collections.singletonList(noLineRange); | 
 |     } | 
 |  | 
 |     public List<MappedRange> getMappedRanges() { | 
 |       return mappedRanges; | 
 |     } | 
 |  | 
 |     @Override | 
 |     public boolean equals(Object o) { | 
 |       if (this == o) { | 
 |         return true; | 
 |       } | 
 |       if (o == null || getClass() != o.getClass()) { | 
 |         return false; | 
 |       } | 
 |  | 
 |       MappedRangesOfName that = (MappedRangesOfName) o; | 
 |  | 
 |       return mappedRanges.equals(that.mappedRanges); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public int hashCode() { | 
 |       return mappedRanges.hashCode(); | 
 |     } | 
 |   } | 
 |  | 
 |   static Builder builder(String renamedName, String originalName) { | 
 |     return new Builder(renamedName, originalName); | 
 |   } | 
 |  | 
 |   public final String originalName; | 
 |   public final String renamedName; | 
 |  | 
 |   /** | 
 |    * Mapping from the renamed signature to the naming information for a member. | 
 |    * | 
 |    * <p>A renamed signature is a signature where the member's name has been renamed but not the type | 
 |    * information. | 
 |    */ | 
 |   private final ImmutableMap<MethodSignature, MemberNaming> methodMembers; | 
 |   private final ImmutableMap<FieldSignature, MemberNaming> fieldMembers; | 
 |  | 
 |   /** Map of renamed name -> MappedRangesOfName */ | 
 |   public final Map<String, MappedRangesOfName> mappedRangesByRenamedName; | 
 |  | 
 |   public final Map<String, List<MemberNaming>> mappedNamingsByName; | 
 |  | 
 |   private ClassNamingForNameMapper( | 
 |       String renamedName, | 
 |       String originalName, | 
 |       Map<MethodSignature, MemberNaming> methodMembers, | 
 |       Map<FieldSignature, MemberNaming> fieldMembers, | 
 |       Map<String, MappedRangesOfName> mappedRangesByRenamedName, | 
 |       Map<String, List<MemberNaming>> mappedNamingsByName) { | 
 |     this.renamedName = renamedName; | 
 |     this.originalName = originalName; | 
 |     this.methodMembers = ImmutableMap.copyOf(methodMembers); | 
 |     this.fieldMembers = ImmutableMap.copyOf(fieldMembers); | 
 |     this.mappedRangesByRenamedName = mappedRangesByRenamedName; | 
 |     this.mappedNamingsByName = mappedNamingsByName; | 
 |   } | 
 |  | 
 |   public MappedRangesOfName getMappedRangesForRenamedName(String renamedName) { | 
 |     return mappedRangesByRenamedName.get(renamedName); | 
 |   } | 
 |  | 
 |   @Override | 
 |   public MemberNaming lookup(Signature renamedSignature) { | 
 |     if (renamedSignature.kind() == SignatureKind.METHOD) { | 
 |       assert renamedSignature instanceof MethodSignature; | 
 |       return methodMembers.get(renamedSignature); | 
 |     } else { | 
 |       assert renamedSignature.kind() == SignatureKind.FIELD; | 
 |       assert renamedSignature instanceof FieldSignature; | 
 |       return fieldMembers.get(renamedSignature); | 
 |     } | 
 |   } | 
 |  | 
 |   @Override | 
 |   public MemberNaming lookupByOriginalSignature(Signature original) { | 
 |     if (original.kind() == SignatureKind.METHOD) { | 
 |       for (MemberNaming memberNaming: methodMembers.values()) { | 
 |         if (memberNaming.signature.equals(original)) { | 
 |           return memberNaming; | 
 |         } | 
 |       } | 
 |       return null; | 
 |     } else { | 
 |       assert original.kind() == SignatureKind.FIELD; | 
 |       for (MemberNaming memberNaming : fieldMembers.values()) { | 
 |         if (memberNaming.signature.equals(original)) { | 
 |           return memberNaming; | 
 |         } | 
 |       } | 
 |       return null; | 
 |     } | 
 |   } | 
 |  | 
 |   public List<MemberNaming> lookupByOriginalName(String originalName) { | 
 |     List<MemberNaming> result = new ArrayList<>(); | 
 |     for (MemberNaming naming : methodMembers.values()) { | 
 |       if (naming.signature.name.equals(originalName)) { | 
 |         result.add(naming); | 
 |       } | 
 |     } | 
 |     for (MemberNaming naming : fieldMembers.values()) { | 
 |       if (naming.signature.name.equals(originalName)) { | 
 |         result.add(naming); | 
 |       } | 
 |     } | 
 |     return result; | 
 |   } | 
 |  | 
 |   @Override | 
 |   public <T extends Throwable> void forAllMemberNaming( | 
 |       ThrowingConsumer<MemberNaming, T> consumer) throws T { | 
 |     forAllFieldNaming(consumer); | 
 |     forAllMethodNaming(consumer); | 
 |   } | 
 |  | 
 |   @Override | 
 |   public <T extends Throwable> void forAllFieldNaming( | 
 |       ThrowingConsumer<MemberNaming, T> consumer) throws T { | 
 |     for (MemberNaming naming : fieldMembers.values()) { | 
 |       consumer.accept(naming); | 
 |     } | 
 |   } | 
 |  | 
 |   public Collection<MemberNaming> allFieldNamings() { | 
 |     return fieldMembers.values(); | 
 |   } | 
 |  | 
 |   @Override | 
 |   public <T extends Throwable> void forAllMethodNaming( | 
 |       ThrowingConsumer<MemberNaming, T> consumer) throws T { | 
 |     for (MemberNaming naming : methodMembers.values()) { | 
 |       consumer.accept(naming); | 
 |     } | 
 |   } | 
 |  | 
 |   public Collection<MemberNaming> allMethodNamings() { | 
 |     return methodMembers.values(); | 
 |   } | 
 |  | 
 |   void write(ChainableStringConsumer consumer) { | 
 |     consumer.accept(originalName).accept(" -> ").accept(renamedName).accept(":\n"); | 
 |  | 
 |     // First print field member namings. | 
 |     forAllFieldNaming(m -> consumer.accept("    ").accept(m.toString()).accept("\n")); | 
 |  | 
 |     // Sort MappedRanges by sequence number to restore construction order (original Proguard-map | 
 |     // input). | 
 |     List<MappedRange> mappedRangesSorted = new ArrayList<>(); | 
 |     for (MappedRangesOfName ranges : mappedRangesByRenamedName.values()) { | 
 |       mappedRangesSorted.addAll(ranges.mappedRanges); | 
 |     } | 
 |     mappedRangesSorted.sort(Comparator.comparingInt(range -> range.sequenceNumber)); | 
 |     for (MappedRange range : mappedRangesSorted) { | 
 |       consumer.accept("    ").accept(range.toString()).accept("\n"); | 
 |     } | 
 |   } | 
 |  | 
 |   @Override | 
 |   public String toString() { | 
 |     StringBuilder builder = new StringBuilder(); | 
 |     write(ChainableStringConsumer.wrap(builder::append)); | 
 |     return builder.toString(); | 
 |   } | 
 |  | 
 |   @Override | 
 |   public boolean equals(Object o) { | 
 |     if (this == o) { | 
 |       return true; | 
 |     } | 
 |     if (!(o instanceof ClassNamingForNameMapper)) { | 
 |       return false; | 
 |     } | 
 |  | 
 |     ClassNamingForNameMapper that = (ClassNamingForNameMapper) o; | 
 |  | 
 |     return originalName.equals(that.originalName) | 
 |         && renamedName.equals(that.renamedName) | 
 |         && methodMembers.equals(that.methodMembers) | 
 |         && fieldMembers.equals(that.fieldMembers) | 
 |         && mappedRangesByRenamedName.equals(that.mappedRangesByRenamedName); | 
 |   } | 
 |  | 
 |   @Override | 
 |   public int hashCode() { | 
 |     int result = originalName.hashCode(); | 
 |     result = 31 * result + renamedName.hashCode(); | 
 |     result = 31 * result + methodMembers.hashCode(); | 
 |     result = 31 * result + fieldMembers.hashCode(); | 
 |     result = 31 * result + mappedRangesByRenamedName.hashCode(); | 
 |     return result; | 
 |   } | 
 |  | 
 |   /** | 
 |    * MappedRange describes an (original line numbers, signature) <-> (minified line numbers) | 
 |    * mapping. It can describe 3 different things: | 
 |    * | 
 |    * <p>1. The method is renamed. The original source lines are preserved. The corresponding | 
 |    * Proguard-map syntax is "a(...) -> b" | 
 |    * | 
 |    * <p>2. The source lines of a method in the original range are renumbered to the minified range. | 
 |    * In this case the {@link MappedRange#originalRange} is either a {@code Range} or null, | 
 |    * indicating that the original range is unknown or is the same as the {@link | 
 |    * MappedRange#minifiedRange}. The corresponding Proguard-map syntax is "x:y:a(...) -> b" or | 
 |    * "x:y:a(...):u:v -> b" | 
 |    * | 
 |    * <p>3. The source line of a method is the inlining caller of the previous {@code MappedRange}. | 
 |    * In this case the {@link MappedRange@originalRange} is either an {@code int} or null, indicating | 
 |    * that the original source line is unknown, or may be identical to a line of the minified range. | 
 |    * The corresponding Proguard-map syntax is "x:y:a(...) -> b" or "x:y:a(...):u -> b" | 
 |    */ | 
 |   public static class MappedRange { | 
 |  | 
 |     private static int nextSequenceNumber = 0; | 
 |  | 
 |     private synchronized int getNextSequenceNumber() { | 
 |       return nextSequenceNumber++; | 
 |     } | 
 |  | 
 |     public final Range minifiedRange; // Can be null, if so then originalRange must also be null. | 
 |     public final MethodSignature signature; | 
 |     public final Object originalRange; // null, Integer or Range. | 
 |     public final String renamedName; | 
 |  | 
 |     /** | 
 |      * The sole purpose of {@link #sequenceNumber} is to preserve the order of members read from a | 
 |      * Proguard-map. | 
 |      */ | 
 |     private final int sequenceNumber = getNextSequenceNumber(); | 
 |  | 
 |     private MappedRange( | 
 |         Range minifiedRange, MethodSignature signature, Object originalRange, String renamedName) { | 
 |  | 
 |       assert minifiedRange != null || originalRange == null; | 
 |       assert originalRange == null | 
 |           || originalRange instanceof Integer | 
 |           || originalRange instanceof Range; | 
 |  | 
 |       this.minifiedRange = minifiedRange; | 
 |       this.signature = signature; | 
 |       this.originalRange = originalRange; | 
 |       this.renamedName = renamedName; | 
 |     } | 
 |  | 
 |     public int getOriginalLineNumber(int lineNumberAfterMinification) { | 
 |       if (minifiedRange == null) { | 
 |         // General mapping without concrete line numbers: "a() -> b" | 
 |         return lineNumberAfterMinification; | 
 |       } | 
 |       assert minifiedRange.contains(lineNumberAfterMinification); | 
 |       if (originalRange == null) { | 
 |         // Concrete identity mapping: "x:y:a() -> b" | 
 |         return lineNumberAfterMinification; | 
 |       } else if (originalRange instanceof Integer) { | 
 |         // Inlinee: "x:y:a():u -> b" | 
 |         return (int) originalRange; | 
 |       } else { | 
 |         // "x:y:a():u:v -> b" | 
 |         assert originalRange instanceof Range; | 
 |         Range originalRange = (Range) this.originalRange; | 
 |         if (originalRange.to == originalRange.from) { | 
 |           // This is a single line mapping which we should report as the actual line. | 
 |           return originalRange.to; | 
 |         } | 
 |         return originalRange.from + lineNumberAfterMinification - minifiedRange.from; | 
 |       } | 
 |     } | 
 |  | 
 |     public int getFirstLineNumberOfOriginalRange() { | 
 |       if (originalRange == null) { | 
 |         return 0; | 
 |       } else if (originalRange instanceof Integer) { | 
 |         return (int) originalRange; | 
 |       } else { | 
 |         return ((Range) originalRange).from; | 
 |       } | 
 |     } | 
 |  | 
 |     @Override | 
 |     public String toString() { | 
 |       StringBuilder builder = new StringBuilder(); | 
 |       if (minifiedRange != null) { | 
 |         builder.append(minifiedRange).append(':'); | 
 |       } | 
 |       builder.append(signature); | 
 |       if (originalRange != null && !minifiedRange.equals(originalRange)) { | 
 |         builder.append(":").append(originalRange); | 
 |       } | 
 |       builder.append(" -> ").append(renamedName); | 
 |       return builder.toString(); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public boolean equals(Object o) { | 
 |       // sequenceNumber is intentionally omitted from equality test since it doesn't contribute to | 
 |       // identity. | 
 |       if (this == o) { | 
 |         return true; | 
 |       } | 
 |       if (!(o instanceof MappedRange)) { | 
 |         return false; | 
 |       } | 
 |  | 
 |       MappedRange that = (MappedRange) o; | 
 |  | 
 |       return Objects.equals(minifiedRange, that.minifiedRange) | 
 |           && Objects.equals(originalRange, that.originalRange) | 
 |           && signature.equals(that.signature) | 
 |           && renamedName.equals(that.renamedName); | 
 |     } | 
 |  | 
 |     @Override | 
 |     public int hashCode() { | 
 |       // sequenceNumber is intentionally omitted from hashCode since it's not used in equality test. | 
 |       int result = Objects.hashCode(minifiedRange); | 
 |       result = 31 * result + Objects.hashCode(originalRange); | 
 |       result = 31 * result + signature.hashCode(); | 
 |       result = 31 * result + renamedName.hashCode(); | 
 |       return result; | 
 |     } | 
 |   } | 
 | } | 
 |  |