package railo.runtime.debug; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import railo.commons.io.SystemUtil; import railo.commons.io.log.LogUtil; import railo.commons.io.res.util.ResourceSnippet; import railo.commons.io.res.util.ResourceSnippetsMap; import railo.commons.lang.StringUtil; import railo.runtime.Component; import railo.runtime.Page; import railo.runtime.PageContext; import railo.runtime.PageContextImpl; import railo.runtime.PageSource; import railo.runtime.PageSourceImpl; import railo.runtime.config.Config; import railo.runtime.config.ConfigImpl; import railo.runtime.db.SQL; import railo.runtime.engine.ThreadLocalPageContext; import railo.runtime.exp.CatchBlock; import railo.runtime.exp.DatabaseException; import railo.runtime.exp.PageException; import railo.runtime.exp.PageExceptionImpl; import railo.runtime.op.Caster; import railo.runtime.type.Array; import railo.runtime.type.ArrayImpl; import railo.runtime.type.Collection; import railo.runtime.type.DebugQueryColumn; import railo.runtime.type.KeyImpl; import railo.runtime.type.Query; import railo.runtime.type.QueryColumn; import railo.runtime.type.QueryImpl; import railo.runtime.type.Struct; import railo.runtime.type.StructImpl; import railo.runtime.type.dt.DateTimeImpl; import railo.runtime.type.util.KeyConstants; /** * Class to debug the application */ public final class DebuggerImpl implements DebuggerPro { private static final long serialVersionUID = 3957043879267494311L; private static final Collection.Key IMPLICIT_ACCESS= KeyImpl.intern("implicitAccess"); private static final Collection.Key PAGE_PARTS= KeyImpl.intern("pageParts"); //private static final Collection.Key OUTPUT_LOG= KeyImpl.intern("outputLog"); private static final int MAX_PARTS = 100; private Map<String,DebugEntryTemplateImpl> entries=new HashMap<String,DebugEntryTemplateImpl>(); private Map<String,DebugEntryTemplatePartImpl> partEntries; private ResourceSnippetsMap snippetsMap = new ResourceSnippetsMap( 1024, 128 ); private List<QueryEntry> queries=new ArrayList<QueryEntry>(); private List<DebugTimerImpl> timers=new ArrayList<DebugTimerImpl>(); private List<DebugTraceImpl> traces=new ArrayList<DebugTraceImpl>(); private List<CatchBlock> exceptions=new ArrayList<CatchBlock>(); private Map<String,ImplicitAccessImpl> implicitAccesses=new HashMap<String,ImplicitAccessImpl>(); private boolean output=true; private long lastEntry; private long lastTrace; private Array historyId=new ArrayImpl(); private Array historyLevel=new ArrayImpl(); private long starttime=System.currentTimeMillis(); private DebugOutputLog outputLog; final static Comparator DEBUG_ENTRY_TEMPLATE_COMPARATOR = new DebugEntryTemplateComparator(); final static Comparator DEBUG_ENTRY_TEMPLATE_PART_COMPARATOR = new DebugEntryTemplatePartComparator(); @Override public void reset() { entries.clear(); if(partEntries!=null)partEntries.clear(); queries.clear(); implicitAccesses.clear(); timers.clear(); traces.clear(); exceptions.clear(); historyId.clear(); historyLevel.clear(); output=true; outputLog=null; } public DebuggerImpl() { } @Override public DebugEntryTemplate getEntry(PageContext pc,PageSource source) { return getEntry(pc,source,null); } @Override public DebugEntryTemplate getEntry(PageContext pc,PageSource source, String key) { lastEntry = System.currentTimeMillis(); String src=DebugEntryTemplateImpl.getSrc(source==null?"":source.getDisplayPath(),key); DebugEntryTemplateImpl de= entries.get(src); if(de!=null ){ de.countPP(); historyId.appendEL(de.getId()); historyLevel.appendEL(Caster.toInteger(pc.getCurrentLevel())); return de; } de=new DebugEntryTemplateImpl(source,key); entries.put(src,de); historyId.appendEL(de.getId()); historyLevel.appendEL(Caster.toInteger(pc.getCurrentLevel())); return de; } @Override public DebugEntryTemplatePart getEntry(PageContext pc, PageSource source, int startPos, int endPos) { String src=DebugEntryTemplatePartImpl.getSrc(source==null?"":source.getDisplayPath(),startPos,endPos); DebugEntryTemplatePartImpl de=null; if(partEntries!=null){ de=partEntries.get(src); if(de!=null ){ de.countPP(); return de; } } else { partEntries=new HashMap<String, DebugEntryTemplatePartImpl>(); } ResourceSnippet snippet = snippetsMap.getSnippet( source, startPos, endPos, pc.getConfig().getResourceCharset() ); de=new DebugEntryTemplatePartImpl(source, startPos, endPos, snippet.getStartLine(), snippet.getEndLine(), snippet.getContent()); partEntries.put(src,de); return de; } private ArrayList<DebugEntryTemplate> toArray() { ArrayList<DebugEntryTemplate> arrPages=new ArrayList<DebugEntryTemplate>(entries.size()); Iterator<String> it = entries.keySet().iterator(); while(it.hasNext()) { DebugEntryTemplate page =entries.get(it.next()); page.resetQueryTime(); arrPages.add(page); } Collections.sort(arrPages, DEBUG_ENTRY_TEMPLATE_COMPARATOR); // Queries int len=queries.size(); for(int i=0;i<len;i++) { QueryEntryPro entry=(QueryEntryPro) queries.get(i); String path=entry.getSrc(); Object o=entries.get(path); if(o!=null) { DebugEntryTemplate oe=(DebugEntryTemplate) o; oe.updateQueryTime(entry.getExecutionTime()); } } return arrPages; } /*private DumpData _toDumpData(int value) { return new SimpleDumpData(_toString(value)); } private DumpData _toDumpData(long value) { return new SimpleDumpData(_toString(value)); }*/ private String _toString(long value) { if(value<=0) return "0"; return String.valueOf(value); } private String _toString(int value) { if(value<=0) return "0"; return String.valueOf(value); } @Override public void addQuery(Query query,String datasource,String name,SQL sql, int recordcount, PageSource src,int time) { queries.add(new QueryEntryImpl(query,datasource,name,sql,recordcount,src.getDisplayPath(),time)); } @Override public void addQuery(Query query,String datasource,String name,SQL sql, int recordcount, PageSource src,long time) { queries.add(new QueryEntryImpl(query,datasource,name,sql,recordcount,src.getDisplayPath(),time)); } @Override public void setOutput(boolean output) { this.output = output; } @Override public List<QueryEntry> getQueries() { return queries; } @Override public void writeOut(PageContext pc) throws IOException { //stop(); if(!output)return; String addr = pc.getHttpServletRequest().getRemoteAddr(); railo.runtime.config.DebugEntry debugEntry = ((ConfigImpl)pc.getConfig()).getDebugEntry(addr, null); // no debug File if(debugEntry==null) { //pc.forceWrite(pc.getConfig().getDefaultDumpWriter().toString(pc,toDumpData(pc, 9999,DumpUtil.toDumpProperties()),true)); return; } Struct args=new StructImpl(); args.setEL(KeyConstants._custom, debugEntry.getCustom()); try { args.setEL(KeyConstants._debugging, pc.getDebugger().getDebuggingData(pc)); } catch (PageException e1) {} try { String path = debugEntry.getPath(); PageSource[] arr = ((PageContextImpl)pc).getPageSources(path); Page p = PageSourceImpl.loadPage(pc, arr,null); // patch for old path String fullname = debugEntry.getFullname(); if(p==null) { if(path!=null) { boolean changed=false; if(path.endsWith("/Modern.cfc") || path.endsWith("\\Modern.cfc")) { path="/railo-server-context/admin/debug/Modern.cfc"; fullname="railo-server-context.admin.debug.Modern"; changed=true; } else if(path.endsWith("/Classic.cfc") || path.endsWith("\\Classic.cfc")) { path="/railo-server-context/admin/debug/Classic.cfc"; fullname="railo-server-context.admin.debug.Classic"; changed=true; } else if(path.endsWith("/Comment.cfc") || path.endsWith("\\Comment.cfc")) { path="/railo-server-context/admin/debug/Comment.cfc"; fullname="railo-server-context.admin.debug.Comment"; changed=true; } if(changed)pc.write("<span style='color:red'>Please update your debug template defintions in the railo admin by going into the detail view and hit the \"update\" button.</span>"); } arr = ((PageContextImpl)pc).getPageSources(path); p = PageSourceImpl.loadPage(pc, arr); } pc.addPageSource(p.getPageSource(), true); try{ Component cfc = pc.loadComponent(fullname); cfc.callWithNamedValues(pc, "output", args); } finally { pc.removeLastPageSource(true); } } catch (PageException e) { pc.handlePageException(e); } } @Override public Struct getDebuggingData(PageContext pc) throws DatabaseException { return getDebuggingData(pc, false); } @Override public Struct getDebuggingData(PageContext pc, boolean addAddionalInfo) throws DatabaseException { List<QueryEntry> queries = getQueries(); Struct qryExe=new StructImpl(); ListIterator<QueryEntry> qryIt = queries.listIterator(); Collection.Key[] cols = new Collection.Key[]{ KeyConstants._name, KeyConstants._time, KeyConstants._sql, KeyConstants._src, KeyConstants._count, KeyConstants._datasource, KeyConstants._usage}; String[] types = new String[]{"VARCHAR","DOUBLE","VARCHAR","VARCHAR","DOUBLE","VARCHAR","ANY"}; //queries Query qryQueries=null; try { qryQueries = new QueryImpl(cols,types,queries.size(),"query"); } catch (DatabaseException e) { qryQueries = new QueryImpl(cols,queries.size(),"query"); } int row=0; try { while(qryIt.hasNext()) { row++; QueryEntryPro qe=(QueryEntryPro) qryIt.next(); qryQueries.setAt(KeyConstants._name,row,qe.getName()==null?"":qe.getName()); qryQueries.setAt(KeyConstants._time,row,Long.valueOf(qe.getExecutionTime())); qryQueries.setAt(KeyConstants._sql,row,qe.getSQL().toString()); qryQueries.setAt(KeyConstants._src,row,qe.getSrc()); qryQueries.setAt(KeyConstants._count,row,Integer.valueOf(qe.getRecordcount())); qryQueries.setAt(KeyConstants._datasource,row,qe.getDatasource()); Struct usage = getUsage(qe); if(usage!=null) qryQueries.setAt(KeyConstants._usage,row,usage); Object o=qryExe.get(KeyImpl.init(qe.getSrc()),null); if(o==null) qryExe.setEL(KeyImpl.init(qe.getSrc()),Long.valueOf(qe.getExecutionTime())); else qryExe.setEL(KeyImpl.init(qe.getSrc()),Long.valueOf(((Long)o).longValue()+qe.getExecutionTime())); } } catch(PageException dbe) {} // Pages // src,load,app,query,total Struct debugging=new StructImpl(); row=0; ArrayList<DebugEntryTemplate> arrPages = toArray(); int len=arrPages.size(); Query qryPage=new QueryImpl( new Collection.Key[]{ KeyConstants._id, KeyConstants._count, KeyConstants._min, KeyConstants._max, KeyConstants._avg ,KeyConstants._app, KeyConstants._load, KeyConstants._query, KeyConstants._total, KeyConstants._src}, len,"query"); try { DebugEntryTemplate de; //PageSource ps; for(int i=0;i<len;i++) { row++; de=arrPages.get(i); //ps = de.getPageSource(); qryPage.setAt(KeyConstants._id,row,de.getId()); qryPage.setAt(KeyConstants._count,row,_toString(de.getCount())); qryPage.setAt(KeyConstants._min,row,_toString(de.getMin())); qryPage.setAt(KeyConstants._max,row,_toString(de.getMax())); qryPage.setAt(KeyConstants._avg,row,_toString(de.getExeTime()/de.getCount())); qryPage.setAt(KeyConstants._app,row,_toString(de.getExeTime()-de.getQueryTime())); qryPage.setAt(KeyConstants._load,row,_toString(de.getFileLoadTime())); qryPage.setAt(KeyConstants._query,row,_toString(de.getQueryTime())); qryPage.setAt(KeyConstants._total,row,_toString( de.getFileLoadTime() + de.getExeTime())); qryPage.setAt(KeyConstants._src,row,de.getSrc()); } } catch(PageException dbe) {} // Pages Parts List<DebugEntryTemplatePart> filteredPartEntries = null; boolean hasParts=partEntries!=null && !partEntries.isEmpty() && !arrPages.isEmpty(); int qrySize=0; if(hasParts) { String slowestTemplate = arrPages.get( 0 ).getPath(); filteredPartEntries = new ArrayList(); java.util.Collection<DebugEntryTemplatePartImpl> col = partEntries.values(); for ( DebugEntryTemplatePart detp : col ) { if ( detp.getPath().equals( slowestTemplate ) ) filteredPartEntries.add( detp ); } qrySize = Math.min( filteredPartEntries.size(), MAX_PARTS ); } Query qryPart = new QueryImpl( new Collection.Key[]{ KeyConstants._id ,KeyConstants._count ,KeyConstants._min ,KeyConstants._max ,KeyConstants._avg ,KeyConstants._total ,KeyConstants._path ,KeyConstants._start ,KeyConstants._end ,KeyConstants._startLine ,KeyConstants._endLine ,KeyConstants._snippet }, qrySize, "query" ); if(hasParts) { row=0; Collections.sort( filteredPartEntries, DEBUG_ENTRY_TEMPLATE_PART_COMPARATOR ); DebugEntryTemplatePart[] parts = new DebugEntryTemplatePart[ qrySize ]; if ( filteredPartEntries.size() > MAX_PARTS ) parts = filteredPartEntries.subList(0, MAX_PARTS).toArray( parts ); else parts = filteredPartEntries.toArray( parts ); try { DebugEntryTemplatePart de; //PageSource ps; for(int i=0;i<parts.length;i++) { row++; de=parts[i]; qryPart.setAt(KeyConstants._id,row,de.getId()); qryPart.setAt(KeyConstants._count,row,_toString(de.getCount())); qryPart.setAt(KeyConstants._min,row,_toString(de.getMin())); qryPart.setAt(KeyConstants._max,row,_toString(de.getMax())); qryPart.setAt(KeyConstants._avg,row,_toString(de.getExeTime()/de.getCount())); qryPart.setAt(KeyConstants._start,row,_toString(de.getStartPosition())); qryPart.setAt(KeyConstants._end,row,_toString(de.getEndPosition())); qryPart.setAt(KeyConstants._total,row,_toString(de.getExeTime())); qryPart.setAt(KeyConstants._path,row,de.getPath()); if ( de instanceof DebugEntryTemplatePartImpl ) { qryPart.setAt( KeyConstants._startLine, row, _toString( ((DebugEntryTemplatePartImpl)de).getStartLine() ) ); qryPart.setAt( KeyConstants._endLine, row, _toString( ((DebugEntryTemplatePartImpl)de).getEndLine() )); qryPart.setAt( KeyConstants._snippet, row, ((DebugEntryTemplatePartImpl)de).getSnippet() ); } } } catch(PageException dbe) {} } // exceptions len = exceptions==null?0:exceptions.size(); Array arrExceptions=new ArrayImpl(); if(len>0) { Iterator<CatchBlock> it = exceptions.iterator(); row=0; while(it.hasNext()) { arrExceptions.appendEL(it.next()); } } // output log //Query qryOutputLog=getOutputText(); // timers len=timers==null?0:timers.size(); Query qryTimers=new QueryImpl( new Collection.Key[]{KeyConstants._label,KeyConstants._time,KeyConstants._template}, len,"timers"); if(len>0) { try { Iterator<DebugTimerImpl> it = timers.iterator(); DebugTimer timer; row=0; while(it.hasNext()) { timer=it.next(); row++; qryTimers.setAt(KeyConstants._label,row,timer.getLabel()); qryTimers.setAt(KeyConstants._template,row,timer.getTemplate()); qryTimers.setAt(KeyConstants._time,row,Caster.toDouble(timer.getTime())); } } catch(PageException dbe) {} } // traces len=traces==null?0:traces.size(); if(!((ConfigImpl)pc.getConfig()).hasDebugOptions(ConfigImpl.DEBUG_TRACING))len=0; Query qryTraces=new QueryImpl( new Collection.Key[]{ KeyConstants._type, KeyConstants._category, KeyConstants._text, KeyConstants._template, KeyConstants._line, KeyConstants._action, KeyConstants._varname, KeyConstants._varvalue, KeyConstants._time}, len,"traces"); if(len>0) { try { Iterator<DebugTraceImpl> it = traces.iterator(); DebugTraceImpl trace; row=0; while(it.hasNext()) { trace= it.next(); row++; qryTraces.setAt(KeyConstants._type,row,LogUtil.toStringType(trace.getType(), "INFO")); if(!StringUtil.isEmpty(trace.getCategory()))qryTraces.setAt(KeyConstants._category,row,trace.getCategory()); if(!StringUtil.isEmpty(trace.getText()))qryTraces.setAt(KeyConstants._text,row,trace.getText()); if(!StringUtil.isEmpty(trace.getTemplate()))qryTraces.setAt(KeyConstants._template,row,trace.getTemplate()); if(trace.getLine()>0)qryTraces.setAt(KeyConstants._line,row,new Double(trace.getLine())); if(!StringUtil.isEmpty(trace.getAction()))qryTraces.setAt(KeyConstants._action,row,trace.getAction()); if(!StringUtil.isEmpty(trace.getVarName()))qryTraces.setAt(KeyImpl.init("varname"),row,trace.getVarName()); if(!StringUtil.isEmpty(trace.getVarValue()))qryTraces.setAt(KeyImpl.init("varvalue"),row,trace.getVarValue()); qryTraces.setAt(KeyConstants._time,row,new Double(trace.getTime())); } } catch(PageException dbe) {} } // scope access len=implicitAccesses==null?0:implicitAccesses.size(); Query qryImplicitAccesseses=new QueryImpl( new Collection.Key[]{ KeyConstants._template, KeyConstants._line, KeyConstants._scope, KeyConstants._count, KeyConstants._name}, len,"implicitAccess"); if(len>0) { try { Iterator<ImplicitAccessImpl> it = implicitAccesses.values().iterator(); ImplicitAccessImpl das; row=0; while(it.hasNext()) { das= it.next(); row++; qryImplicitAccesseses.setAt(KeyConstants._template,row,das.getTemplate()); qryImplicitAccesseses.setAt(KeyConstants._line,row,new Double(das.getLine())); qryImplicitAccesseses.setAt(KeyConstants._scope,row,das.getScope()); qryImplicitAccesseses.setAt(KeyConstants._count,row,new Double(das.getCount())); qryImplicitAccesseses.setAt(KeyConstants._name,row,das.getName()); } } catch(PageException dbe) {} } Query history=new QueryImpl(new Collection.Key[]{},0,"history"); try { history.addColumn(KeyConstants._id, historyId); history.addColumn(KeyConstants._level, historyLevel); } catch (PageException e) { } if(addAddionalInfo) { debugging.setEL(KeyConstants._cgi,pc.cgiScope()); debugging.setEL(KeyImpl.init("starttime"),new DateTimeImpl(starttime,false)); debugging.setEL(KeyConstants._id,pc.getId()); } debugging.setEL(KeyConstants._pages,qryPage); debugging.setEL(PAGE_PARTS,qryPart); debugging.setEL(KeyConstants._queries,qryQueries); debugging.setEL(KeyConstants._timers,qryTimers); debugging.setEL(KeyConstants._traces,qryTraces); debugging.setEL(IMPLICIT_ACCESS,qryImplicitAccesseses); //debugging.setEL(OUTPUT_LOG,qryOutputLog); debugging.setEL(KeyConstants._history,history); debugging.setEL(KeyConstants._exceptions,arrExceptions); return debugging; } private static Struct getUsage(QueryEntry qe) throws PageException { Query qry = ((QueryEntryImpl)qe).getQry(); QueryColumn c; DebugQueryColumn dqc; outer:if(qry!=null) { Struct usage=null; Collection.Key[] columnNames = qry.getColumnNames(); Collection.Key columnName; for(int i=0;i<columnNames.length;i++){ columnName=columnNames[i]; c = qry.getColumn(columnName); if(!(c instanceof DebugQueryColumn)) break outer; dqc=(DebugQueryColumn) c; if(usage==null) usage=new StructImpl(); usage.setEL(columnName, Caster.toBoolean(dqc.isUsed())); } return usage; } return null; } /*private static String getUsageList(QueryEntry qe) throws PageException { Query qry = ((QueryEntryImpl)qe).getQry(); StringBuilder sb=new StringBuilder(); QueryColumn c; DebugQueryColumn dqc; outer:if(qry!=null) { String[] columnNames = qry.getColumns(); Collection.Key colName; for(int i=0;i<columnNames.length;i++){ colName=KeyImpl.init(columnNames[i]); c = qry.getColumn(colName); if(!(c instanceof DebugQueryColumn)) break outer; dqc=(DebugQueryColumn) c; if(!dqc.isUsed()){ if(sb.length()>0) sb.append(", "); sb.append(colName.getString()); } } } return sb.toString(); }*/ @Override public DebugTimer addTimer(String label, long time, String template) { DebugTimerImpl t; timers.add(t=new DebugTimerImpl(label,time,template)); return t; } @Override public DebugTrace addTrace(int type, String category, String text, PageSource page,String varName,String varValue) { long _lastTrace =(traces.isEmpty())?lastEntry: lastTrace; lastTrace = System.currentTimeMillis(); StackTraceElement[] _traces = new Exception("Stack trace").getStackTrace(); String clazz=page.getFullClassName(); int line=0; // line for(int i=0;i<_traces.length;i++) { StackTraceElement trace=_traces[i]; if(trace.getClassName().startsWith(clazz)) { line=trace.getLineNumber(); break; } } DebugTraceImpl t=new DebugTraceImpl(type,category,text,page.getDisplayPath(),line,"",varName,varValue,lastTrace-_lastTrace); traces.add(t); return t; } @Override public DebugTrace addTrace(int type, String category, String text, String template,int line,String action,String varName,String varValue) { long _lastTrace =(traces.isEmpty())?lastEntry: lastTrace; lastTrace = System.currentTimeMillis(); DebugTraceImpl t=new DebugTraceImpl(type,category,text,template,line,action,varName,varValue,lastTrace-_lastTrace); traces.add(t); return t; } @Override public DebugTrace[] getTraces() { return getTraces(ThreadLocalPageContext.get()); } public DebugTrace[] getTraces(PageContext pc) { if(pc!=null && ((ConfigImpl)pc.getConfig()).hasDebugOptions(ConfigImpl.DEBUG_TRACING)) return traces.toArray(new DebugTrace[traces.size()]); return new DebugTrace[0]; } @Override public void addException(Config config,PageException pe) { if(exceptions.size()>1000) return; try { exceptions.add(((PageExceptionImpl)pe).getCatchBlock(config)); } catch(Throwable t){} } @Override public CatchBlock[] getExceptions() { return exceptions.toArray(new CatchBlock[exceptions.size()]); } public void init(Config config) { this.starttime=System.currentTimeMillis()+config.getTimeServerOffset(); } @Override public void addImplicitAccess(String scope, String name) { if(implicitAccesses.size()>1000) return; try { SystemUtil.TemplateLine tl = SystemUtil.getCurrentContext(); String key=tl+":"+scope+":"+name; ImplicitAccessImpl dsc = implicitAccesses.get(key); if(dsc!=null) dsc.inc(); else implicitAccesses.put(key,new ImplicitAccessImpl(scope,name,tl.template,tl.line)); } catch(Throwable t){} } @Override public ImplicitAccess[] getImplicitAccesses(int scope, String name) { return implicitAccesses.values().toArray(new ImplicitAccessImpl[implicitAccesses.size()]); } public void setOutputLog(DebugOutputLog outputLog) { this.outputLog=outputLog; } public DebugTextFragment[] getOutputTextFragments() { return this.outputLog.getFragments(); } public Query getOutputText() throws DatabaseException { DebugTextFragment[] fragments = outputLog.getFragments(); int len = fragments==null?0:fragments.length; Query qryOutputLog=new QueryImpl( new Collection.Key[]{ KeyConstants._line ,KeyConstants._template, KeyConstants._text}, len,"query"); if(len>0) { for(int i=0;i<fragments.length;i++) { qryOutputLog.setAtEL(KeyConstants._line,i+1,fragments[i].line); qryOutputLog.setAtEL(KeyConstants._template,i+1,fragments[i].template); qryOutputLog.setAtEL(KeyConstants._text,i+1,fragments[i].text); } } return qryOutputLog; } public void resetTraces() { traces.clear(); } } final class DebugEntryTemplateComparator implements Comparator<DebugEntryTemplate> { public int compare(DebugEntryTemplate de1,DebugEntryTemplate de2) { long result = ((de2.getExeTime()+de2.getFileLoadTime())-(de1.getExeTime()+de1.getFileLoadTime())); // we do this additional step to try to avoid ticket RAILO-2076 return result>0L?1:(result<0L?-1:0); } } final class DebugEntryTemplatePartComparator implements Comparator<DebugEntryTemplatePart> { @Override public int compare(DebugEntryTemplatePart de1,DebugEntryTemplatePart de2) { long result=de2.getExeTime()-de1.getExeTime(); // we do this additional step to try to avoid ticket RAILO-2076 return result>0L?1:(result<0L?-1:0); } }