|  | // Copyright (c) 2022, 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 static com.android.tools.r8.naming.MappedRangeUtils.isInlineMappedRangeForComposition; | 
|  | import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument; | 
|  |  | 
|  | import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange; | 
|  | import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName; | 
|  | 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.mappinginformation.MapVersionMappingInformation; | 
|  | import com.android.tools.r8.naming.mappinginformation.MappingInformation; | 
|  | import com.android.tools.r8.naming.mappinginformation.OutlineCallsiteMappingInformation; | 
|  | import com.android.tools.r8.naming.mappinginformation.OutlineMappingInformation; | 
|  | import com.android.tools.r8.naming.mappinginformation.ResidualSignatureMappingInformation; | 
|  | import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation; | 
|  | import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation.ThrowsCondition; | 
|  | import com.android.tools.r8.references.ArrayReference; | 
|  | import com.android.tools.r8.references.ClassReference; | 
|  | import com.android.tools.r8.references.MethodReference; | 
|  | import com.android.tools.r8.references.Reference; | 
|  | import com.android.tools.r8.references.TypeReference; | 
|  | import com.android.tools.r8.utils.ChainableStringConsumer; | 
|  | import com.android.tools.r8.utils.ConsumerUtils; | 
|  | import com.android.tools.r8.utils.IntBox; | 
|  | import com.android.tools.r8.utils.InternalOptions; | 
|  | import com.android.tools.r8.utils.ListUtils; | 
|  | import com.android.tools.r8.utils.SegmentTree; | 
|  | import com.android.tools.r8.utils.ThrowingBiFunction; | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import com.google.common.collect.Sets; | 
|  | import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap; | 
|  | import it.unimi.dsi.fastutil.ints.Int2IntSortedMap; | 
|  | import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; | 
|  | import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; | 
|  | import java.util.ArrayList; | 
|  | import java.util.Collections; | 
|  | import java.util.Comparator; | 
|  | import java.util.HashMap; | 
|  | import java.util.HashSet; | 
|  | import java.util.IdentityHashMap; | 
|  | import java.util.List; | 
|  | import java.util.Map; | 
|  | import java.util.Map.Entry; | 
|  | import java.util.Objects; | 
|  | import java.util.Set; | 
|  | import java.util.TreeMap; | 
|  | import java.util.function.Consumer; | 
|  |  | 
|  | public class ComposingBuilder { | 
|  |  | 
|  | private static final Range EMPTY_RANGE = new Range(0, 0); | 
|  |  | 
|  | private MapVersionMappingInformation currentMapVersion = null; | 
|  |  | 
|  | /** | 
|  | * When composing we store a view of the previously known mappings in committed and retain a | 
|  | * current working set. When composing of a new map is finished we commit everything in current | 
|  | * into the committed set. | 
|  | * | 
|  | * <p>The reason for not having just a single set is that we can have a circular mapping as | 
|  | * follows: | 
|  | * | 
|  | * <pre> | 
|  | *   a -> b: | 
|  | *   ... | 
|  | *   b -> a: | 
|  | * </pre> | 
|  | * | 
|  | * After composing our current view of a with the above, we could end up transforming 'a' into 'b' | 
|  | * and then later transforming 'b' back into 'a' again. To ensure we do not mess up namings while | 
|  | * composing classes and methods we resort to a working set and committed set. | 
|  | */ | 
|  | private final ComposingData committed = new ComposingData(); | 
|  |  | 
|  | private ComposingData current; | 
|  | private final InternalOptions options; | 
|  |  | 
|  | public ComposingBuilder(InternalOptions options) { | 
|  | this.options = options; | 
|  | } | 
|  |  | 
|  | public void compose(ClassNameMapper classNameMapper) throws MappingComposeException { | 
|  | current = new ComposingData(); | 
|  | MapVersionMappingInformation newMapVersionInfo = | 
|  | classNameMapper.getFirstMapVersionInformation(); | 
|  | if (newMapVersionInfo == null) { | 
|  | throw new MappingComposeException( | 
|  | "Composition of mapping files supported from map version 2.2."); | 
|  | } | 
|  | MapVersion newMapVersion = newMapVersionInfo.getMapVersion(); | 
|  | if (!ResidualSignatureMappingInformation.isSupported(newMapVersion) | 
|  | || newMapVersion.isUnknown()) { | 
|  | throw new MappingComposeException( | 
|  | "Composition of mapping files supported from map version " | 
|  | + ResidualSignatureMappingInformation.SUPPORTED_VERSION.getName() | 
|  | + "."); | 
|  | } | 
|  | if (currentMapVersion == null) { | 
|  | currentMapVersion = newMapVersionInfo; | 
|  | } else { | 
|  | currentMapVersion = | 
|  | newMapVersionInfo.compose(currentMapVersion).asMapVersionMappingInformation(); | 
|  | } | 
|  | for (ClassNamingForNameMapper classMapping : classNameMapper.getClassNameMappings().values()) { | 
|  | compose(classNameMapper, classMapping); | 
|  | } | 
|  | committed.commit(current, classNameMapper); | 
|  | } | 
|  |  | 
|  | private void compose(ClassNameMapper classNameMapper, ClassNamingForNameMapper classMapping) | 
|  | throws MappingComposeException { | 
|  | String originalName = classMapping.originalName; | 
|  | String renamedName = classMapping.renamedName; | 
|  | ComposingClassBuilder composingClassBuilder = | 
|  | new ComposingClassBuilder(originalName, renamedName, committed, current, options); | 
|  | ComposingClassBuilder duplicateMapping = | 
|  | current.classBuilders.put(renamedName, composingClassBuilder); | 
|  | if (duplicateMapping != null) { | 
|  | throw new MappingComposeException( | 
|  | "Duplicate class mapping. Both '" | 
|  | + duplicateMapping.getOriginalName() | 
|  | + "' and '" | 
|  | + originalName | 
|  | + "' maps to '" | 
|  | + renamedName | 
|  | + "'."); | 
|  | } | 
|  | composingClassBuilder.compose(classNameMapper, classMapping); | 
|  | } | 
|  |  | 
|  | public String finish() { | 
|  | List<ComposingClassBuilder> classBuilders = new ArrayList<>(committed.classBuilders.values()); | 
|  | classBuilders.sort(Comparator.comparing(ComposingClassBuilder::getOriginalName)); | 
|  | StringBuilder sb = new StringBuilder(); | 
|  | committed.preamble.forEach(preambleLine -> sb.append(preambleLine).append("\n")); | 
|  | if (currentMapVersion != null) { | 
|  | sb.append("# ").append(currentMapVersion.serialize()).append("\n"); | 
|  | } | 
|  | ChainableStringConsumer wrap = ChainableStringConsumer.wrap(sb::append); | 
|  | for (ComposingClassBuilder classBuilder : classBuilders) { | 
|  | classBuilder.write(wrap); | 
|  | } | 
|  | return sb.toString(); | 
|  | } | 
|  |  | 
|  | public static class ComposingData { | 
|  |  | 
|  | /** | 
|  | * A map of minified names to their class builders. When committing to a new minified name we | 
|  | * destructively remove the previous minified mapping and replace it with the up-to-date one. | 
|  | */ | 
|  | private Map<String, ComposingClassBuilder> classBuilders = new HashMap<>(); | 
|  | /** | 
|  | * RewriteFrameInformation contains condition clauses that are bound to the residual program. As | 
|  | * a result of that, we have to patch up the conditions when we compose new class mappings. | 
|  | */ | 
|  | private final List<RewriteFrameMappingInformation> rewriteFrameInformation = new ArrayList<>(); | 
|  | /** Map of newly added outline call site informations which do not require any rewriting. */ | 
|  | private Map<ClassTypeNameAndMethodName, OutlineCallsiteMappingInformation> | 
|  | outlineCallsiteInformation = new HashMap<>(); | 
|  | /** | 
|  | * Map of updated outline definitions which has to be committed. The positions in the caller are | 
|  | * fixed at this point since these are local to the method when rewriting. | 
|  | */ | 
|  | private final Map<ClassTypeNameAndMethodName, UpdateOutlineCallsiteInformation> | 
|  | outlineSourcePositionsUpdated = new HashMap<>(); | 
|  |  | 
|  | /** | 
|  | * Map of signatures that should be removed when finalizing the composed map. The key is the | 
|  | * original name of a class. | 
|  | */ | 
|  | private final Map<String, Set<Signature>> signaturesToRemove = new HashMap<>(); | 
|  |  | 
|  | private final List<String> preamble = new ArrayList<>(); | 
|  |  | 
|  | public void commit(ComposingData current, ClassNameMapper classNameMapper) | 
|  | throws MappingComposeException { | 
|  | preamble.addAll(classNameMapper.getPreamble()); | 
|  | commitClassBuilders(current, classNameMapper); | 
|  | commitRewriteFrameInformation(current, classNameMapper); | 
|  | commitOutlineCallsiteInformation(current, classNameMapper); | 
|  | } | 
|  |  | 
|  | private void commitClassBuilders(ComposingData current, ClassNameMapper classNameMapper) | 
|  | throws MappingComposeException { | 
|  | Set<String> updatedClassBuilders = new HashSet<>(); | 
|  | Map<String, ComposingClassBuilder> newClassBuilders = new HashMap<>(); | 
|  | for (Entry<String, ComposingClassBuilder> newEntry : current.classBuilders.entrySet()) { | 
|  | String renamedName = newEntry.getKey(); | 
|  | ComposingClassBuilder classBuilder = newEntry.getValue(); | 
|  | updatedClassBuilders.add(classBuilder.originalName); | 
|  | ComposingClassBuilder existingBuilder = classBuilders.get(classBuilder.originalName); | 
|  | if (existingBuilder != null) { | 
|  | removeSignaturesFromBuilder(current, existingBuilder); | 
|  | classBuilder = existingBuilder.commit(classBuilder); | 
|  | } | 
|  | newClassBuilders.put(renamedName, classBuilder); | 
|  | } | 
|  | for (Entry<String, ComposingClassBuilder> existingEntry : classBuilders.entrySet()) { | 
|  | if (!updatedClassBuilders.contains(existingEntry.getKey())) { | 
|  | ComposingClassBuilder classBuilder = existingEntry.getValue(); | 
|  | removeSignaturesFromBuilder(current, classBuilder); | 
|  | ComposingClassBuilder duplicateMapping = | 
|  | newClassBuilders.put(existingEntry.getKey(), classBuilder); | 
|  | if (duplicateMapping != null) { | 
|  | throw new MappingComposeException( | 
|  | "Duplicate class mapping. Both '" | 
|  | + classBuilder.getOriginalName() | 
|  | + "' and '" | 
|  | + classNameMapper.getClassNaming(existingEntry.getKey()).originalName | 
|  | + "' maps to '" | 
|  | + classBuilder.renamedName | 
|  | + "'."); | 
|  | } | 
|  | } | 
|  | } | 
|  | classBuilders = newClassBuilders; | 
|  | } | 
|  |  | 
|  | private void removeSignaturesFromBuilder( | 
|  | ComposingData current, ComposingClassBuilder classBuilder) { | 
|  | Set<Signature> signaturesToRemove = | 
|  | current.signaturesToRemove.get(classBuilder.getOriginalName()); | 
|  | if (signaturesToRemove == null) { | 
|  | return; | 
|  | } | 
|  | signaturesToRemove.forEach( | 
|  | signatureToRemove -> { | 
|  | if (signatureToRemove.isFieldSignature()) { | 
|  | classBuilder.fieldMembers.remove(signatureToRemove.asFieldSignature()); | 
|  | } else { | 
|  | classBuilder.methodsWithoutPosition.remove(signatureToRemove.asMethodSignature()); | 
|  | classBuilder.methodsWithPosition.remove(signatureToRemove.asMethodSignature()); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | public void addSignatureToRemove( | 
|  | ComposingClassBuilder composingClassBuilder, Signature signature) { | 
|  | signaturesToRemove | 
|  | .computeIfAbsent( | 
|  | composingClassBuilder.getOriginalName(), ignoreArgument(Sets::newHashSet)) | 
|  | .add(signature); | 
|  | } | 
|  |  | 
|  | private void commitRewriteFrameInformation( | 
|  | ComposingData current, ClassNameMapper classNameMapper) { | 
|  | // First update the existing frame information to have new class name mappings. | 
|  | Map<String, String> inverse = classNameMapper.getObfuscatedToOriginalMapping().inverse; | 
|  | for (RewriteFrameMappingInformation rewriteMappingInfo : rewriteFrameInformation) { | 
|  | rewriteMappingInfo | 
|  | .getConditions() | 
|  | .forEach( | 
|  | rewriteCondition -> { | 
|  | ThrowsCondition throwsCondition = rewriteCondition.asThrowsCondition(); | 
|  | if (throwsCondition != null) { | 
|  | throwsCondition.setClassReferenceInternal( | 
|  | mapTypeReference(inverse, throwsCondition.getClassReference()).asClass()); | 
|  | } | 
|  | }); | 
|  | } | 
|  | rewriteFrameInformation.addAll(current.rewriteFrameInformation); | 
|  | } | 
|  |  | 
|  | private void commitOutlineCallsiteInformation( | 
|  | ComposingData current, ClassNameMapper classNameMapper) { | 
|  | // To commit outline call site information, we take the previously committed and bring forward | 
|  | // to a new mapping, and potentially rewrite source positions if available. | 
|  | Map<ClassTypeNameAndMethodName, OutlineCallsiteMappingInformation> newOutlineCallsiteInfo = | 
|  | new HashMap<>(); | 
|  | Map<String, String> inverse = classNameMapper.getObfuscatedToOriginalMapping().inverse; | 
|  | outlineCallsiteInformation.forEach( | 
|  | (holderAndMethodNameOfOutline, outlineInfo) -> { | 
|  | UpdateOutlineCallsiteInformation updateOutlineCallsiteInformation = | 
|  | current.outlineSourcePositionsUpdated.get(holderAndMethodNameOfOutline); | 
|  | String newMethodName = outlineInfo.getOutline().getMethodName(); | 
|  | if (updateOutlineCallsiteInformation != null) { | 
|  | // We have a callsite mapping that we need to update. | 
|  | MappedRangeOriginalToMinifiedMap originalToMinifiedMap = | 
|  | MappedRangeOriginalToMinifiedMap.build( | 
|  | updateOutlineCallsiteInformation.newMappedRanges); | 
|  | Int2IntSortedMap newPositionMap = new Int2IntLinkedOpenHashMap(); | 
|  | outlineInfo | 
|  | .getPositions() | 
|  | .forEach( | 
|  | (originalPosition, destination) -> | 
|  | originalToMinifiedMap.visitMinified( | 
|  | originalPosition, | 
|  | newMinified -> newPositionMap.put(newMinified, destination))); | 
|  | outlineInfo.setPositionsInternal(newPositionMap); | 
|  | newMethodName = updateOutlineCallsiteInformation.newMethodName; | 
|  | } | 
|  | // Holder, return type or formals could have changed the outline descriptor. | 
|  | MethodReference outline = outlineInfo.getOutline(); | 
|  | ClassReference newHolder = | 
|  | mapTypeReference(inverse, outline.getHolderClass()).asClass(); | 
|  | outlineInfo.setOutlineInternal( | 
|  | Reference.method( | 
|  | newHolder, | 
|  | newMethodName, | 
|  | mapTypeReferences(inverse, outline.getFormalTypes()), | 
|  | mapTypeReference(inverse, outline.getReturnType()))); | 
|  | newOutlineCallsiteInfo.put( | 
|  | new ClassTypeNameAndMethodName( | 
|  | newHolder.getTypeName(), holderAndMethodNameOfOutline.getMethodName()), | 
|  | outlineInfo); | 
|  | }); | 
|  | newOutlineCallsiteInfo.putAll(current.outlineCallsiteInformation); | 
|  | outlineCallsiteInformation = newOutlineCallsiteInfo; | 
|  | } | 
|  |  | 
|  | public void addNewOutlineCallsiteInformation( | 
|  | MethodReference outline, OutlineCallsiteMappingInformation outlineCallsiteInfo) { | 
|  | outlineCallsiteInformation.put( | 
|  | new ClassTypeNameAndMethodName( | 
|  | outline.getHolderClass().getTypeName(), outline.getMethodName()), | 
|  | outlineCallsiteInfo); | 
|  | } | 
|  |  | 
|  | public UpdateOutlineCallsiteInformation getUpdateOutlineCallsiteInformation( | 
|  | String originalHolder, String originalMethodName, String newMethodName) { | 
|  | return outlineSourcePositionsUpdated.computeIfAbsent( | 
|  | new ClassTypeNameAndMethodName(originalHolder, originalMethodName), | 
|  | ignore -> new UpdateOutlineCallsiteInformation(newMethodName)); | 
|  | } | 
|  |  | 
|  | private List<TypeReference> mapTypeReferences( | 
|  | Map<String, String> typeNameMap, List<TypeReference> typeReferences) { | 
|  | return ListUtils.map(typeReferences, typeRef -> mapTypeReference(typeNameMap, typeRef)); | 
|  | } | 
|  |  | 
|  | private TypeReference mapTypeReference( | 
|  | Map<String, String> typeNameMap, TypeReference typeReference) { | 
|  | if (typeReference == null || typeReference.isPrimitive()) { | 
|  | return typeReference; | 
|  | } | 
|  | if (typeReference.isArray()) { | 
|  | ArrayReference arrayReference = typeReference.asArray(); | 
|  | return Reference.array( | 
|  | mapTypeReference(typeNameMap, arrayReference.getBaseType()), | 
|  | arrayReference.getDimensions()); | 
|  | } else { | 
|  | assert typeReference.isClass(); | 
|  | String newTypeName = typeNameMap.get(typeReference.getTypeName()); | 
|  | return newTypeName == null ? typeReference : Reference.classFromTypeName(newTypeName); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private static class ClassTypeNameAndMethodName { | 
|  |  | 
|  | private final String holderTypeName; | 
|  | private final String methodName; | 
|  |  | 
|  | public ClassTypeNameAndMethodName(String holderTypeName, String methodName) { | 
|  | this.holderTypeName = holderTypeName; | 
|  | this.methodName = methodName; | 
|  | } | 
|  |  | 
|  | public String getMethodName() { | 
|  | return methodName; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean equals(Object o) { | 
|  | if (this == o) { | 
|  | return true; | 
|  | } | 
|  | if (!(o instanceof ClassTypeNameAndMethodName)) { | 
|  | return false; | 
|  | } | 
|  | ClassTypeNameAndMethodName that = (ClassTypeNameAndMethodName) o; | 
|  | return holderTypeName.equals(that.holderTypeName) && methodName.equals(that.methodName); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int hashCode() { | 
|  | return Objects.hash(holderTypeName, methodName); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static class UpdateOutlineCallsiteInformation { | 
|  |  | 
|  | private List<MappedRange> newMappedRanges; | 
|  | private final String newMethodName; | 
|  |  | 
|  | private UpdateOutlineCallsiteInformation(String newMethodName) { | 
|  | this.newMethodName = newMethodName; | 
|  | } | 
|  |  | 
|  | private void setNewMappedRanges(List<MappedRange> mappedRanges) { | 
|  | newMappedRanges = mappedRanges; | 
|  | } | 
|  | } | 
|  |  | 
|  | private static class MappedRangeOriginalToMinifiedMap { | 
|  |  | 
|  | private final Int2ReferenceMap<List<Integer>> originalToMinified; | 
|  |  | 
|  | private MappedRangeOriginalToMinifiedMap(Int2ReferenceMap<List<Integer>> originalToMinified) { | 
|  | this.originalToMinified = originalToMinified; | 
|  | } | 
|  |  | 
|  | private static MappedRangeOriginalToMinifiedMap build(List<MappedRange> mappedRanges) { | 
|  | Int2ReferenceMap<List<Integer>> positionMap = new Int2ReferenceOpenHashMap<>(); | 
|  | for (MappedRange mappedRange : mappedRanges) { | 
|  | Range originalRange = mappedRange.getOriginalRangeOrIdentity(); | 
|  | for (int position = originalRange.from; position <= originalRange.to; position++) { | 
|  | // It is perfectly fine to have multiple minified ranges mapping to the same source, we | 
|  | // just need to keep the additional information. | 
|  | positionMap | 
|  | .computeIfAbsent(position, ignoreArgument(ArrayList::new)) | 
|  | .add(mappedRange.minifiedRange.from + (position - originalRange.from)); | 
|  | } | 
|  | } | 
|  | return new MappedRangeOriginalToMinifiedMap(positionMap); | 
|  | } | 
|  |  | 
|  | public void visitMinified(int originalPosition, Consumer<Integer> consumer) { | 
|  | List<Integer> minifiedPositions = originalToMinified.get(originalPosition); | 
|  | if (minifiedPositions != null) { | 
|  | minifiedPositions.forEach(consumer); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | public static class ComposingClassBuilder { | 
|  |  | 
|  | private static final String INDENTATION = "    "; | 
|  | private static final int NO_RANGE_FROM = -1; | 
|  |  | 
|  | private final String originalName; | 
|  | private final String renamedName; | 
|  | private final Map<FieldSignature, MemberNaming> fieldMembers = new HashMap<>(); | 
|  | private final Map<MethodSignature, SegmentTree<List<MappedRange>>> methodsWithPosition = | 
|  | new HashMap<>(); | 
|  | private final Map<MethodSignature, MappedRange> methodsWithoutPosition = new HashMap<>(); | 
|  | private final List<MappingInformation> additionalMappingInfo = new ArrayList<>(); | 
|  |  | 
|  | private final ComposingData committed; | 
|  | private final ComposingData current; | 
|  |  | 
|  | private final Map<String, ComposingClassBuilder> committedPreviousClassBuilders; | 
|  | private final ComposingClassBuilder committedPreviousClassBuilder; | 
|  | private final InternalOptions options; | 
|  |  | 
|  | private ComposingClassBuilder( | 
|  | String originalName, | 
|  | String renamedName, | 
|  | ComposingData committed, | 
|  | ComposingData current, | 
|  | InternalOptions options) { | 
|  | this.originalName = originalName; | 
|  | this.renamedName = renamedName; | 
|  | this.current = current; | 
|  | this.committed = committed; | 
|  | this.options = options; | 
|  | committedPreviousClassBuilders = committed.classBuilders; | 
|  | committedPreviousClassBuilder = committedPreviousClassBuilders.get(originalName); | 
|  | } | 
|  |  | 
|  | public String getOriginalName() { | 
|  | return originalName; | 
|  | } | 
|  |  | 
|  | public String getRenamedName() { | 
|  | return renamedName; | 
|  | } | 
|  |  | 
|  | public void compose(ClassNameMapper classNameMapper, ClassNamingForNameMapper mapper) | 
|  | throws MappingComposeException { | 
|  | List<MappingInformation> newMappingInfo = mapper.getAdditionalMappingInfo(); | 
|  | if (newMappingInfo != null) { | 
|  | additionalMappingInfo.addAll(newMappingInfo); | 
|  | } | 
|  | composeFieldNamings(mapper, classNameMapper); | 
|  | composeMethodNamings(mapper, classNameMapper); | 
|  | } | 
|  |  | 
|  | private void composeFieldNamings( | 
|  | ClassNamingForNameMapper mapper, ClassNameMapper classNameMapper) { | 
|  | Map<String, String> inverseClassMapping = | 
|  | classNameMapper.getObfuscatedToOriginalMapping().inverse; | 
|  | mapper.forAllFieldNaming( | 
|  | fieldNaming -> { | 
|  | MemberNaming fieldNamingToAdd = fieldNaming; | 
|  | FieldSignature originalSignature = | 
|  | fieldNaming.getOriginalSignature().asFieldSignature(); | 
|  | FieldSignature residualSignature = | 
|  | fieldNaming | 
|  | .computeResidualSignature(type -> inverseClassMapping.getOrDefault(type, type)) | 
|  | .asFieldSignature(); | 
|  | MemberNaming existingMemberNaming = getExistingMemberNaming(originalSignature); | 
|  | if (existingMemberNaming != null) { | 
|  | Signature existingOriginalSignature = existingMemberNaming.getOriginalSignature(); | 
|  | if (!existingOriginalSignature.isQualified() && originalSignature.isQualified()) { | 
|  | String previousCommittedClassName = | 
|  | getPreviousCommittedClassName(originalSignature.toHolderFromQualified()); | 
|  | if (previousCommittedClassName != null) { | 
|  | existingOriginalSignature = | 
|  | existingOriginalSignature.toQualifiedSignature(previousCommittedClassName); | 
|  | } | 
|  | } | 
|  | fieldNamingToAdd = new MemberNaming(existingOriginalSignature, residualSignature); | 
|  | } | 
|  | MemberNaming existing = fieldMembers.put(residualSignature, fieldNamingToAdd); | 
|  | assert existing == null; | 
|  | }); | 
|  | } | 
|  |  | 
|  | private String getPreviousCommittedClassName(String holder) { | 
|  | ComposingClassBuilder composingClassBuilder = committedPreviousClassBuilders.get(holder); | 
|  | return composingClassBuilder == null ? null : composingClassBuilder.getOriginalName(); | 
|  | } | 
|  |  | 
|  | private MemberNaming getExistingMemberNaming(FieldSignature originalSignature) { | 
|  | ComposingClassBuilder composingClassBuilder = | 
|  | originalSignature.isQualified() | 
|  | ? committedPreviousClassBuilders.get(originalSignature.toHolderFromQualified()) | 
|  | : committedPreviousClassBuilder; | 
|  | if (composingClassBuilder == null) { | 
|  | return null; | 
|  | } | 
|  | FieldSignature signature = | 
|  | (originalSignature.isQualified() | 
|  | ? originalSignature.toUnqualifiedSignature() | 
|  | : originalSignature) | 
|  | .asFieldSignature(); | 
|  | current.addSignatureToRemove(composingClassBuilder, signature); | 
|  | return composingClassBuilder.fieldMembers.get(signature); | 
|  | } | 
|  |  | 
|  | private List<MappedRange> getExistingMappedRanges(MappedRange newMappedRange) | 
|  | throws MappingComposeException { | 
|  | MethodSignature signature = newMappedRange.getOriginalSignature().toUnqualifiedIfQualified(); | 
|  | SegmentTree<List<MappedRange>> listSegmentTree = methodsWithPosition.get(signature); | 
|  | // If there are no previously mapped positions for the range check methods without position. | 
|  | if (listSegmentTree == null) { | 
|  | MappedRange existingMappedRange = methodsWithoutPosition.get(signature); | 
|  | return existingMappedRange == null ? null : Collections.singletonList(existingMappedRange); | 
|  | } | 
|  | // Any new transformation can be lossy - for example, D8 only promises to keep line | 
|  | // positions if the method is not throwing (and is not debug). Additionally, R8/PG | 
|  | // emits `1:1:void foo() -> a` instead of `1:1:void foo():1:1 -> a`, so R8 must | 
|  | // capture the preamble position by explicitly inserting 0 as original range. | 
|  | int firstPositionOfOriginalRange = | 
|  | newMappedRange.getFirstPositionOfOriginalRange(NO_RANGE_FROM); | 
|  | Entry<Integer, List<MappedRange>> firstPositionEntries = | 
|  | listSegmentTree.findEntry(firstPositionOfOriginalRange); | 
|  | if (firstPositionEntries != null) { | 
|  | return firstPositionEntries.getValue(); | 
|  | } | 
|  | // If the first position is 0 we can try at find the existing mapped ranges by last | 
|  | // position. | 
|  | if (firstPositionOfOriginalRange == 0 && !newMappedRange.isOriginalRangePreamble()) { | 
|  | Entry<Integer, List<MappedRange>> lastPositionEntries = | 
|  | listSegmentTree.findEntry(newMappedRange.getLastPositionOfOriginalRange()); | 
|  | if (lastPositionEntries != null) { | 
|  | return lastPositionEntries.getValue(); | 
|  | } | 
|  | } | 
|  | // We assume that all new minified ranges for a method are rewritten in the new map | 
|  | // such that no previous existing positions exists. | 
|  | // The original can be discarded if it no longer exists or if the method is | 
|  | // non-throwing. | 
|  | if (!newMappedRange.isOriginalRangePreamble() | 
|  | && !options.mappingComposeOptions().allowNonExistingOriginalRanges) { | 
|  | throw new MappingComposeException( | 
|  | "Could not find original starting position of '" | 
|  | + newMappedRange.minifiedRange.from | 
|  | + "' which should be " | 
|  | + firstPositionOfOriginalRange); | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | private void composeMethodNamings( | 
|  | ClassNamingForNameMapper mapper, ClassNameMapper classNameMapper) | 
|  | throws MappingComposeException { | 
|  | Map<String, String> inverseClassMapping = | 
|  | classNameMapper.getObfuscatedToOriginalMapping().inverse; | 
|  | for (Entry<String, MappedRangesOfName> entry : mapper.mappedRangesByRenamedName.entrySet()) { | 
|  | List<MappedRangesOfName> mappedRangesOfNames = | 
|  | entry.getValue().partitionOnMethodSignature(); | 
|  | for (MappedRangesOfName rangesOfName : mappedRangesOfNames) { | 
|  | List<MappedRange> newRangesToCompose = rangesOfName.getMappedRanges(); | 
|  | RangeBuilder minified = new RangeBuilder(); | 
|  | // The new minified ranges may have ranges that range over positions that has additional | 
|  | // information, such as inlinees: | 
|  | // Existing: | 
|  | //  1:2:void caller():14:15 -> x | 
|  | //  3:3:void inlinee():42:42 -> x | 
|  | //  3:3:void caller():16:16 -> x | 
|  | //  4:10:void caller():17:23 -> x | 
|  | //  ... | 
|  | // New mapping: | 
|  | //  1:5:void x():1:5 -> y | 
|  | //  6:10:void x():6:6 -> y | 
|  | //  ... | 
|  | // It is important that we therefore split up the new ranges to map everything back to the | 
|  | // existing original mappings: | 
|  | // Resulting mapping: | 
|  | //  1:2:void caller():14:15 -> y | 
|  | //  3:3:void inlinee():42:42 -> y | 
|  | //  3:3:void caller():16:16 -> y | 
|  | //  4:5:void caller():17:18 -> y | 
|  | //  6:10:void caller():19:19 -> y | 
|  | //  ... | 
|  | List<MappedRange> composedRanges = new ArrayList<>(); | 
|  | ComputedOutlineInformation computedOutlineInformation = new ComputedOutlineInformation(); | 
|  | List<MappedRange> composedInlineFrames = new ArrayList<>(); | 
|  | for (int i = 0; i < newRangesToCompose.size(); i++) { | 
|  | MappedRange mappedRange = newRangesToCompose.get(i); | 
|  | minified.addRange(mappedRange.minifiedRange); | 
|  | // Register mapping information that is dependent on the residual naming to allow | 
|  | // updating later on. | 
|  | registerMappingInformationFromMappedRanges(mappedRange); | 
|  |  | 
|  | MethodSignature originalSignature = | 
|  | mappedRange.getOriginalSignature().asMethodSignature(); | 
|  |  | 
|  | List<MappedRange> existingMappedRanges = null; | 
|  | ComposingClassBuilder existingClassBuilder = getExistingClassBuilder(originalSignature); | 
|  | if (existingClassBuilder != null) { | 
|  | MethodSignature signature = | 
|  | (originalSignature.isQualified() | 
|  | ? originalSignature.toUnqualifiedSignature() | 
|  | : originalSignature) | 
|  | .asMethodSignature(); | 
|  | // Remove the existing entry since we are now composing the signature. | 
|  | current.addSignatureToRemove(existingClassBuilder, signature); | 
|  | existingMappedRanges = existingClassBuilder.getExistingMappedRanges(mappedRange); | 
|  | assert existingMappedRanges != null | 
|  | || minified.hasValue() | 
|  | || mappedRange.getOriginalRangeOrIdentity() == null; | 
|  | } | 
|  | // Now that we have obtained a potential existing mapping, we can compose the frames. | 
|  | List<MappedRange> newComposedRange = | 
|  | composeMappedRangesForMethod( | 
|  | existingClassBuilder, | 
|  | existingMappedRanges, | 
|  | mappedRange, | 
|  | computedOutlineInformation, | 
|  | !composedInlineFrames.isEmpty()); | 
|  | composedInlineFrames = composeInlineFrames(newComposedRange, composedInlineFrames); | 
|  | if (!isInlineMappedRangeForComposition(newRangesToCompose, i)) { | 
|  | composedRanges.addAll(composedInlineFrames); | 
|  | composedInlineFrames = Collections.emptyList(); | 
|  | } | 
|  | } | 
|  | composedRanges = | 
|  | fixupOutlines(computedOutlineInformation, newRangesToCompose, composedRanges); | 
|  | MethodSignature residualSignature = | 
|  | rangesOfName | 
|  | .getMemberNaming(mapper) | 
|  | .computeResidualSignature(type -> inverseClassMapping.getOrDefault(type, type)) | 
|  | .asMethodSignature(); | 
|  | if (ListUtils.last(composedRanges).minifiedRange != null) { | 
|  | SegmentTree<List<MappedRange>> listSegmentTree = | 
|  | methodsWithPosition.computeIfAbsent( | 
|  | residualSignature, ignored -> new SegmentTree<>(false)); | 
|  | listSegmentTree.add( | 
|  | minified.getStartOrNoRangeFrom(), minified.getEndOrNoRangeFrom(), composedRanges); | 
|  | } else { | 
|  | assert composedRanges.size() == 1; | 
|  | methodsWithoutPosition.put(residualSignature, ListUtils.last(composedRanges)); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private List<MappedRange> fixupOutlines( | 
|  | ComputedOutlineInformation computedOutlineInformation, | 
|  | List<MappedRange> newRangesToBeComposed, | 
|  | List<MappedRange> composedRanges) | 
|  | throws MappingComposeException { | 
|  | composedRanges = fixupInlinedOutlines(computedOutlineInformation, composedRanges); | 
|  | fixupOutlineInformation(computedOutlineInformation, newRangesToBeComposed, composedRanges); | 
|  | fixupOutlineCallsiteInformation(computedOutlineInformation, composedRanges); | 
|  | return composedRanges; | 
|  | } | 
|  |  | 
|  | @SuppressWarnings("ReferenceEquality") | 
|  | private List<MappedRange> fixupInlinedOutlines( | 
|  | ComputedOutlineInformation computedOutlineInformation, List<MappedRange> composedRanges) | 
|  | throws MappingComposeException { | 
|  | // Check if we could have inlined an outline which is true if we see both an outline and call | 
|  | // site to patch up. When an outline has been inlined we have to retrace through the inlined | 
|  | // positions, by positions only existing in the previous mapped ranges, to form new ranges. | 
|  | if (computedOutlineInformation.seenOutlineMappingInformation.isEmpty() | 
|  | || computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp.isEmpty()) { | 
|  | return composedRanges; | 
|  | } | 
|  | Set<OutlineCallsiteMappingInformation> outlineCallSitesToRemove = Sets.newIdentityHashSet(); | 
|  | Set<OutlineMappingInformation> outlinesToRemove = Sets.newIdentityHashSet(); | 
|  | // We patch up all ranges from top to bottom with the invariant that all at a given | 
|  | // index, all above have been updated correctly. We will do expansion of frames | 
|  | // when separating out single minified lines, but we keep the outline information | 
|  | // present such that we can fix them when seeing them later. | 
|  | int composedRangeIndex = 0; | 
|  | while (composedRangeIndex < composedRanges.size() - 1) { | 
|  | MappedRange outline = composedRanges.get(composedRangeIndex++); | 
|  | MappedRange outlineCallSite = composedRanges.get(composedRangeIndex); | 
|  | if (outline.isOutlineFrame() | 
|  | && isInlineMappedRangeForComposition(outline, outlineCallSite)) { | 
|  | // We should replace the inlined outline frame positions with the synthesized | 
|  | // positions from the outline call site. | 
|  | if (outlineCallSite.getOutlineCallsiteInformation().size() != 1) { | 
|  | // If we have an inlined outline it must be such that the outer frame is an | 
|  | // outline callsite. | 
|  | throw new MappingComposeException( | 
|  | "Expected exactly one outline call site for a mapped range with signature '" | 
|  | + outlineCallSite.getOriginalSignature() | 
|  | + "'."); | 
|  | } | 
|  | OutlineCallsiteMappingInformation outlineCallSiteInformation = | 
|  | outlineCallSite.getOutlineCallsiteInformation().get(0); | 
|  | // The original positions in the outline callsite have been composed, so we have to | 
|  | // find the existing mapped range and iterate the original positions for that range. | 
|  | ComputedMappedRangeForOutline computedInformationForCallSite = | 
|  | computedOutlineInformation.getComputedRange( | 
|  | outlineCallSiteInformation, outlineCallSite); | 
|  | if (computedInformationForCallSite == null) { | 
|  | continue; | 
|  | } | 
|  | Map<Integer, List<MappedRange>> mappedRangesForOutline = | 
|  | new HashMap<>(outlineCallSiteInformation.getPositions().size()); | 
|  | visitOutlineMappedPositions( | 
|  | outlineCallSiteInformation, | 
|  | computedInformationForCallSite.current.getOriginalSignature(), | 
|  | positionInfo -> | 
|  | mappedRangesForOutline.put( | 
|  | positionInfo.outlinePosition(), positionInfo.mappedRanges())); | 
|  | List<MappedRange> newComposedRanges = new ArrayList<>(); | 
|  | // Copy all previous handled mapped ranges into a new list. | 
|  | for (MappedRange previousMappedRanges : composedRanges) { | 
|  | if (previousMappedRanges == outline) { | 
|  | break; | 
|  | } | 
|  | newComposedRanges.add(previousMappedRanges); | 
|  | } | 
|  | // The original positions in the outline have been composed, so we have to find the | 
|  | // existing mapped range and iterate the original positions for that range. | 
|  | ComputedMappedRangeForOutline computedInformationForOutline = | 
|  | computedOutlineInformation.getComputedRange( | 
|  | outline.getOutlineMappingInformation(), outline); | 
|  | if (computedInformationForOutline == null) { | 
|  | continue; | 
|  | } | 
|  | // The outline could have additional inlined positions in it, but we should be | 
|  | // guaranteed to find call site information on all original line numbers. We | 
|  | // therefore iterate one by one and amend the subsequent outer frames as well. | 
|  | MappedRange current = computedInformationForOutline.current; | 
|  | int minifiedLine = outline.minifiedRange.from; | 
|  | for (int originalLine = current.getOriginalRangeOrIdentity().from; | 
|  | originalLine <= current.getOriginalRangeOrIdentity().to; | 
|  | originalLine++) { | 
|  | // If the outline is itself an inline frame it is bound to only have one original | 
|  | // position and we can simply insert all inline frames on that position with the | 
|  | // existing minified range. | 
|  | Range newMinifiedRange = | 
|  | outline.originalRange.isCardinal | 
|  | ? outline.minifiedRange | 
|  | : new Range(minifiedLine, minifiedLine); | 
|  | List<MappedRange> outlineMappedRanges = mappedRangesForOutline.get(originalLine); | 
|  | if (outlineMappedRanges != null) { | 
|  | outlineMappedRanges.forEach( | 
|  | range -> { | 
|  | if (range != ListUtils.last(outlineMappedRanges)) { | 
|  | newComposedRanges.add( | 
|  | new MappedRange( | 
|  | newMinifiedRange, | 
|  | range.getOriginalSignature().asMethodSignature(), | 
|  | range.originalRange, | 
|  | outlineCallSite.getRenamedName())); | 
|  | } | 
|  | }); | 
|  | newComposedRanges.add( | 
|  | new MappedRange( | 
|  | newMinifiedRange, | 
|  | outlineCallSite.getOriginalSignature().asMethodSignature(), | 
|  | ListUtils.last(outlineMappedRanges).originalRange, | 
|  | outlineCallSite.getRenamedName())); | 
|  | } | 
|  | for (int tailInlineFrameIndex = composedRangeIndex + 1; | 
|  | tailInlineFrameIndex < composedRanges.size(); | 
|  | tailInlineFrameIndex++) { | 
|  | MappedRange originalMappedRange = composedRanges.get(tailInlineFrameIndex); | 
|  | if (!originalMappedRange.minifiedRange.equals(outlineCallSite.minifiedRange)) { | 
|  | break; | 
|  | } | 
|  | MappedRange newMappedRange = originalMappedRange.withMinifiedRange(newMinifiedRange); | 
|  | newMappedRange.setAdditionalMappingInformationInternal( | 
|  | originalMappedRange.getAdditionalMappingInformation()); | 
|  | newComposedRanges.add(newMappedRange); | 
|  | } | 
|  | minifiedLine++; | 
|  | } | 
|  | // We have patched up the the inlined outline and all subsequent inline frames | 
|  | // (although some of the subsequent frames above could also be inlined outlines). We | 
|  | // therefore need to copy the remaining frames. | 
|  | boolean seenMinifiedRange = false; | 
|  | for (MappedRange range : composedRanges) { | 
|  | if (range.minifiedRange.equals(outline.minifiedRange)) { | 
|  | seenMinifiedRange = true; | 
|  | } else if (seenMinifiedRange) { | 
|  | newComposedRanges.add(range); | 
|  | } | 
|  | } | 
|  | composedRanges = newComposedRanges; | 
|  | outlineCallSitesToRemove.add(outlineCallSiteInformation); | 
|  | outlinesToRemove.add(outline.getOutlineMappingInformation()); | 
|  | } | 
|  | } | 
|  | // If we removed any outlines or call sites, remove the processing of them. | 
|  | outlineCallSitesToRemove.forEach( | 
|  | computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp::remove); | 
|  | outlinesToRemove.forEach(computedOutlineInformation.seenOutlineMappingInformation::remove); | 
|  | return composedRanges; | 
|  | } | 
|  |  | 
|  | private void fixupOutlineInformation( | 
|  | ComputedOutlineInformation computedOutlineInformation, | 
|  | List<MappedRange> newRangesToBeComposed, | 
|  | List<MappedRange> composedRanges) { | 
|  | if (computedOutlineInformation.seenOutlineMappingInformation.isEmpty()) { | 
|  | return; | 
|  | } | 
|  | MappedRange lastComposedRange = ListUtils.last(composedRanges); | 
|  | current | 
|  | .getUpdateOutlineCallsiteInformation( | 
|  | committedPreviousClassBuilder.getRenamedName(), | 
|  | ListUtils.last(newRangesToBeComposed).signature.getName(), | 
|  | lastComposedRange.getRenamedName()) | 
|  | .setNewMappedRanges(newRangesToBeComposed); | 
|  | } | 
|  |  | 
|  | private void fixupOutlineCallsiteInformation( | 
|  | ComputedOutlineInformation computedOutlineInformation, | 
|  | List<MappedRange> composedRanges) | 
|  | throws MappingComposeException { | 
|  | Map<OutlineCallsiteMappingInformation, List<ComputedMappedRangeForOutline>> | 
|  | outlineCallsitesToPatchUp = | 
|  | computedOutlineInformation.outlineCallsiteMappingInformationToPatchUp; | 
|  | if (outlineCallsitesToPatchUp.isEmpty()) { | 
|  | return; | 
|  | } | 
|  | MappedRange lastComposedRange = ListUtils.last(composedRanges); | 
|  | // Outline positions are synthetic positions, and they have no position in the residual | 
|  | // program. We therefore have to find the original positions and copy all inline frames | 
|  | // and amend the outermost frame with the residual signature and the next free position. | 
|  | List<OutlineCallsiteMappingInformation> outlineCallSites = | 
|  | ListUtils.sort( | 
|  | outlineCallsitesToPatchUp.keySet(), | 
|  | Comparator.comparing(mapping -> mapping.getOutline().toString())); | 
|  | IntBox firstAvailableRange = new IntBox(lastComposedRange.minifiedRange.to + 1); | 
|  | for (OutlineCallsiteMappingInformation outlineCallSite : outlineCallSites) { | 
|  | // It should be sufficient to patch any call-site because they are referencing the same | 
|  | // outline call-site information due to using an identity hashmap. | 
|  | List<ComputedMappedRangeForOutline> computedMappedRangeForOutlines = | 
|  | outlineCallsitesToPatchUp.get(outlineCallSite); | 
|  | assert verifyAllOutlineCallSitesAreEqualTo(outlineCallSite, computedMappedRangeForOutlines); | 
|  | ComputedMappedRangeForOutline computedMappedRangeForOutline = | 
|  | ListUtils.first(computedMappedRangeForOutlines); | 
|  | Int2IntSortedMap newPositionMap = | 
|  | new Int2IntLinkedOpenHashMap(outlineCallSite.getPositions().size()); | 
|  | visitOutlineMappedPositions( | 
|  | outlineCallSite, | 
|  | computedMappedRangeForOutline.current.getOriginalSignature(), | 
|  | positionInfo -> { | 
|  | int newIndex = firstAvailableRange.getAndIncrement(); | 
|  | Range newMinifiedRange = new Range(newIndex, newIndex); | 
|  | boolean isCaller = false; | 
|  | for (MappedRange existingMappedRange : positionInfo.mappedRanges()) { | 
|  | int originalPosition = | 
|  | existingMappedRange.getOriginalLineNumber( | 
|  | positionInfo.outlineCallsitePosition()); | 
|  | Range newOriginalRange = | 
|  | isCaller | 
|  | ? new Range(originalPosition) | 
|  | : new Range(originalPosition, originalPosition); | 
|  | MappedRange newMappedRange = | 
|  | new MappedRange( | 
|  | newMinifiedRange, | 
|  | existingMappedRange.getOriginalSignature(), | 
|  | newOriginalRange, | 
|  | lastComposedRange.getRenamedName()); | 
|  | if (!existingMappedRange.getAdditionalMappingInformation().isEmpty()) { | 
|  | newMappedRange.setAdditionalMappingInformationInternal( | 
|  | existingMappedRange.getAdditionalMappingInformation()); | 
|  | } | 
|  | composedRanges.add(newMappedRange); | 
|  | isCaller = true; | 
|  | } | 
|  | newPositionMap.put(positionInfo.outlinePosition(), newIndex); | 
|  | }); | 
|  | outlineCallSite.setPositionsInternal(newPositionMap); | 
|  | } | 
|  | } | 
|  |  | 
|  | private boolean verifyAllOutlineCallSitesAreEqualTo( | 
|  | OutlineCallsiteMappingInformation outlineCallSite, | 
|  | List<ComputedMappedRangeForOutline> computedMappedRangeForOutlines) { | 
|  | for (ComputedMappedRangeForOutline computedMappedRangeForOutline : | 
|  | computedMappedRangeForOutlines) { | 
|  | for (OutlineCallsiteMappingInformation outlineCallsiteMappingInformation : | 
|  | computedMappedRangeForOutline.composed.getOutlineCallsiteInformation()) { | 
|  | assert outlineCallSite == outlineCallsiteMappingInformation; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * The class contains a list of mapped ranges for a single outline position. | 
|  | * | 
|  | * <p>Outline positions is a key-value map of positions in the outline to positions in the | 
|  | * callsite. An instance of OutlineCallSitePositionWithMappedRanges contains the information | 
|  | * about a single position. | 
|  | */ | 
|  | public interface OutlineCallSitePositionWithMappedRanges { | 
|  | int outlinePosition(); | 
|  |  | 
|  | int outlineCallsitePosition(); | 
|  |  | 
|  | List<MappedRange> mappedRanges(); | 
|  | } | 
|  |  | 
|  | private void visitOutlineMappedPositions( | 
|  | OutlineCallsiteMappingInformation outlineCallSite, | 
|  | MethodSignature originalSignature, | 
|  | Consumer<OutlineCallSitePositionWithMappedRanges> outlinePositionConsumer) | 
|  | throws MappingComposeException { | 
|  | Int2IntSortedMap positionMap = outlineCallSite.getPositions(); | 
|  | ComposingClassBuilder existingClassBuilder = getExistingClassBuilder(originalSignature); | 
|  | if (existingClassBuilder == null) { | 
|  | throw new MappingComposeException( | 
|  | "Could not find builder with original signature '" + originalSignature + "'."); | 
|  | } | 
|  | SegmentTree<List<MappedRange>> outlineSegmentTree = | 
|  | existingClassBuilder.methodsWithPosition.get( | 
|  | originalSignature.toUnqualifiedSignatureIfQualified().asMethodSignature()); | 
|  | if (outlineSegmentTree == null) { | 
|  | throw new MappingComposeException( | 
|  | "Could not find method positions for original signature '" + originalSignature + "'."); | 
|  | } | 
|  | for (Integer keyPosition : positionMap.keySet()) { | 
|  | int outlinePosition = keyPosition; | 
|  | int callsitePosition = positionMap.get(outlinePosition); | 
|  | List<MappedRange> mappedRanges = outlineSegmentTree.find(callsitePosition); | 
|  | if (mappedRanges == null) { | 
|  | throw new MappingComposeException( | 
|  | "Could not find ranges for outline position '" | 
|  | + keyPosition | 
|  | + "' with original signature '" | 
|  | + originalSignature | 
|  | + "'."); | 
|  | } | 
|  | ExistingMappings existingMappings = ExistingMappings.create(mappedRanges); | 
|  | List<MappedRange> mappedRangesForOutlinePosition = | 
|  | existingMappings.getPreviousRanges(callsitePosition); | 
|  | if (mappedRangesForOutlinePosition == null | 
|  | || mappedRangesForOutlinePosition.isEmpty() | 
|  | || !mappedRangesForOutlinePosition.get(0).minifiedRange.contains(callsitePosition)) { | 
|  | throw new MappingComposeException( | 
|  | "Could not find ranges for outline position '" | 
|  | + keyPosition | 
|  | + "' with original signature '" | 
|  | + originalSignature | 
|  | + "'."); | 
|  | } | 
|  | outlinePositionConsumer.accept( | 
|  | new OutlineCallSitePositionWithMappedRanges() { | 
|  | @Override | 
|  | public int outlinePosition() { | 
|  | return outlinePosition; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int outlineCallsitePosition() { | 
|  | return callsitePosition; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public List<MappedRange> mappedRanges() { | 
|  | return mappedRangesForOutlinePosition; | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Utility function to compose inline frames | 
|  | * | 
|  | * <p>Say that we have the following input: | 
|  | * | 
|  | * <pre> | 
|  | *   previousInlineFrames: | 
|  | *     10:10:void m1():10:10 -> w | 
|  | *     10:10:void y():30 -> w | 
|  | *     11:11:void m2(int):20:20 -> w | 
|  | *     11:11:void y():31 -> w | 
|  | *   newComposedRanges: | 
|  | *     10:11:void new_synthetic_method():0 -> w | 
|  | * </pre> | 
|  | * | 
|  | * The newComposedRanges are outer frames, but we cannot place them below since their range | 
|  | * overlap. We therefore need to place a copy of newComposedRanges into the correct position at | 
|  | * the minified range to produce: | 
|  | * | 
|  | * <pre> | 
|  | *   result: | 
|  | *     10:10:void m1():10 -> w | 
|  | *     10:10:void y():30:30 -> w | 
|  | *     10:10:void new_synthetic_method():0 -> w | 
|  | *     11:11:void m2(int):20 -> w | 
|  | *     11:11:void y():31:31 -> w | 
|  | *     11:11:void new_synthetic_method():0 -> w | 
|  | * </pre> | 
|  | */ | 
|  | private List<MappedRange> composeInlineFrames( | 
|  | List<MappedRange> newComposedRanges, List<MappedRange> previousInlineFrames) { | 
|  | if (previousInlineFrames.isEmpty()) { | 
|  | return newComposedRanges; | 
|  | } | 
|  | List<MappedRange> newComposedInlineFrames = new ArrayList<>(); | 
|  | Range lastMinifiedRange = previousInlineFrames.get(0).minifiedRange; | 
|  | for (MappedRange previousFrame : previousInlineFrames) { | 
|  | if (!lastMinifiedRange.equals(previousFrame.minifiedRange)) { | 
|  | for (MappedRange newComposedRange : newComposedRanges) { | 
|  | newComposedInlineFrames.add( | 
|  | newComposedRange.partitionOnMinifiedRange(lastMinifiedRange)); | 
|  | } | 
|  | lastMinifiedRange = previousFrame.minifiedRange; | 
|  | } | 
|  | newComposedInlineFrames.add(previousFrame.partitionOnMinifiedRange(lastMinifiedRange)); | 
|  | } | 
|  | for (MappedRange newComposedRange : newComposedRanges) { | 
|  | newComposedInlineFrames.add(newComposedRange.partitionOnMinifiedRange(lastMinifiedRange)); | 
|  | } | 
|  | return newComposedInlineFrames; | 
|  | } | 
|  |  | 
|  | private ComposingClassBuilder getExistingClassBuilder(MethodSignature originalSignature) { | 
|  | return originalSignature.isQualified() | 
|  | ? committedPreviousClassBuilders.get(originalSignature.toHolderFromQualified()) | 
|  | : committedPreviousClassBuilder; | 
|  | } | 
|  |  | 
|  | @SuppressWarnings("MixedMutabilityReturnType") | 
|  | private void registerMappingInformationFromMappedRanges(MappedRange mappedRange) | 
|  | throws MappingComposeException { | 
|  | for (MappingInformation mappingInformation : mappedRange.getAdditionalMappingInformation()) { | 
|  | if (mappingInformation.isRewriteFrameMappingInformation()) { | 
|  | RewriteFrameMappingInformation rewriteFrameMappingInformation = | 
|  | mappingInformation.asRewriteFrameMappingInformation(); | 
|  | rewriteFrameMappingInformation | 
|  | .getConditions() | 
|  | .forEach( | 
|  | condition -> { | 
|  | if (condition.isThrowsCondition()) { | 
|  | current.rewriteFrameInformation.add(rewriteFrameMappingInformation); | 
|  | } | 
|  | }); | 
|  | } else if (mappingInformation.isOutlineCallsiteInformation()) { | 
|  | OutlineCallsiteMappingInformation outlineCallsiteInfo = | 
|  | mappingInformation.asOutlineCallsiteInformation(); | 
|  | MethodReference outline = outlineCallsiteInfo.getOutline(); | 
|  | if (outline == null) { | 
|  | throw new MappingComposeException( | 
|  | "Unable to compose outline call site information without outline key: " | 
|  | + outlineCallsiteInfo.serialize()); | 
|  | } | 
|  | current.addNewOutlineCallsiteInformation(outline, outlineCallsiteInfo); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | @SuppressWarnings("MixedMutabilityReturnType") | 
|  | private List<MappedRange> composeMappedRangesForMethod( | 
|  | ComposingClassBuilder existingClassBuilder, | 
|  | List<MappedRange> existingRanges, | 
|  | MappedRange newRange, | 
|  | ComputedOutlineInformation computedOutlineInformation, | 
|  | boolean isInlineFrame) | 
|  | throws MappingComposeException { | 
|  | assert newRange != null; | 
|  | // If there are no existing ranges, then just return the new range. | 
|  | if (existingRanges == null || existingRanges.isEmpty()) { | 
|  | return Collections.singletonList(newRange); | 
|  | } | 
|  | MappedRange lastExistingRange = ListUtils.last(existingRanges); | 
|  | // Check if there are no range information in the original range. If not we do a simple | 
|  | // composition of methods. | 
|  | if (newRange.getOriginalRangeOrIdentity() == null) { | 
|  | return Collections.singletonList( | 
|  | composeOnMethodSignature(existingClassBuilder, newRange, lastExistingRange)); | 
|  | } | 
|  | ExistingMappings mappedRangesForPosition = ExistingMappings.create(existingRanges); | 
|  | List<MappedRange> newComposedRanges = new ArrayList<>(); | 
|  | assert newRange.minifiedRange != null; | 
|  | int minifiedStart = newRange.minifiedRange.from; | 
|  | int minifiedEnd = newRange.minifiedRange.to; | 
|  | int originalLineNumberEnd = newRange.getOriginalLineNumber(minifiedEnd); | 
|  | while (minifiedStart <= minifiedEnd) { | 
|  | int originalLineNumber = newRange.getOriginalLineNumber(minifiedStart); | 
|  | // First query if there is an interval ranging over the minifiedStart: | 
|  | // [--existing--] | 
|  | //        [minifiedStart----minifiedEnd] | 
|  | // Note that if existing mapped range has no line information, it will start at position -1. | 
|  | List<MappedRange> existingRangesForPosition = | 
|  | mappedRangesForPosition.getPreviousRanges(originalLineNumber); | 
|  | // If we find no previous ranges or the previous range is outside the minified start, we | 
|  | // query for an interval defined later: | 
|  | //                      [--existing--] | 
|  | // [minifiedStart----minifiedEnd] | 
|  | if (isRangeNullOrBefore(existingRangesForPosition, originalLineNumber)) { | 
|  | existingRangesForPosition = mappedRangesForPosition.getNextRanges(originalLineNumber); | 
|  | } | 
|  | // Check if we have the case: | 
|  | // [--existing--]                                [--existing--] | 
|  | //                [minifiedStart----minifiedEnd] | 
|  | // Where the previous existing interval is not the special one defined on -1. | 
|  | if (isRangeNullOrAfter(existingRangesForPosition, originalLineNumberEnd)) { | 
|  | Range minifiedRange = new Range(minifiedStart, minifiedEnd); | 
|  | newComposedRanges.add(newRange.copyWithNewRanges(minifiedRange, minifiedRange)); | 
|  | minifiedStart = minifiedEnd + 1; | 
|  | } else { | 
|  | // Check if we have an existing mapped range without line information. | 
|  | MappedRange lastMappedRange = ListUtils.last(existingRangesForPosition); | 
|  | if (lastMappedRange.minifiedRange == null) { | 
|  | return Collections.singletonList( | 
|  | composeOnMethodSignature(existingClassBuilder, newRange, lastExistingRange)); | 
|  | } | 
|  | // If we have the case: | 
|  | //                    [--existing--] | 
|  | // [minifiedStart----minifiedEnd] | 
|  | // We have to insert a mapped range from minifiedStart to existingStart. | 
|  | if (originalLineNumber < lastMappedRange.minifiedRange.from) { | 
|  | int tempMinifiedTo = | 
|  | minifiedStart + (lastMappedRange.minifiedRange.from - originalLineNumber - 1); | 
|  | Range minifiedRange = new Range(minifiedStart, tempMinifiedTo); | 
|  | Range originalRange = | 
|  | new Range(originalLineNumber, lastMappedRange.minifiedRange.from - 1); | 
|  | newComposedRanges.add( | 
|  | newRange.copyWithNewRanges( | 
|  | minifiedRange, originalRange.isPreamble() ? originalRange : minifiedRange)); | 
|  | minifiedStart = tempMinifiedTo + 1; | 
|  | } else { | 
|  | // Otherwise we have the case where we can compose directly up until the first end. If | 
|  | // the starting point matches directly, we've found the range when looking up | 
|  | // previousRanges since interval search is inclusive, the intervals are disjoint and the | 
|  | // interval is registered at minifiedStart. | 
|  | // [--existing--] | 
|  | //    [minifiedStart----minifiedEnd] | 
|  | int span = computeSpan(minifiedStart, minifiedEnd, newRange, lastMappedRange); | 
|  | composeMappedRange( | 
|  | existingClassBuilder, | 
|  | newComposedRanges, | 
|  | newRange, | 
|  | existingRangesForPosition, | 
|  | computedOutlineInformation, | 
|  | minifiedStart, | 
|  | minifiedStart + span, | 
|  | isInlineFrame); | 
|  | minifiedStart += span + 1; | 
|  | } | 
|  | } | 
|  | } | 
|  | return newComposedRanges; | 
|  | } | 
|  |  | 
|  | private boolean isRangeNullOrBefore( | 
|  | List<MappedRange> existingRangesForPosition, int originalLineNumber) { | 
|  | return existingRangesForPosition == null | 
|  | || existingRangesForPosition.isEmpty() | 
|  | || (ListUtils.last(existingRangesForPosition).minifiedRange != null | 
|  | && ListUtils.last(existingRangesForPosition).minifiedRange.to < originalLineNumber); | 
|  | } | 
|  |  | 
|  | private boolean isRangeNullOrAfter( | 
|  | List<MappedRange> existingRangesForPosition, int originalLineNumberEnd) { | 
|  | return existingRangesForPosition == null | 
|  | || (ListUtils.last(existingRangesForPosition).minifiedRange != null | 
|  | && originalLineNumberEnd | 
|  | < ListUtils.last(existingRangesForPosition).minifiedRange.from); | 
|  | } | 
|  |  | 
|  | private int computeSpan( | 
|  | int minifiedStart, int minifiedEnd, MappedRange newRange, MappedRange lastMappedRange) { | 
|  | int minifiedSpan = minifiedEnd - minifiedStart; | 
|  | if (minifiedSpan >= 1 && newRange.getOriginalRangeOrIdentity().isSingleLine()) { | 
|  | return minifiedSpan; | 
|  | } | 
|  | return Math.min( | 
|  | minifiedSpan, | 
|  | lastMappedRange.minifiedRange.to - newRange.getOriginalLineNumber(minifiedStart)); | 
|  | } | 
|  |  | 
|  | private MappedRange composeOnMethodSignature( | 
|  | ComposingClassBuilder existingClassBuilder, | 
|  | MappedRange newRange, | 
|  | MappedRange lastExistingRange) | 
|  | throws MappingComposeException { | 
|  | Range originalRange; | 
|  | if (newRange.getOriginalRangeOrIdentity() == null) { | 
|  | originalRange = null; | 
|  | } else { | 
|  | originalRange = | 
|  | lastExistingRange.originalRange != null | 
|  | && lastExistingRange.originalRange.isSingleLine() | 
|  | ? lastExistingRange.originalRange | 
|  | : EMPTY_RANGE; | 
|  | } | 
|  | MappedRange newComposedRange = | 
|  | new MappedRange( | 
|  | newRange.minifiedRange, | 
|  | potentiallyQualifySignature( | 
|  | newRange.signature, | 
|  | lastExistingRange.signature, | 
|  | existingClassBuilder.getOriginalName()), | 
|  | originalRange, | 
|  | newRange.renamedName); | 
|  | composeMappingInformation( | 
|  | newComposedRange.getAdditionalMappingInformation(), | 
|  | lastExistingRange.getAdditionalMappingInformation(), | 
|  | info -> newComposedRange.addMappingInformation(info, ConsumerUtils.emptyConsumer())); | 
|  | return newComposedRange; | 
|  | } | 
|  |  | 
|  | /*** | 
|  | * Builds a range to mapped ranges for mappings by building a lookup table sorted on the | 
|  | * starting coordinate. | 
|  | */ | 
|  | private static class ExistingMappings { | 
|  |  | 
|  | private final TreeMap<Integer, List<MappedRange>> mappedRangesForPosition; | 
|  |  | 
|  | private ExistingMappings(TreeMap<Integer, List<MappedRange>> mappedRangesForPosition) { | 
|  | this.mappedRangesForPosition = mappedRangesForPosition; | 
|  | } | 
|  |  | 
|  | private static ExistingMappings create(List<MappedRange> existingRanges) { | 
|  | TreeMap<Integer, List<MappedRange>> mappedRangesForPosition = new TreeMap<>(); | 
|  | if (!existingRanges.isEmpty()) { | 
|  | ImmutableList.Builder<MappedRange> builder = ImmutableList.builder(); | 
|  | Range lastRange = existingRanges.get(0).minifiedRange; | 
|  | if (lastRange == null) { | 
|  | assert existingRanges.size() == 1; | 
|  | mappedRangesForPosition.put( | 
|  | NO_RANGE_FROM, Collections.singletonList(existingRanges.get(0))); | 
|  | } else { | 
|  | for (MappedRange mappedRange : existingRanges) { | 
|  | if (!lastRange.equals(mappedRange.minifiedRange)) { | 
|  | mappedRangesForPosition.put(lastRange.from, builder.build()); | 
|  | builder = new ImmutableList.Builder<>(); | 
|  | lastRange = mappedRange.minifiedRange; | 
|  | } | 
|  | builder.add(mappedRange); | 
|  | } | 
|  | mappedRangesForPosition.put(lastRange.from, builder.build()); | 
|  | } | 
|  | } | 
|  | return new ExistingMappings(mappedRangesForPosition); | 
|  | } | 
|  |  | 
|  | private List<MappedRange> getNextRanges(int startPosition) { | 
|  | Integer ceilingKey = mappedRangesForPosition.ceilingKey(startPosition); | 
|  | return ceilingKey == null ? null : mappedRangesForPosition.get(ceilingKey); | 
|  | } | 
|  |  | 
|  | private List<MappedRange> getPreviousRanges(int startPosition) { | 
|  | Integer floorKey = mappedRangesForPosition.floorKey(startPosition); | 
|  | return floorKey == null ? null : mappedRangesForPosition.get(floorKey); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void composeMappedRange( | 
|  | ComposingClassBuilder existingClassBuilder, | 
|  | List<MappedRange> newComposedRanges, | 
|  | MappedRange newMappedRange, | 
|  | List<MappedRange> existingMappedRanges, | 
|  | ComputedOutlineInformation computedOutlineInformation, | 
|  | int lastStartingMinifiedFrom, | 
|  | int lastEndingMinifiedTo, | 
|  | boolean isInlineFrame) | 
|  | throws MappingComposeException { | 
|  | Range existingRange = existingMappedRanges.get(0).minifiedRange; | 
|  | assert existingMappedRanges.stream().allMatch(x -> x.minifiedRange.equals(existingRange)); | 
|  | Range newMinifiedRange = new Range(lastStartingMinifiedFrom, lastEndingMinifiedTo); | 
|  | for (MappedRange existingMappedRange : existingMappedRanges) { | 
|  | Range existingOriginalRange = existingMappedRange.getOriginalRangeOrIdentity(); | 
|  | Range newOriginalRange; | 
|  | if (newMappedRange.minifiedRange.equals(newMinifiedRange) | 
|  | && canUseOriginalRange( | 
|  | existingOriginalRange, newMappedRange.getOriginalRangeOrIdentity())) { | 
|  | newOriginalRange = existingOriginalRange; | 
|  | } else { | 
|  | // Find the window that the new range points to into the original range. | 
|  | int existingMinifiedPos = newMappedRange.getOriginalLineNumber(lastStartingMinifiedFrom); | 
|  | int newOriginalStart = existingMappedRange.getOriginalLineNumber(existingMinifiedPos); | 
|  | if (isInlineFrame || existingOriginalRange.isCardinal) { | 
|  | newOriginalRange = new Range(newOriginalStart); | 
|  | } else if (newMappedRange.getOriginalRangeOrIdentity().span() == 1) { | 
|  | newOriginalRange = new Range(newOriginalStart, newOriginalStart); | 
|  | } else { | 
|  | assert newMinifiedRange.span() <= existingOriginalRange.span(); | 
|  | newOriginalRange = | 
|  | new Range(newOriginalStart, newOriginalStart + newMinifiedRange.span() - 1); | 
|  | } | 
|  | } | 
|  | MappedRange computedRange = | 
|  | new MappedRange( | 
|  | newMinifiedRange, | 
|  | potentiallyQualifySignature( | 
|  | newMappedRange.signature, | 
|  | existingMappedRange.signature, | 
|  | existingClassBuilder.getOriginalName()), | 
|  | newOriginalRange, | 
|  | newMappedRange.renamedName); | 
|  | List<MappingInformation> mappingInformationToCompose = new ArrayList<>(); | 
|  | existingMappedRange | 
|  | .getAdditionalMappingInformation() | 
|  | .forEach( | 
|  | info -> { | 
|  | if (info.isOutlineMappingInformation()) { | 
|  | computedOutlineInformation | 
|  | .seenOutlineMappingInformation | 
|  | .computeIfAbsent( | 
|  | info.asOutlineMappingInformation(), ignoreArgument(ArrayList::new)) | 
|  | .add(new ComputedMappedRangeForOutline(newMappedRange, computedRange)); | 
|  | } else if (info.isOutlineCallsiteInformation()) { | 
|  | computedOutlineInformation | 
|  | .outlineCallsiteMappingInformationToPatchUp | 
|  | .computeIfAbsent( | 
|  | info.asOutlineCallsiteInformation(), ignoreArgument(ArrayList::new)) | 
|  | .add(new ComputedMappedRangeForOutline(newMappedRange, computedRange)); | 
|  | } | 
|  | mappingInformationToCompose.add(info); | 
|  | }); | 
|  | composeMappingInformation( | 
|  | computedRange.getAdditionalMappingInformation(), | 
|  | mappingInformationToCompose, | 
|  | info -> computedRange.addMappingInformation(info, ConsumerUtils.emptyConsumer())); | 
|  | newComposedRanges.add(computedRange); | 
|  | } | 
|  | } | 
|  |  | 
|  | private boolean canUseOriginalRange(Range existingRange, Range newRange) { | 
|  | if (newRange.equals(existingRange)) { | 
|  | return true; | 
|  | } | 
|  | if (newRange.isCardinal) { | 
|  | return false; | 
|  | } | 
|  | return existingRange == null || existingRange.span() == 1; | 
|  | } | 
|  |  | 
|  | /*** | 
|  | * Populates newMappingInformation with existingMappingInformation. | 
|  | */ | 
|  | private void composeMappingInformation( | 
|  | List<MappingInformation> newMappingInformation, | 
|  | List<MappingInformation> existingMappingInformation, | 
|  | Consumer<MappingInformation> consumer) | 
|  | throws MappingComposeException { | 
|  | Set<MappingInformation> nonCompasableNewInfos = Sets.newIdentityHashSet(); | 
|  | for (MappingInformation existingInfo : existingMappingInformation) { | 
|  | boolean hasBeenComposed = false; | 
|  | for (MappingInformation newInfo : newMappingInformation) { | 
|  | if (newInfo.shouldCompose(existingInfo)) { | 
|  | nonCompasableNewInfos.add(newInfo); | 
|  | consumer.accept(newInfo.compose(existingInfo)); | 
|  | hasBeenComposed = true; | 
|  | } | 
|  | } | 
|  | if (!hasBeenComposed) { | 
|  | consumer.accept(existingInfo); | 
|  | } | 
|  | } | 
|  | newMappingInformation.forEach( | 
|  | info -> { | 
|  | if (!nonCompasableNewInfos.contains(info) && !info.isFileNameInformation()) { | 
|  | consumer.accept(info); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | public void write(ChainableStringConsumer consumer) { | 
|  | consumer.accept(originalName).accept(" -> ").accept(renamedName).accept(":\n"); | 
|  | additionalMappingInfo.forEach(info -> consumer.accept("# " + info.serialize()).accept("\n")); | 
|  | writeFields(consumer); | 
|  | writeMethods(consumer); | 
|  | } | 
|  |  | 
|  | private void writeFields(ChainableStringConsumer consumer) { | 
|  | ArrayList<MemberNaming> fieldNamings = new ArrayList<>(fieldMembers.values()); | 
|  | fieldNamings.sort(Comparator.comparing(MemberNaming::getOriginalName)); | 
|  | fieldNamings.forEach( | 
|  | naming -> consumer.accept(INDENTATION).accept(naming.toString()).accept("\n")); | 
|  | } | 
|  |  | 
|  | private void writeMethods(ChainableStringConsumer consumer) { | 
|  | methodsWithoutPosition.forEach( | 
|  | (ignored, mapped) -> { | 
|  | consumer.accept(INDENTATION).accept(mapped.toString()).accept("\n"); | 
|  | for (MappingInformation info : mapped.getAdditionalMappingInformation()) { | 
|  | consumer.accept(INDENTATION).accept("# ").accept(info.serialize()).accept("\n"); | 
|  | } | 
|  | }); | 
|  | ArrayList<MethodSignature> sortedSignatures = new ArrayList<>(methodsWithPosition.keySet()); | 
|  | sortedSignatures.sort(Comparator.comparing(Signature::getName)); | 
|  | for (MethodSignature key : sortedSignatures) { | 
|  | methodsWithPosition | 
|  | .get(key) | 
|  | .visitSegments( | 
|  | mappedRanges -> | 
|  | mappedRanges.forEach( | 
|  | mappedRange -> { | 
|  | consumer.accept(INDENTATION).accept(mappedRange.toString()).accept("\n"); | 
|  | for (MappingInformation info : | 
|  | mappedRange.getAdditionalMappingInformation()) { | 
|  | consumer | 
|  | .accept(INDENTATION) | 
|  | .accept("# ") | 
|  | .accept(info.serialize()) | 
|  | .accept("\n"); | 
|  | } | 
|  | })); | 
|  | } | 
|  | } | 
|  |  | 
|  | public ComposingClassBuilder commit(ComposingClassBuilder classBuilder) | 
|  | throws MappingComposeException { | 
|  | ComposingClassBuilder newClassBuilder = | 
|  | new ComposingClassBuilder( | 
|  | originalName, classBuilder.renamedName, committed, null, options); | 
|  | composeMappingInformation( | 
|  | classBuilder.additionalMappingInfo, | 
|  | additionalMappingInfo, | 
|  | newClassBuilder.additionalMappingInfo::add); | 
|  | // Composed field namings and method namings should be freely composable by addition since | 
|  | // any renaming/position change should have removed the existing committed mapping. | 
|  | putAll( | 
|  | newClassBuilder.fieldMembers, | 
|  | fieldMembers, | 
|  | classBuilder.fieldMembers, | 
|  | (committed, add) -> { | 
|  | assert committed == null; | 
|  | return add; | 
|  | }); | 
|  | putAll( | 
|  | newClassBuilder.methodsWithoutPosition, | 
|  | methodsWithoutPosition, | 
|  | classBuilder.methodsWithoutPosition, | 
|  | (committed, add) -> { | 
|  | if (committed != null && add != null) { | 
|  | throw new MappingComposeException( | 
|  | "Cannot compose duplicate methods without position in class '" | 
|  | + renamedName | 
|  | + "': '" | 
|  | + committed | 
|  | + "' and '" | 
|  | + add); | 
|  | } | 
|  | return committed != null ? committed : add; | 
|  | }); | 
|  | putAll( | 
|  | newClassBuilder.methodsWithPosition, | 
|  | methodsWithPosition, | 
|  | classBuilder.methodsWithPosition, | 
|  | (committed, add) -> add); | 
|  | return newClassBuilder; | 
|  | } | 
|  |  | 
|  | private <S extends Signature, V> void putAll( | 
|  | Map<S, V> output, | 
|  | Map<S, V> committed, | 
|  | Map<S, V> toAdd, | 
|  | ThrowingBiFunction<V, V, V, MappingComposeException> compose) | 
|  | throws MappingComposeException { | 
|  | assert output.isEmpty(); | 
|  | output.putAll(committed); | 
|  | for (Entry<S, V> kvEntry : toAdd.entrySet()) { | 
|  | output.put( | 
|  | kvEntry.getKey(), compose.apply(output.get(kvEntry.getKey()), kvEntry.getValue())); | 
|  | } | 
|  | } | 
|  |  | 
|  | private MethodSignature potentiallyQualifySignature( | 
|  | MethodSignature newSignature, MethodSignature signature, String originalHolder) { | 
|  | return !newSignature.isQualified() || signature.isQualified() | 
|  | ? signature | 
|  | : new MethodSignature( | 
|  | originalHolder + "." + signature.name, signature.type, signature.parameters); | 
|  | } | 
|  |  | 
|  | private static class RangeBuilder { | 
|  |  | 
|  | private int start = Integer.MAX_VALUE; | 
|  | private int end = Integer.MIN_VALUE; | 
|  |  | 
|  | private void addRange(Range range) { | 
|  | if (range != null) { | 
|  | start = Math.min(start, range.from); | 
|  | end = Math.max(end, range.to); | 
|  | } | 
|  | } | 
|  |  | 
|  | private boolean hasValue() { | 
|  | return start < Integer.MAX_VALUE; | 
|  | } | 
|  |  | 
|  | private int getStartOrNoRangeFrom() { | 
|  | return hasValue() ? start : NO_RANGE_FROM; | 
|  | } | 
|  |  | 
|  | private int getEndOrNoRangeFrom() { | 
|  | return hasValue() ? end : NO_RANGE_FROM; | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | private static class ComputedOutlineInformation { | 
|  | private final Map<OutlineCallsiteMappingInformation, List<ComputedMappedRangeForOutline>> | 
|  | outlineCallsiteMappingInformationToPatchUp = new IdentityHashMap<>(); | 
|  | private final Map<OutlineMappingInformation, List<ComputedMappedRangeForOutline>> | 
|  | seenOutlineMappingInformation = new IdentityHashMap<>(); | 
|  |  | 
|  | private ComputedMappedRangeForOutline getComputedRange( | 
|  | MappingInformation outline, MappedRange current) { | 
|  | List<ComputedMappedRangeForOutline> outlineMappingInformations = | 
|  | outline.isOutlineMappingInformation() | 
|  | ? seenOutlineMappingInformation.get(outline.asOutlineMappingInformation()) | 
|  | : outlineCallsiteMappingInformationToPatchUp.get( | 
|  | outline.asOutlineCallsiteInformation()); | 
|  | if (outlineMappingInformations == null) { | 
|  | return null; | 
|  | } | 
|  | return ListUtils.firstMatching( | 
|  | outlineMappingInformations, | 
|  | pair -> pair.composed.minifiedRange.contains(current.minifiedRange.from)); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static class ComputedMappedRangeForOutline { | 
|  | private final MappedRange current; | 
|  | private final MappedRange composed; | 
|  |  | 
|  | private ComputedMappedRangeForOutline(MappedRange current, MappedRange composed) { | 
|  | this.current = current; | 
|  | this.composed = composed; | 
|  | } | 
|  | } | 
|  | } | 
|  | } |