1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.databinding.tool.processing;
18
19
20import com.google.common.base.Joiner;
21import com.google.common.base.Splitter;
22import com.google.common.base.Strings;
23
24import android.databinding.tool.store.Location;
25import android.databinding.tool.util.L;
26import android.databinding.tool.util.StringUtils;
27
28import java.util.ArrayList;
29import java.util.List;
30import java.util.regex.Matcher;
31import java.util.regex.Pattern;
32
33/**
34 * An exception that contains scope information.
35 */
36public class ScopedException extends RuntimeException {
37    public static final String ERROR_LOG_PREFIX = "****/ data binding error ****";
38    public static final String ERROR_LOG_SUFFIX = "****\\ data binding error ****";
39    public static final String MSG_KEY = "msg:";
40    public static final String LOCATION_KEY = "loc:";
41    public static final String FILE_KEY = "file:";
42    private static boolean sEncodeOutput = false;
43    private ScopedErrorReport mScopedErrorReport;
44    private String mScopeLog;
45
46    public ScopedException(String message, Object... args) {
47        super(message == null ? "unknown data binding exception" :
48                args.length == 0 ? message : String.format(message, args));
49        mScopedErrorReport = Scope.createReport();
50        mScopeLog = L.isDebugEnabled() ? Scope.produceScopeLog() : null;
51    }
52
53    ScopedException(String message, ScopedErrorReport scopedErrorReport) {
54        super(message);
55        mScopedErrorReport = scopedErrorReport;
56    }
57
58    public String getBareMessage() {
59        return super.getMessage();
60    }
61
62    @Override
63    public String getMessage() {
64        return sEncodeOutput ? createEncodedMessage() : createHumanReadableMessage();
65    }
66
67    public String createHumanReadableMessage() {
68        ScopedErrorReport scopedError = getScopedErrorReport();
69        StringBuilder sb = new StringBuilder();
70        sb.append(super.getMessage()).append("\n")
71                .append("file://").append(scopedError.getFilePath());
72        if (scopedError.getLocations() != null && scopedError.getLocations().size() > 0) {
73            sb.append(" Line:");
74            sb.append(scopedError.getLocations().get(0).startLine);
75        }
76        sb.append("\n");
77        return sb.toString();
78    }
79
80    private String createEncodedMessage() {
81        ScopedErrorReport scopedError = getScopedErrorReport();
82        StringBuilder sb = new StringBuilder();
83        sb.append(ERROR_LOG_PREFIX)
84                .append(MSG_KEY).append(super.getMessage()).append("\n")
85                .append(FILE_KEY).append(scopedError.getFilePath()).append("\n");
86        if (scopedError.getLocations() != null) {
87            for (Location location : scopedError.getLocations()) {
88                sb.append(LOCATION_KEY).append(location.toUserReadableString()).append("\n");
89            }
90        }
91        sb.append(ERROR_LOG_SUFFIX);
92        return Joiner.on(' ').join(Splitter.on(StringUtils.LINE_SEPARATOR).split(sb));
93    }
94
95    public ScopedErrorReport getScopedErrorReport() {
96        return mScopedErrorReport;
97    }
98
99    public boolean isValid() {
100        return mScopedErrorReport.isValid();
101    }
102
103    public static ScopedException createFromOutput(String output) {
104        String message = "";
105        String file = "";
106        List<Location> locations = new ArrayList<Location>();
107        int msgStart = output.indexOf(MSG_KEY);
108        if (msgStart < 0) {
109            message = output;
110        } else {
111            int fileStart = output.indexOf(FILE_KEY, msgStart + MSG_KEY.length());
112            if (fileStart < 0) {
113                message = output;
114            } else {
115                message = output.substring(msgStart + MSG_KEY.length(), fileStart);
116                int locStart = output.indexOf(LOCATION_KEY, fileStart + FILE_KEY.length());
117                if (locStart < 0) {
118                    file = output.substring(fileStart + FILE_KEY.length());
119                } else {
120                    file = output.substring(fileStart + FILE_KEY.length(), locStart);
121                    int nextLoc = 0;
122                    while(nextLoc >= 0) {
123                        nextLoc = output.indexOf(LOCATION_KEY, locStart + LOCATION_KEY.length());
124                        Location loc;
125                        if (nextLoc < 0) {
126                            loc = Location.fromUserReadableString(
127                                    output.substring(locStart + LOCATION_KEY.length()));
128                        } else {
129                            loc = Location.fromUserReadableString(
130                                    output.substring(locStart + LOCATION_KEY.length(), nextLoc));
131                        }
132                        if (loc != null && loc.isValid()) {
133                            locations.add(loc);
134                        }
135                        locStart = nextLoc;
136                    }
137                }
138            }
139        }
140        return new ScopedException(message.trim(),
141                new ScopedErrorReport(Strings.isNullOrEmpty(file) ? null : file.trim(), locations));
142    }
143
144    public static List<ScopedException> extractErrors(String output) {
145        List<ScopedException> errors = new ArrayList<ScopedException>();
146        int index = output.indexOf(ERROR_LOG_PREFIX);
147        final int limit = output.length();
148        while (index >= 0 && index < limit) {
149            int end = output.indexOf(ERROR_LOG_SUFFIX, index + ERROR_LOG_PREFIX.length());
150            if (end == -1) {
151                errors.add(createFromOutput(output.substring(index + ERROR_LOG_PREFIX.length())));
152                break;
153            } else {
154                errors.add(createFromOutput(output.substring(index + ERROR_LOG_PREFIX.length(),
155                        end)));
156                index = output.indexOf(ERROR_LOG_PREFIX, end + ERROR_LOG_SUFFIX.length());
157            }
158        }
159        return errors;
160    }
161
162    public static void encodeOutput(boolean encodeOutput) {
163        sEncodeOutput = encodeOutput;
164    }
165
166    public static boolean issEncodeOutput() {
167        return sEncodeOutput;
168    }
169}
170