@Override
	public void autoMerge(int progressMin, int progressMax, TaskMonitor monitor)
			throws ProgramConflictException, MemoryAccessException, CancelledException {

		initializeAutoMerge("Auto-merging User Defined Properties and determining conflicts.",
			progressMin, progressMax, monitor);

		// Get where each changed user defined props.
		// Check the overlap to see which truly are conflicts.
		AddressSetView latestDetailSet =
			listingMergeMgr.diffOriginalLatest.getTypeDiffs(ProgramDiffFilter.USER_DEFINED_DIFFS,
				listingMergeMgr.latestSet, monitor);
		myDetailSet =
			listingMergeMgr.diffOriginalMy.getTypeDiffs(ProgramDiffFilter.USER_DEFINED_DIFFS,
				listingMergeMgr.mySet, monitor);
		AddressSet tmpAutoSet = new AddressSet();
		AddressSet overlapSet = new AddressSet();
		MergeUtilities.adjustSets(latestDetailSet, myDetailSet, tmpAutoSet, overlapSet);
		// mergeProperties() won't try to merge Unsupported user defined properties.
		listingMergeMgr.mergeMy.mergeProperties(tmpAutoSet, monitor);

		propNames = getPropertyNames();
		int numProps = propNames.length;
		totalChanges = numProps;
		changeNum = 0;
		conflictSets = new AddressSet[numProps];
		sameOption = new int[numProps];
		for (int i = 0; i < numProps; i++) {
			conflictSets[i] = new AddressSet();
			sameOption[i] = ASK_USER;
		}

		for (int i = 0; i < propNames.length; i++) {
			propertyIndex = i;
			PropertyMap<?> latestMap = latestPMM.getPropertyMap(propNames[i]);
			PropertyMap<?> myMap = myPMM.getPropertyMap(propNames[i]);
			PropertyMap<?> originalMap = originalPMM.getPropertyMap(propNames[i]);
			// Handle case where the class for a Saveable property is missing.
			if (isUnsupportedMap(latestMap) || isUnsupportedMap(myMap)) {
				String msg =
					"Encountered unsupported property: " + propNames[i] +
						"\nYour Ghidra may be missing the java class for this property." +
						"\n\nAny changes you have made to this property type will be lost" +
						"\nif you check-in your changes.";
				Msg.showError(this, this.listingMergePanel, "User Defined Property Merge Error",
					msg);
				continue; // ignore property that isn't supported.
			}
			if (!samePropertyTypes(latestMap, myMap)) {

				// TODO: improve handling of incompatibl map types - address level conflicts
				// resolution may be inappropriate since you can't pick-and-choose - only one map
				// can be retained. (see GP-2585)

				String msg =
					LATEST_TITLE + " and " + MY_TITLE +
						" program versions do not have the same type for '" +
						propNames[i] + "' property.";
				Msg.showError(this, this.listingMergePanel, "User Defined Property Merge Error",
					msg);
			}
			else if (isUnsupportedMap(latestMap) || isUnsupportedMap(myMap)) {
				String msg =
					LATEST_TITLE + " and/or " + MY_TITLE + " program versions have unsupported " +
						"property map '" + propNames[i] + "' which will be ignored.";
				Msg.showError(this, this.listingMergePanel, "User Defined Property Merge Error",
					msg);
				continue;
			}

			AddressIterator latestIter =
				(latestMap != null) ? latestMap.getPropertyIterator(overlapSet) : null;
			AddressIterator myIter = (myMap != null) ? myMap.getPropertyIterator(overlapSet) : null;
			AddressIterator originalIter =
				(originalMap != null) ? originalMap.getPropertyIterator(overlapSet) : null;

			MultiAddressIterator addrIter =
				new MultiAddressIterator(new AddressIterator[] { latestIter, myIter, originalIter });
			while (addrIter.hasNext()) {
				Address addr = addrIter.next();
				Object latestObj = latestMap != null ? latestMap.get(addr) : null;
				Object myObj = myMap != null ? myMap.get(addr) : null;
				Object originalObj = originalMap != null ? originalMap.get(addr) : null;

				boolean sameLatestMy = Objects.equals(latestObj, myObj);
				if (sameLatestMy) {
					// My is already like latest, so do nothing.
					continue;
				}
				boolean sameOriginalLatest = Objects.equals(originalObj, latestObj);
				boolean sameOriginalMy = Objects.equals(originalObj, myObj);
				if (sameOriginalLatest) {
					// Only My changed so autoMerge.
					merge(propNames[i], addr, KEEP_MY);
				}
				else if (!sameOriginalMy) {
					// Both changed, so conflict.
					conflictSets[i].addRange(addr, addr);
					conflictSet.addRange(addr, addr);
				}
				// Otherwise only latest changed, so do nothing.
			}
			incrementProgress(1);
		}

		updateProgress(100, "Done auto-merging User Defined Properties and determining conflicts.");
	}
Constructs a user defined properties merger.
@param listingMergeMgr the listing merge manager that owns this merger.
Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/UserDefinedPropertyMerger.java