All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.archivepatcher.explainer.PatchExplanation Maven / Gradle / Ivy

The newest version!
// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.archivepatcher.explainer;

import com.google.archivepatcher.generator.RecommendationReason;
import com.google.archivepatcher.generator.TotalRecompressionLimiter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

/**
 * Aggregate explanation for a collection of {@link EntryExplanation} objects. This class is a
 * convenience for tools that process the output of {@link PatchExplainer}.
 */
public class PatchExplanation {

  /**
   * A {@link Comparator} that performs lexical sorting of the path names within
   * {@link EntryExplanation} objects by treating them as UTF-8 strings.
   */
  private static class EntryExplanationLexicalComparator implements Comparator {
    @Override
    public int compare(EntryExplanation o1, EntryExplanation o2) {
      return path(o1).compareTo(path(o2));
    }
  }

  /**
   * All entries that are new (only present in the new archive).
   */
  private final List explainedAsNew;

  /**
   * All entries that are changed (present in both the old and new archive, but different content).
   */
  private final List explainedAsChanged;

  /**
   * All entries that either unchanged or have a zero-cost in the patch (i.e., a copy or rename).
   */
  private final List explainedAsUnchangedOrFree;

  /**
   * All entries that could have been uncompressed for patching but have been prevented due to
   * resource constraints by a {@link TotalRecompressionLimiter}.
   */
  private final List explainedAsResourceConstrained;

  /**
   * The sum total of the sizes of all the entries that are new in the patch stream.
   */
  private final long estimatedNewSize;

  /**
   * The sum total of the sizes of all the entries that are changed in the patch stream.
   */
  private final long estimatedChangedSize;

  /**
   * The sum total of the sizes of all the entries that were changed but that have been prevented
   * from being uncompressed during patch generation due to resource constraints by a {@link
   * TotalRecompressionLimiter}.
   */
  private final long estimatedResourceConstrainedSize;

  /**
   * Constructs a new aggregate explanation for the specified {@link EntryExplanation}s.
   * @param entryExplanations the explanations for all of the individual entries in the patch
   */
  public PatchExplanation(List entryExplanations) {
    List tempExplainedAsNew = new ArrayList<>();
    List tempExplainedAsChanged = new ArrayList<>();
    List tempExplainedAsUnchangedOrFree = new ArrayList<>();
    List tempExplainedAsResourceConstrained = new ArrayList<>();
    long tempEstimatedNewSize = 0;
    long tempEstimatedChangedSize = 0;
    long tempEstimatedResourceConstrainedSize = 0;
    for (EntryExplanation explanation : entryExplanations) {
      if (explanation.isNew()) {
        tempEstimatedNewSize += explanation.getCompressedSizeInPatch();
        tempExplainedAsNew.add(explanation);
      } else if (explanation.getReasonIncludedIfNotNew()
          == RecommendationReason.RESOURCE_CONSTRAINED) {
        tempEstimatedResourceConstrainedSize += explanation.getCompressedSizeInPatch();
        tempExplainedAsResourceConstrained.add(explanation);
      } else if (explanation.getCompressedSizeInPatch() > 0) {
        tempEstimatedChangedSize += explanation.getCompressedSizeInPatch();
        tempExplainedAsChanged.add(explanation);
      } else {
        tempExplainedAsUnchangedOrFree.add(explanation);
      }
    }
    Comparator comparator = new EntryExplanationLexicalComparator();
    Collections.sort(tempExplainedAsNew, comparator);
    Collections.sort(tempExplainedAsChanged, comparator);
    Collections.sort(tempExplainedAsUnchangedOrFree, comparator);
    Collections.sort(tempExplainedAsResourceConstrained, comparator);
    explainedAsNew = Collections.unmodifiableList(tempExplainedAsNew);
    explainedAsChanged = Collections.unmodifiableList(tempExplainedAsChanged);
    explainedAsUnchangedOrFree = Collections.unmodifiableList(tempExplainedAsUnchangedOrFree);
    explainedAsResourceConstrained =
        Collections.unmodifiableList(tempExplainedAsResourceConstrained);
    estimatedNewSize = tempEstimatedNewSize;
    estimatedChangedSize = tempEstimatedChangedSize;
    estimatedResourceConstrainedSize = tempEstimatedResourceConstrainedSize;
  }

  /**
   * Returns a read-only view of all entries that are new (only present in the new archive), sorted
   * in ascending order lexicographically by path.
   * @return as described
   */
  public List getExplainedAsNew() {
    return explainedAsNew;
  }

  /**
   * Returns a read-only view of all entries that are changed (present in both the old and new
   * archive, but different content), sorted in ascending order lexicographically by path.
   * @return as described
   */
  public List getExplainedAsChanged() {
    return explainedAsChanged;
  }

  /**
   * Returns a read-only view of all entries that either unchanged or have a zero-cost in the patch
   * (i.e., a copy or rename), sorted in ascending order lexicographically by path.
   * @return as described
   */
  public List getExplainedAsUnchangedOrFree() {
    return explainedAsUnchangedOrFree;
  }

  /**
   * Returns a read-only view of all entries that could have been uncompressed for patching but have
   * been prevented due to resource constraints by a {@link TotalRecompressionLimiter}, sorted in
   * ascending order lexicographically by path.
   *
   * @return as described
   */
  public List getExplainedAsResourceConstrained() {
    return explainedAsResourceConstrained;
  }

  /**
   * Returns the sum total of the sizes of all the entries that are new in the patch stream. As
   * noted in {@link EntryExplanation#getCompressedSizeInPatch()}, this is an
   * approximation.
   * @return as described
   */
  public long getEstimatedNewSize() {
    return estimatedNewSize;
  }

  /**
   * Returns the sum total of the sizes of all the entries that are changed in the patch stream. As
   * noted in {@link EntryExplanation#getCompressedSizeInPatch()}, this is an
   * approximation.
   * @return as described
   */
  public long getEstimatedChangedSize() {
    return estimatedChangedSize;
  }

  /**
   * Returns the sum total of the sizes of all entries that could have been uncompressed for
   * patching but have been prevented due to resource constraints by a {@link
   * TotalRecompressionLimiter}. As noted in {@link EntryExplanation#getCompressedSizeInPatch()},
   * this is an approximation.
   *
   * @return as described
   */
  public long getEstimatedResourceConstrainedSize() {
    return estimatedResourceConstrainedSize;
  }

  /**
   * Writes a JSON representation of the data to the specified {@link PrintWriter}. The data has the
   * following form: 
   * 
{ *
  estimatedNewSize = <number>, *
  estimatedChangedSize = <number>, *
  explainedAsNew = [ *
    <entry_list> *
  ], *
  explainedAsChanged = [ *
    <entry_list> *
  ], *
  explainedAsUnchangedOrFree = [ *
    <entry_list> *
  ] *
} *

* Where <entry_list> is a list of zero or more entries of the following form: * *
{ path: '<path_string>', isNew: <true|false>, * reasonIncluded: <undefined|'<reason_string'>, compressedSizeInPatch: <number> * } *
* * @param writer the writer to write the JSON to */ public void writeJson(PrintWriter writer) { StringBuilder buffer = new StringBuilder(); // For convenience buffer.append("{\n"); buffer.append(" estimatedNewSize: ").append(getEstimatedNewSize()).append(",\n"); buffer.append(" estimatedChangedSize: ").append(getEstimatedChangedSize()).append(",\n"); buffer .append(" estimatedResourceConstrainedSize: ") .append(getEstimatedResourceConstrainedSize()) .append(",\n"); dumpJson(getExplainedAsNew(), "explainedAsNew", buffer, " "); buffer.append(",\n"); dumpJson(getExplainedAsChanged(), "explainedAsChanged", buffer, " "); buffer.append(",\n"); dumpJson(getExplainedAsUnchangedOrFree(), "explainedAsUnchangedOrFree", buffer, " "); buffer.append(",\n"); dumpJson(getExplainedAsResourceConstrained(), "explainedAsResourceConstrained", buffer, " "); buffer.append("\n"); buffer.append("}"); writer.write(buffer.toString()); writer.flush(); } private void dumpJson( List explanations, String listName, StringBuilder buffer, String indent) { buffer.append(indent).append(listName).append(": [\n"); Iterator iterator = explanations.iterator(); while (iterator.hasNext()) { EntryExplanation explanation = iterator.next(); dumpJson(explanation, buffer, indent + " "); if (iterator.hasNext()) { buffer.append(","); } buffer.append("\n"); } buffer.append(indent).append("]"); } private void dumpJson(EntryExplanation entryExplanation, StringBuilder buffer, String indent) { String reasonString = entryExplanation.isNew() ? "undefined" : "'" + entryExplanation.getReasonIncludedIfNotNew().toString() + "'"; buffer .append(indent) .append("{ ") .append("path: '") .append(path(entryExplanation)) .append("', ") .append("isNew: ") .append(entryExplanation.isNew()) .append(", ") .append("reasonIncluded: ") .append(reasonString) .append(", ") .append("compressedSizeInPatch: ") .append(entryExplanation.getCompressedSizeInPatch()) .append(" }"); } /** * Returns the path from an {@link EntryExplanation} as a UTF-8 string. * @param explanation the {@link EntryExplanation} to extract the path from * @return as described */ private static String path(EntryExplanation explanation) { try { return new String(explanation.getPath().getData(), "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("System doesn't support UTF-8", e); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy