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