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: Text.java 468650 2006-10-28 07:03:30Z minchau $
020     */
021    
022    package org.apache.xalan.xsltc.compiler;
023    
024    import org.apache.bcel.generic.ConstantPoolGen;
025    import org.apache.bcel.generic.GETSTATIC;
026    import org.apache.bcel.generic.INVOKEINTERFACE;
027    import org.apache.bcel.generic.InstructionList;
028    import org.apache.bcel.generic.PUSH;
029    import org.apache.xalan.xsltc.compiler.util.ClassGenerator;
030    import org.apache.xalan.xsltc.compiler.util.MethodGenerator;
031    import org.apache.xalan.xsltc.compiler.util.Util;
032    
033    /**
034     * @author Jacek Ambroziak
035     * @author Santiago Pericas-Geertsen
036     * @author Morten Jorgensen
037     */
038    final class Text extends Instruction {
039    
040        private String _text;
041        private boolean _escaping = true;
042        private boolean _ignore = false;
043        private boolean _textElement = false;
044    
045        /**
046         * Create a blank Text syntax tree node.
047         */
048        public Text() {
049            _textElement = true;
050        }
051    
052        /**
053         * Create text syntax tree node.
054         * @param text is the text to put in the node.
055         */
056        public Text(String text) {
057            _text = text;
058        }
059    
060        /**
061         * Returns the text wrapped inside this node
062         * @return The text wrapped inside this node
063         */
064        protected String getText() {
065            return _text;
066        }
067    
068        /**
069         * Set the text for this node. Appends the given text to any already
070         * existing text (using string concatenation, so use only when needed).
071         * @param text is the text to wrap inside this node.
072         */
073        protected void setText(String text) {
074            if (_text == null)
075                _text = text;
076            else
077                _text = _text + text;
078        }
079    
080        public void display(int indent) {
081            indent(indent);
082            Util.println("Text");
083            indent(indent + IndentIncrement);
084            Util.println(_text);
085        }
086                    
087        public void parseContents(Parser parser) {
088            final String str = getAttribute("disable-output-escaping");
089            if ((str != null) && (str.equals("yes"))) _escaping = false;
090    
091            parseChildren(parser);
092    
093            if (_text == null) {
094                if (_textElement) {
095                    _text = EMPTYSTRING;
096                }
097                else {
098                    _ignore = true;
099                }
100            }
101            else if (_textElement) {
102                if (_text.length() == 0) _ignore = true;
103            }
104            else if (getParent() instanceof LiteralElement) {
105                LiteralElement element = (LiteralElement)getParent();
106                String space = element.getAttribute("xml:space");
107                if ((space == null) || (!space.equals("preserve")))
108            {
109                int i;
110                final int textLength = _text.length();
111                for (i = 0; i < textLength; i++) {
112                    char c = _text.charAt(i);
113                    if (!isWhitespace(c))
114                        break;
115                }
116                if (i == textLength)
117                    _ignore = true;
118            }
119            }
120            else {
121            int i;
122            final int textLength = _text.length();
123            for (i = 0; i < textLength; i++) 
124            {
125                char c = _text.charAt(i);
126                if (!isWhitespace(c))
127                    break;
128            }
129            if (i == textLength)
130                _ignore = true;
131            }
132        }
133    
134        public void ignore() {
135            _ignore = true;
136        }
137    
138        public boolean isIgnore() {
139            return _ignore;
140        }
141        
142        public boolean isTextElement() {
143            return _textElement;
144        }
145    
146        protected boolean contextDependent() {
147            return false;
148        }
149     
150        private static boolean isWhitespace(char c)
151        {
152            return (c == 0x20 || c == 0x09 || c == 0x0A || c == 0x0D);
153        }
154     
155        public void translate(ClassGenerator classGen, MethodGenerator methodGen) {
156            final ConstantPoolGen cpg = classGen.getConstantPool();
157            final InstructionList il = methodGen.getInstructionList();
158    
159            if (!_ignore) {
160                // Turn off character escaping if so is wanted.
161                final int esc = cpg.addInterfaceMethodref(OUTPUT_HANDLER,
162                                                          "setEscaping", "(Z)Z");
163                if (!_escaping) {
164                    il.append(methodGen.loadHandler());
165                    il.append(new PUSH(cpg, false));
166                    il.append(new INVOKEINTERFACE(esc, 2));
167                }
168    
169                il.append(methodGen.loadHandler());
170    
171                // Call characters(String) or characters(char[],int,int), as
172                // appropriate.
173                if (!canLoadAsArrayOffsetLength()) {
174                    final int characters = cpg.addInterfaceMethodref(OUTPUT_HANDLER,
175                                                               "characters",
176                                                               "("+STRING_SIG+")V");
177                    il.append(new PUSH(cpg, _text));
178                    il.append(new INVOKEINTERFACE(characters, 2));
179                } else {
180                    final int characters = cpg.addInterfaceMethodref(OUTPUT_HANDLER,
181                                                                     "characters",
182                                                                     "([CII)V");
183                    loadAsArrayOffsetLength(classGen, methodGen);
184                    il.append(new INVOKEINTERFACE(characters, 4));
185                }
186    
187                // Restore character escaping setting to whatever it was.
188                // Note: setEscaping(bool) returns the original (old) value
189                if (!_escaping) {
190                    il.append(methodGen.loadHandler());
191                    il.append(SWAP);
192                    il.append(new INVOKEINTERFACE(esc, 2));
193                    il.append(POP);
194                }
195            }
196            translateContents(classGen, methodGen);
197        }
198    
199        /**
200         * Check whether this Text node can be stored in a char[] in the translet.
201         * Calling this is precondition to calling loadAsArrayOffsetLength.
202         * @see #loadAsArrayOffsetLength(ClassGenerator,MethodGenerator)
203         * @return true if this Text node can be
204         */
205        public boolean canLoadAsArrayOffsetLength() {
206            // Magic number!  21845*3 == 65535.  BCEL uses a DataOutputStream to
207            // serialize class files.  The Java run-time places a limit on the size
208            // of String data written using a DataOutputStream - it cannot require
209            // more than 64KB when represented as UTF-8.  The number of bytes
210            // required to represent a Java string as UTF-8 cannot be greater
211            // than three times the number of char's in the string, hence the
212            // check for 21845.
213    
214            return (_text.length() <= 21845);
215        }
216    
217        /**
218         * Generates code that loads the array that will contain the character
219         * data represented by this Text node, followed by the offset of the
220         * data from the start of the array, and then the length of the data.
221         *
222         * The pre-condition to calling this method is that
223         * canLoadAsArrayOffsetLength() returns true.
224         * @see #canLoadArrayOffsetLength()
225         */
226        public void loadAsArrayOffsetLength(ClassGenerator classGen,
227                                            MethodGenerator methodGen) {
228            final ConstantPoolGen cpg = classGen.getConstantPool();
229            final InstructionList il = methodGen.getInstructionList();
230            final XSLTC xsltc = classGen.getParser().getXSLTC();
231    
232            // The XSLTC object keeps track of character data
233            // that is to be stored in char arrays.
234            final int offset = xsltc.addCharacterData(_text);
235            final int length = _text.length();
236            String charDataFieldName =
237                STATIC_CHAR_DATA_FIELD + (xsltc.getCharacterDataCount()-1);
238    
239            il.append(new GETSTATIC(cpg.addFieldref(xsltc.getClassName(),
240                                           charDataFieldName,
241                                           STATIC_CHAR_DATA_FIELD_SIG)));
242            il.append(new PUSH(cpg, offset));
243            il.append(new PUSH(cpg, _text.length()));
244        }
245    }