/*
* Copyright 2013-present Facebook, Inc.
*
* 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.facebook.buck.dalvik;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.jvm.java.classes.ClasspathTraversal;
import com.facebook.buck.jvm.java.classes.DefaultClasspathTraverser;
import com.facebook.buck.jvm.java.classes.FileLike;
import com.facebook.buck.jvm.java.classes.FileLikes;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.step.StepExecutionResult;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
public class EstimateDexWeightStep implements Step, Supplier<Integer> {
@VisibleForTesting
static interface DexWeightEstimator {
public int getEstimate(FileLike fileLike) throws IOException;
}
/**
* This is an estimator that uses the size of the {@code .class} file as the estimate. This should
* be used when speed is more important than accuracy.
*/
private static final DexWeightEstimator DEFAULT_ESTIMATOR = fileLike -> (int) fileLike.getSize();
private final ProjectFilesystem filesystem;
private final Path pathToJarOrClassesDirectory;
private final DexWeightEstimator dexWeightEstimator;
private int weightEstimate = -1;
/**
* This uses an estimator that uses the size of the {@code .class} file as the estimate. This
* should be used when speed is more important than accuracy.
*/
public EstimateDexWeightStep(ProjectFilesystem filesystem, Path pathToJarOrClassesDirectory) {
this(filesystem, pathToJarOrClassesDirectory, DEFAULT_ESTIMATOR);
}
@VisibleForTesting
EstimateDexWeightStep(
ProjectFilesystem filesystem,
Path pathToJarOrClassesDirectory,
DexWeightEstimator dexWeightEstimator) {
this.filesystem = filesystem;
this.pathToJarOrClassesDirectory = pathToJarOrClassesDirectory;
this.dexWeightEstimator = dexWeightEstimator;
}
@Override
public StepExecutionResult execute(ExecutionContext context) {
Path path = filesystem.resolve(pathToJarOrClassesDirectory);
ClasspathTraversal traversal =
new ClasspathTraversal(Collections.singleton(path), filesystem) {
private int totalWeightEstimate = 0;
@Override
public void visit(FileLike fileLike) throws IOException {
// When traversing a JAR file, it may have resources or directory entries that do not end
// in .class, which should be ignored.
if (!FileLikes.isClassFile(fileLike)) {
return;
}
totalWeightEstimate += dexWeightEstimator.getEstimate(fileLike);
}
@Override
public Integer getResult() {
return totalWeightEstimate;
}
};
try {
new DefaultClasspathTraverser().traverse(traversal);
} catch (IOException e) {
context.logError(e, "Error accumulating class names for %s.", pathToJarOrClassesDirectory);
return StepExecutionResult.ERROR;
}
this.weightEstimate = (Integer) traversal.getResult();
return StepExecutionResult.SUCCESS;
}
@Override
public Integer get() {
Preconditions.checkState(weightEstimate >= 0, "If less than zero, was not set.");
return weightEstimate;
}
@Override
public String getShortName() {
return "estimate_dex_weight";
}
@Override
public String getDescription(ExecutionContext context) {
return "estimate_dex_weight";
}
@VisibleForTesting
public void setWeightEstimateForTesting(int weightEstimate) {
this.weightEstimate = weightEstimate;
}
}