| /******************************************************************************* |
| * Copyright (c) 2006 Oracle Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Oracle Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.bpel.validator.model; |
| |
| /** |
| * Java JDK dependencies here please ... |
| */ |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.FileReader; |
| import java.io.PrintStream; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.Hashtable; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TreeMap; |
| |
| import javax.xml.namespace.QName; |
| |
| import org.eclipse.bpel.validator.rules.CValidator; |
| |
| |
| /** |
| * @author Michal Chmielewski (michal.chmielewski@oracle.com) |
| * @date Sep 14, 2006 |
| * |
| */ |
| |
| @SuppressWarnings("nls") |
| |
| public class Rules { |
| |
| /** |
| * Rules are methods whose name follows the pattern |
| * <pre> |
| * rule_<RuleName>_<Index> |
| * </pre> |
| * where <Index> is a number. If index is missing as a suffix, |
| * then it is assumed it will be 0. |
| * <p> |
| * Rules will be ordered according to the index and executed |
| * in that order. |
| * |
| */ |
| |
| |
| final static String RULE_NAME_PREFIX = "rule_"; //$NON-NLS-1$ |
| |
| /** |
| * The rules discovered from introspection ... |
| */ |
| |
| List<Rule> mRules = new ArrayList<Rule>(); |
| |
| |
| /** |
| * The registry of such rules ... |
| */ |
| |
| static final Map<Class<? extends Validator>,Rules> RULES_BY_CLASS = new HashMap<Class<? extends Validator>,Rules> (); |
| |
| |
| /** |
| * Return the rules for the given class object. Rules are just methods |
| * with special names. The class's inherited members are also introspected to |
| * see if rule methods are inherited. The rule methods must be public |
| * and take no arguments. |
| * |
| * @param clazz |
| * @return the Rules object which contains the list of rules that will be run |
| */ |
| |
| static final public Rules getRules ( Class<? extends Validator> clazz ) { |
| |
| Rules rules = RULES_BY_CLASS.get( clazz ); |
| if (rules != null) { |
| return rules; |
| } |
| |
| // compute them |
| rules = new Rules ( clazz ); |
| |
| // store for later use |
| synchronized ( RULES_BY_CLASS ) { |
| RULES_BY_CLASS.put(clazz, rules); |
| } |
| return rules; |
| } |
| |
| |
| |
| /** |
| * Create a new set of rules for the given class by introspecting it. |
| * |
| * @param clazz |
| */ |
| |
| |
| public Rules ( Class<? extends Validator> clazz ) { |
| |
| for(Method m : clazz.getMethods()) { |
| if (isRule(m)) { |
| mRules.add( new Rule(m)); |
| } |
| } |
| if (mRules.size() > 1) { |
| // sort according to suffix digit |
| Collections.sort( mRules ); |
| } |
| } |
| |
| |
| |
| |
| /** |
| * Answer true if the method passed is something that we understand to |
| * be a rule. |
| * @param m |
| * @return true if rule, false if not. |
| */ |
| |
| static public boolean isRule ( Method m ) { |
| ARule a = m.getAnnotation(ARule.class); |
| return (m.getName().startsWith(RULE_NAME_PREFIX) || (a != null && a.sa() >= 0)); |
| } |
| |
| |
| /** |
| * An IndexFilter. Filters out rules based on the index. |
| * |
| * @author Michal Chmielewski (michal.chmielewski@oracle.com) |
| * @date Oct 12, 2006 |
| */ |
| |
| static public class IndexFilter implements IFilter<Rule> { |
| int low; |
| int high; |
| String tag; |
| |
| /** |
| * Brand new index based filter. |
| * |
| * @param l |
| * @param h |
| */ |
| public IndexFilter (int l, int h) { |
| low = Math.max(0, l); |
| high = Math.min(h, 65536); |
| } |
| |
| /** |
| * Index based filter. |
| * @param l low value |
| * @param h high value |
| * @param t tag value |
| */ |
| |
| public IndexFilter (int l, int h, String t) { |
| this(l,h); |
| tag = t; |
| } |
| |
| |
| /** (non-Javadoc) |
| * @param rule |
| * @return true if to select, false otherwise. |
| */ |
| |
| public boolean select (Rule rule) { |
| boolean s = low <= rule.getIndex() && rule.getIndex() <= high; |
| if (tag == null) { |
| return s; |
| } |
| return s && tag.equals(rule.getTag()); |
| } |
| } |
| |
| |
| |
| /** |
| * A class that represents a simple validation rule. |
| * |
| * Rules are automatically executed by the validator code. Zero argument rules are |
| * called in the sequence implied by the rule methods. |
| * |
| * Rules which have some arguments must be called explicitely. |
| * |
| * @author Michal Chmielewski (michal.chmielewski@oracle.com) |
| * @date Sep 20, 2006 |
| */ |
| |
| |
| @SuppressWarnings("nls") |
| |
| public class Rule implements Comparable<Rule> { |
| |
| Method method; |
| String name ; |
| int index; |
| String fullName; |
| ARule aRule; |
| |
| |
| Rule ( Method m ) { |
| |
| method = m; |
| aRule = method.getAnnotation(ARule.class); |
| |
| name = parseName( m.getName() ); |
| index = parseIndex ( m.getName() ); |
| fullName = m.getDeclaringClass().getSimpleName() + "." + name + "." + index; |
| } |
| |
| String parseName ( String n ) { |
| |
| int idx = n.lastIndexOf('_'); |
| int start = 0; |
| if (n.startsWith(RULE_NAME_PREFIX)) { |
| start = RULE_NAME_PREFIX.length(); |
| } |
| if (idx > 0 && start < idx) { |
| return n.substring(start , idx); |
| } |
| return n.substring(start); |
| } |
| |
| int parseIndex ( String n ) { |
| |
| int idx = n.lastIndexOf('_'); |
| if (idx < 0) { |
| if (aRule != null) { |
| return aRule.order(); |
| } |
| return 0; |
| } |
| |
| try { |
| return Integer.parseInt( n.substring(idx+1) ); |
| } catch (NumberFormatException nfe) { |
| if (aRule != null) { |
| return aRule.order(); |
| } |
| return 0; |
| } |
| } |
| |
| /** |
| * Return the name of the rule. |
| * |
| * @return the name of the rule |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * Return the index of the rule (the order in which it will be run). |
| * |
| * @return the index of the rule |
| */ |
| public int getIndex() { |
| return index; |
| } |
| |
| /** |
| * |
| * @return return the tag associated with this rule. |
| */ |
| |
| public String getTag () { |
| if (aRule == null) { |
| return Validator.PASS1; |
| } |
| return aRule.tag(); |
| } |
| |
| |
| |
| /** |
| * Return the rule annotations for this rule. |
| * @return the rule annotations for this rule. |
| */ |
| |
| public ARule getARule () { |
| return aRule; |
| } |
| |
| /** |
| * @param context |
| * @param args |
| * @return whatever the rule returns |
| * @throws Exception |
| */ |
| |
| public Object invoke (Object context, Object args[]) throws Exception { |
| return method.invoke(context, args); |
| } |
| |
| /** |
| * @param rule |
| * @return the compare to result |
| * @see java.lang.Comparable#compareTo(java.lang.Object) |
| */ |
| |
| public int compareTo (Rule rule) { |
| return this.index - rule.index; |
| } |
| |
| /** |
| * Return the full name of the Rule |
| * @return full name of the rule |
| */ |
| public String getFullName() { |
| return fullName; |
| } |
| |
| } |
| |
| |
| |
| static Comparator<Rule> SORTER = new Comparator<Rule> () { |
| |
| /** (non-Javadoc) |
| * @param o1 rule 1 |
| * @param o2 rule 2 |
| * @return result of compare |
| * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) |
| */ |
| public int compare(Rule o1, Rule o2) { |
| int res = o1.getTag().compareTo(o2.getTag()); |
| if (res != 0) { |
| return res; |
| } |
| return o1.index - o2.index; |
| } |
| }; |
| |
| |
| static PrintStream OUT = System.out; |
| |
| /** |
| * Attempt to print documentation from the source about the types of rules |
| * present in the validator. |
| * |
| * @param args |
| * @throws FileNotFoundException |
| */ |
| |
| @SuppressWarnings({ "nls", "boxing" }) |
| public static void main (String[] args) throws FileNotFoundException { |
| |
| |
| // Hmm this should be somehow done in a different way ... |
| RuleFactory.INSTANCE.registerFactory( org.eclipse.bpel.validator.rules.Factory.INSTANCE ); |
| RuleFactory.INSTANCE.registerFactory( org.eclipse.bpel.validator.wsdl.Factory.INSTANCE ); |
| RuleFactory.INSTANCE.registerFactory( org.eclipse.bpel.validator.plt.Factory.INSTANCE ); |
| RuleFactory.INSTANCE.registerFactory( org.eclipse.bpel.validator.vprop.Factory.INSTANCE ); |
| // RuleFactory.INSTANCE.registerFactory( new org.eclipse.bpel.validator.xpath.Factory.INSTANCE ); |
| RuleFactory.INSTANCE.registerFactory( org.eclipse.bpel.validator.xpath0.Factory.INSTANCE ); |
| |
| |
| // |
| Field fields[] = IConstants.class.getFields(); |
| java.text.DecimalFormat saNumberFormat = new java.text.DecimalFormat("00000"); |
| |
| File outputFile = null; |
| File cssFile = null; |
| |
| if (args.length > 0) { |
| outputFile = new File(args[0]); |
| OUT = new PrintStream( outputFile ); |
| cssFile = new File( outputFile.getParent() + File.separator + "rules.css"); |
| } |
| |
| |
| if (cssFile != null && cssFile.exists()) { |
| |
| p("<style type=\"text/css\">"); |
| BufferedReader reader = null; |
| |
| try { |
| reader = new BufferedReader(new FileReader(cssFile)); |
| while (true) { |
| String line = reader.readLine(); |
| if (line == null) { |
| break; |
| } |
| p(line); |
| } |
| } catch (Throwable t) { |
| // |
| } finally { |
| try { |
| if (reader != null) { |
| reader.close(); |
| } |
| } catch (Throwable t) { |
| // |
| } |
| } |
| |
| p("</style>"); |
| } |
| |
| |
| int totalRules = 0; |
| |
| Map<ARule,Rule> annotationHash = new TreeMap<ARule,Rule>( |
| new Comparator<ARule>() { |
| public int compare(ARule o1, ARule o2) { |
| return o1.sa() - o2.sa(); |
| } |
| }); |
| |
| List<QName> nodes = new LinkedList<QName>(); |
| |
| // Fish out the nodes. |
| for (Field f : fields) { |
| |
| String n = f.getName(); |
| if (n.startsWith("ND_") == false && n.contains("_ND_") == false) { //$NON-NLS-1$ |
| continue; |
| } |
| |
| System.out.println("Field: " + n); |
| QName qtype ; |
| try { |
| Object o = f.get(null); |
| if ((o instanceof QName) == false) { |
| continue; |
| } |
| qtype = (QName) o; |
| } catch (Exception ex) { |
| continue; |
| } |
| |
| nodes.add(qtype); |
| } |
| |
| // For evaluating conditions |
| |
| nodes.add ( new QName ( IConstants.XMLNS_XPATH_EXPRESSION_LANGUAGE, IConstants.ND_CONDITION.getLocalPart() ) ); |
| nodes.add ( new QName ( IConstants.XMLNS_XPATH_EXPRESSION_LANGUAGE, IConstants.ND_BRANCHES.getLocalPart() ) ); |
| nodes.add ( new QName ( IConstants.XMLNS_XPATH_EXPRESSION_LANGUAGE, IConstants.ND_FINAL_COUNTER_VALUE.getLocalPart() ) ); |
| nodes.add ( new QName ( IConstants.XMLNS_XPATH_EXPRESSION_LANGUAGE, IConstants.ND_START_COUNTER_VALUE.getLocalPart() ) ); |
| nodes.add ( new QName ( IConstants.XMLNS_XPATH_EXPRESSION_LANGUAGE, IConstants.ND_FOR.getLocalPart() ) ); |
| nodes.add ( new QName ( IConstants.XMLNS_XPATH_EXPRESSION_LANGUAGE, IConstants.ND_FROM.getLocalPart() ) ); |
| nodes.add ( new QName ( IConstants.XMLNS_XPATH_EXPRESSION_LANGUAGE, IConstants.ND_JOIN_CONDITION.getLocalPart() ) ); |
| nodes.add ( new QName ( IConstants.XMLNS_XPATH_EXPRESSION_LANGUAGE, IConstants.ND_REPEAT_EVERY.getLocalPart() ) ); |
| nodes.add ( new QName ( IConstants.XMLNS_XPATH_EXPRESSION_LANGUAGE, IConstants.ND_TO.getLocalPart() ) ); |
| nodes.add ( new QName ( IConstants.XMLNS_XPATH_EXPRESSION_LANGUAGE, IConstants.ND_TRANSITION_CONDITION.getLocalPart() ) ); |
| nodes.add ( new QName ( IConstants.XMLNS_XPATH_EXPRESSION_LANGUAGE, IConstants.ND_UNTIL.getLocalPart() ) ); |
| nodes.add ( new QName ( IConstants.XMLNS_XPATH_EXPRESSION_LANGUAGE, IConstants.ND_QUERY.getLocalPart() ) ); |
| |
| |
| // |
| // Sort on QName |
| |
| Collections.sort (nodes, |
| new Comparator<QName>() { |
| public int compare(QName o1, QName o2) { |
| return o1.getLocalPart().compareTo( o2.getLocalPart() ); |
| } |
| } |
| ); |
| |
| |
| |
| p("<h2>Validators</h2>"); |
| |
| p("<ol class='validators'>"); |
| |
| for(QName qtype: nodes) { |
| |
| Validator obj = RuleFactory.INSTANCE.createValidator( qtype ); |
| |
| if (obj == null) { |
| p("<li><div class='missing'>{0} - no validator.</div></li>", qtype); //$NON-NLS-1$ |
| continue; |
| } |
| |
| Class<? extends Validator> clazz = obj.getClass(); |
| |
| Rules rules = getRules(clazz); |
| int length = rules.mRules.size(); |
| |
| |
| p("<li> <b>{0}</b><p>{1} rules in class <a href=\"source.php?c={2}\">{2}</a></p>", //$NON-NLS-1$ |
| qtype,length,clazz.getName() ,clazz.getName() ); |
| |
| totalRules += length; |
| |
| p("<table class='av2'>"); |
| |
| p("<tr>"); |
| p("<th class='w1'>#</th>"); |
| p("<th class='w4'>Rule</th>"); |
| p("<th class='w1'>Seq</th>"); |
| p("<th class='w2'>Tag</th>"); |
| p("<th>Description</th>"); |
| p("<th class='w2'>Date</th>"); |
| p("<th class='w1'>SA</th>"); |
| p("</tr>"); |
| |
| int cnt = 0; |
| List<Rule> rlist = new ArrayList<Rule>( rules.mRules ); |
| Collections.sort(rlist,SORTER); |
| |
| for(Rule r : rlist) { |
| cnt += 1; |
| |
| p("<tr>"); |
| p(" <td>{0}</td>",cnt); |
| p(" <td><a href=\"source.php?c={1}&m={2}\">{0}</a></td>", |
| r.name,clazz.getName(),r.method.getName()); |
| |
| p(" <td>{0}</td>",r.getIndex()); |
| |
| ARule a = r.method.getAnnotation( org.eclipse.bpel.validator.model.ARule.class ); |
| |
| |
| if (a != null) { |
| p(" <td>{0}</td>",a.tag()); |
| p(" <td>{0}<br/><span class='author'>Author: {1}</span></td>", a.desc(), a.author() ); |
| p(" <td>{0}</td>", a.date()); |
| if (((Integer)a.sa()).equals(Integer.valueOf(0))) |
| p(" <td>N/A</td>"); |
| else |
| p(" <td><a href=\"http://docs.oasis-open.org/wsbpel/2.0/OS/wsbpel-v2.0-OS.html#SA{0}_table\">{0}</a></td>", saNumberFormat.format(a.sa())); |
| annotationHash.put(a,r); |
| } else { |
| p(" <td>{0}</td>",Validator.PASS1); |
| p(" <td colspan='2'>-</td>"); |
| p(" <td>0</td>"); |
| } |
| p("</tr>"); |
| } |
| |
| p("</table>"); |
| } |
| p("</ol>"); |
| |
| |
| |
| // Stats |
| |
| p("<h2>Statistics </h2>"); |
| p("<table class='av'>"); |
| p(" <tr><th>Total Rules:</th><td>{0}</td></tr>",totalRules); |
| p(" <tr><th>Annotated Rules:</th><td>{0}</td></tr>",annotationHash.size() ); |
| p(" <tr><th>Total Nodes:</th><td>{0}</td></tr>",nodes.size()); |
| p("</table>"); |
| |
| // Print the list of static analysis checks which are made |
| // which reference the SA codes from the BPEL spec. These point to the |
| // rules where these checks are done. |
| |
| p("<h2>SA Checks done (against the spec)</h2>"); |
| |
| p("<table class='av2'>"); |
| p("<tr>"); |
| p("<th class='w1'>SA</th>"); |
| p("<th>Description</th>"); |
| p("<th class='w5'>Method</th>"); |
| p("</tr>"); |
| |
| |
| int saNumber = 0; |
| |
| int missingSA = 0; |
| int totalSA = 94; |
| |
| for(ARule a : annotationHash.keySet()) { |
| |
| if (a.sa() <= 0) { |
| continue; |
| } |
| |
| if (a.sa() - saNumber > 1 && a.sa() <= totalSA ) { |
| for( int i=saNumber + 1, j = a.sa(); i < j; i++) { |
| missingSA += 1; |
| p("<tr>"); |
| p(" <td class='warn'>{0}</td>", i); |
| p(" <td colspan='2'><div class='warn'>Check for SA code {0} is missing</td></tr>",i); |
| p("</tr>"); |
| } |
| } |
| |
| Rule r = annotationHash.get(a); |
| p("<tr>"); |
| p("<td>{0}</td>",a.sa() ); |
| p("<td>{0}<br/><span class='author'>Author: {1}<br/>Date: {2}</span></td>", |
| toSafeHTML(a.desc()), |
| toSafeHTML(a.author()), |
| toSafeHTML(a.date()) |
| ); |
| |
| p("<td>Class: <tt>{0}</tt><br/>",r.method.getDeclaringClass().getSimpleName() ); |
| p("Method: <a href=\"source.php?c={0}&m={1}\" alt=\"Checked by {3}.{1}\"><tt>{1}</tt></a></td>", |
| r.method.getDeclaringClass().getName(), |
| r.method.getName(), |
| r.method.getDeclaringClass().getName(), |
| r.method.getDeclaringClass().getName() ); |
| p("</tr>"); |
| |
| saNumber = a.sa(); |
| } |
| |
| p("</table>"); |
| |
| |
| p("<h2>Completeness of SA checks</h2>"); |
| |
| p("<table class='av'>"); |
| p(" <tr><th>Total SA Checks:</th><td>{0}</td></tr>",totalSA); |
| p(" <tr><th>Implemented SA Checks:</th><td>{0}</td></tr>", (totalSA - missingSA) ); |
| p(" <tr><th>Missing SA Checks:</th><td>{0}</td></tr>",missingSA ); |
| |
| p(" <tr><th>% Complete:</th><td>{0,number,0.00}</td></tr>", |
| 100.0 * (totalSA - missingSA) / totalSA ); |
| p(" <tr><th>% TODO:</th><td>{0,number,0.00}</td></tr>", 100.0 * (missingSA) / totalSA); |
| p("</table>"); |
| |
| |
| |
| |
| /////////////////////////////////////////////////////// |
| // print error, warning and info messages and which SA checks they report against. |
| |
| |
| ArrayList<Integer> saList; |
| Hashtable<String,ArrayList<Integer>> errorList = new Hashtable<String,ArrayList<Integer>>(); |
| Hashtable<String,ArrayList<Integer>> warningList = new Hashtable<String,ArrayList<Integer>>(); |
| Hashtable<String,ArrayList<Integer>> infoList = new Hashtable<String,ArrayList<Integer>>(); |
| |
| for(ARule a : annotationHash.keySet()) { |
| |
| buildMsgList(a,a.errors(),errorList); |
| buildMsgList(a,a.warnings(),warningList); |
| buildMsgList(a,a.infos(),infoList); |
| } |
| |
| printMsgList("Error",errorList); |
| printMsgList("Warning",warningList); |
| printMsgList("Information",infoList); |
| } |
| |
| static Hashtable<String,ArrayList<Integer>> buildMsgList(ARule a, String msgs, Hashtable<String,ArrayList<Integer>> msgList) { |
| |
| ArrayList<Integer> saList; |
| String[] msgArray = msgs.split(","); |
| |
| for (String msg : msgArray) { |
| if (msg.equals("")) |
| continue; |
| saList = null; |
| try { |
| saList = msgList.get(msg); |
| } catch (Exception e){} |
| if (saList==null) { |
| saList = new ArrayList<Integer>(); |
| saList.add(a.sa()); |
| msgList.put(msg, saList); |
| } |
| else { |
| saList.add(a.sa()); |
| } |
| } |
| return msgList; |
| } |
| |
| static void printMsgList(String type, Hashtable<String,ArrayList<Integer>> msgList) { |
| |
| Problem pRules = new Problem(new org.eclipse.bpel.validator.rules.CValidator()); |
| Problem pModel = new Problem(new org.eclipse.bpel.validator.model.Validator()); |
| Problem pUnsupported = new Problem(new org.eclipse.bpel.validator.unsupported.Process()); |
| Problem pVprop = new Problem(new org.eclipse.bpel.validator.vprop.Property()); |
| Problem pWsdl = new Problem(new org.eclipse.bpel.validator.wsdl.Definitions()); |
| Problem pXPath0 = new Problem(new org.eclipse.bpel.validator.xpath0.XPathValidator()); |
| |
| p("<h2>{0} messages being reported against SA checks</h2>", type); |
| |
| p("<table class='av2'>"); |
| p("<tr>"); |
| p("<th class='w5'>Message Text</th>"); |
| p("<th class='w1'>SA</th>"); |
| p("</tr>"); |
| |
| for (Map.Entry<String,ArrayList<Integer>> e : msgList.entrySet()) { |
| p("<tr>"); |
| String key = e.getKey(); |
| String msg = pRules.getMessage(key, null); |
| // if not a BPEL rules validation message, try other packages |
| if (msg==null) |
| msg = pModel.getMessage(key, null); |
| if (msg==null) |
| msg = pUnsupported.getMessage(key, null); |
| if (msg==null) |
| msg = pVprop.getMessage(key, null); |
| if (msg==null) |
| msg = pWsdl.getMessage(key, null); |
| if (msg==null) |
| msg = pXPath0.getMessage(key, null); |
| // fallback is to print the key |
| if (msg==null) |
| msg = key; |
| |
| p(" <td>{0}</td>", msg); |
| p(" <td>"); |
| |
| ArrayList<Integer> values = e.getValue(); |
| for (int i=0; i<values.size(); ++i) { |
| printSAlink(values.get(i)); |
| if (i!=values.size()-1) { |
| p("<br/>"); |
| } |
| } |
| p(" </td>"); |
| p("</tr>"); |
| } |
| |
| p("</table>"); |
| |
| } |
| |
| static void printSAlink(Integer sa) { |
| java.text.DecimalFormat saNumberFormat = new java.text.DecimalFormat("00000"); |
| p("<a href=\"http://docs.oasis-open.org/wsbpel/2.0/OS/wsbpel-v2.0-OS.html#SA{0}_table\">{0}</a>", saNumberFormat.format(sa)); |
| |
| } |
| |
| static void p (String msg, Object ... args ) { |
| |
| if (args.length == 0) { |
| OUT.println(msg); |
| } else { |
| OUT.println(MessageFormat.format(msg, args)); |
| } |
| } |
| |
| |
| |
| static String toSafeHTML ( String s ) { |
| |
| StringBuilder sb = new StringBuilder ( s.length() + s.length() / 4 ) ; |
| |
| for (char ch : s.toCharArray() ) { |
| switch (ch) { |
| case '<' : |
| sb.append("<"); |
| break; |
| case '&' : |
| sb.append("&"); |
| break; |
| case '>' : |
| sb.append(">"); |
| break; |
| default : |
| sb.append(ch); |
| } |
| } |
| |
| return sb.toString(); |
| |
| } |
| } |