File Source: ProgramError.java
1 /*
2 * Copyright (c) 2006-2009 Chris Smith, Shane Mc Cormack, Gregory Holmes
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 */
22
23 package com.dmdirc.logger;
24
25 import com.dmdirc.Main;
26 import com.dmdirc.config.IdentityManager;
27 import com.dmdirc.util.Downloader;
28
29 import java.io.File;
30 import java.io.FileOutputStream;
31 import java.io.IOException;
32 import java.io.OutputStream;
33 import java.io.PrintWriter;
34 import java.io.Serializable;
35 import java.net.MalformedURLException;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.Date;
39 import java.util.HashMap;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.concurrent.Semaphore;
43
44 /**
45 * Stores a program error.
46 */
47 public final class ProgramError implements Serializable {
48
49 /**
50 * A version number for this class. It should be changed whenever the class
51 * structure is changed (or anything else that would prevent serialized
52 * objects being unserialized with the new class).
53 */
54 private static final long serialVersionUID = 3;
55
56 /** Directory used to store errors. */
57 private static File errorDir;
58
59 /** Semaphore used to serialise write access. */
/*
P/P * Method: com.dmdirc.logger.ProgramError__static_init
*
* Postconditions:
* writingSem == &new Semaphore(ProgramError__static_init#1)
* new Semaphore(ProgramError__static_init#1) num objects == 1
*/
60 private static final Semaphore writingSem = new Semaphore(1);
61
62 /** Error ID. */
63 private final long id;
64
65 /** Error icon. */
66 private final ErrorLevel level;
67
68 /** Error message. */
69 private final String message;
70
71 /** Error trace. */
72 private final String[] trace;
73
74 /** Date/time error occurred. */
75 private final Date date;
76
77 /** Error report Status. */
78 private ErrorReportStatus reportStatus;
79
80 /** Error fixed Status. */
81 private ErrorFixedStatus fixedStatus;
82
83 /**
84 * Creates a new instance of ProgramError.
85 *
86 * @param id error id
87 * @param level Error level
88 * @param message Error message
89 * @param trace Error trace
90 * @param date Error time and date
91 */
92 public ProgramError(final long id, final ErrorLevel level,
/*
P/P * Method: void com.dmdirc.logger.ProgramError(long, ErrorLevel, String, String[], Date)
*
* Preconditions:
* date != null
* id >= 0
* level != null
* message != null
* trace != null
* trace.length <= 232-1
*
* Presumptions:
* java.lang.String:isEmpty(...)@103 == 0
*
* Postconditions:
* this.date != null
* this.fixedStatus == &com.dmdirc.logger.ErrorFixedStatus__static_init.new ErrorFixedStatus(ErrorFixedStatus__static_init#7)
* this.id == id
* this.id >= 0
* this.level == level
* this.level != null
* this.message == message
* this.message != null
* this.reportStatus == &com.dmdirc.logger.ErrorReportStatus__static_init.new ErrorReportStatus(ErrorReportStatus__static_init#6)
* init'ed(this.trace)
*/
93 final String message, final String[] trace, final Date date) {
94
95 if (id < 0) {
96 throw new IllegalArgumentException("ID must be a positive integer: " + id);
97 }
98
99 if (level == null) {
100 throw new IllegalArgumentException("Level cannot be null");
101 }
102
103 if (message == null || message.isEmpty()) {
104 throw new IllegalArgumentException("Message cannot be null or an empty string");
105 }
106
107 if (trace == null) {
108 throw new IllegalArgumentException("Trace cannot be null");
109 }
110
111 if (date == null) {
112 throw new IllegalArgumentException("date cannot be null");
113 }
114
115 this.id = id;
116 this.level = level;
117 this.message = message;
118 this.trace = Arrays.copyOf(trace, trace.length);
119 this.date = (Date) date.clone();
120 this.reportStatus = ErrorReportStatus.WAITING;
121 this.fixedStatus = ErrorFixedStatus.UNKNOWN;
122 }
123
124 /**
125 * Returns this errors level.
126 *
127 * @return Error level
128 */
129 public ErrorLevel getLevel() {
/*
P/P * Method: ErrorLevel getLevel()
*
* Postconditions:
* return_value == this.level
* init'ed(return_value)
*/
130 return level;
131 }
132
133 /**
134 * Returns this errors message.
135 *
136 * @return Error message
137 */
138 public String getMessage() {
/*
P/P * Method: String getMessage()
*
* Postconditions:
* return_value == this.message
* init'ed(return_value)
*/
139 return message;
140 }
141
142 /**
143 * Returns this errors trace.
144 *
145 * @return Error trace
146 */
147 public String[] getTrace() {
/*
P/P * Method: String[] getTrace()
*
* Preconditions:
* this.trace != null
* this.trace.length <= 232-1
*
* Postconditions:
* init'ed(return_value)
*/
148 return Arrays.copyOf(trace, trace.length);
149 }
150
151 /**
152 * Returns this errors time.
153 *
154 * @return Error time
155 */
156 public Date getDate() {
/*
P/P * Method: Date getDate()
*
* Preconditions:
* this.date != null
*
* Postconditions:
* return_value != null
*/
157 return (Date) date.clone();
158 }
159
160 /**
161 * Returns the reportStatus of this error.
162 *
163 * @return Error reportStatus
164 */
165 public ErrorReportStatus getReportStatus() {
/*
P/P * Method: ErrorReportStatus getReportStatus()
*
* Preconditions:
* init'ed(this.reportStatus)
*
* Postconditions:
* return_value == this.reportStatus
* init'ed(return_value)
*/
166 return reportStatus;
167 }
168
169 /**
170 * Returns the fixed status of this error.
171 *
172 * @return Error fixed status
173 */
174 public ErrorFixedStatus getFixedStatus() {
/*
P/P * Method: ErrorFixedStatus getFixedStatus()
*
* Preconditions:
* init'ed(this.fixedStatus)
*
* Postconditions:
* return_value == this.fixedStatus
* init'ed(return_value)
*/
175 return fixedStatus;
176 }
177
178 /**
179 * Sets the report Status of this error.
180 *
181 * @param newStatus new ErrorReportStatus for the error
182 */
183 public void setReportStatus(final ErrorReportStatus newStatus) {
/*
P/P * Method: void setReportStatus(ErrorReportStatus)
*
* Preconditions:
* (soft) init'ed(com.dmdirc.ui.FatalErrorDialog$4__static_init.new int[](FatalErrorDialog$4__static_init#1)[...])
* (soft) this.reportStatus != null
*
* Postconditions:
* this.reportStatus == One-of{old this.reportStatus, newStatus}
* this.reportStatus != null
*
* Test Vectors:
* newStatus: Addr_Set{null}, Inverse{null}
* com.dmdirc.logger.ErrorReportStatus:equals(...)@184: {1}, {0}
*/
184 if (newStatus != null && !reportStatus.equals(newStatus)) {
185 reportStatus = newStatus;
186 ErrorManager.getErrorManager().fireErrorStatusChanged(this);
187
188 synchronized (this) {
189 notifyAll();
190 }
191 }
192 }
193
194 /**
195 * Sets the fixed status of this error.
196 *
197 * @param newStatus new ErrorFixedStatus for the error
198 */
199 public void setFixedStatus(final ErrorFixedStatus newStatus) {
/*
P/P * Method: void setFixedStatus(ErrorFixedStatus)
*
* Preconditions:
* (soft) init'ed(com.dmdirc.ui.FatalErrorDialog$4__static_init.new int[](FatalErrorDialog$4__static_init#1)[...])
* (soft) this.fixedStatus != null
* (soft) this.reportStatus != null
*
* Postconditions:
* this.fixedStatus == One-of{old this.fixedStatus, newStatus}
* this.fixedStatus != null
*
* Test Vectors:
* newStatus: Addr_Set{null}, Inverse{null}
* com.dmdirc.logger.ErrorFixedStatus:equals(...)@200: {1}, {0}
*/
200 if (newStatus != null && !fixedStatus.equals(newStatus)) {
201 fixedStatus = newStatus;
202 ErrorManager.getErrorManager().fireErrorStatusChanged(this);
203
204 synchronized (this) {
205 notifyAll();
206 }
207 }
208 }
209
210 /**
211 * Returns the ID of this error.
212 *
213 * @return Error ID
214 */
215 public long getID() {
/*
P/P * Method: long getID()
*
* Postconditions:
* return_value == this.id
* init'ed(return_value)
*/
216 return id;
217 }
218
219 /**
220 * Saves this error to disk.
221 */
222 public void save() {
/*
P/P * Method: void save()
*
* Preconditions:
* init'ed(errorDir)
* this.date != null
* this.trace != null
* this.trace.length <= 232-1
*
* Presumptions:
* arr$.length <= 232-1
* java.util.Arrays:copyOf(...)@148 != null
*
* Postconditions:
* errorDir == One-of{old errorDir, &new File(getErrorFile#1)}
* errorDir != null
* new File(getErrorFile#1) num objects <= 1
*/
223 final PrintWriter out = new PrintWriter(getErrorFile(), true);
224 out.println("Date:" + getDate());
225 out.println("Level: " + getLevel());
226 out.println("Description: " + getMessage());
227 out.println("Details:");
228
229 for (String traceLine : getTrace()) {
230 out.println('\t' + traceLine);
231 }
232
233 out.close();
234 }
235
236 /**
237 * Creates a new file for an error and returns the output stream.
238 *
239 * @return BufferedOutputStream to write to the error file
240 */
241 @SuppressWarnings("PMD.SystemPrintln")
242 private OutputStream getErrorFile() {
/*
P/P * Method: OutputStream getErrorFile()
*
* Preconditions:
* init'ed(errorDir)
* this.date != null
*
* Presumptions:
* java.lang.System.err != null
*
* Postconditions:
* errorDir == One-of{old errorDir, &new File(getErrorFile#1)}
* errorDir != null
* return_value in Addr_Set{&new FileOutputStream(getErrorFile#8),&new NullOutputStream(getErrorFile#9)}
* new File(getErrorFile#1) num objects <= 1
* new FileOutputStream(getErrorFile#8) num objects <= 1
* new NullOutputStream(getErrorFile#9) num objects <= 1
*
* Test Vectors:
* errorDir: Addr_Set{null}, Inverse{null}
* java.io.File:exists(...)@245: {1}, {0}
* java.io.File:exists(...)@247: {1}, {0}
* java.io.File:exists(...)@256: {0}, {1}
*/
243 writingSem.acquireUninterruptibly();
244
245 if (errorDir == null || !errorDir.exists()) {
246 errorDir = new File(Main.getConfigDir() + "errors");
247 if (!errorDir.exists()) {
248 errorDir.mkdirs();
249 }
250 }
251
252 final String logName = getDate().getTime() + "-" + getLevel();
253
254 final File errorFile = new File(errorDir, logName + ".log");
255
256 if (errorFile.exists()) {
257 boolean rename = false;
258 int i = 0;
259 while (!rename) {
260 i++;
261 rename = errorFile.renameTo(new File(errorDir, logName + "-" + i + ".log"));
262 }
263 }
264
265 try {
266 errorFile.createNewFile();
267 return new FileOutputStream(errorFile);
268 } catch (IOException ex) {
269 System.err.println("Error creating new file: ");
270 ex.printStackTrace();
271 return new NullOutputStream();
272 } finally {
273 writingSem.release();
274 }
275 }
276
277 /**
278 * Sends this error report to the DMDirc developers.
279 */
280 public void send() {
/*
P/P * Method: void send()
*
* Preconditions:
* this.trace != null
* this.trace.length <= 232-1
* (soft) init'ed(com.dmdirc.ui.FatalErrorDialog$4__static_init.new int[](FatalErrorDialog$4__static_init#1)[...])
* (soft) this.fixedStatus != null
* (soft) this.reportStatus != null
*
* Presumptions:
* com.dmdirc.config.IdentityManager:getGlobalConfig(...)@287 != null
* com.dmdirc.util.Downloader:getPage(...)@300 != null
* java.util.List:get(...)@310 != null
* java.util.List:size(...)@310 >= -231+1
* this.fixedStatus@294 != null
* ...
*
* Postconditions:
* this.fixedStatus != null
* this.reportStatus != null
*
* Test Vectors:
* java.lang.String:equalsIgnoreCase(...)@310: {1}, {0}
* java.util.List:isEmpty(...)@310: {1}, {0}
*/
281 final Map<String, String> postData = new HashMap<String, String>();
282 List<String> response = new ArrayList<String>();
283 int tries = 0;
284
285 postData.put("message", getMessage());
286 postData.put("trace", Arrays.toString(getTrace()));
287 postData.put("version", IdentityManager.getGlobalConfig().getOption("version", "version"));
288
289 setReportStatus(ErrorReportStatus.SENDING);
290
291 do {
292 if (tries != 0) {
293 try {
294 Thread.sleep(5000);
295 } catch (InterruptedException ex) {
296 //Ignore
297 }
298 }
299 try {
300 response = Downloader.getPage("http://www.dmdirc.com/error.php", postData);
301 } catch (MalformedURLException ex) {
302 //Ignore, wont happen
303 } catch (IOException ex) {
304 //Ignore being handled
305 }
306
307 tries++;
308 } while ((response.isEmpty() || !response.get(response.size() - 1).
309 equalsIgnoreCase("Error report submitted. Thank you."))
310 && tries <= 5);
311
312 checkResponses(response);
313 }
314
315 /**
316 * Checks the responses and sets status accordingly.
317 *
318 * @param error Error to check response
319 * @param response Response to check
320 */
321 private void checkResponses(final List<String> response) {
/*
P/P * Method: void checkResponses(List)
*
* Preconditions:
* response != null
* (soft) init'ed(com.dmdirc.ui.FatalErrorDialog$4__static_init.new int[](FatalErrorDialog$4__static_init#1)[...])
* (soft) this.fixedStatus != null
* (soft) this.reportStatus != null
*
* Presumptions:
* java.util.List:get(...)@322 != null
* java.util.List:get(...)@335 != null
* java.util.List:size(...)@322 >= -231+1
*
* Postconditions:
* this.fixedStatus == One-of{old this.fixedStatus, &com.dmdirc.logger.ErrorFixedStatus__static_init.new ErrorFixedStatus(ErrorFixedStatus__static_init#1), &com.dmdirc.logger.ErrorFixedStatus__static_init.new ErrorFixedStatus(ErrorFixedStatus__static_init#3), &com.dmdirc.logger....
* this.fixedStatus != null
* this.reportStatus == One-of{old this.reportStatus, &com.dmdirc.logger.ErrorReportStatus__static_init.new ErrorReportStatus(ErrorReportStatus__static_init#4), &com.dmdirc.logger.ErrorReportStatus__static_init.new ErrorReportStatus(ErrorReportStatus__static_init#2)}
* this.reportStatus != null
*
* Test Vectors:
* java.lang.String:equalsIgnoreCase(...)@322: {0}, {1}
* java.lang.String:matches(...)@336: {0}, {1}
* java.lang.String:matches(...)@338: {0}, {1}
* java.lang.String:matches(...)@340: {0}, {1}
* java.lang.String:matches(...)@342: {0}, {1}
* java.util.List:isEmpty(...)@322: {1}, {0}
* java.util.List:size(...)@330: {-231..0, 2..232-1}, {1}
*/
322 if (!response.isEmpty() && response.get(response.size() - 1).
323 equalsIgnoreCase("Error report submitted. Thank you.")) {
324 setReportStatus(ErrorReportStatus.FINISHED);
325 } else {
326 setReportStatus(ErrorReportStatus.ERROR);
327 return;
328 }
329
330 if (response.size() == 1) {
331 setFixedStatus(ErrorFixedStatus.NEW);
332 return;
333 }
334
335 final String responseToCheck = response.get(0);
336 if (responseToCheck.matches(".*fixed.*")) {
337 setFixedStatus(ErrorFixedStatus.FIXED);
338 } else if (responseToCheck.matches(".*more recent version.*")) {
339 setFixedStatus(ErrorFixedStatus.TOOOLD);
340 } else if (responseToCheck.matches(".*invalid.*")) {
341 setFixedStatus(ErrorFixedStatus.INVALID);
342 } else if (responseToCheck.matches(".*previously.*")) {
343 setFixedStatus(ErrorFixedStatus.KNOWN);
344 } else {
345 setFixedStatus(ErrorFixedStatus.NEW);
346 }
347 }
348
349 /**
350 * Determines whether or not the stack trace associated with this error
351 * is from a valid source. A valid source is one that is within a DMDirc
352 * package (com.dmdirc), and is not the DMDirc event queue.
353 *
354 * @return True if the source is valid, false otherwise
355 */
356 public boolean isValidSource() {
/*
P/P * Method: bool isValidSource()
*
* Preconditions:
* this.trace != null
* this.trace.length in {1..232-1}
* (soft) this.trace[0] != null
* (soft) this.trace[...] != null
*
* Postconditions:
* init'ed(return_value)
*/
357 final String line = getSourceLine();
358
359 return line.startsWith("com.dmdirc")
360 && !line.startsWith("com.dmdirc.addons.ui_swing.DMDircEventQueue");
361 }
362
363 /**
364 * Returns the "source line" of this error, which is defined as the first
365 * line starting with a DMDirc package name (com.dmdirc). If no such line
366 * is found, returns the first line of the message.
367 *
368 * @return This error's source line
369 */
370 public String getSourceLine() {
/*
P/P * Method: String getSourceLine()
*
* Preconditions:
* this.trace != null
* this.trace.length in {1..232-1}
* (soft) this.trace[0] != null
* (soft) this.trace[...] != null
*
* Postconditions:
* return_value != null
*
* Test Vectors:
* java.lang.String:startsWith(...)@372: {0}, {1}
*/
371 for (String line : trace) {
372 if (line.startsWith("com.dmdirc")) {
373 return line;
374 }
375 }
376
377 return trace[0];
378 }
379
380 /** {@inheritDoc} */
381 @Override
382 public String toString() {
/*
P/P * Method: String toString()
*
* Preconditions:
* init'ed(this.reportStatus)
*
* Postconditions:
* init'ed(java.lang.StringBuilder:toString(...)._tainted)
* return_value == &java.lang.StringBuilder:toString(...)
*/
383 return "ID" + id + " Level: " + getLevel() + " Status: " + getReportStatus()
384 + " Message: '" + getMessage() + "'";
385 }
386
387 /** {@inheritDoc} */
388 @Override
389 public boolean equals(final Object obj) {
/*
P/P * Method: bool equals(Object)
*
* Preconditions:
* (soft) this.message != null
*
* Postconditions:
* init'ed(return_value)
*
* Test Vectors:
* obj: Inverse{null}, Addr_Set{null}
* this.level == obj.level: {1}, {0}
* java.lang.String:equals(...)@403: {1}, {0}
* java.util.Arrays:equals(...)@407: {1}, {0}
*/
390 if (obj == null) {
391 return false;
392 }
393
394 if (getClass() != obj.getClass()) {
395 return false;
396 }
397
398 final ProgramError other = (ProgramError) obj;
399 if (this.level != other.level) {
400 return false;
401 }
402
403 if (!this.message.equals(other.message)) {
404 return false;
405 }
406
407 if (!Arrays.equals(this.trace, other.trace)) {
408 return false;
409 }
410
411 return true;
412 }
413
414 /** {@inheritDoc} */
415 @Override
416 public int hashCode() {
/*
P/P * Method: int hashCode()
*
* Preconditions:
* this.level != null
* this.message != null
*
* Presumptions:
* (com.dmdirc.logger.ErrorLevel:hashCode(...)@418*67 + java.lang.String:hashCode(...)@419)*67 + java.util.Arrays:hashCode(...)@420 in {-2_149_588_989..4_292_861_954}
* com.dmdirc.logger.ErrorLevel:hashCode(...)@418 in {-65_539_622..33_486_689}
* com.dmdirc.logger.ErrorLevel:hashCode(...)@418*67 + java.lang.String:hashCode(...)@419 in {-96_187_407..96_124_561}
*
* Postconditions:
* init'ed(return_value)
*/
417 int hash = 7;
418 hash = 67 * hash + this.level.hashCode();
419 hash = 67 * hash + this.message.hashCode();
420 hash = 67 * hash + Arrays.hashCode(this.trace);
421 return hash;
422 }
423
424 }
SofCheck Inspector Build Version : 2.17854
| ProgramError.java |
2009-Jun-25 01:54:24 |
| ProgramError.class |
2009-Sep-02 17:04:15 |