Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

ProgramError.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. /*
  2. * Copyright (c) 2006-2011 DMDirc Developers
  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. package com.dmdirc.logger;
  23. import com.dmdirc.Main;
  24. import com.dmdirc.config.IdentityManager;
  25. import com.dmdirc.util.io.Downloader;
  26. import java.io.File;
  27. import java.io.FileOutputStream;
  28. import java.io.IOException;
  29. import java.io.OutputStream;
  30. import java.io.PrintWriter;
  31. import java.io.Serializable;
  32. import java.net.MalformedURLException;
  33. import java.text.DateFormat;
  34. import java.text.SimpleDateFormat;
  35. import java.util.ArrayList;
  36. import java.util.Arrays;
  37. import java.util.Date;
  38. import java.util.HashMap;
  39. import java.util.List;
  40. import java.util.Map;
  41. import java.util.concurrent.Semaphore;
  42. import java.util.concurrent.atomic.AtomicInteger;
  43. /**
  44. * Stores a program error.
  45. */
  46. public final class ProgramError implements Serializable {
  47. /**
  48. * A version number for this class. It should be changed whenever the class
  49. * structure is changed (or anything else that would prevent serialized
  50. * objects being unserialized with the new class).
  51. */
  52. private static final long serialVersionUID = 3;
  53. /** Directory used to store errors. */
  54. private static File errorDir;
  55. /** Semaphore used to serialise write access. */
  56. private static final Semaphore WRITING_SEM = new Semaphore(1);
  57. /** Error ID. */
  58. private final long id;
  59. /** Error icon. */
  60. private final ErrorLevel level;
  61. /** Error message. */
  62. private final String message;
  63. /** Error trace. */
  64. private final String[] trace;
  65. /** Date/time error first occurred. */
  66. private final Date firstDate;
  67. /** Date/time error last occurred. */
  68. private Date lastDate;
  69. /** Number of occurrences. */
  70. private AtomicInteger count;
  71. /** Error report Status. */
  72. private ErrorReportStatus reportStatus;
  73. /** Error fixed Status. */
  74. private ErrorFixedStatus fixedStatus;
  75. /**
  76. * Creates a new instance of ProgramError.
  77. *
  78. * @param id error id
  79. * @param level Error level
  80. * @param message Error message
  81. * @param trace Error trace
  82. * @param date Error time and date
  83. */
  84. public ProgramError(final long id, final ErrorLevel level,
  85. final String message, final String[] trace, final Date date) {
  86. if (id < 0) {
  87. throw new IllegalArgumentException("ID must be a positive integer: " + id);
  88. }
  89. if (level == null) {
  90. throw new IllegalArgumentException("Level cannot be null");
  91. }
  92. if (message == null || message.isEmpty()) {
  93. throw new IllegalArgumentException("Message cannot be null or an empty string");
  94. }
  95. if (trace == null) {
  96. throw new IllegalArgumentException("Trace cannot be null");
  97. }
  98. if (date == null) {
  99. throw new IllegalArgumentException("date cannot be null");
  100. }
  101. this.id = id;
  102. this.level = level;
  103. this.message = message;
  104. this.trace = Arrays.copyOf(trace, trace.length);
  105. this.firstDate = (Date) date.clone();
  106. this.lastDate = (Date) date.clone();
  107. this.count = new AtomicInteger(1);
  108. this.reportStatus = ErrorReportStatus.WAITING;
  109. this.fixedStatus = ErrorFixedStatus.UNKNOWN;
  110. }
  111. /**
  112. * Returns this errors level.
  113. *
  114. * @return Error level
  115. */
  116. public ErrorLevel getLevel() {
  117. return level;
  118. }
  119. /**
  120. * Returns this errors message.
  121. *
  122. * @return Error message
  123. */
  124. public String getMessage() {
  125. return message;
  126. }
  127. /**
  128. * Returns this errors trace.
  129. *
  130. * @return Error trace
  131. */
  132. public String[] getTrace() {
  133. return Arrays.copyOf(trace, trace.length);
  134. }
  135. /**
  136. * Returns this errors time.
  137. *
  138. * @return Error time
  139. */
  140. public Date getDate() {
  141. return (Date) firstDate.clone();
  142. }
  143. /**
  144. * Returns the number of times this error has occurred.
  145. *
  146. * @return Error count
  147. */
  148. public int getCount() {
  149. return count.get();
  150. }
  151. /**
  152. * Returns the last time this error occurred.
  153. *
  154. * @return Last occurrence
  155. */
  156. public Date getLastDate() {
  157. return (Date) lastDate.clone();
  158. }
  159. /**
  160. * Returns the reportStatus of this error.
  161. *
  162. * @return Error reportStatus
  163. */
  164. public ErrorReportStatus getReportStatus() {
  165. return reportStatus;
  166. }
  167. /**
  168. * Returns the fixed status of this error.
  169. *
  170. * @return Error fixed status
  171. */
  172. public ErrorFixedStatus getFixedStatus() {
  173. return fixedStatus;
  174. }
  175. /**
  176. * Sets the report Status of this error.
  177. *
  178. * @param newStatus new ErrorReportStatus for the error
  179. */
  180. public void setReportStatus(final ErrorReportStatus newStatus) {
  181. if (newStatus != null && !reportStatus.equals(newStatus)) {
  182. reportStatus = newStatus;
  183. ErrorManager.getErrorManager().fireErrorStatusChanged(this);
  184. synchronized (this) {
  185. notifyAll();
  186. }
  187. }
  188. }
  189. /**
  190. * Sets the fixed status of this error.
  191. *
  192. * @param newStatus new ErrorFixedStatus for the error
  193. */
  194. public void setFixedStatus(final ErrorFixedStatus newStatus) {
  195. if (newStatus != null && !fixedStatus.equals(newStatus)) {
  196. fixedStatus = newStatus;
  197. ErrorManager.getErrorManager().fireErrorStatusChanged(this);
  198. synchronized (this) {
  199. notifyAll();
  200. }
  201. }
  202. }
  203. /**
  204. * Returns the ID of this error.
  205. *
  206. * @return Error ID
  207. */
  208. public long getID() {
  209. return id;
  210. }
  211. /**
  212. * Saves this error to disk.
  213. */
  214. public void save() {
  215. final PrintWriter out = new PrintWriter(getErrorFile(), true);
  216. out.println("Date:" + getDate());
  217. out.println("Level: " + getLevel());
  218. out.println("Description: " + getMessage());
  219. out.println("Details:");
  220. for (String traceLine : getTrace()) {
  221. out.println('\t' + traceLine);
  222. }
  223. out.close();
  224. }
  225. /**
  226. * Creates a new file for an error and returns the output stream.
  227. *
  228. * @return BufferedOutputStream to write to the error file
  229. */
  230. @SuppressWarnings("PMD.SystemPrintln")
  231. private OutputStream getErrorFile() {
  232. WRITING_SEM.acquireUninterruptibly();
  233. if (errorDir == null || !errorDir.exists()) {
  234. errorDir = new File(Main.getConfigDir() + "errors");
  235. if (!errorDir.exists()) {
  236. errorDir.mkdirs();
  237. }
  238. }
  239. final String logName = getDate().getTime() + "-" + getLevel();
  240. final File errorFile = new File(errorDir, logName + ".log");
  241. if (errorFile.exists()) {
  242. boolean rename = false;
  243. for (int i = 0; !rename; i++) {
  244. rename = errorFile.renameTo(new File(errorDir, logName + "-" + i + ".log"));
  245. }
  246. }
  247. try {
  248. errorFile.createNewFile();
  249. return new FileOutputStream(errorFile);
  250. } catch (IOException ex) {
  251. System.err.println("Error creating new file: ");
  252. ex.printStackTrace();
  253. return new NullOutputStream();
  254. } finally {
  255. WRITING_SEM.release();
  256. }
  257. }
  258. /**
  259. * Sends this error report to the DMDirc developers.
  260. */
  261. public void send() {
  262. final Map<String, String> postData = new HashMap<String, String>();
  263. List<String> response = new ArrayList<String>();
  264. int tries = 0;
  265. String traceString = Arrays.toString(getTrace());
  266. if (traceString.isEmpty() || traceString.equals("[]")) {
  267. traceString = "[No Trace]";
  268. }
  269. postData.put("message", getMessage());
  270. postData.put("trace", traceString);
  271. postData.put("version", IdentityManager.getGlobalConfig().getOption("version", "version"));
  272. setReportStatus(ErrorReportStatus.SENDING);
  273. do {
  274. if (tries != 0) {
  275. try {
  276. Thread.sleep(5000);
  277. } catch (InterruptedException ex) {
  278. //Ignore
  279. }
  280. }
  281. try {
  282. response = Downloader.getPage("http://www.dmdirc.com/error.php", postData);
  283. } catch (MalformedURLException ex) {
  284. //Ignore, wont happen
  285. } catch (IOException ex) {
  286. //Ignore being handled
  287. }
  288. tries++;
  289. } while ((response.isEmpty() || !response.get(response.size() - 1).
  290. equalsIgnoreCase("Error report submitted. Thank you."))
  291. && tries <= 5);
  292. checkResponses(response);
  293. }
  294. /**
  295. * Checks the responses and sets status accordingly.
  296. *
  297. * @param error Error to check response
  298. * @param response Response to check
  299. */
  300. private void checkResponses(final List<String> response) {
  301. if (!response.isEmpty() && response.get(response.size() - 1).
  302. equalsIgnoreCase("Error report submitted. Thank you.")) {
  303. setReportStatus(ErrorReportStatus.FINISHED);
  304. } else {
  305. setReportStatus(ErrorReportStatus.ERROR);
  306. return;
  307. }
  308. if (response.size() == 1) {
  309. setFixedStatus(ErrorFixedStatus.NEW);
  310. return;
  311. }
  312. final String responseToCheck = response.get(0);
  313. if (responseToCheck.matches(".*fixed.*")) {
  314. setFixedStatus(ErrorFixedStatus.FIXED);
  315. } else if (responseToCheck.matches(".*more recent version.*")) {
  316. setFixedStatus(ErrorFixedStatus.TOOOLD);
  317. } else if (responseToCheck.matches(".*invalid.*")) {
  318. setFixedStatus(ErrorFixedStatus.INVALID);
  319. } else if (responseToCheck.matches(".*previously.*")) {
  320. setFixedStatus(ErrorFixedStatus.KNOWN);
  321. } else {
  322. setFixedStatus(ErrorFixedStatus.NEW);
  323. }
  324. }
  325. /**
  326. * Determines whether or not the stack trace associated with this error
  327. * is from a valid source. A valid source is one that is within a DMDirc
  328. * package (com.dmdirc), and is not the DMDirc event queue.
  329. *
  330. * @return True if the source is valid, false otherwise
  331. */
  332. public boolean isValidSource() {
  333. final String line = getSourceLine();
  334. return line.startsWith("com.dmdirc")
  335. && !line.startsWith("com.dmdirc.addons.ui_swing.DMDircEventQueue");
  336. }
  337. /**
  338. * Returns the "source line" of this error, which is defined as the first
  339. * line starting with a DMDirc package name (com.dmdirc). If no such line
  340. * is found, returns the first line of the message.
  341. *
  342. * @return This error's source line
  343. */
  344. public String getSourceLine() {
  345. for (String line : trace) {
  346. if (line.startsWith("com.dmdirc")) {
  347. return line;
  348. }
  349. }
  350. return trace[0];
  351. }
  352. /**
  353. * Updates the last date this error occurred.
  354. */
  355. public void updateLastDate() {
  356. updateLastDate(new Date());
  357. }
  358. /**
  359. * Updates the last date this error occurred.
  360. *
  361. * @param date Date error occurred
  362. */
  363. public void updateLastDate(final Date date) {
  364. lastDate = date;
  365. count.getAndIncrement();
  366. ErrorManager.getErrorManager().fireErrorStatusChanged(this);
  367. synchronized (this) {
  368. notifyAll();
  369. }
  370. }
  371. /**
  372. * Retruns a human readable string describing the number of times
  373. * this error occurred and when these occurrences were.
  374. *
  375. * @return Occurrences description
  376. */
  377. public String occurrencesString() {
  378. final DateFormat format = new SimpleDateFormat("MMM dd hh:mm aa");
  379. if (count.get() == 1) {
  380. return "1 occurrence on " + format.format(getDate());
  381. } else {
  382. return count.get() + " occurrences between " + format.format(
  383. getDate()) + " and " + format.format(getLastDate()) + ".";
  384. }
  385. }
  386. /** {@inheritDoc} */
  387. @Override
  388. public String toString() {
  389. return "ID" + id + " Level: " + getLevel() + " Status: " + getReportStatus()
  390. + " Message: '" + getMessage() + "'";
  391. }
  392. /** {@inheritDoc} */
  393. @Override
  394. public boolean equals(final Object obj) {
  395. if (obj == null) {
  396. return false;
  397. }
  398. if (getClass() != obj.getClass()) {
  399. return false;
  400. }
  401. final ProgramError other = (ProgramError) obj;
  402. if (this.level != other.level) {
  403. return false;
  404. }
  405. if (!this.message.equals(other.message)) {
  406. return false;
  407. }
  408. if (!Arrays.equals(this.trace, other.trace)) {
  409. return false;
  410. }
  411. return true;
  412. }
  413. /** {@inheritDoc} */
  414. @Override
  415. public int hashCode() {
  416. int hash = 7;
  417. hash = 67 * hash + this.level.hashCode();
  418. hash = 67 * hash + this.message.hashCode();
  419. hash = 67 * hash + Arrays.hashCode(this.trace);
  420. return hash;
  421. }
  422. }