/*
 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
 */
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sun.org.apache.xalan.internal.xsltc.compiler;

import com.sun.org.apache.bcel.internal.generic.ALOAD;
import com.sun.org.apache.bcel.internal.generic.BranchHandle;
import com.sun.org.apache.bcel.internal.generic.ConstantPoolGen;
import com.sun.org.apache.bcel.internal.generic.IF_ICMPEQ;
import com.sun.org.apache.bcel.internal.generic.ILOAD;
import com.sun.org.apache.bcel.internal.generic.INVOKEINTERFACE;
import com.sun.org.apache.bcel.internal.generic.INVOKEVIRTUAL;
import com.sun.org.apache.bcel.internal.generic.InstructionHandle;
import com.sun.org.apache.bcel.internal.generic.InstructionList;
import com.sun.org.apache.bcel.internal.generic.PUSH;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ClassGenerator;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ErrorMsg;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.MethodGenerator;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Type;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.TypeCheckError;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Util;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

Author:Morten Jorgensen
@LastModified: Oct 2017
/** * @author Morten Jorgensen * @LastModified: Oct 2017 */
final class Whitespace extends TopLevelElement { // Three possible actions for the translet: public static final int USE_PREDICATE = 0; public static final int STRIP_SPACE = 1; public static final int PRESERVE_SPACE = 2; // The 3 different categories of strip/preserve rules (order important) public static final int RULE_NONE = 0; public static final int RULE_ELEMENT = 1; // priority 0 public static final int RULE_NAMESPACE = 2; // priority -1/4 public static final int RULE_ALL = 3; // priority -1/2 private String _elementList; private int _action; private int _importPrecedence;
Auxillary class for encapsulating a single strip/preserve rule
/** * Auxillary class for encapsulating a single strip/preserve rule */
final static class WhitespaceRule { private final int _action; private String _namespace; // Should be replaced by NS type (int) private String _element; // Should be replaced by node type (int) private int _type; private int _priority;
Strip/preserve rule constructor
/** * Strip/preserve rule constructor */
public WhitespaceRule(int action, String element, int precedence) { // Determine the action (strip or preserve) for this rule _action = action; // Get the namespace and element name for this rule final int colon = element.lastIndexOf(':'); if (colon >= 0) { _namespace = element.substring(0,colon); _element = element.substring(colon+1,element.length()); } else { _namespace = Constants.EMPTYSTRING; _element = element; } // Determine the initial priority for this rule _priority = precedence << 2; // Get the strip/preserve type; either "NS:EL", "NS:*" or "*" if (_element.equals("*")) { if (_namespace == Constants.EMPTYSTRING) { _type = RULE_ALL; // Strip/preserve _all_ elements _priority += 2; // Lowest priority } else { _type = RULE_NAMESPACE; // Strip/reserve elements within NS _priority += 1; // Medium priority } } else { _type = RULE_ELEMENT; // Strip/preserve single element } }
For sorting rules depending on priority
/** * For sorting rules depending on priority */
public int compareTo(WhitespaceRule other) { return _priority < other._priority ? -1 : _priority > other._priority ? 1 : 0; } public int getAction() { return _action; } public int getStrength() { return _type; } public int getPriority() { return _priority; } public String getElement() { return _element; } public String getNamespace() { return _namespace; } }
Parse the attributes of the xsl:strip/preserve-space element. The element should have not contents (ignored if any).
/** * Parse the attributes of the xsl:strip/preserve-space element. * The element should have not contents (ignored if any). */
public void parseContents(Parser parser) { // Determine if this is an xsl:strip- or preserve-space element _action = _qname.getLocalPart().endsWith("strip-space") ? STRIP_SPACE : PRESERVE_SPACE; // Determine the import precedence _importPrecedence = parser.getCurrentImportPrecedence(); // Get the list of elements to strip/preserve _elementList = getAttribute("elements"); if (_elementList == null || _elementList.length() == 0) { reportError(this, parser, ErrorMsg.REQUIRED_ATTR_ERR, "elements"); return; } final SymbolTable stable = parser.getSymbolTable(); StringTokenizer list = new StringTokenizer(_elementList); StringBuffer elements = new StringBuffer(Constants.EMPTYSTRING); while (list.hasMoreElements()) { String token = list.nextToken(); String prefix; String namespace; int col = token.indexOf(':'); if (col != -1) { namespace = lookupNamespace(token.substring(0,col)); if (namespace != null) { elements.append(namespace).append(':').append(token.substring(col + 1)); } else { elements.append(token); } } else { elements.append(token); } if (list.hasMoreElements()) elements.append(" "); } _elementList = elements.toString(); }
De-tokenize the elements listed in the 'elements' attribute and instanciate a set of strip/preserve rules.
/** * De-tokenize the elements listed in the 'elements' attribute and * instanciate a set of strip/preserve rules. */
public List<WhitespaceRule> getRules() { final List<WhitespaceRule> rules = new ArrayList<>(); // Go through each element and instanciate strip/preserve-object final StringTokenizer list = new StringTokenizer(_elementList); while (list.hasMoreElements()) { rules.add(new WhitespaceRule(_action, list.nextToken(), _importPrecedence)); } return rules; }
Scans through the rules vector and looks for a rule of higher priority that contradicts the current rule.
/** * Scans through the rules vector and looks for a rule of higher * priority that contradicts the current rule. */
@SuppressWarnings("fallthrough") // case RULE_NAMESPACE private static WhitespaceRule findContradictingRule(List<WhitespaceRule> rules, WhitespaceRule rule) { for (WhitespaceRule currentRule : rules) { // We only consider rules with higher priority if (currentRule == rule) { return null; } /* * See if there is a contradicting rule with higher priority. * If the rules has the same action then this rule is redundant, * if they have different action then this rule will never win. */ switch (currentRule.getStrength()) { case RULE_ALL: return currentRule; case RULE_ELEMENT: if (!rule.getElement().equals(currentRule.getElement())) { break; } // intentional fall-through case RULE_NAMESPACE: if (rule.getNamespace().equals(currentRule.getNamespace())) { return currentRule; } break; } } return null; }
Orders a set or rules by priority, removes redundant rules and rules that are shadowed by stronger, contradicting rules.
/** * Orders a set or rules by priority, removes redundant rules and rules * that are shadowed by stronger, contradicting rules. */
private static int prioritizeRules(List<WhitespaceRule> rules) { WhitespaceRule currentRule; int defaultAction = PRESERVE_SPACE; // Sort all rules with regard to priority quicksort(rules, 0, rules.size()-1); // Check if there are any "xsl:strip-space" elements at all. // If there are no xsl:strip elements we can ignore all xsl:preserve // elements and signal that all whitespaces should be preserved boolean strip = false; for (int i = 0; i < rules.size(); i++) { currentRule = rules.get(i); if (currentRule.getAction() == STRIP_SPACE) { strip = true; } } // Return with default action: PRESERVE_SPACE if (!strip) { rules.clear(); return PRESERVE_SPACE; } // Remove all rules that are contradicted by rules with higher priority for (int idx = 0; idx < rules.size(); ) { currentRule = rules.get(idx); // Remove this single rule if it has no purpose if (findContradictingRule(rules,currentRule) != null) { rules.remove(idx); } else { // Remove all following rules if this one overrides all if (currentRule.getStrength() == RULE_ALL) { defaultAction = currentRule.getAction(); for (int i = idx; i < rules.size(); i++) { rules.remove(i); } } // Skip to next rule (there might not be any)... idx++; } } // The rules vector could be empty if first rule has strength RULE_ALL if (rules.isEmpty()) { return defaultAction; } // Now work backwards and strip away all rules that have the same // action as the default rule (no reason the check them at the end). do { currentRule = rules.get(rules.size() - 1); if (currentRule.getAction() == defaultAction) { rules.remove(rules.size() - 1); } else { break; } } while (rules.size() > 0); // Signal that whitespace detection predicate must be used. return defaultAction; } public static void compileStripSpace(BranchHandle strip[], int sCount, InstructionList il) { final InstructionHandle target = il.append(ICONST_1); il.append(IRETURN); for (int i = 0; i < sCount; i++) { strip[i].setTarget(target); } } public static void compilePreserveSpace(BranchHandle preserve[], int pCount, InstructionList il) { final InstructionHandle target = il.append(ICONST_0); il.append(IRETURN); for (int i = 0; i < pCount; i++) { preserve[i].setTarget(target); } } /* private static void compileDebug(ClassGenerator classGen, InstructionList il) { final ConstantPoolGen cpg = classGen.getConstantPool(); final int prt = cpg.addMethodref("java/lang/System/out", "println", "(Ljava/lang/String;)V"); il.append(DUP); il.append(new INVOKESTATIC(prt)); } */
Compiles the predicate method
/** * Compiles the predicate method */
private static void compilePredicate(List<WhitespaceRule> rules, int defaultAction, ClassGenerator classGen) { final ConstantPoolGen cpg = classGen.getConstantPool(); final InstructionList il = new InstructionList(); final XSLTC xsltc = classGen.getParser().getXSLTC(); // private boolean Translet.stripSpace(int type) - cannot be static final MethodGenerator stripSpace = new MethodGenerator(ACC_PUBLIC | ACC_FINAL , com.sun.org.apache.bcel.internal.generic.Type.BOOLEAN, new com.sun.org.apache.bcel.internal.generic.Type[] { Util.getJCRefType(DOM_INTF_SIG), com.sun.org.apache.bcel.internal.generic.Type.INT, com.sun.org.apache.bcel.internal.generic.Type.INT }, new String[] { "dom","node","type" }, "stripSpace",classGen.getClassName(),il,cpg); classGen.addInterface("com/sun/org/apache/xalan/internal/xsltc/StripFilter"); final int paramDom = stripSpace.getLocalIndex("dom"); final int paramCurrent = stripSpace.getLocalIndex("node"); final int paramType = stripSpace.getLocalIndex("type"); BranchHandle strip[] = new BranchHandle[rules.size()]; BranchHandle preserve[] = new BranchHandle[rules.size()]; int sCount = 0; int pCount = 0; // Traverse all strip/preserve rules for (int i = 0; i<rules.size(); i++) { // Get the next rule in the prioritised list WhitespaceRule rule = rules.get(i); // Returns the namespace for a node in the DOM final int gns = cpg.addInterfaceMethodref(DOM_INTF, "getNamespaceName", "(I)Ljava/lang/String;"); final int strcmp = cpg.addMethodref("java/lang/String", "compareTo", "(Ljava/lang/String;)I"); // Handle elements="ns:*" type rule if (rule.getStrength() == RULE_NAMESPACE) { il.append(new ALOAD(paramDom)); il.append(new ILOAD(paramCurrent)); il.append(new INVOKEINTERFACE(gns,2)); il.append(new PUSH(cpg, rule.getNamespace())); il.append(new INVOKEVIRTUAL(strcmp)); il.append(ICONST_0); if (rule.getAction() == STRIP_SPACE) { strip[sCount++] = il.append(new IF_ICMPEQ(null)); } else { preserve[pCount++] = il.append(new IF_ICMPEQ(null)); } } // Handle elements="ns:el" type rule else if (rule.getStrength() == RULE_ELEMENT) { // Create the QName for the element final Parser parser = classGen.getParser(); QName qname; if (rule.getNamespace() != Constants.EMPTYSTRING ) qname = parser.getQName(rule.getNamespace(), null, rule.getElement()); else qname = parser.getQName(rule.getElement()); // Register the element. final int elementType = xsltc.registerElement(qname); il.append(new ILOAD(paramType)); il.append(new PUSH(cpg, elementType)); // Compare current node type with wanted element type if (rule.getAction() == STRIP_SPACE) strip[sCount++] = il.append(new IF_ICMPEQ(null)); else preserve[pCount++] = il.append(new IF_ICMPEQ(null)); } } if (defaultAction == STRIP_SPACE) { compileStripSpace(strip, sCount, il); compilePreserveSpace(preserve, pCount, il); } else { compilePreserveSpace(preserve, pCount, il); compileStripSpace(strip, sCount, il); } classGen.addMethod(stripSpace); }
Compiles the predicate method
/** * Compiles the predicate method */
private static void compileDefault(int defaultAction, ClassGenerator classGen) { final ConstantPoolGen cpg = classGen.getConstantPool(); final InstructionList il = new InstructionList(); final XSLTC xsltc = classGen.getParser().getXSLTC(); // private boolean Translet.stripSpace(int type) - cannot be static final MethodGenerator stripSpace = new MethodGenerator(ACC_PUBLIC | ACC_FINAL , com.sun.org.apache.bcel.internal.generic.Type.BOOLEAN, new com.sun.org.apache.bcel.internal.generic.Type[] { Util.getJCRefType(DOM_INTF_SIG), com.sun.org.apache.bcel.internal.generic.Type.INT, com.sun.org.apache.bcel.internal.generic.Type.INT }, new String[] { "dom","node","type" }, "stripSpace",classGen.getClassName(),il,cpg); classGen.addInterface("com/sun/org/apache/xalan/internal/xsltc/StripFilter"); if (defaultAction == STRIP_SPACE) il.append(ICONST_1); else il.append(ICONST_0); il.append(IRETURN); classGen.addMethod(stripSpace); }
Takes a vector of WhitespaceRule objects and generates a predicate method. This method returns the translets default action for handling whitespace text-nodes: - USE_PREDICATE (run the method generated by this method) - STRIP_SPACE (always strip whitespace text-nodes) - PRESERVE_SPACE (always preserve whitespace text-nodes)
/** * Takes a vector of WhitespaceRule objects and generates a predicate * method. This method returns the translets default action for handling * whitespace text-nodes: * - USE_PREDICATE (run the method generated by this method) * - STRIP_SPACE (always strip whitespace text-nodes) * - PRESERVE_SPACE (always preserve whitespace text-nodes) */
public static int translateRules(List<WhitespaceRule> rules, ClassGenerator classGen) { // Get the core rules in prioritized order final int defaultAction = prioritizeRules(rules); // The rules vector may be empty after prioritising if (rules.size() == 0) { compileDefault(defaultAction,classGen); return defaultAction; } // Now - create a predicate method and sequence through rules... compilePredicate(rules, defaultAction, classGen); // Return with the translets required action ( return USE_PREDICATE; }
Sorts a range of rules with regard to PRIORITY only
/** * Sorts a range of rules with regard to PRIORITY only */
private static void quicksort(List<WhitespaceRule> rules, int p, int r) { while (p < r) { final int q = partition(rules, p, r); quicksort(rules, p, q); p = q + 1; } }
Used with quicksort method above
/** * Used with quicksort method above */
private static int partition(List<WhitespaceRule> rules, int p, int r) { final WhitespaceRule x = rules.get((p+r) >>> 1); int i = p - 1, j = r + 1; while (true) { while (x.compareTo(rules.get(--j)) < 0) { } while (x.compareTo(rules.get(++i)) > 0) { } if (i < j) { final WhitespaceRule tmp = rules.get(i); rules.set(i, rules.get(j)); rules.set(j, tmp); } else { return j; } } }
Type-check contents/attributes - nothing to do...
/** * Type-check contents/attributes - nothing to do... */
public Type typeCheck(SymbolTable stable) throws TypeCheckError { return Type.Void; // We don't return anything. }
This method should not produce any code
/** * This method should not produce any code */
public void translate(ClassGenerator classGen, MethodGenerator methodGen) { } }