blob: 2f2d717fb8b42a8eb8134ffd6c02ce210af0c0ce [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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.android.manifmerger;
import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
import com.android.annotations.concurrency.Immutable;
import com.android.ide.common.blame.SourceFile;
import com.android.ide.common.blame.SourceFilePosition;
import com.android.ide.common.blame.SourcePosition;
import com.android.utils.ILogger;
import com.google.common.base.CaseFormat;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
/**
* Contains the result of 2 files merging.
*
* TODO: more work necessary, this is pretty raw as it stands.
*/
@Immutable
public class MergingReport {
private final Optional<XmlDocument> mMergedDocument;
private final Result mResult;
// list of logging events, ordered by their recording time.
private final ImmutableList<Record> mRecords;
private final ImmutableList<String> mIntermediaryStages;
private final Actions mActions;
private MergingReport(Optional<XmlDocument> mergedDocument,
@NonNull Result result,
@NonNull ImmutableList<Record> records,
@NonNull ImmutableList<String> intermediaryStages,
@NonNull Actions actions) {
mMergedDocument = mergedDocument;
mResult = result;
mRecords = records;
mIntermediaryStages = intermediaryStages;
mActions = actions;
}
/**
* dumps all logging records to a logger.
*/
public void log(ILogger logger) {
for (Record record : mRecords) {
switch(record.mSeverity) {
case WARNING:
logger.warning(record.toString());
break;
case ERROR:
logger.error(null /* throwable */, record.toString());
break;
case INFO:
logger.verbose(record.toString());
break;
default:
logger.error(null /* throwable */, "Unhandled record type " + record.mSeverity);
}
}
mActions.log(logger);
if (!mResult.isSuccess()) {
logger.warning("\nSee http://g.co/androidstudio/manifest-merger for more information"
+ " about the manifest merger.\n");
}
}
/**
* Return the resulting merged document.
*/
public Optional<XmlDocument> getMergedDocument() {
return mMergedDocument;
}
/**
* Returns all the merging intermediary stages if
* {@link com.android.manifmerger.ManifestMerger2.Invoker.Feature#KEEP_INTERMEDIARY_STAGES}
* is set.
*/
public ImmutableList<String> getIntermediaryStages() {
return mIntermediaryStages;
}
/**
* Overall result of the merging process.
*/
public enum Result {
SUCCESS,
WARNING,
ERROR;
public boolean isSuccess() {
return this == SUCCESS || this == WARNING;
}
public boolean isWarning() {
return this == WARNING;
}
public boolean isError() {
return this == ERROR;
}
}
@NonNull
public Result getResult() {
return mResult;
}
@NonNull
public ImmutableList<Record> getLoggingRecords() {
return mRecords;
}
@NonNull
public Actions getActions() {
return mActions;
}
@NonNull
public String getReportString() {
switch (mResult) {
case SUCCESS:
return "Manifest merger executed successfully";
case WARNING:
return mRecords.size() > 1
? "Manifest merger exited with warnings, see logs"
: "Manifest merger warning : " + mRecords.get(0).mLog;
case ERROR:
return mRecords.size() > 1
? "Manifest merger failed with multiple errors, see logs"
: "Manifest merger failed : " + mRecords.get(0).mLog;
default:
return "Manifest merger returned an invalid result " + mResult;
}
}
/**
* Log record. This is used to give users some information about what is happening and
* what might have gone wrong.
*/
public static class Record {
public enum Severity {WARNING, ERROR, INFO }
private final Severity mSeverity;
private final String mLog;
private final SourceFilePosition mSourceLocation;
private Record(
@NonNull SourceFilePosition sourceLocation,
@NonNull Severity severity,
@NonNull String mLog) {
this.mSourceLocation = sourceLocation;
this.mSeverity = severity;
this.mLog = mLog;
}
public Severity getSeverity() {
return mSeverity;
}
public String getMessage() {
return mLog;
}
@Override
public String toString() {
return mSourceLocation.toString() // needs short string.
+ " "
+ CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, mSeverity.toString())
+ ":\n\t"
+ mLog;
}
}
/**
* This builder is used to accumulate logging, action recording and intermediary results as
* well as final result of the merging activity.
*
* Once the merging is finished, the {@link #build()} is called to return an immutable version
* of itself with all the logging, action recordings and xml files obtainable.
*
*/
static class Builder {
private Optional<XmlDocument> mMergedDocument = Optional.absent();
private ImmutableList.Builder<Record> mRecordBuilder = new ImmutableList.Builder<Record>();
private ImmutableList.Builder<String> mIntermediaryStages = new ImmutableList.Builder<String>();
private boolean mHasWarnings = false;
private boolean mHasErrors = false;
private ActionRecorder mActionRecorder = new ActionRecorder();
private final ILogger mLogger;
Builder(ILogger logger) {
mLogger = logger;
}
Builder setMergedDocument(@NonNull XmlDocument mergedDocument) {
mMergedDocument = Optional.of(mergedDocument);
return this;
}
@VisibleForTesting
Builder addMessage(@NonNull SourceFile sourceFile,
int line,
int column,
@NonNull Record.Severity severity,
@NonNull String message) {
// The line and column used are 1-based, but SourcePosition uses zero-based.
return addMessage(
new SourceFilePosition(sourceFile, new SourcePosition(line - 1, column -1, -1)),
severity,
message);
}
Builder addMessage(@NonNull SourceFile sourceFile,
@NonNull Record.Severity severity,
@NonNull String message) {
return addMessage(
new SourceFilePosition(sourceFile, SourcePosition.UNKNOWN),
severity,
message);
}
Builder addMessage(@NonNull SourceFilePosition sourceFilePosition,
@NonNull Record.Severity severity,
@NonNull String message) {
switch (severity) {
case ERROR:
mHasErrors = true;
break;
case WARNING:
mHasWarnings = true;
break;
}
mRecordBuilder.add(new Record(sourceFilePosition, severity, message));
return this;
}
Builder addMergingStage(String xml) {
mIntermediaryStages.add(xml);
return this;
}
/**
* Returns true if some fatal errors were reported.
*/
boolean hasErrors() {
return mHasErrors;
}
ActionRecorder getActionRecorder() {
return mActionRecorder;
}
MergingReport build() {
Result result = mHasErrors
? Result.ERROR
: mHasWarnings
? Result.WARNING
: Result.SUCCESS;
return new MergingReport(
mMergedDocument,
result,
mRecordBuilder.build(),
mIntermediaryStages.build(),
mActionRecorder.build());
}
public ILogger getLogger() {
return mLogger;
}
}
}