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 }