001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: Whitespace.java 468650 2006-10-28 07:03:30Z minchau $
020     */
021    
022    package org.apache.xalan.xsltc.compiler;
023    
024    import java.util.StringTokenizer;
025    import java.util.Vector;
026    
027    import org.apache.bcel.generic.ALOAD;
028    import org.apache.bcel.generic.BranchHandle;
029    import org.apache.bcel.generic.ConstantPoolGen;
030    import org.apache.bcel.generic.IF_ICMPEQ;
031    import org.apache.bcel.generic.ILOAD;
032    import org.apache.bcel.generic.INVOKEINTERFACE;
033    import org.apache.bcel.generic.INVOKEVIRTUAL;
034    import org.apache.bcel.generic.InstructionHandle;
035    import org.apache.bcel.generic.InstructionList;
036    import org.apache.bcel.generic.PUSH;
037    import org.apache.xalan.xsltc.compiler.util.ClassGenerator;
038    import org.apache.xalan.xsltc.compiler.util.ErrorMsg;
039    import org.apache.xalan.xsltc.compiler.util.MethodGenerator;
040    import org.apache.xalan.xsltc.compiler.util.Type;
041    import org.apache.xalan.xsltc.compiler.util.TypeCheckError;
042    import org.apache.xalan.xsltc.compiler.util.Util;
043    
044    /**
045     * @author Morten Jorgensen
046     */
047    final class Whitespace extends TopLevelElement {
048        // Three possible actions for the translet:
049        public static final int USE_PREDICATE  = 0;
050        public static final int STRIP_SPACE    = 1;
051        public static final int PRESERVE_SPACE = 2;
052    
053        // The 3 different categories of strip/preserve rules (order important)
054        public static final int RULE_NONE      = 0; 
055        public static final int RULE_ELEMENT   = 1; // priority 0
056        public static final int RULE_NAMESPACE = 2; // priority -1/4
057        public static final int RULE_ALL       = 3; // priority -1/2
058    
059        private String _elementList;
060        private int    _action;
061        private int    _importPrecedence;
062    
063        /**
064         * Auxillary class for encapsulating a single strip/preserve rule
065         */
066        private final static class WhitespaceRule {
067            private final int _action;
068            private String _namespace; // Should be replaced by NS type (int)
069            private String _element;   // Should be replaced by node type (int)
070            private int    _type;
071            private int    _priority;
072            
073            /**
074             * Strip/preserve rule constructor
075             */
076            public WhitespaceRule(int action, String element, int precedence) {
077                // Determine the action (strip or preserve) for this rule
078                _action = action;
079    
080                // Get the namespace and element name for this rule
081                final int colon = element.lastIndexOf(':');
082                if (colon >= 0) {
083                    _namespace = element.substring(0,colon);
084                    _element = element.substring(colon+1,element.length());
085                }
086                else {
087                    _namespace = Constants.EMPTYSTRING;
088                    _element = element;
089                }
090    
091                // Determine the initial priority for this rule
092                _priority = precedence << 2;
093    
094                // Get the strip/preserve type; either "NS:EL", "NS:*" or "*"
095                if (_element.equals("*")) {
096                    if (_namespace == Constants.EMPTYSTRING) {
097                        _type = RULE_ALL;       // Strip/preserve _all_ elements
098                        _priority += 2;         // Lowest priority
099                    }
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+":"+
159                                        token.substring(col+1,token.length()));
160                    } else {
161                        elements.append(token);
162                    }
163                } else {
164                    elements.append(token);
165                }
166    
167                if (list.hasMoreElements())
168                    elements.append(" ");
169            }
170            _elementList = elements.toString();
171        }
172    
173    
174        /**
175         * De-tokenize the elements listed in the 'elements' attribute and
176         * instanciate a set of strip/preserve rules.
177         */
178        public Vector getRules() {
179            final Vector rules = new Vector();
180            // Go through each element and instanciate strip/preserve-object
181            final StringTokenizer list = new StringTokenizer(_elementList);
182            while (list.hasMoreElements()) {
183                rules.add(new WhitespaceRule(_action,
184                                             list.nextToken(),
185                                             _importPrecedence));
186            }
187            return rules;
188        }
189        
190        
191        /**
192         * Scans through the rules vector and looks for a rule of higher
193         * priority that contradicts the current rule.
194         */
195        private static WhitespaceRule findContradictingRule(Vector rules,
196                                                            WhitespaceRule rule) {
197            for (int i = 0; i < rules.size(); i++) {
198                // Get the next rule in the prioritized list
199                WhitespaceRule currentRule = (WhitespaceRule)rules.elementAt(i);
200                // We only consider rules with higher priority
201                if (currentRule == rule) {
202                    return null;
203                }
204                
205                /*
206                 * See if there is a contradicting rule with higher priority.
207                 * If the rules has the same action then this rule is redundant,
208                 * if they have different action then this rule will never win.
209                 */
210                switch (currentRule.getStrength()) {
211                case RULE_ALL:
212                    return currentRule;
213                    
214                case RULE_ELEMENT:
215                    if (!rule.getElement().equals(currentRule.getElement())) {
216                        break;
217                    }
218                    // intentional fall-through
219                case RULE_NAMESPACE:
220                    if (rule.getNamespace().equals(currentRule.getNamespace())) {
221                        return currentRule;
222                    }
223                    break;
224                }
225            }
226            return null;
227        }
228    
229    
230        /**
231         * Orders a set or rules by priority, removes redundant rules and rules
232         * that are shadowed by stronger, contradicting rules.
233         */
234        private static int prioritizeRules(Vector rules) {
235            WhitespaceRule currentRule;
236            int defaultAction = PRESERVE_SPACE;
237    
238            // Sort all rules with regard to priority
239            quicksort(rules, 0, rules.size()-1);
240    
241            // Check if there are any "xsl:strip-space" elements at all.
242            // If there are no xsl:strip elements we can ignore all xsl:preserve
243            // elements and signal that all whitespaces should be preserved
244            boolean strip = false;
245            for (int i = 0; i < rules.size(); i++) {
246                currentRule = (WhitespaceRule)rules.elementAt(i);
247                if (currentRule.getAction() == STRIP_SPACE) {
248                    strip = true;
249                }
250            }
251            // Return with default action: PRESERVE_SPACE
252            if (!strip) {
253                rules.removeAllElements();
254                return PRESERVE_SPACE;
255            }
256    
257            // Remove all rules that are contradicted by rules with higher priority
258            for (int idx = 0; idx < rules.size(); ) {
259                currentRule = (WhitespaceRule)rules.elementAt(idx);
260            
261                // Remove this single rule if it has no purpose
262                if (findContradictingRule(rules,currentRule) != null) {
263                    rules.remove(idx);
264                }
265                else {
266                    // Remove all following rules if this one overrides all
267                    if (currentRule.getStrength() == RULE_ALL) {
268                        defaultAction = currentRule.getAction();
269                        for (int i = idx; i < rules.size(); i++) {
270                            rules.removeElementAt(i);
271                        }
272                    }
273                    // Skip to next rule (there might not be any)...
274                    idx++;
275                }
276            }
277    
278            // The rules vector could be empty if first rule has strength RULE_ALL
279            if (rules.size() == 0) {
280                return defaultAction;
281            }
282    
283            // Now work backwards and strip away all rules that have the same
284            // action as the default rule (no reason the check them at the end).
285            do {
286                currentRule = (WhitespaceRule)rules.lastElement();
287                if (currentRule.getAction() == defaultAction) {
288                    rules.removeElementAt(rules.size() - 1);
289                }
290                else {
291                    break;
292                }
293            } while (rules.size() > 0);
294            
295            // Signal that whitespace detection predicate must be used.
296            return defaultAction;
297        }
298    
299        public static void compileStripSpace(BranchHandle strip[], 
300                                             int sCount,
301                                             InstructionList il) {
302            final InstructionHandle target = il.append(ICONST_1);
303            il.append(IRETURN);
304            for (int i = 0; i < sCount; i++) {
305                strip[i].setTarget(target);
306            }
307        }
308    
309        public static void compilePreserveSpace(BranchHandle preserve[], 
310                                                int pCount,
311                                                InstructionList il) {
312            final InstructionHandle target = il.append(ICONST_0);
313            il.append(IRETURN);
314            for (int i = 0; i < pCount; i++) {
315                preserve[i].setTarget(target);
316            }
317        }
318    
319        /*
320        private static void compileDebug(ClassGenerator classGen,
321                                         InstructionList il) {
322            final ConstantPoolGen cpg = classGen.getConstantPool();
323            final int prt = cpg.addMethodref("java/lang/System/out",
324                                             "println",
325                                             "(Ljava/lang/String;)V");
326            il.append(DUP);
327            il.append(new INVOKESTATIC(prt));
328        }
329        */
330    
331        /**
332         * Compiles the predicate method
333         */
334        private static void compilePredicate(Vector rules,
335                                             int defaultAction,
336                                             ClassGenerator classGen) {
337            final ConstantPoolGen cpg = classGen.getConstantPool();
338            final InstructionList il = new InstructionList();
339            final XSLTC xsltc = classGen.getParser().getXSLTC();
340    
341            // private boolean Translet.stripSpace(int type) - cannot be static
342            final MethodGenerator stripSpace =
343                new MethodGenerator(ACC_PUBLIC | ACC_FINAL ,
344                            org.apache.bcel.generic.Type.BOOLEAN, 
345                            new org.apache.bcel.generic.Type[] {
346                                Util.getJCRefType(DOM_INTF_SIG),
347                                org.apache.bcel.generic.Type.INT,
348                                org.apache.bcel.generic.Type.INT
349                            },
350                            new String[] { "dom","node","type" },
351                            "stripSpace",classGen.getClassName(),il,cpg);
352    
353            classGen.addInterface("org/apache/xalan/xsltc/StripFilter");
354    
355            final int paramDom = stripSpace.getLocalIndex("dom");
356            final int paramCurrent = stripSpace.getLocalIndex("node");
357            final int paramType = stripSpace.getLocalIndex("type");
358    
359            BranchHandle strip[] = new BranchHandle[rules.size()];
360            BranchHandle preserve[] = new BranchHandle[rules.size()];
361            int sCount = 0;
362            int pCount = 0;
363    
364            // Traverse all strip/preserve rules
365            for (int i = 0; i<rules.size(); i++) {
366                // Get the next rule in the prioritised list
367                WhitespaceRule rule = (WhitespaceRule)rules.elementAt(i);
368    
369                // Returns the namespace for a node in the DOM
370                final int gns = cpg.addInterfaceMethodref(DOM_INTF,
371                                                          "getNamespaceName",
372                                                          "(I)Ljava/lang/String;");
373    
374                final int strcmp = cpg.addMethodref("java/lang/String",
375                                                    "compareTo",
376                                                    "(Ljava/lang/String;)I");
377    
378                // Handle elements="ns:*" type rule
379                if (rule.getStrength() == RULE_NAMESPACE) {
380                    il.append(new ALOAD(paramDom));
381                    il.append(new ILOAD(paramCurrent));
382                    il.append(new INVOKEINTERFACE(gns,2));
383                    il.append(new PUSH(cpg, rule.getNamespace()));
384                    il.append(new INVOKEVIRTUAL(strcmp));
385                    il.append(ICONST_0);
386    
387                    if (rule.getAction() == STRIP_SPACE) {
388                        strip[sCount++] = il.append(new IF_ICMPEQ(null));
389                    }
390                    else {
391                        preserve[pCount++] = il.append(new IF_ICMPEQ(null));
392                    }
393                }
394                // Handle elements="ns:el" type rule
395                else if (rule.getStrength() == RULE_ELEMENT) {
396                    // Create the QName for the element
397                    final Parser parser = classGen.getParser();
398                    QName qname;
399                    if (rule.getNamespace() != Constants.EMPTYSTRING )
400                        qname = parser.getQName(rule.getNamespace(), null,
401                                                rule.getElement());
402                    else
403                        qname = parser.getQName(rule.getElement());
404    
405                    // Register the element.
406                    final int elementType = xsltc.registerElement(qname);
407                    il.append(new ILOAD(paramType));
408                    il.append(new PUSH(cpg, elementType));
409    
410                    // Compare current node type with wanted element type
411                    if (rule.getAction() == STRIP_SPACE)
412                        strip[sCount++] = il.append(new IF_ICMPEQ(null));
413                    else
414                        preserve[pCount++] = il.append(new IF_ICMPEQ(null));
415                }
416            }
417    
418            if (defaultAction == STRIP_SPACE) {
419                compileStripSpace(strip, sCount, il);
420                compilePreserveSpace(preserve, pCount, il);
421            }
422            else {
423                compilePreserveSpace(preserve, pCount, il);
424                compileStripSpace(strip, sCount, il);
425            }
426    
427            classGen.addMethod(stripSpace);
428        }
429    
430        /**
431         * Compiles the predicate method
432         */
433        private static void compileDefault(int defaultAction,
434                                           ClassGenerator classGen) {
435            final ConstantPoolGen cpg = classGen.getConstantPool();
436            final InstructionList il = new InstructionList();
437            final XSLTC xsltc = classGen.getParser().getXSLTC();
438    
439            // private boolean Translet.stripSpace(int type) - cannot be static
440            final MethodGenerator stripSpace =
441                new MethodGenerator(ACC_PUBLIC | ACC_FINAL ,
442                            org.apache.bcel.generic.Type.BOOLEAN, 
443                            new org.apache.bcel.generic.Type[] {
444                                Util.getJCRefType(DOM_INTF_SIG),
445                                org.apache.bcel.generic.Type.INT,
446                                org.apache.bcel.generic.Type.INT
447                            },
448                            new String[] { "dom","node","type" },
449                            "stripSpace",classGen.getClassName(),il,cpg);
450    
451            classGen.addInterface("org/apache/xalan/xsltc/StripFilter");
452    
453            if (defaultAction == STRIP_SPACE)
454                il.append(ICONST_1);
455            else
456                il.append(ICONST_0);
457            il.append(IRETURN);
458    
459            classGen.addMethod(stripSpace);
460        }
461    
462    
463        /**
464         * Takes a vector of WhitespaceRule objects and generates a predicate
465         * method. This method returns the translets default action for handling
466         * whitespace text-nodes:
467         *    - USE_PREDICATE  (run the method generated by this method)
468         *    - STRIP_SPACE    (always strip whitespace text-nodes)
469         *    - PRESERVE_SPACE (always preserve whitespace text-nodes)
470         */
471        public static int translateRules(Vector rules,
472                                         ClassGenerator classGen) {
473            // Get the core rules in prioritized order
474            final int defaultAction = prioritizeRules(rules);
475            // The rules vector may be empty after prioritising
476            if (rules.size() == 0) {
477                compileDefault(defaultAction,classGen);
478                return defaultAction;
479            }
480            // Now - create a predicate method and sequence through rules...
481            compilePredicate(rules, defaultAction, classGen);
482            // Return with the translets required action (
483            return USE_PREDICATE;
484        }
485    
486        /**
487         * Sorts a range of rules with regard to PRIORITY only
488         */
489        private static void quicksort(Vector rules, int p, int r) {
490            while (p < r) {
491                final int q = partition(rules, p, r);
492                quicksort(rules, p, q);
493                p = q + 1;
494            }
495        }
496        
497        /**
498         * Used with quicksort method above
499         */
500        private static int partition(Vector rules, int p, int r) {
501            final WhitespaceRule x = (WhitespaceRule)rules.elementAt((p+r) >>> 1);
502            int i = p - 1, j = r + 1;
503            while (true) {
504                while (x.compareTo((WhitespaceRule)rules.elementAt(--j)) < 0) {
505                }
506                while (x.compareTo((WhitespaceRule)rules.elementAt(++i)) > 0) {
507                }
508                if (i < j) {
509                    final WhitespaceRule tmp = (WhitespaceRule)rules.elementAt(i);
510                    rules.setElementAt(rules.elementAt(j), i);
511                    rules.setElementAt(tmp, j);
512                }
513                else {
514                    return j;
515                }
516            }
517        }
518        
519        /**
520         * Type-check contents/attributes - nothing to do...
521         */
522        public Type typeCheck(SymbolTable stable) throws TypeCheckError {
523            return Type.Void; // We don't return anything.
524        }
525    
526        /**
527         * This method should not produce any code
528         */
529        public void translate(ClassGenerator classGen, MethodGenerator methodGen) {
530        }
531    }