/* * 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; } } }