| /* |
| * Copyright (C) 2013 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.ide.common.res2; |
| |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.ide.common.blame.Message; |
| import com.android.ide.common.blame.Message.Kind; |
| import com.android.ide.common.blame.SourceFile; |
| import com.android.ide.common.blame.SourceFilePosition; |
| import com.android.ide.common.blame.SourcePosition; |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Objects; |
| import com.google.common.base.Throwables; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| |
| import org.xml.sax.SAXParseException; |
| |
| import java.io.File; |
| import java.util.Collection; |
| import java.util.List; |
| |
| /** |
| * Exception for errors during merging. |
| */ |
| public class MergingException extends Exception { |
| |
| public static final String MULTIPLE_ERRORS = "Multiple errors:"; |
| |
| @NonNull |
| private final List<Message> mMessages; |
| |
| /** |
| * For internal use. Creates a new MergingException |
| * |
| * @param cause the original exception. May be null. |
| * @param messages the messaged. Must contain at least one item. |
| */ |
| protected MergingException(@Nullable Throwable cause, @NonNull Message... messages) { |
| super(messages.length == 1 ? messages[0].getText() : MULTIPLE_ERRORS, cause); |
| mMessages = ImmutableList.copyOf(messages); |
| } |
| |
| public static class Builder { |
| |
| @Nullable |
| private Throwable mCause = null; |
| |
| @Nullable |
| private String mMessageText = null; |
| |
| @Nullable |
| private String mOriginalMessageText = null; |
| |
| @NonNull |
| private SourceFile mFile = SourceFile.UNKNOWN; |
| |
| @NonNull |
| private SourcePosition mPosition = SourcePosition.UNKNOWN; |
| |
| private Builder() { |
| } |
| |
| public Builder wrapException(@NonNull Throwable cause) { |
| mCause = cause; |
| mOriginalMessageText = Throwables.getStackTraceAsString(cause); |
| return this; |
| } |
| |
| public Builder withFile(@NonNull File file) { |
| mFile = new SourceFile(file); |
| return this; |
| } |
| |
| public Builder withFile(@NonNull SourceFile file) { |
| mFile = file; |
| return this; |
| } |
| |
| public Builder withPosition(@NonNull SourcePosition position) { |
| mPosition = position; |
| return this; |
| } |
| |
| public Builder withMessage(@NonNull String messageText, Object... args) { |
| mMessageText = args.length == 0 ? messageText : String.format(messageText, args); |
| return this; |
| } |
| |
| public MergingException build() { |
| if (mCause != null) { |
| if (mMessageText == null) { |
| mMessageText = Objects.firstNonNull( |
| mCause.getLocalizedMessage(), mCause.getClass().getCanonicalName()); |
| } |
| if (mPosition == SourcePosition.UNKNOWN && mCause instanceof SAXParseException) { |
| SAXParseException exception = (SAXParseException) mCause; |
| int lineNumber = exception.getLineNumber(); |
| if (lineNumber != -1) { |
| // Convert positions to be 0-based for SourceFilePosition. |
| mPosition = new SourcePosition(lineNumber - 1, |
| exception.getColumnNumber() - 1, -1); |
| } |
| } |
| } |
| |
| if (mMessageText == null) { |
| mMessageText = "Unknown error."; |
| } |
| |
| return new MergingException( |
| mCause, |
| new Message( |
| Kind.ERROR, |
| mMessageText, |
| Objects.firstNonNull(mOriginalMessageText, mMessageText), |
| new SourceFilePosition(mFile, mPosition))); |
| } |
| |
| } |
| |
| public static Builder wrapException(@NonNull Throwable cause) { |
| return new Builder().wrapException(cause); |
| } |
| |
| public static Builder withMessage(@NonNull String message, Object... args) { |
| return new Builder().withMessage(message, args); |
| } |
| |
| |
| public static void throwIfNonEmpty(Collection<Message> messages) throws MergingException { |
| if (!messages.isEmpty()) { |
| throw new MergingException(null, Iterables.toArray(messages, Message.class)); |
| } |
| } |
| |
| @NonNull |
| public List<Message> getMessages() { |
| return mMessages; |
| } |
| |
| /** |
| * Computes the error message to display for this error |
| */ |
| @NonNull |
| @Override |
| public String getMessage() { |
| List<String> messages = Lists.newArrayListWithCapacity(mMessages.size()); |
| for (Message message : mMessages) { |
| StringBuilder sb = new StringBuilder(); |
| List<SourceFilePosition> sourceFilePositions = message.getSourceFilePositions(); |
| if (sourceFilePositions.size() > 1 || !sourceFilePositions.get(0) |
| .equals(SourceFilePosition.UNKNOWN)) { |
| sb.append(Joiner.on('\t').join(sourceFilePositions)); |
| } |
| |
| String text = message.getText(); |
| if (sb.length() > 0) { |
| sb.append(':').append(' '); |
| |
| // ALWAYS insert the string "Error:" between the path and the message. |
| // This is done to make the error messages more simple to detect |
| // (since a generic path: message pattern can match a lot of output, basically |
| // any labeled output, and we don't want to do file existence checks on any random |
| // string to the left of a colon.) |
| if (!text.startsWith("Error: ")) { |
| sb.append("Error: "); |
| } |
| } else if (!text.contains("Error: ")) { |
| sb.append("Error: "); |
| } |
| |
| // If the error message already starts with the path, strip it out. |
| // This avoids redundant looking error messages you can end up with |
| // like for example for permission denied errors where the error message |
| // string itself contains the path as a prefix: |
| // /my/full/path: /my/full/path (Permission denied) |
| if (sourceFilePositions.size() == 1) { |
| File file = sourceFilePositions.get(0).getFile().getSourceFile(); |
| if (file != null) { |
| String path = file.getAbsolutePath(); |
| if (text.startsWith(path)) { |
| int stripStart = path.length(); |
| if (text.length() > stripStart && text.charAt(stripStart) == ':') { |
| stripStart++; |
| } |
| if (text.length() > stripStart && text.charAt(stripStart) == ' ') { |
| stripStart++; |
| } |
| text = text.substring(stripStart); |
| } |
| } |
| } |
| |
| sb.append(text); |
| messages.add(sb.toString()); |
| } |
| return Joiner.on('\n').join(messages); |
| } |
| |
| @Override |
| public String toString() { |
| return getMessage(); |
| } |
| } |