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          */
WhitespaceRule(int action, String element, int precedence)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          */
compareTo(WhitespaceRule other)115         public int compareTo(WhitespaceRule other) {
116             return _priority < other._priority
117                 ? -1
118                 : _priority > other._priority ? 1 : 0;
119         }
120 
getAction()121         public int getAction() { return _action; }
getStrength()122         public int getStrength() { return _type; }
getPriority()123         public int getPriority() { return _priority; }
getElement()124         public String getElement() { return _element; }
getNamespace()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      */
parseContents(Parser parser)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      */
getRules()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      */
findContradictingRule(Vector rules, WhitespaceRule rule)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      */
prioritizeRules(Vector rules)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 
compileStripSpace(BranchHandle strip[], int sCount, InstructionList il)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 
compilePreserveSpace(BranchHandle preserve[], int pCount, InstructionList il)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      */
compilePredicate(Vector rules, int defaultAction, ClassGenerator classGen)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      */
compileDefault(int defaultAction, ClassGenerator classGen)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      */
translateRules(Vector rules, ClassGenerator classGen)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      */
quicksort(Vector rules, int p, int r)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      */
partition(Vector rules, int p, int r)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      */
typeCheck(SymbolTable stable)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      */
translate(ClassGenerator classGen, MethodGenerator methodGen)530     public void translate(ClassGenerator classGen, MethodGenerator methodGen) {
531     }
532 }
533