com.google.common.collect.Maps#difference ( )源码实例Demo

下面列出了com.google.common.collect.Maps#difference ( ) 实例代码,或者点击链接到github查看源代码,也可以在右侧发表评论。

@Override
protected boolean doEquivalent(SolrInputField o1, SolrInputField o2) {
  if (o1.getValue() instanceof SolrInputDocument) {
    if (!(o2.getValue() instanceof SolrInputDocument)) {
      return false;
    }
    final MapDifference<String, SolrInputField> difference = Maps.difference(
        (SolrInputDocument) o1.getValue(),
        (SolrInputDocument) o2.getValue(),
        this
    );
    if (!difference.areEqual()) {
      return false;
    }
  } else {
    if (o1.getValue() != o2.getValue()) {
      return false;
    }
  }
  return true;
}
 
源代码2 项目: kylin-on-parquet-v2   文件: Coordinator.java
private CubeAssignment reassignCubeImpl(String cubeName, CubeAssignment preAssignments,
        CubeAssignment newAssignments) {
    logger.info("start cube reBalance, cube:{}, previous assignments:{}, new assignments:{}", cubeName,
            preAssignments, newAssignments);
    if (newAssignments.equals(preAssignments)) {
        logger.info("the new assignment is the same as the previous assignment, do nothing for this reassignment");
        return newAssignments;
    }
    CubeInstance cubeInstance = CubeManager.getInstance(KylinConfig.getInstanceFromEnv()).getCube(cubeName);
    doReassign(cubeInstance, preAssignments, newAssignments);
    MapDifference<Integer, List<Partition>> assignDiff = Maps.difference(preAssignments.getAssignments(),
            newAssignments.getAssignments());

    // add empty partitions to the removed replica sets, means that there's still data in the replica set, but no new data will be consumed.
    Map<Integer, List<Partition>> removedAssign = assignDiff.entriesOnlyOnLeft();
    for (Integer removedReplicaSet : removedAssign.keySet()) {
        newAssignments.addAssignment(removedReplicaSet, Lists.<Partition> newArrayList());
    }
    streamMetadataStore.saveNewCubeAssignment(newAssignments);
    AssignmentsCache.getInstance().clearCubeCache(cubeName);
    return newAssignments;
}
 
@NotAtomicAndNotIdempotent
void reassignCubeImpl(String cubeName, CubeAssignment preAssignments, CubeAssignment newAssignments) {
    logger.info("start cube reBalance, cube:{}, previous assignments:{}, new assignments:{}", cubeName,
            preAssignments, newAssignments);
    if (newAssignments.equals(preAssignments)) {
        logger.info("the new assignment is the same as the previous assignment, do nothing for this reassignment");
        return;
    }
    CubeInstance cubeInstance = getCoordinator().getCubeManager().getCube(cubeName);
    doReassignWithoutCommit(cubeInstance, preAssignments, newAssignments);

    // add empty partitions to the removed replica sets, means that there's still data in the replica set, but no new data will be consumed.
    MapDifference<Integer, List<Partition>> assignDiff = Maps.difference(preAssignments.getAssignments(),
            newAssignments.getAssignments());
    Map<Integer, List<Partition>> removedAssign = assignDiff.entriesOnlyOnLeft();
    for (Integer removedReplicaSet : removedAssign.keySet()) {
        newAssignments.addAssignment(removedReplicaSet, Lists.<Partition> newArrayList());
    }

    logger.info("Commit reassign {} transaction.", cubeName);
    getCoordinator().getStreamMetadataStore().saveNewCubeAssignment(newAssignments);
    AssignmentsCache.getInstance().clearCubeCache(cubeName);
}
 
源代码4 项目: attic-aurora   文件: JobDiff.java
private static JobDiff computeUnscoped(
    Map<Integer, ITaskConfig> currentState,
    IJobKey job,
    Map<Integer, ITaskConfig> proposedState) {

  requireNonNull(job);
  requireNonNull(proposedState);

  MapDifference<Integer, ITaskConfig> diff = Maps.difference(currentState, proposedState);

  Map<Integer, ITaskConfig> removedInstances = ImmutableMap.<Integer, ITaskConfig>builder()
      .putAll(diff.entriesOnlyOnLeft())
      .putAll(Maps.transformValues(diff.entriesDiffering(), JobDiff.leftValue()))
      .build();

  Set<Integer> addedInstances = ImmutableSet.<Integer>builder()
      .addAll(diff.entriesOnlyOnRight().keySet())
      .addAll(diff.entriesDiffering().keySet())
      .build();

  return new JobDiff(
      removedInstances,
      addedInstances,
      ImmutableMap.copyOf(diff.entriesInCommon()));
}
 
源代码5 项目: onedev   文件: DefaultMembershipManager.java
@Override
public void syncMemberships(User user, Collection<String> groupNames) {
   	Map<String, Membership> syncMap = new HashMap<>();
   	for (String groupName: groupNames) {
   		Group group = groupManager.find(groupName);
   		if (group == null) {
   			logger.warn("Unable to find group: " + groupName);
   		} else {
   			Membership membership = new Membership();
   			membership.setGroup(group);
   			membership.setUser(user);
   			syncMap.put(groupName, membership);
   		}
   	}

   	Map<String, Membership> currentMap = new HashMap<>();
	user.getMemberships().forEach(membership -> 
			currentMap.put(membership.getGroup().getName(), membership));
	
	MapDifference<String, Membership> diff = Maps.difference(currentMap, syncMap);
	
	diff.entriesOnlyOnLeft().values().forEach(membership -> delete(membership));
	diff.entriesOnlyOnRight().values().forEach(membership -> save(membership));		
}
 
static void validateRegions(Map<String, Collection<String>> regionsToAdd,
                            Map<String, Collection<String>> supportedRegions) {
  MapDifference<String, Collection<String>>
      comparison =
      Maps.difference(regionsToAdd, supportedRegions);
  checkArgument(comparison.entriesOnlyOnLeft().isEmpty(), "unsupported regions: %s", comparison
      .entriesOnlyOnLeft().keySet());
  for (Entry<String, Collection<String>> entry : regionsToAdd.entrySet()) {
    ImmutableSet<String> toAdd = ImmutableSet.copyOf(entry.getValue());
    SetView<String> intersection = Sets.intersection(toAdd,
                                                     ImmutableSet.copyOf(
                                                         supportedRegions.get(entry.getKey())));
    SetView<String> unsupported = Sets.difference(toAdd, intersection);
    checkArgument(unsupported.isEmpty(), "unsupported territories in %s:", entry.getKey(),
                  unsupported);
  }
}
 
源代码7 项目: onedev   文件: DefaultSshKeyManager.java
@Transactional
  @Override
  public void syncSshKeys(User user, Collection<String> sshKeys) {
  	Map<String, SshKey> syncMap = new HashMap<>();
  	for (String content: sshKeys) {
  		try {
  			PublicKey pubEntry = SshKeyUtils.decodeSshPublicKey(content);
  	        String digest = KeyUtils.getFingerPrint(SshKey.DIGEST_FORMAT, pubEntry);
  			
  	        SshKey sshKey = new SshKey();
  	        sshKey.setDigest(digest);
  	        sshKey.setContent(content);
  	        sshKey.setOwner(user);
  	        sshKey.setDate(new Date());
  	        syncMap.put(content, sshKey);
  		} catch (IOException | GeneralSecurityException e) {
  			logger.error("Error parsing SSH key", e);
  		}
  	}

  	Map<String, SshKey> currentMap = new HashMap<>();
user.getSshKeys().forEach(sshKey -> currentMap.put(sshKey.getContent(), sshKey));

MapDifference<String, SshKey> diff = Maps.difference(currentMap, syncMap);

diff.entriesOnlyOnLeft().values().forEach(sshKey -> delete(sshKey));

diff.entriesOnlyOnRight().values().forEach(sshKey -> {
	if (findByDigest(sshKey.getDigest()) == null) 
		save(sshKey);	
	else 
		logger.warn("SSH key is already in use (digest: {})", sshKey.getDigest());
});

  }
 
源代码8 项目: airsonic   文件: JWTAuthenticationProvider.java
private static boolean roughlyEqual(String expectedRaw, String requestedPathRaw) {
    LOG.debug("Comparing expected [{}] vs requested [{}]", expectedRaw, requestedPathRaw);
    if (StringUtils.isEmpty(expectedRaw)) {
        LOG.debug("False: empty expected");
        return false;
    }
    try {
        UriComponents expected = UriComponentsBuilder.fromUriString(expectedRaw).build();
        UriComponents requested = UriComponentsBuilder.fromUriString(requestedPathRaw).build();

        if (!Objects.equals(expected.getPath(), requested.getPath())) {
            LOG.debug("False: expected path [{}] does not match requested path [{}]",
                    expected.getPath(), requested.getPath());
            return false;
        }

        MapDifference<String, List<String>> difference = Maps.difference(expected.getQueryParams(),
                requested.getQueryParams());

        if (!difference.entriesDiffering().isEmpty() ||
                !difference.entriesOnlyOnLeft().isEmpty() ||
                difference.entriesOnlyOnRight().size() != 1 ||
                difference.entriesOnlyOnRight().get(JWTSecurityService.JWT_PARAM_NAME) == null) {
            LOG.debug("False: expected query params [{}] do not match requested query params [{}]", expected.getQueryParams(), requested.getQueryParams());
            return false;
        }

    } catch (Exception e) {
        LOG.warn("Exception encountered while comparing paths", e);
        return false;
    }
    return true;
}
 
源代码9 项目: syndesis   文件: AbstractResourceUpdateHandler.java
protected List<LeveledMessage> computePropertiesDiffMessages(
    Supplier<LeveledMessage.Builder> supplier, Map<String, ConfigurationProperty> left, Map<String, ConfigurationProperty> right) {

    final List<LeveledMessage> messages = new ArrayList<>();
    final MapDifference<String, ConfigurationProperty> diff = Maps.difference(left, right);

    for (Map.Entry<String, MapDifference.ValueDifference<ConfigurationProperty>> entry: diff.entriesDiffering().entrySet()) {
        final MapDifference.ValueDifference<ConfigurationProperty> value = entry.getValue();
        final ConfigurationProperty leftValue = value.leftValue();
        final ConfigurationProperty rightValue = value.rightValue();

        // Special handling because of dynamic metadata
        if (!equals(leftValue, rightValue)) {
            messages.add(
                supplier.get()
                    .level(LeveledMessage.Level.INFO)
                    .code(LeveledMessage.Code.SYNDESIS001).build()
            );

            break;
        }
    }

    if (!diff.entriesOnlyOnLeft().isEmpty() || !diff.entriesOnlyOnRight().isEmpty()) {
        messages.add(
            supplier.get()
                .level(LeveledMessage.Level.WARN)
                .code(LeveledMessage.Code.SYNDESIS002).build()
        );
    }

    return messages;
}
 
源代码10 项目: oxTrust   文件: ConfigureNameIdAction.java
public Map<String, String> getAvailableNamedIds(NameIdConfig config) {
	MapDifference<String, String> diff = Maps.difference(availableNamedIds, usedNamedIds);
	Map<String, String> value = diff.entriesOnlyOnLeft();
	Map<String, String> result = Maps.newHashMap(value);
	if (config.getNameIdType() != null) {
		result.put(config.getNameIdType(), config.getNameIdType());
	}
	return result;
}
 
源代码11 项目: metacat   文件: DirectSqlDatabase.java
/**
 * Updates the database object.
 * @param databaseInfo database object
 */
public void update(final DatabaseInfo databaseInfo) {
    log.debug("Start: Database update using direct sql for {}", databaseInfo.getName());
    final long start = registry.clock().wallTime();
    try {
        final Long databaseId = getDatabaseId(databaseInfo.getName());
        final DatabaseInfo existingDatabaseInfo = getDatabaseById(databaseId, databaseInfo.getName());
        final Map<String, String> newMetadata = databaseInfo.getMetadata() == null ? Maps.newHashMap()
            : databaseInfo.getMetadata();
        final MapDifference<String, String> diff = Maps.difference(existingDatabaseInfo.getMetadata(), newMetadata);
        insertDatabaseParams(databaseId, diff.entriesOnlyOnRight());
        final Map<String, String> updateParams = diff.entriesDiffering().entrySet().stream()
            .collect(Collectors.toMap(Map.Entry::getKey, s -> s.getValue().rightValue()));
        updateDatabaseParams(databaseId, updateParams);
        final String uri =
            Strings.isNullOrEmpty(databaseInfo.getUri()) ? existingDatabaseInfo.getUri() : databaseInfo.getUri();
        final String newOwner = getOwner(databaseInfo.getAudit());
        final String owner =
            Strings.isNullOrEmpty(newOwner) ? newOwner : existingDatabaseInfo.getAudit().getCreatedBy();
        jdbcTemplate.update(SQL.UPDATE_DATABASE, new SqlParameterValue(Types.VARCHAR, uri),
            new SqlParameterValue(Types.VARCHAR, owner),
            new SqlParameterValue(Types.BIGINT, databaseId));
    } finally {
        this.fastServiceMetric.recordTimer(
            HiveMetrics.TagAlterDatabase.getMetricName(), registry.clock().wallTime() - start);
        log.debug("End: Database update using direct sql for {}", databaseInfo.getName());
    }
}
 
源代码12 项目: connect-utils   文件: GenericAssertions.java
public static void assertMap(Map<String, ?> expected, Map<String, ?> actual, String message) {
  if (null == expected && null == actual) {
    return;
  }

  String prefix = Strings.isNullOrEmpty(message) ? "" : message + ": ";
  assertNotNull(expected, prefix + "expected cannot be null");
  assertNotNull(actual, prefix + "actual cannot be null");
  MapDifference<String, ?> mapDifference = Maps.difference(expected, actual);
  assertTrue(mapDifference.areEqual(), new MapDifferenceSupplier(mapDifference, prefix));
}
 
private static boolean roughlyEqual(String expectedRaw, String requestedPathRaw) {
    LOG.debug("Comparing expected [{}] vs requested [{}]", expectedRaw, requestedPathRaw);
    if (StringUtils.isEmpty(expectedRaw)) {
        LOG.debug("False: empty expected");
        return false;
    }
    try {
        UriComponents expected = UriComponentsBuilder.fromUriString(expectedRaw).build();
        UriComponents requested = UriComponentsBuilder.fromUriString(requestedPathRaw).build();

        if (!Objects.equals(expected.getPath(), requested.getPath())) {
            LOG.debug("False: expected path [{}] does not match requested path [{}]",
                    expected.getPath(), requested.getPath());
            return false;
        }

        Map<String, List<String>> left = new HashMap<>(expected.getQueryParams());
        Map<String, List<String>> right = new HashMap<>(requested.getQueryParams());

        /*
            If the equality test uses the size parameter on the request, it is possible that the equality test will fail because Sonos
            changes the size parameter according to the client.

            All parameters should be removed, but this would require too much retrofit work throughout the code.
         */
        left.remove("size");
        right.remove("size");

        MapDifference<String, List<String>> difference = Maps.difference(left, right);

        if (difference.entriesDiffering().isEmpty() || difference.entriesOnlyOnLeft().isEmpty()
                || (difference.entriesOnlyOnRight().size() == 1 && difference.entriesOnlyOnRight().get(JWTSecurityService.JWT_PARAM_NAME) != null)) {
            return true;
        }

        LOG.debug("False: expected query params [{}] do not match requested query params [{}]", expected.getQueryParams(), requested.getQueryParams());
        return false;

    } catch (Exception e) {
        LOG.warn("Exception encountered while comparing paths", e);
        return false;
    }
}
 
源代码14 项目: tutorials   文件: HashMapComparisonUnitTest.java
@Test
public void givenSimilarMapsWithArrayValue_whenCompareUsingGuava_thenFail() {
    MapDifference<String, String[]> diff = Maps.difference(asiaCity1, asiaCity2);
    assertFalse(diff.areEqual());
}
 
源代码15 项目: apollo   文件: NamespaceUnlockAspect.java
boolean isModified(Namespace namespace) {
  Release release = releaseService.findLatestActiveRelease(namespace);
  List<Item> items = itemService.findItemsWithoutOrdered(namespace.getId());

  if (release == null) {
    return hasNormalItems(items);
  }

  Map<String, String> releasedConfiguration = gson.fromJson(release.getConfigurations(), GsonType.CONFIG);
  Map<String, String> configurationFromItems = generateConfigurationFromItems(namespace, items);

  MapDifference<String, String> difference = Maps.difference(releasedConfiguration, configurationFromItems);

  return !difference.areEqual();

}
 
源代码16 项目: buck   文件: ConfigDifference.java
/**
 * Compares sets of config options, and returns the difference as a map of 'section.key' strings
 * to pairs containing the different values.
 */
@VisibleForTesting
public static Map<String, ConfigChange> compare(
    ImmutableMap<String, ImmutableMap<String, String>> rawConfig1,
    ImmutableMap<String, ImmutableMap<String, String>> rawConfig2) {
  MapDifference<String, ImmutableMap<String, String>> diffSections =
      Maps.difference(rawConfig1, rawConfig2);
  if (!diffSections.areEqual()) {
    ImmutableMap.Builder<String, ConfigChange> result = ImmutableMap.builder();

    BiConsumer<String, Map<String, ValueDifference<String>>> appendChange =
        (section, diff) ->
            diff.forEach(
                (option, value) ->
                    result.put(
                        section + "." + option,
                        ImmutableConfigChange.of(value.leftValue(), value.rightValue())));
    BiConsumer<String, Map<String, String>> appendLeft =
        (section, diff) ->
            diff.forEach(
                (option, value) ->
                    result.put(section + "." + option, ImmutableConfigChange.of(value, null)));
    BiConsumer<String, Map<String, String>> appendRight =
        (section, diff) ->
            diff.forEach(
                (option, value) ->
                    result.put(section + "." + option, ImmutableConfigChange.of(null, value)));

    diffSections
        .entriesDiffering()
        .forEach(
            (section, diff) -> {
              MapDifference<String, String> sectionDiff =
                  Maps.difference(diff.leftValue(), diff.rightValue());
              appendChange.accept(section, sectionDiff.entriesDiffering());
              appendLeft.accept(section, sectionDiff.entriesOnlyOnLeft());
              appendRight.accept(section, sectionDiff.entriesOnlyOnRight());
            });

    diffSections.entriesOnlyOnLeft().forEach(appendLeft);
    diffSections.entriesOnlyOnRight().forEach(appendRight);
    return result.build();
  }
  return ImmutableMap.of();
}
 
源代码17 项目: attic-aurora   文件: DataCompatibilityTest.java
@Test
public void testWriteFormatUnchanged() {
  // Attempts to flag any changes in the storage format.  While thorough, this check is not
  // complete.  It attempts to capture the entire schema by synthesizing a fully-populated
  // instance of each Op type.  For TUnions, the struct generator picks an arbitrary field to set,
  // meaning that it will only see one of the multiple possible schemas for any given TUnion.
  // These generated structs effectively give a view of the struct schema, which is compared to
  // golden files in `goldens/current`.

  Map<String, String> schemasByName = generateOpSchemas();
  File goldensDir = getGoldensDir("current");
  Map<String, String> goldensByName = loadGoldenSchemas(goldensDir);

  MapDifference<String, String> difference = Maps.difference(goldensByName, schemasByName);
  if (difference.areEqual()) {
    return;
  }

  StringBuilder error = new StringBuilder();
  StringBuilder remedy = new StringBuilder();

  Set<String> removedOps = difference.entriesOnlyOnLeft().keySet();
  if (!removedOps.isEmpty()) {
    error.append("Removal of storage Op(s): ").append(removedOps)
        .append("\nOps may only be removed after a release that")
        .append("\n  * formally deprecates the Op in release notes")
        .append("\n  * performs a no-op read of the Op type")
        .append("\n  * included warning logging when the Op was read")
        .append("\n  * ensures the Op is removed from storage")
        .append("\n\nHowever, you should also consider leaving the Op indefinitely and removing")
        .append("\nall fields as a safer alternative.");

    remedy.append("deleting the files ")
        .append(removedOps.stream()
            .map(removed -> new File(goldensDir, removed).getAbsolutePath())
            .collect(Collectors.joining(", ")));
  }

  String goldenChangeInstructions = Streams.concat(
      difference.entriesOnlyOnRight().entrySet().stream(),
      difference.entriesDiffering().entrySet().stream()
          .map(entry ->
              new SimpleImmutableEntry<>(entry.getKey(), entry.getValue().rightValue())))
      .map(entry -> new StringBuilder()
          .append("\n").append(new File(goldensDir, entry.getKey()).getPath()).append(":")
          .append("\n").append(entry.getValue())
          .toString())
      .collect(Collectors.joining("\n"));

  Set<String> addedOps = difference.entriesOnlyOnRight().keySet();
  if (!addedOps.isEmpty()) {
    error.append("Addition of storage Op(s): ").append(addedOps)
        .append("\nOps may only be introduced")
        .append("\n  a.) in a release that supports reading but not writing the Op")
        .append("\n  b.) in a release that writes the Op only with an operator-controlled flag");

    remedy.append("creating the following files")
        .append(goldenChangeInstructions);
  }

  Map<String, ValueDifference<String>> modified = difference.entriesDiffering();
  if (!modified.isEmpty()) {
    error.append("Schema changes to Op(s): " + modified.keySet())
        .append("\nThis check detects that changes occurred, not how the schema changed.")
        .append("\nSome guidelines for evolving schemas:")
        .append("\n  * Introducing fields: you must handle reading records that do not")
        .append("\n    yet have the field set.  This can be done with a backfill routine during")
        .append("\n    storage recovery if a field is required in some parts of the code")
        .append("\n  * Removing fields: must only be done after a release in which the field")
        .append("\n    is unused and announced as deprecated")
        .append("\n  * Changed fields: the type or thrift field ID of a field must never change");

    remedy.append("changing the following files")
        .append(goldenChangeInstructions);
  }

  fail(new StringBuilder()
      .append("**** Storage compatibility change detected ****")
      .append("\n")
      .append(error)
      .append("\n\nIf the necessary compatibility procedures have been performed,")
      .append("\nyou may clear this check by ")
      .append(remedy)
      .toString());
}
 
源代码18 项目: bazel   文件: BuildOptions.java
/**
 * Returns true if the passed parsing result's options have the same value as these options.
 *
 * <p>If a native parsed option is passed whose fragment has been trimmed in these options it is
 * considered to match.
 *
 * <p>If no options are present in the parsing result or all options in the parsing result have
 * been trimmed the result is considered not to match. This is because otherwise the parsing
 * result would match any options in a similar trimmed state, regardless of contents.
 *
 * @param parsingResult parsing result to be compared to these options
 * @return true if all non-trimmed values match
 * @throws OptionsParsingException if options cannot be parsed
 */
public boolean matches(OptionsParsingResult parsingResult) throws OptionsParsingException {
  Set<OptionDefinition> ignoredDefinitions = new HashSet<>();
  for (ParsedOptionDescription parsedOption : parsingResult.asListOfExplicitOptions()) {
    OptionDefinition optionDefinition = parsedOption.getOptionDefinition();

    // All options obtained from an options parser are guaranteed to have been defined in an
    // FragmentOptions class.
    @SuppressWarnings("unchecked")
    Class<? extends FragmentOptions> fragmentClass =
        (Class<? extends FragmentOptions>) optionDefinition.getField().getDeclaringClass();

    FragmentOptions originalFragment = fragmentOptionsMap.get(fragmentClass);
    if (originalFragment == null) {
      // Ignore flags set in trimmed fragments.
      ignoredDefinitions.add(optionDefinition);
      continue;
    }
    Object originalValue = originalFragment.asMap().get(optionDefinition.getOptionName());
    if (!Objects.equals(originalValue, parsedOption.getConvertedValue())) {
      return false;
    }
  }

  Map<Label, Object> starlarkOptions =
      labelizeStarlarkOptions(parsingResult.getStarlarkOptions());
  MapDifference<Label, Object> starlarkDifference =
      Maps.difference(starlarkOptionsMap, starlarkOptions);
  if (starlarkDifference.entriesInCommon().size() < starlarkOptions.size()) {
    return false;
  }

  if (ignoredDefinitions.size() == parsingResult.asListOfExplicitOptions().size()
      && starlarkOptions.isEmpty()) {
    // Zero options were compared, either because none were passed or because all of them were
    // trimmed.
    return false;
  }

  return true;
}
 
public static void assertSolrInputDocument(SolrInputDocument expected, SolrInputDocument actual) {
  assertNotNull(actual);
  MapDifference<String, SolrInputField> difference = Maps.difference(expected, actual, new SolrInputFieldEquivalence());
  assertTrue(difference.areEqual(), new MapDifferenceSupplier(difference));
}
 
源代码20 项目: vjtools   文件: MapUtil.java
/**
 * 对两个Map进行比较,返回MapDifference,然后各种妙用.
 * 
 * 包括key的差集,key的交集,以及key相同但value不同的元素。
 * 
 * @see com.google.common.collect.MapDifference
 */
public static <K, V> MapDifference<K, V> difference(Map<? extends K, ? extends V> left,
		Map<? extends K, ? extends V> right) {
	return Maps.difference(left, right);
}