mkwan 2003/06/23 11:23:15
Modified: java/src/org/apache/xalan/xsltc/compiler CallTemplate.java
Mode.java Param.java Template.java WithParam.java
java/src/org/apache/xalan/xsltc/compiler/util
NamedMethodGenerator.java
Log:
Performance improvement for XSLTC
New codegen solution for parameter passing in named templates,
which provides significant improvement for stylesheets that use
xsl:call-template and xsl:with-param heavily.
The old solution generates code to call Translet.addParameter(),
which seems to be too expensive. In the new solution, parameters
are passed to named templates via method arguments. The method
signature for a named template is not fixed. It depends on the number
of parameters declared in the template.
The caller (xsl:call-template) is responsible for generating the
correct parameter list and passes it to the called template. This is
done in the CallTemplate class, which finds out the corresponding
called template and processes its own xsl:with-param children together
with the xsl:params in the called template to generate an effective
parameter list. In the case where a xsl:param references another xsl:param,
local variables are generated to hold the temporary parameter value.
Revision Changes Path
1.13 +211 -18
xml-xalan/java/src/org/apache/xalan/xsltc/compiler/CallTemplate.java
Index: CallTemplate.java
===================================================================
RCS file:
/home/cvs/xml-xalan/java/src/org/apache/xalan/xsltc/compiler/CallTemplate.java,v
retrieving revision 1.12
retrieving revision 1.13
diff -u -r1.12 -r1.13
--- CallTemplate.java 30 Jan 2003 18:45:59 -0000 1.12
+++ CallTemplate.java 23 Jun 2003 18:23:14 -0000 1.13
@@ -64,9 +64,12 @@
package org.apache.xalan.xsltc.compiler;
+import org.apache.bcel.generic.ALOAD;
+import org.apache.bcel.generic.ASTORE;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.INVOKEVIRTUAL;
import org.apache.bcel.generic.InstructionList;
+import org.apache.bcel.generic.LocalVariableGen;
import org.apache.xalan.xsltc.compiler.util.ClassGenerator;
import org.apache.xalan.xsltc.compiler.util.ErrorMsg;
import org.apache.xalan.xsltc.compiler.util.MethodGenerator;
@@ -74,8 +77,25 @@
import org.apache.xalan.xsltc.compiler.util.TypeCheckError;
import org.apache.xalan.xsltc.compiler.util.Util;
+import java.util.Vector;
+
final class CallTemplate extends Instruction {
private QName _name;
+
+ // The array of effective parameters in this CallTemplate.
+ // An object in this array can be either a WithParam or
+ // a Param if no WithParam exists for a particular parameter.
+ private Object[] _parameters = null;
+
+ // True if we need to create temporary variables to hold
+ // the parameter values.
+ private boolean _createTempVar = false;
+
+ // The corresponding template which this CallTemplate calls.
+ private Template _calleeTemplate = null;
+
+ // The array to hold the old load instructions for the parameters.
+ private org.apache.bcel.generic.Instruction[] _oldLoadInstructions;
public void display(int indent) {
indent(indent);
@@ -121,14 +141,25 @@
if (stylesheet.hasLocalParams() || hasContents()) {
- // Push parameter frame
- final int push = cpg.addMethodref(TRANSLET_CLASS,
- PUSH_PARAM_FRAME,
- PUSH_PARAM_FRAME_SIG);
- il.append(classGen.loadTranslet());
- il.append(new INVOKEVIRTUAL(push));
- // Translate with-params
- translateContents(classGen, methodGen);
+ _calleeTemplate = getCalleeTemplate();
+
+ // Build the parameter list if the called template is
+ // a simple named template.
+ if (_calleeTemplate != null) {
+ buildParameterList();
+ }
+ // This is only needed when the called template is not
+ // a simple named template.
+ else {
+ // Push parameter frame
+ final int push = cpg.addMethodref(TRANSLET_CLASS,
+ PUSH_PARAM_FRAME,
+ PUSH_PARAM_FRAME_SIG);
+ il.append(classGen.loadTranslet());
+ il.append(new INVOKEVIRTUAL(push));
+ // Translate with-params
+ translateContents(classGen, methodGen);
+ }
}
final String className = stylesheet.getClassName();
@@ -140,17 +171,85 @@
il.append(methodGen.loadIterator());
il.append(methodGen.loadHandler());
il.append(methodGen.loadCurrentNode());
+ String methodSig = "(" + DOM_INTF_SIG + NODE_ITERATOR_SIG
+ + TRANSLET_OUTPUT_SIG + NODE_SIG;
+
+ if (_calleeTemplate != null) {
+ Vector calleeParams = _calleeTemplate.getParameters();
+ int numParams = _parameters.length;
+ org.apache.bcel.generic.Type objectType = null;
+
+ if (_createTempVar) {
+ _oldLoadInstructions = new
org.apache.bcel.generic.Instruction[numParams];
+ objectType = Util.getJCRefType(OBJECT_SIG);
+ }
+
+ // Translate all effective WithParams and Params in the list
+ for (int i = 0; i < numParams; i++) {
+ methodSig = methodSig + OBJECT_SIG;
+ SyntaxTreeNode node = (SyntaxTreeNode)_parameters[i];
+ node.translate(classGen, methodGen);
+
+ // Store the parameter value in a local variable if default
+ // parameters are used. In this case the default value of a
+ // parameter may reference another parameter declared earlier.
+ if (_createTempVar) {
+ il.append(DUP);
+
+ // The name of the variable used to hold the value of a
+ // parameter
+ String name = "call$template$" +
Util.escape(_name.toString())
+ + "$" + getParameterName(node);
+
+ // Search for the local variable first, only add it
+ // if it does not exist.
+ LocalVariableGen local = methodGen.getLocalVariable(name);
+
+ if (local == null) {
+ local = methodGen.addLocalVariable2(name,
+ objectType,
+ il.getEnd());
+ }
+
+ // Store the parameter value into a variable.
+ il.append(new ASTORE(local.getIndex()));
+
+ // Update the load instructions of the Param objects so that
+ // they point to the local variables. Store the old load
+ // instructions in the _oldLoadInstructions array so that
+ // we can restore them later.
+ if (node instanceof Param) {
+ Param param = (Param)node;
+ org.apache.bcel.generic.Instruction oldInstruction =
+ param.setLoadInstruction(new
ALOAD(local.getIndex()));
+ _oldLoadInstructions[param.getIndex()] = oldInstruction;
+ }
+ else if (node instanceof WithParam) {
+ Param param = (Param)calleeParams.elementAt(i);
+ org.apache.bcel.generic.Instruction oldInstruction =
+ param.setLoadInstruction(new
ALOAD(local.getIndex()));
+ _oldLoadInstructions[param.getIndex()] = oldInstruction;
+ }
+ }
+ }
+
+ // Restore the old load instructions for the Params.
+ if (_createTempVar) {
+ for (int i = 0; i < numParams; i++) {
+ Param param = (Param)calleeParams.elementAt(i);
+ param.setLoadInstruction(_oldLoadInstructions[i]);
+ }
+ }
+ }
+
+ methodSig = methodSig + ")V";
il.append(new INVOKEVIRTUAL(cpg.addMethodref(className,
methodName,
- "("
- + DOM_INTF_SIG
- + NODE_ITERATOR_SIG
- + TRANSLET_OUTPUT_SIG
- + NODE_SIG
- +")V")));
+ methodSig)));
-
- if (stylesheet.hasLocalParams() || hasContents()) {
+ // Do not need to call Translet.popParamFrame() if we are
+ // calling a simple named template.
+ if (_calleeTemplate == null && (stylesheet.hasLocalParams() ||
hasContents())) {
// Pop parameter frame
final int pop = cpg.addMethodref(TRANSLET_CLASS,
POP_PARAM_FRAME,
@@ -159,4 +258,98 @@
il.append(new INVOKEVIRTUAL(pop));
}
}
-}
+
+ /**
+ * Return the name of a Param or WithParam.
+ */
+ private String getParameterName(SyntaxTreeNode node) {
+ if (node instanceof Param) {
+ return Util.escape(((Param)node).getName().toString());
+ }
+ else if (node instanceof WithParam) {
+ WithParam withParam = (WithParam)node;
+ return Util.escape(withParam.getName().toString());
+ }
+ else
+ return null;
+ }
+
+ /**
+ * Return the simple named template which this CallTemplate calls.
+ * Return false if there is no matched template or the matched
+ * template is not a simple named template.
+ */
+ public Template getCalleeTemplate() {
+ Stylesheet stylesheet = getStylesheet();
+ Vector templates = stylesheet.getTemplates();
+ int size = templates.size();
+ for (int i = 0; i < size; i++) {
+ Template t = (Template)templates.elementAt(i);
+ if (t.getName() == _name && t.isSimpleNamedTemplate()) {
+ return t;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Build the list of effective parameters in this CallTemplate.
+ * The parameters of the called template are put into the array first.
+ * Then we visit the WithParam children of this CallTemplate and replace
+ * the Param with a corresponding WithParam having the same name.
+ */
+ private void buildParameterList() {
+ // Put the parameters from the called template into the array first.
+ // This is to ensure the order of the parameters.
+ Vector defaultParams = _calleeTemplate.getParameters();
+ int numParams = defaultParams.size();
+ _parameters = new Object[numParams];
+ for (int i = 0; i < numParams; i++) {
+ _parameters[i] = defaultParams.elementAt(i);
+ }
+
+ // Replace a Param with a WithParam if they have the same name.
+ int count = elementCount();
+ for (int i = 0; i < count; i++) {
+ Object node = elementAt(i);
+ if (node instanceof WithParam) {
+ WithParam withParam = (WithParam)node;
+ QName name = withParam.getName();
+ for (int k = 0; k < numParams; k++) {
+ Object object = _parameters[k];
+ if (object instanceof Param
+ && ((Param)object).getName() == name) {
+ withParam.setDoParameterOptimization(true);
+ _parameters[k] = withParam;
+ break;
+ }
+ else if (object instanceof WithParam
+ && ((WithParam)object).getName() == name) {
+ withParam.setDoParameterOptimization(true);
+ _parameters[k] = withParam;
+ break;
+ }
+ }
+ }
+ }
+
+ // Set the _createTempVar flag to true if the select expression
+ // in a parameter may reference another parameter.
+ for (int i = 0; i < numParams; i++) {
+ if (_parameters[i] instanceof Param) {
+ Param param = (Param)_parameters[i];
+ Expression expr = param.getExpression();
+ if ((expr != null || param.elementCount() != 0)
+ && !(expr instanceof CastExpr
+ && (((CastExpr)expr).getExpr() instanceof LiteralExpr
+ || ((CastExpr)expr).getExpr() instanceof
BooleanExpr)))
+ {
+ _createTempVar = true;
+ break;
+ }
+ }
+ }
+ }
+
+}
+
1.30 +33 -19
xml-xalan/java/src/org/apache/xalan/xsltc/compiler/Mode.java
Index: Mode.java
===================================================================
RCS file:
/home/cvs/xml-xalan/java/src/org/apache/xalan/xsltc/compiler/Mode.java,v
retrieving revision 1.29
retrieving revision 1.30
diff -u -r1.29 -r1.30
--- Mode.java 1 Apr 2003 21:08:59 -0000 1.29
+++ Mode.java 23 Jun 2003 18:23:15 -0000 1.30
@@ -577,25 +577,39 @@
final InstructionList il = new InstructionList();
String methodName = Util.escape(template.getName().toString());
- final NamedMethodGenerator methodGen =
- new NamedMethodGenerator(ACC_PUBLIC,
- org.apache.bcel.generic.Type.VOID,
- new org.apache.bcel.generic.Type[] {
- Util.getJCRefType(DOM_INTF_SIG),
- Util.getJCRefType(NODE_ITERATOR_SIG),
- Util.getJCRefType(TRANSLET_OUTPUT_SIG),
- org.apache.bcel.generic.Type.INT
- },
- new String[] {
- DOCUMENT_PNAME,
- ITERATOR_PNAME,
- TRANSLET_OUTPUT_PNAME,
- NODE_PNAME
- },
- methodName,
- getClassName(),
- il, cpg);
+ int numParams = 0;
+ if (template.isSimpleNamedTemplate()) {
+ Vector parameters = template.getParameters();
+ numParams = parameters.size();
+ }
+
+ // Initialize the types and names arrays for the NamedMethodGenerator.
+ org.apache.bcel.generic.Type[] types =
+ new org.apache.bcel.generic.Type[4 + numParams];
+ String[] names = new String[4 + numParams];
+ types[0] = Util.getJCRefType(DOM_INTF_SIG);
+ types[1] = Util.getJCRefType(NODE_ITERATOR_SIG);
+ types[2] = Util.getJCRefType(TRANSLET_OUTPUT_SIG);
+ types[3] = org.apache.bcel.generic.Type.INT;
+ names[0] = DOCUMENT_PNAME;
+ names[1] = ITERATOR_PNAME;
+ names[2] = TRANSLET_OUTPUT_PNAME;
+ names[3] = NODE_PNAME;
+ // For simple named templates, the signature of the generated method
+ // is not fixed. It depends on the number of parameters declared in the
+ // template.
+ for (int i = 4; i < 4 + numParams; i++) {
+ types[i] = Util.getJCRefType(OBJECT_SIG);
+ names[i] = "param" + String.valueOf(i-4);
+ }
+
+ NamedMethodGenerator methodGen =
+ new NamedMethodGenerator(ACC_PUBLIC,
+ org.apache.bcel.generic.Type.VOID,
+ types, names, methodName,
+ getClassName(), il, cpg);
+
il.append(template.compile(classGen, methodGen));
il.append(RETURN);
1.24 +49 -3
xml-xalan/java/src/org/apache/xalan/xsltc/compiler/Param.java
Index: Param.java
===================================================================
RCS file:
/home/cvs/xml-xalan/java/src/org/apache/xalan/xsltc/compiler/Param.java,v
retrieving revision 1.23
retrieving revision 1.24
diff -u -r1.23 -r1.24
--- Param.java 30 Jan 2003 18:46:01 -0000 1.23
+++ Param.java 23 Jun 2003 18:23:15 -0000 1.24
@@ -70,6 +70,7 @@
import org.apache.bcel.generic.CHECKCAST;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.INVOKEVIRTUAL;
+import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.PUSH;
import org.apache.bcel.generic.PUTFIELD;
@@ -82,6 +83,15 @@
final class Param extends VariableBase {
+ // True if this Param is declared in a simple named template.
+ // This is used to optimize codegen for parameter passing
+ // in named templates.
+ private boolean _isInSimpleNamedTemplate = false;
+
+ // The order index of the parameter, which is only used when
+ // the Param is declared in a simple named template.
+ private int _index = 0;
+
/**
* Display variable as single string
*/
@@ -90,6 +100,20 @@
}
/**
+ * Set the index of this parameter
+ */
+ public void setIndex(int index) {
+ _index = index;
+ }
+
+ /**
+ * Return the index of this parameter
+ */
+ public int getIndex() {
+ return _index;
+ }
+
+ /**
* Display variable in a full AST dump
*/
public void display(int indent) {
@@ -141,8 +165,12 @@
parser.getSymbolTable().addParam(this);
}
else if (parent instanceof Template) {
+ Template template = (Template) parent;
_isLocal = true;
- ((Template)parent).hasParams(true);
+ template.addParameter(this);
+ if (template.isSimpleNamedTemplate()) {
+ _isInSimpleNamedTemplate = true;
+ }
}
}
@@ -174,7 +202,7 @@
final InstructionList il = methodGen.getInstructionList();
if (_ignore) return;
- _ignore = true;
+ // _ignore = true;
final String name = getVariable();
final String signature = _type.toSignature();
@@ -182,6 +210,14 @@
if (isLocal()) {
+ // %OPT% Only translate the value and put it on the
+ // stack if this Param is in a simple named template.
+ // No need to call Translet.addParameter().
+ if (_isInSimpleNamedTemplate) {
+ translateValue(classGen, methodGen);
+ return;
+ }
+
il.append(classGen.loadTranslet());
il.append(new PUSH(cpg, name));
translateValue(classGen, methodGen);
@@ -235,6 +271,16 @@
name, signature)));
}
}
+ }
+
+ /**
+ * Set the instruction handle for loading the value of this
+ * variable onto the JVM stack and returns the old instruction handle.
+ */
+ public Instruction setLoadInstruction(Instruction instruction) {
+ Instruction tmp = _loadInstruction;
+ _loadInstruction = instruction;
+ return tmp;
}
}
1.21 +82 -10
xml-xalan/java/src/org/apache/xalan/xsltc/compiler/Template.java
Index: Template.java
===================================================================
RCS file:
/home/cvs/xml-xalan/java/src/org/apache/xalan/xsltc/compiler/Template.java,v
retrieving revision 1.20
retrieving revision 1.21
diff -u -r1.20 -r1.21
--- Template.java 30 Jan 2003 18:46:02 -0000 1.20
+++ Template.java 23 Jun 2003 18:23:15 -0000 1.21
@@ -74,6 +74,7 @@
import org.apache.xalan.xsltc.compiler.util.ClassGenerator;
import org.apache.xalan.xsltc.compiler.util.ErrorMsg;
import org.apache.xalan.xsltc.compiler.util.MethodGenerator;
+import org.apache.xalan.xsltc.compiler.util.NamedMethodGenerator;
import org.apache.xalan.xsltc.compiler.util.Type;
import org.apache.xalan.xsltc.compiler.util.TypeCheckError;
import org.apache.xalan.xsltc.compiler.util.Util;
@@ -87,15 +88,18 @@
private int _position; // Position within stylesheet (prio.
resolution)
private boolean _disabled = false;
private boolean _compiled = false;//make sure it is compiled only once
- private boolean _hasParams = false;
private boolean _simplified = false;
+ // True if this is a simple named template. A simple named
+ // template is a template which only has a name but no match pattern.
+ private boolean _isSimpleNamedTemplate = false;
+
+ // The list of parameters in this template. This is only used
+ // for simple named templates.
+ private Vector _parameters = new Vector();
+
public boolean hasParams() {
- return _hasParams;
- }
-
- public void hasParams(boolean hasParams) {
- _hasParams = hasParams;
+ return _parameters.size() > 0;
}
public boolean isSimplified() {
@@ -106,6 +110,19 @@
_simplified = true;
}
+ public boolean isSimpleNamedTemplate() {
+ return _isSimpleNamedTemplate;
+ }
+
+ public void addParameter(Param param) {
+ param.setIndex(_parameters.size());
+ _parameters.addElement(param);
+ }
+
+ public Vector getParameters() {
+ return _parameters;
+ }
+
public void disable() {
_disabled = true;
}
@@ -247,6 +264,10 @@
new ErrorMsg(ErrorMsg.TEMPLATE_REDEF_ERR, _name, this);
parser.reportError(Constants.ERROR, err);
}
+ // Is this a simple named template?
+ if (_pattern == null && _mode == null) {
+ _isSimpleNamedTemplate = true;
+ }
}
parser.setTemplate(this); // set current template
@@ -324,10 +345,61 @@
if (_compiled) return;
_compiled = true;
+
+ // %OPT% Special handling for simple named templates.
+ if (_isSimpleNamedTemplate && methodGen instanceof
NamedMethodGenerator) {
+ NamedMethodGenerator namedMethodGen =
(NamedMethodGenerator)methodGen;
+ int numParams = _parameters.size();
+ // Update the load instructions of the Params, so that they
+ // are loaded from the method parameters.
+ for (int i = 0; i < numParams; i++) {
+ Param param = (Param)_parameters.elementAt(i);
+ param.setLoadInstruction(namedMethodGen.loadParameter(i));
+ }
+ // Translate all children except the parameters.
+ // The parameters are translated in CallTemplates if necessary.
+ translateContentsWithoutParams(classGen, methodGen);
+ }
+ else {
+ translateContents(classGen, methodGen);
+ }
- final InstructionHandle start = il.getEnd();
- translateContents(classGen, methodGen);
- final InstructionHandle end = il.getEnd();
il.setPositions(true);
}
+
+ /**
+ * Call translate() on all child syntax tree nodes except the parameters.
+ *
+ * This is used for a simple named template, where the paramters are
+ * translated in the CallTemplate if necessary.
+ *
+ * @param classGen BCEL Java class generator
+ * @param methodGen BCEL Java method generator
+ */
+ protected void translateContentsWithoutParams(
+ ClassGenerator classGen,
+ MethodGenerator methodGen) {
+ // Call translate() on all child nodes
+ final int n = elementCount();
+ for (int i = 0; i < n; i++) {
+ final SyntaxTreeNode item = (SyntaxTreeNode) elementAt(i);
+ if (!(item instanceof Param)) {
+ item.translate(classGen, methodGen);
+ }
+ }
+
+ // After translation, unmap any registers for any
+ // variables / parameters
+ // that were declared in this scope. Performing this unmapping in the
+ // same AST scope as the declaration deals with the problems of
+ // references falling out-of-scope inside the for-each element.
+ // (the cause of which being 'lazy' register allocation for
references)
+ for (int i = 0; i < n; i++) {
+ if (elementAt(i) instanceof Variable) {
+ final Variable var = (Variable) elementAt(i);
+ var.unmapRegister(methodGen);
+ }
+ }
+ }
+
}
1.13 +29 -1
xml-xalan/java/src/org/apache/xalan/xsltc/compiler/WithParam.java
Index: WithParam.java
===================================================================
RCS file:
/home/cvs/xml-xalan/java/src/org/apache/xalan/xsltc/compiler/WithParam.java,v
retrieving revision 1.12
retrieving revision 1.13
diff -u -r1.12 -r1.13
--- WithParam.java 30 Jan 2003 18:46:02 -0000 1.12
+++ WithParam.java 23 Jun 2003 18:23:15 -0000 1.13
@@ -82,6 +82,13 @@
private QName _name;
private Expression _select;
+ // %OPT% This is set to true when the WithParam is used in a
+ // CallTemplate for a simple named template. If this is true,
+ // the parameters are passed to the named template through method
+ // arguments rather than using the expensive Translet.addParameter()
+ // call.
+ private boolean _doParameterOptimization = false;
+
/**
* Displays the contents of this element
*/
@@ -94,6 +101,21 @@
}
displayContents(indent + IndentIncrement);
}
+
+ /**
+ * Return the name of this WithParam.
+ */
+ public QName getName() {
+ return _name;
+ }
+
+ /**
+ * Set the do parameter optimization flag
+ */
+ public void setDoParameterOptimization(boolean flag) {
+ _doParameterOptimization = flag;
+ }
+
/**
* The contents of a <xsl:with-param> elements are either in the
element's
@@ -165,6 +187,12 @@
final ConstantPoolGen cpg = classGen.getConstantPool();
final InstructionList il = methodGen.getInstructionList();
+ // Translate the value and put it on the stack
+ if (_doParameterOptimization) {
+ translateValue(classGen, methodGen);
+ return;
+ }
+
// Make name acceptable for use as field name in class
String name = Util.escape(_name.getLocalPart());
1.5 +10 -1
xml-xalan/java/src/org/apache/xalan/xsltc/compiler/util/NamedMethodGenerator.java
Index: NamedMethodGenerator.java
===================================================================
RCS file:
/home/cvs/xml-xalan/java/src/org/apache/xalan/xsltc/compiler/util/NamedMethodGenerator.java,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- NamedMethodGenerator.java 30 Jan 2003 18:46:09 -0000 1.4
+++ NamedMethodGenerator.java 23 Jun 2003 18:23:15 -0000 1.5
@@ -63,7 +63,9 @@
package org.apache.xalan.xsltc.compiler.util;
+import org.apache.bcel.generic.ALOAD;
import org.apache.bcel.generic.ConstantPoolGen;
+import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.Type;
@@ -73,6 +75,9 @@
*/
public final class NamedMethodGenerator extends MethodGenerator {
protected static int CURRENT_INDEX = 4;
+
+ // The index of the first parameter (after dom/iterator/handler/current)
+ private static final int PARAM_START_INDEX = 5;
public NamedMethodGenerator(int access_flags, Type return_type,
Type[] arg_types, String[] arg_names,
@@ -87,5 +92,9 @@
return CURRENT_INDEX;
}
return super.getLocalIndex(name);
+ }
+
+ public Instruction loadParameter(int index) {
+ return new ALOAD(index + PARAM_START_INDEX);
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]