From 663631f0456fcc245dd835889f86541d75161c53 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 28 Aug 2014 20:52:43 +0400 Subject: java-decompiler: post-import cleanup (classes moved) --- .../java/decompiler/code/cfg/BasicBlock.java | 265 ++++++ .../java/decompiler/code/cfg/ControlFlowGraph.java | 887 +++++++++++++++++++++ .../decompiler/code/cfg/ExceptionRangeCFG.java | 128 +++ 3 files changed, 1280 insertions(+) create mode 100644 src/org/jetbrains/java/decompiler/code/cfg/BasicBlock.java create mode 100644 src/org/jetbrains/java/decompiler/code/cfg/ControlFlowGraph.java create mode 100644 src/org/jetbrains/java/decompiler/code/cfg/ExceptionRangeCFG.java (limited to 'src/org/jetbrains/java/decompiler/code/cfg') diff --git a/src/org/jetbrains/java/decompiler/code/cfg/BasicBlock.java b/src/org/jetbrains/java/decompiler/code/cfg/BasicBlock.java new file mode 100644 index 0000000..917da08 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/code/cfg/BasicBlock.java @@ -0,0 +1,265 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.code.cfg; + +import java.util.ArrayList; +import java.util.List; + +import org.jetbrains.java.decompiler.code.Instruction; +import org.jetbrains.java.decompiler.code.InstructionSequence; +import org.jetbrains.java.decompiler.code.SimpleInstructionSequence; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.modules.decompiler.decompose.IGraphNode; + +public class BasicBlock implements IGraphNode { + + // ***************************************************************************** + // public fields + // ***************************************************************************** + + public int id = 0; + + public int mark = 0; + + // ***************************************************************************** + // private fields + // ***************************************************************************** + + private InstructionSequence seq = new SimpleInstructionSequence(); + + private List preds = new ArrayList(); + + private List succs = new ArrayList(); + + private List instrOldOffsets = new ArrayList(); + + private List predExceptions = new ArrayList(); + + private List succExceptions = new ArrayList(); + + + + public BasicBlock() {} + + public BasicBlock(int id) { + this.id = id; + } + + // ***************************************************************************** + // public methods + // ***************************************************************************** + + public Object clone() { + + BasicBlock block = new BasicBlock(); + block.id = id; + block.setSeq(seq.clone()); + block.setInstrOldOffsets(new ArrayList(instrOldOffsets)); + + return block; + } + + public void free() { + preds.clear(); + succs.clear(); + instrOldOffsets.clear(); + succExceptions.clear(); + seq = new SimpleInstructionSequence(); + } + + public Instruction getInstruction(int index) { + return seq.getInstr(index); + } + + public Instruction getLastInstruction() { + if(seq.isEmpty()) { + return null; + } else { + return seq.getLastInstr(); + } + } + + public int size() { + return seq.length(); + } + + public void addPredecessor(BasicBlock block) { + preds.add(block); + } + + public void removePredecessor(BasicBlock block) { + while(preds.remove(block)); + } + + public void addSuccessor(BasicBlock block) { + succs.add(block); + block.addPredecessor(this); + } + + public void removeSuccessor(BasicBlock block) { + while(succs.remove(block)); + block.removePredecessor(this); + } + + // FIXME: unify block comparisons: id or direkt equality + public void replaceSuccessor(BasicBlock oldBlock, BasicBlock newBlock) { + for(int i=0;i getInstrOldOffsets() { + return instrOldOffsets; + } + + public void setInstrOldOffsets(List instrInds) { + this.instrOldOffsets = instrInds; + } + + public List getPredecessors() { + List lst = new ArrayList(preds); + lst.addAll(predExceptions); + return lst; + } + + public List getPreds() { + return preds; + } + + public void setPreds(List preds) { + this.preds = preds; + } + + public InstructionSequence getSeq() { + return seq; + } + + public void setSeq(InstructionSequence seq) { + this.seq = seq; + } + + public List getSuccs() { + return succs; + } + + public void setSuccs(List succs) { + this.succs = succs; + } + + + public List getSuccExceptions() { + return succExceptions; + } + + + public void setSuccExceptions(List succExceptions) { + this.succExceptions = succExceptions; + } + + public List getPredExceptions() { + return predExceptions; + } + + public void setPredExceptions(List predExceptions) { + this.predExceptions = predExceptions; + } + + +} diff --git a/src/org/jetbrains/java/decompiler/code/cfg/ControlFlowGraph.java b/src/org/jetbrains/java/decompiler/code/cfg/ControlFlowGraph.java new file mode 100644 index 0000000..824e8af --- /dev/null +++ b/src/org/jetbrains/java/decompiler/code/cfg/ControlFlowGraph.java @@ -0,0 +1,887 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.code.cfg; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.code.ExceptionHandler; +import org.jetbrains.java.decompiler.code.Instruction; +import org.jetbrains.java.decompiler.code.InstructionSequence; +import org.jetbrains.java.decompiler.code.JumpInstruction; +import org.jetbrains.java.decompiler.code.SimpleInstructionSequence; +import org.jetbrains.java.decompiler.code.SwitchInstruction; +import org.jetbrains.java.decompiler.code.interpreter.InstructionImpact; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.modules.code.DeadCodeHelper; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.consts.ConstantPool; +import org.jetbrains.java.decompiler.struct.gen.DataPoint; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.ListStack; +import org.jetbrains.java.decompiler.util.VBStyleCollection; + +public class ControlFlowGraph implements CodeConstants { + + public int last_id = 0; + + // ***************************************************************************** + // private fields + // ***************************************************************************** + + private VBStyleCollection blocks; + + private BasicBlock first; + + private BasicBlock last; + + private List exceptions; + + private HashMap subroutines; + + private HashSet finallyExits = new HashSet(); + + // ***************************************************************************** + // constructors + // ***************************************************************************** + + public ControlFlowGraph(InstructionSequence seq) { + buildBlocks(seq); + } + + + // ***************************************************************************** + // public methods + // ***************************************************************************** + + public void free() { + + for(BasicBlock block: blocks) { + block.free(); + } + + blocks.clear(); + first = null; + last = null; + exceptions.clear(); + finallyExits.clear(); + } + + public void removeMarkers() { + for(BasicBlock block: blocks) { + block.mark = 0; + } + } + + public String toString() { + + String new_line_separator = DecompilerContext.getNewLineSeparator(); + + StringBuffer buf = new StringBuffer(); + + for(BasicBlock block: blocks) { + buf.append("----- Block "+block.id+" -----" + new_line_separator); + buf.append(block.toString()); + buf.append("----- Edges -----" + new_line_separator); + + List suc = block.getSuccs(); + for(int j=0;j>>>>>>>(regular) Block "+((BasicBlock)suc.get(j)).id+new_line_separator); + } + suc = block.getSuccExceptions(); + for(int j=0;j>>>>>>>(exception) Block "+handler.id+"\t"+"ERROR: range not found!"+new_line_separator); + } else { + List exceptionTypes = range.getExceptionTypes(); + if(exceptionTypes == null) { + buf.append(">>>>>>>>(exception) Block "+handler.id+"\t"+"NULL"+new_line_separator); + } else { + for(String exceptionType : exceptionTypes) { + buf.append(">>>>>>>>(exception) Block "+handler.id+"\t"+exceptionType+new_line_separator); + } + } + } + } + buf.append("----- ----- -----" + new_line_separator); + + } + + return buf.toString(); + + } + + public void inlineJsr(StructMethod mt) { + processJsr(); + removeJsr(mt); + + removeMarkers(); + + DeadCodeHelper.removeEmptyBlocks(this); + } + + public void removeBlock(BasicBlock block) { + + while(block.getSuccs().size()>0) { + block.removeSuccessor((BasicBlock)block.getSuccs().get(0)); + } + + while(block.getSuccExceptions().size()>0) { + block.removeSuccessorException((BasicBlock)block.getSuccExceptions().get(0)); + } + + while(block.getPreds().size()>0) { + ((BasicBlock)block.getPreds().get(0)).removeSuccessor(block); + } + + while(block.getPredExceptions().size()>0) { + ((BasicBlock)block.getPredExceptions().get(0)).removeSuccessorException(block); + } + + last.removePredecessor(block); + + blocks.removeWithKey(block.id); + + for(int i=exceptions.size()-1;i>=0;i--) { + ExceptionRangeCFG range = (ExceptionRangeCFG)exceptions.get(i); + if(range.getHandler() == block) { + exceptions.remove(i); + } else { + List lstRange = range.getProtectedRange(); + lstRange.remove(block); + + if(lstRange.isEmpty()) { + exceptions.remove(i); + } + } + } + + Iterator> it = subroutines.entrySet().iterator(); + while(it.hasNext()) { + Entry ent = it.next(); + if(ent.getKey() == block || ent.getValue() == block) { + it.remove(); + } + } + + } + + public ExceptionRangeCFG getExceptionRange(BasicBlock handler, BasicBlock block) { + + //List ranges = new ArrayList(); + + for(int i=exceptions.size()-1;i>=0;i--) { + ExceptionRangeCFG range = exceptions.get(i); + if(range.getHandler() == handler && range.getProtectedRange().contains(block)) { + return range; + //ranges.add(range); + } + } + + return null; + //return ranges.isEmpty() ? null : ranges; + } + +// public String getExceptionsUniqueString(BasicBlock handler, BasicBlock block) { +// +// List ranges = getExceptionRange(handler, block); +// +// if(ranges == null) { +// return null; +// } else { +// Set setExceptionStrings = new HashSet(); +// for(ExceptionRangeCFG range : ranges) { +// setExceptionStrings.add(range.getExceptionType()); +// } +// +// String ret = ""; +// for(String exception : setExceptionStrings) { +// ret += exception; +// } +// +// return ret; +// } +// } + + + // ***************************************************************************** + // private methods + // ***************************************************************************** + + private void buildBlocks(InstructionSequence instrseq) { + + short[] states = findStartInstructions(instrseq); + + HashMap mapInstrBlocks = new HashMap(); + VBStyleCollection colBlocks = createBasicBlocks(states, instrseq, mapInstrBlocks); + + blocks = colBlocks; + + connectBlocks(colBlocks, mapInstrBlocks); + + setExceptionEdges(instrseq, mapInstrBlocks); + + setSubroutineEdges(); + + setFirstAndLastBlocks(); + } + + private short[] findStartInstructions(InstructionSequence seq) { + + int len = seq.length(); + short[] inststates = new short[len]; + + HashSet excSet = new HashSet(); + + for(ExceptionHandler handler : seq.getExceptionTable().getHandlers()) { + excSet.add(handler.from_instr); + excSet.add(handler.to_instr); + excSet.add(handler.handler_instr); + } + + + for(int i=0;i=0;j--) { + inststates[dests[j]] = 1; + } + inststates[swinstr.getDefaultdest()] = 1; + if(i+1 < len) { + inststates[i+1] = 1; + } + } + } + + // first instruction + inststates[0] = 1; + + return inststates; + } + + + private VBStyleCollection createBasicBlocks(short[] startblock, InstructionSequence instrseq, + HashMap mapInstrBlocks) { + + VBStyleCollection col = new VBStyleCollection(); + + InstructionSequence currseq = null; + ArrayList lstOffs = null; + + int len = startblock.length; + short counter = 0; + int blockoffset = 0; + + BasicBlock currentBlock = null; + for(int i=0;i(); + + currentBlock.setSeq(currseq); + currentBlock.setInstrOldOffsets(lstOffs); + col.addWithKey(currentBlock, currentBlock.id); + + blockoffset = instrseq.getOffset(i); + } + + startblock[i] = counter; + mapInstrBlocks.put(i, currentBlock); + + currseq.addInstruction(instrseq.getInstr(i), instrseq.getOffset(i)-blockoffset); + lstOffs.add(instrseq.getOffset(i)); + } + + last_id = counter; + + return col; + } + + + private void connectBlocks(List lstbb, HashMap mapInstrBlocks) { + + for(int i=0;i instrBlocks) { + + exceptions = new ArrayList(); + + Map mapRanges = new HashMap(); + + for(ExceptionHandler handler : instrseq.getExceptionTable().getHandlers()) { + + BasicBlock from = instrBlocks.get(handler.from_instr); + BasicBlock to = instrBlocks.get(handler.to_instr); + BasicBlock handle = instrBlocks.get(handler.handler_instr); + + String key = from.id + ":" + to.id + ":" + handle.id; + + if(mapRanges.containsKey(key)) { + ExceptionRangeCFG range = mapRanges.get(key); + range.addExceptionType(handler.exceptionClass); + } else { + + List protectedRange = new ArrayList(); + for(int j=from.id;j subroutines = new HashMap(); + + for(BasicBlock block : blocks) { + + if(block.getSeq().getLastInstr().opcode == CodeConstants.opc_jsr) { + + LinkedList stack = new LinkedList(); + LinkedList> stackJsrStacks = new LinkedList>(); + + HashSet setVisited = new HashSet(); + + stack.add(block); + stackJsrStacks.add(new LinkedList()); + + while(!stack.isEmpty()) { + + BasicBlock node = stack.removeFirst(); + LinkedList jsrstack = stackJsrStacks.removeFirst(); + + setVisited.add(node); + + switch(node.getSeq().getLastInstr().opcode) { + case CodeConstants.opc_jsr: + jsrstack.add(node); + break; + case CodeConstants.opc_ret: + BasicBlock enter = jsrstack.getLast(); + BasicBlock exit = blocks.getWithKey(enter.id + 1); // FIXME: find successor in a better way + + if(exit!=null) { + if(!node.isSuccessor(exit)) { + node.addSuccessor(exit); + } + jsrstack.removeLast(); + subroutines.put(enter, exit); + } else { + throw new RuntimeException("ERROR: last instruction jsr"); + } + } + + if(!jsrstack.isEmpty()) { + for(BasicBlock succ : node.getSuccs()) { + if(!setVisited.contains(succ)) { + stack.add(succ); + stackJsrStacks.add(new LinkedList(jsrstack)); + } + } + } + } + } + } + + this.subroutines = subroutines; + } + + private void processJsr() { + + while(processJsrRanges()!=0); + } + + private int processJsrRanges() { + + List lstJsrAll = new ArrayList(); + + // get all jsr ranges + for(Entry ent : subroutines.entrySet()){ + BasicBlock jsr = ent.getKey(); + BasicBlock ret = ent.getValue(); + + lstJsrAll.add(new Object[]{jsr, getJsrRange(jsr, ret), ret}); + } + + // sort ranges + // FIXME: better sort order + List lstJsr = new ArrayList(); + for(Object[] arr : lstJsrAll) { + int i=0; + for(;i)arrJsr[1]).contains(arr[0])) { + break; + } + } + + lstJsr.add(i, arr); + } + + // find the first intersection + for(int i=0;i set = (HashSet)arr[1]; + + for(int j=i+1;j set1 = (HashSet)arr1[1]; + + if(!set.contains(arr1[0]) && !set1.contains(arr[0])) { // rang 0 doesn't contain entry 1 and vice versa + HashSet setc = new HashSet(set); + setc.retainAll(set1); + + if(!setc.isEmpty()) { + splitJsrRange((BasicBlock)arr[0], (BasicBlock)arr[2], setc); + return 1; + } + } + } + } + + return 0; + } + + private HashSet getJsrRange(BasicBlock jsr, BasicBlock ret) { + + HashSet blocks = new HashSet(); + + LinkedList lstNodes = new LinkedList(); + lstNodes.add(jsr); + + BasicBlock dom = jsr.getSuccs().get(0); + + while(!lstNodes.isEmpty()) { + + BasicBlock node = lstNodes.remove(0); + + for(int j=0;j<2;j++) { + List lst; + if(j==0) { + if(node.getLastInstruction().opcode == CodeConstants.opc_ret) { + if(node.getSuccs().contains(ret)) { + continue; + } + } + lst = node.getSuccs(); + } else { + if(node == jsr) { + continue; + } + lst = node.getSuccExceptions(); + } + + CHILD: + for(int i=lst.size()-1;i>=0;i--) { + + BasicBlock child = lst.get(i); + if(!blocks.contains(child)) { + + if(node != jsr) { + for(int k=0;k common_blocks) { + + LinkedList lstNodes = new LinkedList(); + HashMap mapNewNodes = new HashMap(); + + lstNodes.add(jsr); + mapNewNodes.put(jsr.id, jsr); + + while(!lstNodes.isEmpty()) { + + BasicBlock node = lstNodes.remove(0); + + for(int j=0;j<2;j++) { + List lst; + if(j==0) { + if(node.getLastInstruction().opcode == CodeConstants.opc_ret) { + if(node.getSuccs().contains(ret)) { + continue; + } + } + lst = node.getSuccs(); + } else { + if(node == jsr) { + continue; + } + lst = node.getSuccExceptions(); + } + + + for(int i=lst.size()-1;i>=0;i--) { + + BasicBlock child = (BasicBlock)lst.get(i); + Integer childid = child.id; + + if(mapNewNodes.containsKey(childid)) { + node.replaceSuccessor(child, (BasicBlock)mapNewNodes.get(childid)); + } else if(common_blocks.contains(child)) { + + // make a copy of the current block + BasicBlock copy = (BasicBlock)child.clone(); + copy.id = ++last_id; + // copy all successors + if(copy.getLastInstruction().opcode == CodeConstants.opc_ret && + child.getSuccs().contains(ret)) { + copy.addSuccessor(ret); + child.removeSuccessor(ret); + } else { + for(int k=0;k common_blocks, HashMap mapNewNodes) { + + for(int i=exceptions.size()-1;i>=0;i--) { + + ExceptionRangeCFG range = (ExceptionRangeCFG)exceptions.get(i); + List lstRange = range.getProtectedRange(); + + HashSet setBoth = new HashSet(common_blocks); + setBoth.retainAll(lstRange); + + if(setBoth.size()>0) { + List lstNewRange; + + if(setBoth.size()==lstRange.size()) { + lstNewRange = new ArrayList(); + ExceptionRangeCFG newRange = new ExceptionRangeCFG(lstNewRange, + (BasicBlock)mapNewNodes.get(range.getHandler().id),range.getExceptionTypes()); + exceptions.add(newRange); + } else { + lstNewRange = lstRange; + } + + for(BasicBlock block : setBoth) { + lstNewRange.add(mapNewNodes.get(block.id)); + } + } + } + + } + + private void removeJsr(StructMethod mt) { + removeJsrInstructions(mt.getClassStruct().getPool(), first, DataPoint.getInitialDataPoint(mt)); + } + + private void removeJsrInstructions(ConstantPool pool, BasicBlock block, DataPoint data) { + + ListStack stack = data.getStack(); + + InstructionSequence seq = block.getSeq(); + for(int i=0;i(data.getLocalVariables())); + point.getStack().push(new VarType(CodeConstants.TYPE_OBJECT, 0, null)); + + removeJsrInstructions(pool, suc, point); + } + } + + } + + private void setFirstAndLastBlocks() { + + first = blocks.get(0); + + last = new BasicBlock(); + last.id = ++last_id; + last.setSeq(new SimpleInstructionSequence()); + + for(BasicBlock block: blocks) { + if(block.getSuccs().isEmpty()) { + last.addPredecessor(block); + } + } + } + + public List getReversePostOrder() { + + LinkedList res = new LinkedList(); + addToReversePostOrderListIterative(first, res); + + return res; + } + + private void addToReversePostOrderListIterative(BasicBlock root, List lst) { + + LinkedList stackNode = new LinkedList(); + LinkedList stackIndex = new LinkedList(); + + HashSet setVisited = new HashSet(); + + stackNode.add(root); + stackIndex.add(0); + + while(!stackNode.isEmpty()) { + + BasicBlock node = stackNode.getLast(); + int index = stackIndex.removeLast(); + + setVisited.add(node); + + List lstSuccs = new ArrayList(node.getSuccs()); + lstSuccs.addAll(node.getSuccExceptions()); + + for(;index getBlocks() { + return blocks; + } + + public void setBlocks(VBStyleCollection blocks) { + this.blocks = blocks; + } + + public BasicBlock getFirst() { + return first; + } + + public void setFirst(BasicBlock first) { + this.first = first; + } + + public List getEndBlocks() { + return last.getPreds(); + } + + public List getExceptions() { + return exceptions; + } + + public void setExceptions(List exceptions) { + this.exceptions = exceptions; + } + + + public BasicBlock getLast() { + return last; + } + + + public void setLast(BasicBlock last) { + this.last = last; + } + + + public HashMap getSubroutines() { + return subroutines; + } + + + public void setSubroutines(HashMap subroutines) { + this.subroutines = subroutines; + } + + + public HashSet getFinallyExits() { + return finallyExits; + } + + + public void setFinallyExits(HashSet finallyExits) { + this.finallyExits = finallyExits; + } + +} diff --git a/src/org/jetbrains/java/decompiler/code/cfg/ExceptionRangeCFG.java b/src/org/jetbrains/java/decompiler/code/cfg/ExceptionRangeCFG.java new file mode 100644 index 0000000..53f4a77 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/code/cfg/ExceptionRangeCFG.java @@ -0,0 +1,128 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.code.cfg; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.jetbrains.java.decompiler.main.DecompilerContext; + +public class ExceptionRangeCFG { + + private List protectedRange = new ArrayList(); // FIXME: replace with set + + private BasicBlock handler; + + private List exceptionTypes; + + public ExceptionRangeCFG(List protectedRange, BasicBlock handler, List exceptionType) { + this.protectedRange = protectedRange; + this.handler = handler; + + if(exceptionType != null) { + this.exceptionTypes = new ArrayList(exceptionType); + } + } + + public boolean isCircular() { + return protectedRange.contains(handler); + } + + public String toString() { + + String new_line_separator = DecompilerContext.getNewLineSeparator(); + + StringBuffer buf = new StringBuffer(); + + buf.append("exceptionType:"); + for(String exception_type : exceptionTypes) { + buf.append(" "+exception_type); + } + buf.append(new_line_separator); + + buf.append("handler: "+handler.id+new_line_separator); + buf.append("range: "); + for(int i=0;i getProtectedRange() { + return protectedRange; + } + + public void setProtectedRange(List protectedRange) { + this.protectedRange = protectedRange; + } + + public List getExceptionTypes() { + return this.exceptionTypes; + } + + public void addExceptionType(String exceptionType) { + + if(this.exceptionTypes == null) { + return; + } + + if(exceptionType == null) { + this.exceptionTypes = null; + } else { + this.exceptionTypes.add(exceptionType); + } + } + + public String getUniqueExceptionsString() { + + if(exceptionTypes == null) { + return null; + } + + Set setExceptionStrings = new HashSet(); + + for(String exceptionType : exceptionTypes) { // normalize order + setExceptionStrings.add(exceptionType); + } + + String ret = ""; + for(String exception : setExceptionStrings) { + if(!ret.isEmpty()) { + ret += ":"; + } + ret += exception; + } + + return ret; + } + + +// public void setExceptionType(String exceptionType) { +// this.exceptionType = exceptionType; +// } + +} -- cgit v1.2.3