/** * Copyright 2013-, Cloudsmith Inc. * * 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 org.cloudsmith.geppetto.diagnostic; import static java.lang.String.format; import java.io.File; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; public class Diagnostic implements Serializable, Iterable<Diagnostic> { public static final DiagnosticType CHAIN = new DiagnosticType("CHAIN", Diagnostic.class.getName()); private static final long serialVersionUID = 1L; public static final int FATAL = 5; public static final int ERROR = 4; public static final int WARNING = 3; public static final int INFO = 2; public static final int DEBUG = 1; public static final int OK = 0; private static final String[] severityStrings = new String[] { "OK", "DEBUG", "INFO", "WARNING", "ERROR", "FATAL" }; /** * Return the severity as a string. The string "UNKNOWN(<severity>)" will be returned if the * argument represents an unknown severity. * * @param severity * @return A string representing the severity */ public static String getSeverityString(int severity) { return severity >= 0 && severity < severityStrings.length ? severityStrings[severity] : format("UNKNOWN(%d)", severity); } private long timestamp; private int severity; private String message; private List<Diagnostic> children; private DiagnosticType type; private String issue; private String[] issueData; public Diagnostic() { setType(CHAIN); setSeverity(OK); } /** * Creates a new Diagnostic instance * * @param severity * Severity (see constants in {@link MessageWithSeverity}) * @param type * The type of message * @param message * The textual content of the message */ public Diagnostic(int severity, DiagnosticType type, String message) { setSeverity(severity); setMessage(message); setType(type); setTimestamp(System.currentTimeMillis()); } public void addChild(Diagnostic child) { if(getSeverity() < child.getSeverity()) setSeverity(child.getSeverity()); if(children == null) children = new ArrayList<Diagnostic>(); children.add(child); childAdded(child); } public void addChildren(Collection<? extends Diagnostic> children) { for(Diagnostic child : children) addChild(child); } public boolean appendLocationLabel(StringBuilder builder, boolean withOffsets) { return false; } /** * Implementors may want to override this method for direct logging purposes * * @param child * The child that was added to this instance */ protected void childAdded(Diagnostic child) { // Default is to do nothing. } public List<Diagnostic> getChildren() { return children == null ? Collections.<Diagnostic> emptyList() : children; } public String getErrorText() { StringBuilder bld = new StringBuilder(); toString(ERROR, bld, false, 0); return bld.toString(); } /** * Scans the children, depth first, until an ExceptionDiagnostic is found. The exception held by that diagnostic is * return. * * @return The first exception found or <code>null</code> if no exception exists. */ public Exception getException() { if(children != null) for(Diagnostic child : children) { Exception found = child.getException(); if(found != null) return found; } return null; } /** * Returns <tt>null</tt> unless subclassed * * @return <tt>null</tt> * @see FileDiagnostic */ public File getFile() { return null; } /** * The issue is a String naming a particular issue that makes it possible to have a more detailed understanding of * an error and what could be done to repair it. (As opposed to parsing the error message to gain an understanding). * Error messages may * * @return the value of the '<em>issue</em>' attribute. */ public String getIssue() { return issue; } /** * The issue data is optional data associated with a particular issue - it is typically used to pass values * calculated during validation and that may be meaningful to code that tries to repair or analyze a particular * problem and where it may be expensive to recompute these values. * * @return the value of the '<em>issueData</em>' attribute. */ public String[] getIssueData() { return issueData; } /** * Returns <tt>-1</tt> unless subclassed * * @return <tt>-1</tt> * @see FileDiagnostic */ public int getLineNumber() { return -1; } /** * Returns the result of calling {{@link #appendLocationLabel(StringBuilder, boolean)} on a StringBuilder or * <tt>null</tt> if no location label is present. * * @param withOffsets * Flag that indicates if offsets from the beginning of file are of interest (can be used for * highlighting in editors). * @return The location label or <tt>null</tt> * @see FileDiagnostic */ public String getLocationLabel(boolean withOffsets) { StringBuilder bld = new StringBuilder(); return appendLocationLabel(bld, withOffsets) ? bld.toString() : null; } /** * @return the message */ public String getMessage() { return message; } /** * @return the severity */ public int getSeverity() { return severity; } public String getSeverityString() { return getSeverityString(getSeverity()); } /** * @return the source */ public String getSource() { return type.getSource(); } public long getTimestamp() { return timestamp; } /** * @return The type of diagnostic */ public DiagnosticType getType() { return type; } @Override public Iterator<Diagnostic> iterator() { return getChildren().iterator(); } /** * This method is needed by net.sf.json but should not otherwise be used. * * @param children * The new children to set * @see #addChild(Diagnostic) * @see #addChildren(Collection) */ public void setChildren(List<Diagnostic> children) { this.children = null; addChildren(children); } public void setIssue(String newIssue) { issue = newIssue; } public void setIssueData(String[] newIssueData) { issueData = newIssueData; } /** * Ensures that the severity of the diagnostic and all its children is equal to or below the given <code>max</code>. * * @param max * The max severity */ public void setMaxSeverity(int max) { if(severity > max) severity = max; if(children != null) for(Diagnostic child : children) child.setMaxSeverity(severity); } /** * @param message * the message to set */ public void setMessage(String message) { this.message = message; } /** * @param severity * the severity to set */ public void setSeverity(int severity) { if(severity < this.severity) // Ensures children severity is not above this one setMaxSeverity(severity); else // Severity increase. Does not affect children this.severity = severity; } public void setTimestamp(long timestamp) { this.timestamp = timestamp; } public void setType(DiagnosticType type) { this.type = type; } @Override public final String toString() { StringBuilder bld = new StringBuilder(); toString(bld, 0); return bld.toString(); } private void toString(int severity, StringBuilder bld, boolean includeTopSeverity, int indent) { if(getSeverity() < severity) // Severity is transitive, so nothing to add here return; for(int idx = 0; idx < indent; ++idx) bld.append(' '); String resourcePath = getFile() == null ? null : getFile().getPath(); if(getMessage() == null && resourcePath == null) { if(children == null) { bld.append(getSeverityString()); return; } } else { if(includeTopSeverity || indent > 0) { bld.append(getSeverityString()); bld.append(':'); } if(resourcePath != null) { bld.append(resourcePath); bld.append(':'); } if(appendLocationLabel(bld, true)) bld.append(':'); if(getMessage() != null) { bld.append(getMessage()); bld.append(':'); } bld.setLength(bld.length() - 1); if(children != null) { bld.append('\n'); indent += 4; } } if(children != null) { int top = children.size(); int idx = 0; for(;;) { children.get(idx).toString(severity, bld, includeTopSeverity, indent); if(++idx >= top) break; bld.append('\n'); } } } public void toString(StringBuilder bld, int indent) { toString(INFO, bld, true, indent); } }