1 /*
   2  * reserved comment block
   3  * DO NOT REMOVE OR ALTER!
   4  */
   5 /*
   6  * Copyright 2001-2004 The Apache Software Foundation.
   7  *
   8  * Licensed under the Apache License, Version 2.0 (the "License");
   9  * you may not use this file except in compliance with the License.
  10  * You may obtain a copy of the License at
  11  *
  12  *     http://www.apache.org/licenses/LICENSE-2.0
  13  *
  14  * Unless required by applicable law or agreed to in writing, software
  15  * distributed under the License is distributed on an "AS IS" BASIS,
  16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17  * See the License for the specific language governing permissions and
  18  * limitations under the License.
  19  */
  20 /*
  21  * $Id: Whitespace.java,v 1.5 2005/09/28 13:48:18 pvedula Exp $
  22  */
  23 
  24 package com.sun.org.apache.xalan.internal.xsltc.compiler;
  25 
  26 import java.util.StringTokenizer;
  27 import java.util.Vector;
  28 
  29 import com.sun.org.apache.bcel.internal.generic.ALOAD;
  30 import com.sun.org.apache.bcel.internal.generic.BranchHandle;
  31 import com.sun.org.apache.bcel.internal.generic.ConstantPoolGen;
  32 import com.sun.org.apache.bcel.internal.generic.IF_ICMPEQ;
  33 import com.sun.org.apache.bcel.internal.generic.ILOAD;
  34 import com.sun.org.apache.bcel.internal.generic.INVOKEINTERFACE;
  35 import com.sun.org.apache.bcel.internal.generic.INVOKEVIRTUAL;
  36 import com.sun.org.apache.bcel.internal.generic.InstructionHandle;
  37 import com.sun.org.apache.bcel.internal.generic.InstructionList;
  38 import com.sun.org.apache.bcel.internal.generic.PUSH;
  39 import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ClassGenerator;
  40 import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ErrorMsg;
  41 import com.sun.org.apache.xalan.internal.xsltc.compiler.util.MethodGenerator;
  42 import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Type;
  43 import com.sun.org.apache.xalan.internal.xsltc.compiler.util.TypeCheckError;
  44 import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Util;
  45 
  46 /**
  47  * @author Morten Jorgensen
  48  */
  49 final class Whitespace extends TopLevelElement {
  50     // Three possible actions for the translet:
  51     public static final int USE_PREDICATE  = 0;
  52     public static final int STRIP_SPACE    = 1;
  53     public static final int PRESERVE_SPACE = 2;
  54 
  55     // The 3 different categories of strip/preserve rules (order important)
  56     public static final int RULE_NONE      = 0;
  57     public static final int RULE_ELEMENT   = 1; // priority 0
  58     public static final int RULE_NAMESPACE = 2; // priority -1/4
  59     public static final int RULE_ALL       = 3; // priority -1/2
  60 
  61     private String _elementList;
  62     private int    _action;
  63     private int    _importPrecedence;
  64 
  65     /**
  66      * Auxillary class for encapsulating a single strip/preserve rule
  67      */
  68     private final static class WhitespaceRule {
  69         private final int _action;
  70         private String _namespace; // Should be replaced by NS type (int)
  71         private String _element;   // Should be replaced by node type (int)
  72         private int    _type;
  73         private int    _priority;
  74 
  75         /**
  76          * Strip/preserve rule constructor
  77          */
  78         public WhitespaceRule(int action, String element, int precedence) {
  79             // Determine the action (strip or preserve) for this rule
  80             _action = action;
  81 
  82             // Get the namespace and element name for this rule
  83             final int colon = element.lastIndexOf(':');
  84             if (colon >= 0) {
  85                 _namespace = element.substring(0,colon);
  86                 _element = element.substring(colon+1,element.length());
  87             }
  88             else {
  89                 _namespace = Constants.EMPTYSTRING;
  90                 _element = element;
  91             }
  92 
  93             // Determine the initial priority for this rule
  94             _priority = precedence << 2;
  95 
  96             // Get the strip/preserve type; either "NS:EL", "NS:*" or "*"
  97             if (_element.equals("*")) {
  98                 if (_namespace == Constants.EMPTYSTRING) {
  99                     _type = RULE_ALL;       // Strip/preserve _all_ elements
 100                     _priority += 2;         // Lowest priority
 101                 }
 102                 else {
 103                     _type = RULE_NAMESPACE; // Strip/reserve elements within NS
 104                     _priority += 1;         // Medium priority
 105                 }
 106             }
 107             else {
 108                 _type = RULE_ELEMENT;       // Strip/preserve single element
 109             }
 110         }
 111 
 112         /**
 113          * For sorting rules depending on priority
 114          */
 115         public int compareTo(WhitespaceRule other) {
 116             return _priority < other._priority
 117                 ? -1
 118                 : _priority > other._priority ? 1 : 0;
 119         }
 120 
 121         public int getAction() { return _action; }
 122         public int getStrength() { return _type; }
 123         public int getPriority() { return _priority; }
 124         public String getElement() { return _element; }
 125         public String getNamespace() { return _namespace; }
 126     }
 127 
 128     /**
 129      * Parse the attributes of the xsl:strip/preserve-space element.
 130      * The element should have not contents (ignored if any).
 131      */
 132     public void parseContents(Parser parser) {
 133         // Determine if this is an xsl:strip- or preserve-space element
 134         _action = _qname.getLocalPart().endsWith("strip-space")
 135             ? STRIP_SPACE : PRESERVE_SPACE;
 136 
 137         // Determine the import precedence
 138         _importPrecedence = parser.getCurrentImportPrecedence();
 139 
 140         // Get the list of elements to strip/preserve
 141         _elementList = getAttribute("elements");
 142         if (_elementList == null || _elementList.length() == 0) {
 143             reportError(this, parser, ErrorMsg.REQUIRED_ATTR_ERR, "elements");
 144             return;
 145         }
 146 
 147         final SymbolTable stable = parser.getSymbolTable();
 148         StringTokenizer list = new StringTokenizer(_elementList);
 149         StringBuffer elements = new StringBuffer(Constants.EMPTYSTRING);
 150 
 151         while (list.hasMoreElements()) {
 152             String token = list.nextToken();
 153             String prefix;
 154             String namespace;
 155             int col = token.indexOf(':');
 156 
 157             if (col != -1) {
 158                 namespace = lookupNamespace(token.substring(0,col));
 159                 if (namespace != null) {
 160                     elements.append(namespace).append(':').append(token.substring(col + 1));
 161                 } else {
 162                     elements.append(token);
 163                 }
 164             } else {
 165                 elements.append(token);
 166             }
 167 
 168             if (list.hasMoreElements())
 169                 elements.append(" ");
 170         }
 171         _elementList = elements.toString();
 172     }
 173 
 174 
 175     /**
 176      * De-tokenize the elements listed in the 'elements' attribute and
 177      * instanciate a set of strip/preserve rules.
 178      */
 179     public Vector getRules() {
 180         final Vector rules = new Vector();
 181         // Go through each element and instanciate strip/preserve-object
 182         final StringTokenizer list = new StringTokenizer(_elementList);
 183         while (list.hasMoreElements()) {
 184             rules.add(new WhitespaceRule(_action,
 185                                          list.nextToken(),
 186                                          _importPrecedence));
 187         }
 188         return rules;
 189     }
 190 
 191 
 192     /**
 193      * Scans through the rules vector and looks for a rule of higher
 194      * priority that contradicts the current rule.
 195      */
 196     private static WhitespaceRule findContradictingRule(Vector rules,
 197                                                         WhitespaceRule rule) {
 198         for (int i = 0; i < rules.size(); i++) {
 199             // Get the next rule in the prioritized list
 200             WhitespaceRule currentRule = (WhitespaceRule)rules.elementAt(i);
 201             // We only consider rules with higher priority
 202             if (currentRule == rule) {
 203                 return null;
 204             }
 205 
 206             /*
 207              * See if there is a contradicting rule with higher priority.
 208              * If the rules has the same action then this rule is redundant,
 209              * if they have different action then this rule will never win.
 210              */
 211             switch (currentRule.getStrength()) {
 212             case RULE_ALL:
 213                 return currentRule;
 214 
 215             case RULE_ELEMENT:
 216                 if (!rule.getElement().equals(currentRule.getElement())) {
 217                     break;
 218                 }
 219                 // intentional fall-through
 220             case RULE_NAMESPACE:
 221                 if (rule.getNamespace().equals(currentRule.getNamespace())) {
 222                     return currentRule;
 223                 }
 224                 break;
 225             }
 226         }
 227         return null;
 228     }
 229 
 230 
 231     /**
 232      * Orders a set or rules by priority, removes redundant rules and rules
 233      * that are shadowed by stronger, contradicting rules.
 234      */
 235     private static int prioritizeRules(Vector rules) {
 236         WhitespaceRule currentRule;
 237         int defaultAction = PRESERVE_SPACE;
 238 
 239         // Sort all rules with regard to priority
 240         quicksort(rules, 0, rules.size()-1);
 241 
 242         // Check if there are any "xsl:strip-space" elements at all.
 243         // If there are no xsl:strip elements we can ignore all xsl:preserve
 244         // elements and signal that all whitespaces should be preserved
 245         boolean strip = false;
 246         for (int i = 0; i < rules.size(); i++) {
 247             currentRule = (WhitespaceRule)rules.elementAt(i);
 248             if (currentRule.getAction() == STRIP_SPACE) {
 249                 strip = true;
 250             }
 251         }
 252         // Return with default action: PRESERVE_SPACE
 253         if (!strip) {
 254             rules.removeAllElements();
 255             return PRESERVE_SPACE;
 256         }
 257 
 258         // Remove all rules that are contradicted by rules with higher priority
 259         for (int idx = 0; idx < rules.size(); ) {
 260             currentRule = (WhitespaceRule)rules.elementAt(idx);
 261 
 262             // Remove this single rule if it has no purpose
 263             if (findContradictingRule(rules,currentRule) != null) {
 264                 rules.remove(idx);
 265             }
 266             else {
 267                 // Remove all following rules if this one overrides all
 268                 if (currentRule.getStrength() == RULE_ALL) {
 269                     defaultAction = currentRule.getAction();
 270                     for (int i = idx; i < rules.size(); i++) {
 271                         rules.removeElementAt(i);
 272                     }
 273                 }
 274                 // Skip to next rule (there might not be any)...
 275                 idx++;
 276             }
 277         }
 278 
 279         // The rules vector could be empty if first rule has strength RULE_ALL
 280         if (rules.size() == 0) {
 281             return defaultAction;
 282         }
 283 
 284         // Now work backwards and strip away all rules that have the same
 285         // action as the default rule (no reason the check them at the end).
 286         do {
 287             currentRule = (WhitespaceRule)rules.lastElement();
 288             if (currentRule.getAction() == defaultAction) {
 289                 rules.removeElementAt(rules.size() - 1);
 290             }
 291             else {
 292                 break;
 293             }
 294         } while (rules.size() > 0);
 295 
 296         // Signal that whitespace detection predicate must be used.
 297         return defaultAction;
 298     }
 299 
 300     public static void compileStripSpace(BranchHandle strip[],
 301                                          int sCount,
 302                                          InstructionList il) {
 303         final InstructionHandle target = il.append(ICONST_1);
 304         il.append(IRETURN);
 305         for (int i = 0; i < sCount; i++) {
 306             strip[i].setTarget(target);
 307         }
 308     }
 309 
 310     public static void compilePreserveSpace(BranchHandle preserve[],
 311                                             int pCount,
 312                                             InstructionList il) {
 313         final InstructionHandle target = il.append(ICONST_0);
 314         il.append(IRETURN);
 315         for (int i = 0; i < pCount; i++) {
 316             preserve[i].setTarget(target);
 317         }
 318     }
 319 
 320     /*
 321     private static void compileDebug(ClassGenerator classGen,
 322                                      InstructionList il) {
 323         final ConstantPoolGen cpg = classGen.getConstantPool();
 324         final int prt = cpg.addMethodref("java/lang/System/out",
 325                                          "println",
 326                                          "(Ljava/lang/String;)V");
 327         il.append(DUP);
 328         il.append(new INVOKESTATIC(prt));
 329     }
 330     */
 331 
 332     /**
 333      * Compiles the predicate method
 334      */
 335     private static void compilePredicate(Vector rules,
 336                                          int defaultAction,
 337                                          ClassGenerator classGen) {
 338         final ConstantPoolGen cpg = classGen.getConstantPool();
 339         final InstructionList il = new InstructionList();
 340         final XSLTC xsltc = classGen.getParser().getXSLTC();
 341 
 342         // private boolean Translet.stripSpace(int type) - cannot be static
 343         final MethodGenerator stripSpace =
 344             new MethodGenerator(ACC_PUBLIC | ACC_FINAL ,
 345                         com.sun.org.apache.bcel.internal.generic.Type.BOOLEAN,
 346                         new com.sun.org.apache.bcel.internal.generic.Type[] {
 347                             Util.getJCRefType(DOM_INTF_SIG),
 348                             com.sun.org.apache.bcel.internal.generic.Type.INT,
 349                             com.sun.org.apache.bcel.internal.generic.Type.INT
 350                         },
 351                         new String[] { "dom","node","type" },
 352                         "stripSpace",classGen.getClassName(),il,cpg);
 353 
 354         classGen.addInterface("com/sun/org/apache/xalan/internal/xsltc/StripFilter");
 355 
 356         final int paramDom = stripSpace.getLocalIndex("dom");
 357         final int paramCurrent = stripSpace.getLocalIndex("node");
 358         final int paramType = stripSpace.getLocalIndex("type");
 359 
 360         BranchHandle strip[] = new BranchHandle[rules.size()];
 361         BranchHandle preserve[] = new BranchHandle[rules.size()];
 362         int sCount = 0;
 363         int pCount = 0;
 364 
 365         // Traverse all strip/preserve rules
 366         for (int i = 0; i<rules.size(); i++) {
 367             // Get the next rule in the prioritised list
 368             WhitespaceRule rule = (WhitespaceRule)rules.elementAt(i);
 369 
 370             // Returns the namespace for a node in the DOM
 371             final int gns = cpg.addInterfaceMethodref(DOM_INTF,
 372                                                       "getNamespaceName",
 373                                                       "(I)Ljava/lang/String;");
 374 
 375             final int strcmp = cpg.addMethodref("java/lang/String",
 376                                                 "compareTo",
 377                                                 "(Ljava/lang/String;)I");
 378 
 379             // Handle elements="ns:*" type rule
 380             if (rule.getStrength() == RULE_NAMESPACE) {
 381                 il.append(new ALOAD(paramDom));
 382                 il.append(new ILOAD(paramCurrent));
 383                 il.append(new INVOKEINTERFACE(gns,2));
 384                 il.append(new PUSH(cpg, rule.getNamespace()));
 385                 il.append(new INVOKEVIRTUAL(strcmp));
 386                 il.append(ICONST_0);
 387 
 388                 if (rule.getAction() == STRIP_SPACE) {
 389                     strip[sCount++] = il.append(new IF_ICMPEQ(null));
 390                 }
 391                 else {
 392                     preserve[pCount++] = il.append(new IF_ICMPEQ(null));
 393                 }
 394             }
 395             // Handle elements="ns:el" type rule
 396             else if (rule.getStrength() == RULE_ELEMENT) {
 397                 // Create the QName for the element
 398                 final Parser parser = classGen.getParser();
 399                 QName qname;
 400                 if (rule.getNamespace() != Constants.EMPTYSTRING )
 401                     qname = parser.getQName(rule.getNamespace(), null,
 402                                             rule.getElement());
 403                 else
 404                     qname = parser.getQName(rule.getElement());
 405 
 406                 // Register the element.
 407                 final int elementType = xsltc.registerElement(qname);
 408                 il.append(new ILOAD(paramType));
 409                 il.append(new PUSH(cpg, elementType));
 410 
 411                 // Compare current node type with wanted element type
 412                 if (rule.getAction() == STRIP_SPACE)
 413                     strip[sCount++] = il.append(new IF_ICMPEQ(null));
 414                 else
 415                     preserve[pCount++] = il.append(new IF_ICMPEQ(null));
 416             }
 417         }
 418 
 419         if (defaultAction == STRIP_SPACE) {
 420             compileStripSpace(strip, sCount, il);
 421             compilePreserveSpace(preserve, pCount, il);
 422         }
 423         else {
 424             compilePreserveSpace(preserve, pCount, il);
 425             compileStripSpace(strip, sCount, il);
 426         }
 427 
 428         classGen.addMethod(stripSpace);
 429     }
 430 
 431     /**
 432      * Compiles the predicate method
 433      */
 434     private static void compileDefault(int defaultAction,
 435                                        ClassGenerator classGen) {
 436         final ConstantPoolGen cpg = classGen.getConstantPool();
 437         final InstructionList il = new InstructionList();
 438         final XSLTC xsltc = classGen.getParser().getXSLTC();
 439 
 440         // private boolean Translet.stripSpace(int type) - cannot be static
 441         final MethodGenerator stripSpace =
 442             new MethodGenerator(ACC_PUBLIC | ACC_FINAL ,
 443                         com.sun.org.apache.bcel.internal.generic.Type.BOOLEAN,
 444                         new com.sun.org.apache.bcel.internal.generic.Type[] {
 445                             Util.getJCRefType(DOM_INTF_SIG),
 446                             com.sun.org.apache.bcel.internal.generic.Type.INT,
 447                             com.sun.org.apache.bcel.internal.generic.Type.INT
 448                         },
 449                         new String[] { "dom","node","type" },
 450                         "stripSpace",classGen.getClassName(),il,cpg);
 451 
 452         classGen.addInterface("com/sun/org/apache/xalan/internal/xsltc/StripFilter");
 453 
 454         if (defaultAction == STRIP_SPACE)
 455             il.append(ICONST_1);
 456         else
 457             il.append(ICONST_0);
 458         il.append(IRETURN);
 459 
 460         classGen.addMethod(stripSpace);
 461     }
 462 
 463 
 464     /**
 465      * Takes a vector of WhitespaceRule objects and generates a predicate
 466      * method. This method returns the translets default action for handling
 467      * whitespace text-nodes:
 468      *    - USE_PREDICATE  (run the method generated by this method)
 469      *    - STRIP_SPACE    (always strip whitespace text-nodes)
 470      *    - PRESERVE_SPACE (always preserve whitespace text-nodes)
 471      */
 472     public static int translateRules(Vector rules,
 473                                      ClassGenerator classGen) {
 474         // Get the core rules in prioritized order
 475         final int defaultAction = prioritizeRules(rules);
 476         // The rules vector may be empty after prioritising
 477         if (rules.size() == 0) {
 478             compileDefault(defaultAction,classGen);
 479             return defaultAction;
 480         }
 481         // Now - create a predicate method and sequence through rules...
 482         compilePredicate(rules, defaultAction, classGen);
 483         // Return with the translets required action (
 484         return USE_PREDICATE;
 485     }
 486 
 487     /**
 488      * Sorts a range of rules with regard to PRIORITY only
 489      */
 490     private static void quicksort(Vector rules, int p, int r) {
 491         while (p < r) {
 492             final int q = partition(rules, p, r);
 493             quicksort(rules, p, q);
 494             p = q + 1;
 495         }
 496     }
 497 
 498     /**
 499      * Used with quicksort method above
 500      */
 501     private static int partition(Vector rules, int p, int r) {
 502         final WhitespaceRule x = (WhitespaceRule)rules.elementAt((p+r) >>> 1);
 503         int i = p - 1, j = r + 1;
 504         while (true) {
 505             while (x.compareTo((WhitespaceRule)rules.elementAt(--j)) < 0) {
 506             }
 507             while (x.compareTo((WhitespaceRule)rules.elementAt(++i)) > 0) {
 508             }
 509             if (i < j) {
 510                 final WhitespaceRule tmp = (WhitespaceRule)rules.elementAt(i);
 511                 rules.setElementAt(rules.elementAt(j), i);
 512                 rules.setElementAt(tmp, j);
 513             }
 514             else {
 515                 return j;
 516             }
 517         }
 518     }
 519 
 520     /**
 521      * Type-check contents/attributes - nothing to do...
 522      */
 523     public Type typeCheck(SymbolTable stable) throws TypeCheckError {
 524         return Type.Void; // We don't return anything.
 525     }
 526 
 527     /**
 528      * This method should not produce any code
 529      */
 530     public void translate(ClassGenerator classGen, MethodGenerator methodGen) {
 531     }
 532 }