KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openlaszlo > compiler > ViewCompiler


1 /* ****************************************************************************
2  * ViewCompiler.java
3  * ****************************************************************************/

4
5 /* J_LZ_COPYRIGHT_BEGIN *******************************************************
6 * Copyright 2001-2004 Laszlo Systems, Inc. All Rights Reserved. *
7 * Use is subject to license terms. *
8 * J_LZ_COPYRIGHT_END *********************************************************/

9
10 package org.openlaszlo.compiler;
11
12 import java.io.*;
13 import java.text.DecimalFormat JavaDoc;
14 import java.text.FieldPosition JavaDoc;
15 import java.util.*;
16
17 import org.openlaszlo.css.CSSParser;
18 import org.openlaszlo.sc.ScriptCompiler;
19 import org.openlaszlo.server.LPS;
20 import org.openlaszlo.xml.internal.Schema;
21 import org.openlaszlo.xml.internal.MissingAttributeException;
22 import org.openlaszlo.utils.ChainedException;
23 import org.openlaszlo.xml.internal.XMLUtils;
24 import org.apache.log4j.*;
25 import org.apache.oro.text.regex.*;
26 import org.openlaszlo.compiler.ViewSchema.ColorFormatException;
27 import org.openlaszlo.utils.FileUtils;
28
29 import java.io.*;
30 import java.util.*;
31 import org.jdom.Attribute;
32 import org.jdom.Element;
33
34 /** Responsible for compiling elements that compile into instances of
35  * LzNode. This is called ViewCompiler for historical reasons; it
36  * would more appropriately be called NodeCompiler.
37  *
38  * Node compilation consists of these steps:<ul>
39  * <li> compute text metrics
40  * <li> call the XML compiler to generate bytecodes that recreate the
41  * view template subtree and pass it to a runtime view instantiation
42  * library
43  * </ul>
44  */

45 public class ViewCompiler extends ElementCompiler {
46     // +=2 so you can see cursor at end of line
47
private static final int INPUT_TEXTWIDTH_FUDGE_FACTOR = 2;
48
49     private static final String JavaDoc FONTSTYLE_ATTRIBUTE = "fontstyle";
50     private static final String JavaDoc WHEN_IMMEDIATELY = "immediately";
51     private static final String JavaDoc WHEN_ONCE = "once";
52     private static final String JavaDoc WHEN_ALWAYS = "always";
53     private static final String JavaDoc WHEN_PATH = "path";
54     
55     private ViewSchema mSchema;
56     
57     private static Logger mLogger = Logger.getLogger(ViewCompiler.class);
58     private static Logger mTraceLogger = Logger.getLogger("trace.xml");
59     
60     public ViewCompiler(CompilationEnvironment env) {
61         super(env);
62         mSchema = env.getSchema();
63         mTraceLogger.setLevel(Level.INFO);
64     }
65
66
67     private static final String JavaDoc SERVERLESS_WARNINGS_PROPERTY_FILE = (
68         LPS.getMiscDirectory() + File.separator + "lzx-server-only-apis.properties"
69         );
70     private static final Properties sServerOnlyTags = new Properties();
71
72     static {
73         try {
74             InputStream is = new FileInputStream(SERVERLESS_WARNINGS_PROPERTY_FILE);
75             try {
76                 sServerOnlyTags.load(is);
77             } finally {
78                 is.close();
79             }
80         } catch (java.io.IOException JavaDoc e) {
81             mLogger.warn("Can't find server-only APIs config file " + SERVERLESS_WARNINGS_PROPERTY_FILE);
82         }
83     }
84
85     public void compile(Element element) throws CompilationError
86     {
87         preprocess(element, mEnv);
88         FontInfo fontInfo;
89
90         String JavaDoc name = element.getName();
91         if (name != null
92             && !mEnv.getCanvas().isProxied()
93             && sServerOnlyTags.containsKey(name)) {
94             mEnv.warn("The tag '"+name+"' will not work as expected when used in a serverless application.");
95         }
96
97         // We need to have already run mapTextMetricsCompilation, so
98
// that input text widths have been calculated for us to get
99
// the fixed-size optimization.
100
fontInfo = new FontInfo(mEnv.getCanvas().getFontInfo());
101         if (mEnv.getSWFVersion().equals("swf5")) {
102
103         /*
104           if (LPS.getProperty("compiler.measureText", "true").equals("true")) {
105             try {
106                 fontInfo = new FontInfo(mEnv.getCanvas().getFontInfo());
107                 mapTextMetricsCompilation(element, mEnv, fontInfo, new HashSet());
108             } catch (NumberFormatException e) {
109                 throw new CompilationError(e.getMessage());
110             }
111         }
112         */

113             collectInputFonts(element, mEnv, fontInfo, new HashSet());
114         }
115         
116         compileXML(element, fontInfo);
117     }
118
119     /** Returns true if this node applies to the element. Anything
120      * that the interface compiler doesn't recognize is considered a
121      * view node, so this always returns true.
122      * @param element an element
123      * @return see doc
124      */

125     static boolean isElement(Element element) {
126         return true;
127     }
128     
129     /** Collect the names of classes that are referenced. */
130     static void collectElementNames(Element element, Set names) {
131         names.add(element.getName());
132         collectLayoutElement(element, names);
133         for (Iterator iter = element.getChildren().iterator();
134              iter.hasNext(); ) {
135             collectElementNames((Element) iter.next(), names);
136         }
137     }
138     
139     static void collectLayoutElement(Element element, Set names) {
140         if (element.getAttributeValue("layout") != null) {
141             try {
142                 Map properties = new CSSParser
143                   (new AttributeStream(element, "layout")).Parse();
144                 String JavaDoc layoutClass = (String JavaDoc) properties.get("class");
145                 if (layoutClass == null)
146                     layoutClass = "simplelayout";
147                 names.add(layoutClass);
148             } catch (org.openlaszlo.css.ParseException e) {
149             } catch (org.openlaszlo.css.TokenMgrError e) {
150                 // The compilation phase will report the error.
151
}
152         }
153     }
154
155     /** Compile a XML element and generate code that binds it to a
156      * runtime data structure named _lzViewTemplate.
157      *
158      * @param element an element
159      * @param schema a schema
160      * @param fontInfo font info inherited from canvas
161      */

162     void compileXML(Element element, FontInfo fontInfo)
163     {
164         // TODO: [12-27-2002 ows] use the log4j API instead of this property
165
boolean tracexml = mEnv.getBooleanProperty("trace.xml");
166         if (tracexml) {
167             mTraceLogger.info("compiling XML:");
168             org.jdom.output.XMLOutputter outputter =
169                 new org.jdom.output.XMLOutputter();
170             mTraceLogger.info(outputter.outputString(element));
171         }
172         
173         NodeModel model = NodeModel.elementAsModel(element, mSchema, mEnv);
174         model = model.expandClassDefinitions();
175         String JavaDoc script = VIEW_INSTANTIATION_FNAME + "(" +
176             model.asJavascript() + ", " + model.totalSubnodes() +
177             ");";
178         
179         // Don't keep non-class models around
180
if (!element.getName().equals("class")) {
181             ((ElementWithLocationInfo) element).model = null;
182         }
183         
184         if (tracexml) {
185             mLogger.debug("compiled to:\n" + script + "\n");
186         }
187         try {
188             mEnv.compileScript(script, element);
189         } catch (CompilationError e) {
190             String JavaDoc solution = SolutionMessages.findSolution(e.getMessage());
191             e.setSolution(solution);
192             throw e;
193         }
194     }
195     
196     /**
197      * Modify the DOM in place, to what the runtime expects. This
198      * function encapsulates the behavior that is common to root
199      * views, and class definitions.
200      *
201      * Preprocessing consists of compiling resources, and turning
202      * view-specific source-format attributes into runtime format
203      *
204      * @param elt an <code>Element</code> value
205      * @param env a <code>CompilationEnvironment</code> value
206      */

207     static void preprocess(Element elt, CompilationEnvironment env) {
208         compileResources(elt, env);
209         compileClickResources(elt, env);
210         compileAttributes(elt, env);
211     }
212
213     /**
214      * Modify elt and its children to replace source attribute values
215      * by runtime values.
216      */

217     static void compileAttributes(Element elt, CompilationEnvironment env) {
218         if (elt.getName().equals("dataset")) {
219             String JavaDoc src = elt.getAttributeValue("src");
220             if (src == null) {
221                 // This is a local dataset. DataCompiler has already
222
// processed it. TBD: move this check to isElement,
223
// and make it an assert since DataCompiler should
224
// have already processed it.
225
return;
226             }
227             src = env.adjustRelativeURL(src, elt);
228             elt.setAttribute("src", src);
229         }
230         Iterator iter;
231         for (iter = elt.getChildren().iterator(); iter.hasNext(); ) {
232                 compileAttributes((Element)iter.next(), env);
233         }
234     }
235
236     static HashMap sUnsupportedServerlessFiletypes = new HashMap();
237     {
238         sUnsupportedServerlessFiletypes.put("gif", "true");
239         sUnsupportedServerlessFiletypes.put("bmp", "true");
240         sUnsupportedServerlessFiletypes.put("png", "true");
241         sUnsupportedServerlessFiletypes.put("tiff", "true");
242         sUnsupportedServerlessFiletypes.put("tif", "true");
243         sUnsupportedServerlessFiletypes.put("wmf", "true");
244         sUnsupportedServerlessFiletypes.put("wmv", "true");
245     }
246     
247     static void checkUnsupportedMediaTypes(CompilationEnvironment env, Element elt, String JavaDoc url) {
248         String JavaDoc suffix = FileUtils.getExtension(url);
249         if (sUnsupportedServerlessFiletypes.containsKey(suffix.toLowerCase())) {
250             env.warn("The runtime loadable resource type '"+url+" is not supported by the Flash runtime. Supported resource types are JPEG (non-interlaced), SWF, and MP3", elt);
251         }
252     }
253
254     /**
255      * Compiles all resources under the current element
256      *
257      * @param env
258      * @param elt
259      */

260     static void compileResources(Element elt,
261                                  CompilationEnvironment env) {
262         final String JavaDoc RESOURCE_ATTR_NAME = "resource";
263
264         // check for immediate <attribute name="resource" .../> children
265
for (Iterator iter = elt.getChildren().iterator();
266              iter.hasNext(); ) {
267             Element child = (Element) iter.next();
268             if (child.getName().equals("attribute") &&
269                 RESOURCE_ATTR_NAME.equals(child.getAttributeValue("name"))) {
270                 String JavaDoc val = child.getAttributeValue("value");
271                 // You are not allowed declare a resource attribute value twice
272
if (val == null) {
273                     continue;
274                 } else {
275                     String JavaDoc val2 = elt.getAttributeValue(RESOURCE_ATTR_NAME);
276                     if (val2 != null) {
277                         env.warn("The resource attribute on this view was declared more than once, as '"+val2+"', and as '"+val+"'", elt);
278                     }
279
280                     // This is needed for backward compatibility with
281
// the deprecated "when='...'" syntax
282
String JavaDoc when = child.getAttributeValue("when");
283                     if (when != null) {
284                         val = "$" + when + "{" + val + "}";
285                     }
286
287                     elt.setAttribute(RESOURCE_ATTR_NAME, val);
288                     // remove this <attribute name="resource" .../>
289
// child because we just copied the value to the
290
// parent elt.
291
iter.remove();
292                 }
293             }
294         }
295
296         String JavaDoc value = elt.getAttributeValue(RESOURCE_ATTR_NAME);
297
298         if (value != null) {
299             if (value.startsWith("$(")) {
300                 env.warn(
301                     "The syntax '$(...)' is not valid, "
302                     + "you probably meant to use curly-braces instead '${...}'", elt);
303             } else if (value.startsWith("$") && value.endsWith("}")) {
304                 // It's a $xxx{...} attribute initializer, let's not
305
// do anything at all, and let the viewsystem takes
306
// care of finding the resource by id.
307
} else if (ScriptCompiler.isIdentifier(value)) {
308                 // id: leave intact: nothing to do
309
Set resourceNames = env.getResourceNames();
310                 if (!resourceNames.contains(value)) {
311                     // Add this reference to be checked again after
312
// we've fully parsed the whole app.
313
env.addResourceReference(value, elt);
314                 }
315             } else if (XMLUtils.isURL(value)) {
316                 if (!env.getCanvas().isProxied()) {
317                     checkUnsupportedMediaTypes(env, elt, value);
318                 }
319                 // URL: relativize, and rename to "source" for runtime
320
value = env.adjustRelativeURL(value, elt);
321                 // If it's a relative pathname with no hostname
322
// (e.g. "http:resource"), the runtime expects a
323
// bare name (e.g. "resource")
324
try {
325                     java.net.URL JavaDoc url = new java.net.URL JavaDoc(value);
326                     if (url.getHost().equals("") && !url.getPath().startsWith("/")) {
327                         value = url.getPath();
328                         if (url.getQuery() != null && url.getQuery().length() > 0) {
329                             value += "?" + url.getQuery();
330                         }
331                     }
332                 } catch (java.net.MalformedURLException JavaDoc e) {
333                     throw new ChainedException(e);
334                 }
335                 elt.removeAttribute(RESOURCE_ATTR_NAME);
336                 elt.setAttribute("source", ScriptCompiler.quote(value));
337
338             } else {
339                 // pathname: turn into an id
340
Element info = new Element("resolve");
341                 info.setAttribute("src", elt.getAttributeValue(RESOURCE_ATTR_NAME));
342
343                 File file = env.resolveReference(elt, RESOURCE_ATTR_NAME);
344                 Element rinfo = new Element("resolve");
345                     rinfo.setAttribute("src", elt.getAttributeValue(RESOURCE_ATTR_NAME));
346                     rinfo.setAttribute("pathname", file.getPath());
347                 env.getCanvas().addInfo(rinfo);
348                 // N.B.: Resources are always imported into the main
349
// program for the Flash target, hence the use of
350
// getResourceGenerator below
351
try {
352                     value = env.getResourceGenerator().importResource(file);
353                 } catch (SWFWriter.ImportResourceError e) {
354                     env.warn(e, elt);
355                 }
356                 elt.setAttribute(RESOURCE_ATTR_NAME, value);
357
358                 try {
359                     info.setAttribute("pathname", file.getCanonicalPath());
360                 } catch (java.io.IOException JavaDoc ioe) {
361                     mLogger.warn("Can't canonicalize " + file.toString());
362                 }
363                 env.getCanvas().addInfo(info);
364             }
365         }
366         
367         // Recurse
368
Iterator iter;
369         for (iter = elt.getChildren().iterator();
370              iter.hasNext(); ) {
371             compileResources((Element) iter.next(), env);
372         }
373     }
374
375     static void compileClickResources(Element elt,
376                                       CompilationEnvironment env) {
377         final String JavaDoc ATTR_NAME = "clickregion";
378         String JavaDoc value = elt.getAttributeValue(ATTR_NAME);
379         
380         if (value != null) {
381             if (value.startsWith("$(") ||
382                 (value.startsWith("$") && value.endsWith("}")) ||
383                 ScriptCompiler.isIdentifier(value) ||
384                 XMLUtils.isURL(value)) {
385                 env.warn("The value of the " + ATTR_NAME + "attribute" +
386                          "must be a file name.", elt);
387             } else {
388                 // pathname: turn into an id
389
File file = env.resolveReference(elt, ATTR_NAME);
390                 try {
391                     value = env.getResourceGenerator().importClickResource(file);
392                 } catch (SWFWriter.ImportResourceError e) {
393                     env.warn(e, elt);
394                 }
395                 elt.setAttribute(ATTR_NAME, value);
396             }
397         }
398         
399         // Recurse
400
Iterator iter;
401         for (iter = elt.getChildren().iterator();
402              iter.hasNext(); ) {
403             compileClickResources((Element) iter.next(), env);
404         }
405     }
406     
407     static void checkUnresolvedResourceReferences (CompilationEnvironment env) {
408         Map refs = env.resourceReferences();
409         Set resourceNames = env.getResourceNames();
410         for (Iterator iter = refs.keySet().iterator();
411              iter.hasNext(); ) {
412             String JavaDoc resourceId = (String JavaDoc) iter.next();
413             Element elt = (Element) refs.get(resourceId);
414             if (!resourceNames.contains(resourceId)) {
415                 env.warn("The resource named '"+resourceId+"' has not been declared", elt);
416             }
417         }
418     }
419
420     /**
421      * Walk the whole superclass chain, starting at the root, merging fontInfo.
422      */

423     protected static void mergeClassFontInfo (Element elt, FontInfo fontInfo,
424                                               CompilationEnvironment env) {
425         String JavaDoc classname = elt.getName();
426         // check for a cached fontInfo on the class
427
FontInfo cachedInfo = env.getClassFontInfo(classname);
428         if (cachedInfo != null) {
429             fontInfo.mergeFontInfoFrom(cachedInfo);
430             return;
431         }
432
433         ViewSchema schema = env.getSchema();
434         ClassModel classinfo = schema.getClassModel(classname);
435         if (classinfo == null || classinfo.definition == null) {
436             return;
437         }
438
439         // Build a list of superclasses
440
Vector parents = new Vector();
441         ClassModel lzxclass = classinfo;
442         // walk
443
while (lzxclass != null) {
444             parents.insertElementAt(lzxclass, 0);
445             lzxclass = lzxclass.superclass;
446         }
447
448         // A blank FontInfo with all empty slots
449
FontInfo cinfo = FontInfo.blankFontInfo();
450
451         // Pop off elements starting at base class
452
while (parents.size() > 0) {
453             lzxclass = (ClassModel) parents.firstElement();
454             parents.removeElementAt(0); // pop
455
mergeClassFontInfo(lzxclass, cinfo);
456         }
457
458         env.addClassFontInfo(classname, cinfo);
459         // apply the class' style changes, if any, to our fontInfo arg
460
fontInfo.mergeFontInfoFrom(cinfo);
461     }
462
463     /**
464      * Merge FontInfo from a class definition.
465      */

466     protected static void mergeClassFontInfo (ClassModel classinfo,
467                                               FontInfo fontInfo) {
468         if (classinfo != null && classinfo.definition != null) {
469             Element celt = classinfo.definition;
470             mergeFontInfo(celt, fontInfo);
471             // Propagate width/height info so children can compute
472
// ${parent.width}, ${parent.height}
473
mergeWidth(fontInfo, getAttributeValue(celt, "width"));
474             mergeHeight(fontInfo, getAttributeValue(celt, "height"));
475         }
476     }
477
478     /**
479      * Adds in text widths for all text views below this element that
480      * need them. This walks down into class definitions, merging
481      * font info as it goes. We don't need to walk into class defs
482      * for measuring text, since we have no way to pass those text
483      * widths to the runtime, but we do need this to check if we need
484      * to import the default bold or italic fonts.
485      *
486      *
487      *
488      * @param env
489      * @param elt
490      * @param fontInfo the current font name/style/size
491      */

492     protected void mapTextMetricsCompilation(Element elt,
493                                              CompilationEnvironment env,
494                                              FontInfo fontInfo,
495                                              Set classList) {
496
497         classList = new HashSet(classList);
498
499         // Clone a copy of the font info
500
fontInfo = new FontInfo(fontInfo);
501
502         // Check class defaults for font info
503
mergeClassFontInfo (elt, fontInfo, env);
504         // Now override with any directly declared attributes
505
mergeFontInfo(elt, fontInfo);
506         
507         FontManager fmgr = env.getGenerator().getFontManager();
508         String JavaDoc fontName = fontInfo.getName();
509         if (env.getSWFVersion().equals("swf5") &&
510             (fmgr.getFontFamily(fontInfo.getName()) == null) &&
511             (!env.getGenerator().isDeviceFont(fontName)) &&
512             !(env.getCanvas().defaultFont.equals(fontName))) {
513             env.warn("Font '"
514                 + fontName
515                 + "' used but not defined", elt);
516         }
517
518         if ((fmgr.getFontFamily(fontInfo.getName()) != null) &&
519             fontInfo.isFullySpecified() &&
520             (mSchema.hasHTMLContent(elt) || mSchema.hasTextContent(elt))) {
521             compileTextMetrics(elt, env, fontInfo);
522         }
523         ClassModel classinfo = env.getSchema().getClassModel(elt.getName());
524
525         // If this invokes a 'user-defined' class, let's walk that
526
// class's source tree now
527
if (classinfo != null && classinfo.definition != null) {
528             // check if we are in an instance of a class that we are
529
// already descended into (loop detection)
530
if (classList.contains(elt.getName().intern())) {
531                 return;
532             }
533             for (Iterator iter = classinfo.definition.getChildren().iterator(); iter.hasNext();
534                  ) {
535                 Element e = (Element) iter.next();
536                 String JavaDoc ename = e.getName();
537                 if (!(ename.equals("method") || ename.equals("attribute"))) {
538                     // Avoid recursively traversing class definitions.
539
// Mark this class as having been traversed, to
540
// avoid loops.
541
classList.add(classinfo.className.intern());
542                     mapTextMetricsCompilation(e, env, fontInfo, classList);
543                 }
544             }
545         }
546
547         // Now do immediate children
548
for (Iterator iter = elt.getChildren().iterator(); iter.hasNext();
549              ) {
550             Element e = (Element) iter.next();
551             mapTextMetricsCompilation(e, env, fontInfo, classList);
552         }
553     }
554
555     /** Merges font name/size/style from an element's direct
556      * attributes into a FontInfo */

557     protected static void mergeFontAttributes (Element elt, FontInfo fontInfo) {
558         String JavaDoc myfont = getAttributeValue(elt, "font");
559         if (myfont != null) {
560             if (matchesPattern(myfont, sFontNamePat)) {
561                 fontInfo.setName(myfont);
562             } else {
563                 // we don't know what font value is, so set back to the 'unknown' value
564
fontInfo.setName(FontInfo.NULL_FONT);
565             }
566         }
567
568         String JavaDoc mysize = getAttributeValue(elt, "fontsize");
569         if (mysize != null) {
570             if (matchesPattern(mysize, sFontSizePat)) {
571                 fontInfo.setSize(mysize);
572             } else {
573                 // we don't know what font size is, so set back to the
574
// 'unknown' value
575
fontInfo.setSize(FontInfo.NULL_SIZE);
576             }
577         }
578
579         String JavaDoc mystyle = getAttributeValue(elt, NodeModel.FONTSTYLE_ATTRIBUTE);
580         if (mystyle != null) {
581             if (matchesPattern(mystyle, sFontstylePat)) {
582                 fontInfo.setStyle(mystyle);
583             } else {
584                 // we don't know what font size is, so set back to the 'unknown' value
585
fontInfo.setStyleBits(FontInfo.NULL_STYLE);
586             }
587         }
588     }
589     
590     /** Merge in font attribute info from an element into a FontInfo.
591      *
592      * @param elt the element to look for font attributes on
593      * @param fontInfo merge font attribute info into this struct
594      */

595     private static void mergeFontInfo(Element elt, FontInfo fontInfo) {
596         mergeFontAttributes(elt, fontInfo);
597
598         // Static sized textfield optimization; need to cascade resizable
599
String JavaDoc resizable = getAttributeValue(elt, "resizable");
600         if ("true".equals(resizable)) {
601             fontInfo.resizable = FontInfo.FONTINFO_TRUE;
602         } else if ("false".equals(resizable)) {
603             fontInfo.resizable = FontInfo.FONTINFO_FALSE;
604         }
605
606         // Static sized textfield optimization; need to cascade multiline
607
String JavaDoc multiline = getAttributeValue(elt, "multiline");
608         if ("true".equals(multiline)) {
609             fontInfo.multiline = FontInfo.FONTINFO_TRUE;
610         } else if ("false".equals(multiline)) {
611             fontInfo.multiline = FontInfo.FONTINFO_FALSE;
612         }
613     }
614
615     /** Merge a width attribute into a fontInfo. The width is a string
616      * which could have these possible values:
617      *
618      * [1] numeric constant
619      * [2] $once{parent.xxx +/- nnn}
620      * [3] other... set width to NULL_SIZE
621     */

622     protected static void mergeWidth(FontInfo fontInfo, String JavaDoc width) {
623         if (width != null) {
624             int val = evalWidth(fontInfo, width);
625             fontInfo.setWidth(val);
626         }
627     }
628
629     protected static void mergeHeight(FontInfo fontInfo, String JavaDoc height) {
630         if (height != null) {
631             int val = evalHeight(fontInfo, height);
632             fontInfo.setHeight(val);
633         }
634     }
635
636
637     /** Evaluate a width expression in the context of a parent view
638      *(the context is held by the fontInfo).
639      *
640      * The width is a string which could have these
641      *
642      * possible values:
643      * [1] numeric constant
644      * [2] $once{parent.xxx +/- nnn}
645      * [3] other... set width to NULL_SIZE
646      *
647      * If the case is [2], a constraint, we evaluate with respect to the parent
648      * width info, which is in fontInfo.width
649      *
650      * @return a width or if the width is not known, return the
651      * special "null width" FontInfo.NULL_SIZE
652     */

653     protected static int evalWidth(FontInfo fontInfo, String JavaDoc width) {
654         if (width == null) {
655             return FontInfo.NULL_SIZE;
656         }
657
658         if (matchesPattern(width, sConstPat)) {
659             return Integer.parseInt(width);
660         } else if (matchesPattern(width, sParentWidthPat)) {
661             // if there is no defined parent width, we can't compute
662
// any meaningful result
663
if (fontInfo.getWidth() == FontInfo.NULL_SIZE) {
664                 return FontInfo.NULL_SIZE;
665             }
666             // Get numeric operator and arg
667
String JavaDoc operator = sMatcher.getMatch().group(3);
668             if (operator.equals("")) {
669                 operator = "+";
670             }
671
672             int arg = 0;
673             String JavaDoc argstr = sMatcher.getMatch().group(4);
674             try {
675                 if (argstr != null) {
676                     arg = Integer.parseInt(argstr);
677                 }
678             } catch (NumberFormatException JavaDoc e) {}
679             if (operator.equals("+")) {
680                 return (fontInfo.getWidth() + arg);
681             } else if (operator.equals("-")) {
682                 return (fontInfo.getWidth() - arg);
683             } else {
684                 throw new CompilationError("Unknown operator in width attribute: "+operator);
685             }
686         } else {
687             // We cannot determine what the dimension is, so set it
688
// return the unknown value
689
return FontInfo.NULL_SIZE;
690         }
691     }
692
693     protected static int evalHeight(FontInfo fontInfo, String JavaDoc height) {
694         if (height == null) {
695             return FontInfo.NULL_SIZE;
696         }
697
698         if (matchesPattern(height, sConstPat)) {
699             return Integer.parseInt(height);
700         } else if (matchesPattern(height, sParentHeightPat)) {
701             // if there is no defined parent height, we can't computer
702
// any meaningful result
703
if (fontInfo.getHeight() == FontInfo.NULL_SIZE) {
704                 return FontInfo.NULL_SIZE;
705             }
706             // Get numeric operator and arg
707
String JavaDoc operator = sMatcher.getMatch().group(3);
708             if (operator.equals("")) {
709                 operator = "+";
710             }
711
712             int arg = 0;
713             String JavaDoc argstr = sMatcher.getMatch().group(4);
714             try {
715                 if (argstr != null) {
716                     arg = Integer.parseInt(argstr);
717                 }
718             } catch (NumberFormatException JavaDoc e) {}
719             if (operator.equals("+")) {
720                 return (fontInfo.getHeight() + arg);
721             } else if (operator.equals("-")) {
722                 return (fontInfo.getHeight() - arg);
723             } else {
724                 throw new CompilationError("Unknown operator in height attribute: "+operator);
725             }
726         } else {
727             // We cannot determine what the dimension is, so set it return the unknown value
728
return FontInfo.NULL_SIZE;
729         }
730     }
731
732     /** Pattern matcher for compile-time optimizations */
733     static final PatternMatcher sMatcher = new Perl5Matcher();
734
735     static final Pattern sBooleanPat; // true|false
736
static final Pattern sConstPat; // DDDD
737
static final Pattern sParentWidthPat; // $once{parent.width +|- DDDD}
738
static final Pattern sParentHeightPat; // $once{parent.height +|- DDDD}
739

740     static final Pattern sFontstylePat;
741     static final Pattern sFontSizePat;
742     static final Pattern sFontNamePat;
743     static {
744         try {
745             Perl5Compiler compiler = new Perl5Compiler();
746             // $once{parent +|- DDDDD}
747
sBooleanPat = compiler.compile("\\s*(true|false)\\s*");
748             sConstPat = compiler.compile("\\s*(\\d*)\\s*");
749             sParentWidthPat = compiler.compile("\\s*\\$(once|always)?\\s*{(parent.width)\\s*([+-]?)\\s*(\\d*)\\s*}\\s*");
750             sParentHeightPat = compiler.compile("\\s*\\$(once|always)?\\s*{(parent.height)\\s*([+-]?)\\s*(\\d*)\\s*}\\s*");
751             sFontstylePat = compiler.compile("\\s*(bold italic|bold-italic|bold|plain|italic)\\s*");
752             sFontSizePat = compiler.compile("\\s*\\d*\\s*");
753             sFontNamePat = compiler.compile("\\s*[^${}]*\\s*");
754         } catch (MalformedPatternException e) {
755             throw new ChainedException(e);
756         }
757     }
758
759
760     /** Matches "$once{parent +|- DDDD}"
761      */

762     protected static boolean matchesPattern (String JavaDoc str, Pattern pattern) {
763         return sMatcher.matches(str, pattern);
764     }
765
766
767     /** return true if element has an attribute named ATTRIBUTE in
768      * it's attribute list, or has a child lzx element
769      * <attribute name="ATTRIBUTE"/>
770      */

771     protected static boolean hasAttribute(Element elt, String JavaDoc attrName) {
772         if (elt.getAttributeValue(attrName) != null) {
773             return true;
774         }
775
776         Iterator iter;
777         for (iter = elt.getChildren().iterator(); iter.hasNext(); ) {
778             Element child = (Element)iter.next();
779             if ((child.getName().equals("attribute")) &&
780                 (child.getAttribute(attrName) != null)) {
781                 return true;
782             }
783         }
784         return false;
785     }
786
787
788     /** return value if element has an attribute named ATTRNAME in
789      * it's attribute list, or has a child lzx element
790      * <attribute name="ATTRNAME" value="VAL"/>
791      */

792     protected static String JavaDoc getAttributeValue(Element elt, String JavaDoc attrName) {
793         String JavaDoc attrval = elt.getAttributeValue(attrName);
794         if (attrval != null) {
795             return attrval;
796         }
797
798         Iterator iter;
799         for (iter = elt.getChildren().iterator(); iter.hasNext(); ) {
800             Element child = (Element)iter.next();
801             if ((child.getName().equals("attribute")) &&
802                 attrName.equals(child.getAttributeValue("name"))) {
803                 return child.getAttributeValue("value");
804             }
805         }
806         return null;
807     }
808
809
810     /**
811      * Collect font/size/style used by input text elements, and tell
812      * the SWFWriter about them.
813      *
814      * @param env
815      * @param elt
816      * @param fontInfo the current font name/style/size
817      */

818     protected static void collectInputFonts(Element elt,
819                                             CompilationEnvironment env,
820                                             FontInfo fontInfo,
821                                             Set classList)
822     {
823         // mLogger.debug("collectInputFonts");
824
ViewSchema schema = env.getSchema();
825
826         // Clone a copy of the font info
827
fontInfo = new FontInfo(fontInfo);
828         // Add in class defaults for font info
829
mergeClassFontInfo (elt, fontInfo, env);
830         mergeFontInfo(elt, fontInfo);
831
832         // If this element is input text, tell the generator about what font it needs
833
if (schema.isInputTextElement(elt)) {
834             final SWFWriter generator = env.getGenerator();
835             boolean password = elt.getAttributeValue("password", "false").equals("true");
836             boolean multiline = (fontInfo.multiline == FontInfo.FONTINFO_TRUE);
837             int resizable = fontInfo.resizable;
838
839             int width = evalWidth(fontInfo, elt.getAttributeValue("width"));
840             // Use height attribute if any, otherwise check 'textheight' attribute
841
String JavaDoc heightstr = elt.getAttributeValue("height");
842             if (heightstr == null) {
843                 heightstr = elt.getAttributeValue("textheight");
844             }
845             int height = evalHeight(fontInfo, heightstr);
846
847             // If this is a single line textfield, then we can get the
848
// height from the font height
849
if (!multiline && (height == FontInfo.NULL_SIZE) && fontInfo.isFullySpecified()) {
850                 height = (int) Math.ceil(generator.getLFCLineHeight(fontInfo, fontInfo.getSize()));
851             }
852
853             // Late breaking news: if we have no width yet, let's use
854
// default text width.
855
if (width == FontInfo.NULL_SIZE) {
856                 width = env.getDefaultTextWidth();
857             }
858             
859             // If width and height are not both integers, must be resizable
860
if ((width == FontInfo.NULL_SIZE) || (height == FontInfo.NULL_SIZE)) {
861                 resizable = FontInfo.FONTINFO_TRUE;
862             }
863             boolean fixedsize = (resizable != FontInfo.FONTINFO_TRUE);
864
865             // must have concrete values for font name/size/style
866
if (!fontInfo.isFullySpecified()) {
867                 //mLogger.info("[1] elt="+elt.getName()+" font not fully specified: "+fontInfo);
868
return;
869             }
870
871             if (fixedsize) {
872                 generator.addCustomInputText(fontInfo, width, height, multiline, password);
873             }
874             // failsafe - generate resizable text field
875
// If password bit is set, allocate both a password and non-password resource,
876
// in case it's a component which wants to do both.
877
if (hasAttribute(elt, "password")) {
878                 generator.addInputText(fontInfo, true);
879                 generator.addInputText(fontInfo, false);
880             } else {
881                 //mLogger.info("[2] elt="+elt.getName()+" add inputfont "+fontInfo);
882
generator.addInputText(fontInfo, false);
883             }
884         }
885         
886         // Propagate width/height info so children can compute ${parent.width}, ${parent.height}
887
mergeWidth(fontInfo, getAttributeValue(elt, "width"));
888         mergeHeight(fontInfo, getAttributeValue(elt, "height"));
889
890         ClassModel classinfo = schema.getClassModel(elt.getName());
891
892         // Build a list of superclasses
893
Vector parents = new Vector();
894         ClassModel lzxclass = classinfo;
895         // walk
896
while (lzxclass != null) {
897             parents.insertElementAt(lzxclass, 0);
898             lzxclass = lzxclass.superclass;
899         }
900
901         // Pop off elements starting at base class
902
while (parents.size() > 0) {
903             lzxclass = (ClassModel) parents.firstElement();
904             parents.removeElementAt(0); // pop
905
//mergeClassFontInfo(lzxclass, cinfo);
906
if (lzxclass.definition != null) {
907                 // check if we are in an instance of a class that we are already descended into (loop detection)
908
if (classList.contains(elt.getName().intern())) {
909                     return;
910                 }
911                 for (Iterator iter = lzxclass.definition.getChildren().iterator(); iter.hasNext();
912                      ) {
913                     Element e = (Element) iter.next();
914                     String JavaDoc ename = e.getName();
915                     if (!(ename.equals("method") || ename.equals("attribute"))) {
916                         // Avoid recursively traversing class definitions.
917
// Mark this class as having been traversed, to avoid loops.
918
HashSet nclassList = new HashSet(classList);
919                         nclassList.add(lzxclass.className.intern());
920                         collectInputFonts(e, env, fontInfo, nclassList);
921                     }
922                 }
923             }
924         }
925
926         // Now do immediate children
927
for (Iterator iter = elt.getChildren().iterator(); iter.hasNext();
928              ) {
929             Element e = (Element) iter.next();
930             String JavaDoc ename = e.getName();
931             if (!(ename.equals("method") || ename.equals("attribute"))) {
932                 collectInputFonts(e, env, fontInfo, classList);
933             }
934         }
935     }
936
937     /**
938      * Adds in text metrics for this element.
939      *
940      * @param env
941      * @param elt
942      * @param fontInfo font information for this element
943      */

944     private void compileTextMetrics(Element elt,
945                                     CompilationEnvironment env,
946                                     FontInfo fontInfo) {
947         SWFWriter generator = env.getGenerator();
948         double wd = 0;
949         double textheight = 0;
950         ViewSchema schema = env.getSchema();
951         boolean isInputTextElement = schema.isInputTextElement(elt);
952
953         String JavaDoc widthAttr = elt.getAttributeValue("width");
954         String JavaDoc heightAttr = elt.getAttributeValue("height");
955         try {
956             if (widthAttr != null) {
957                 wd = Double.parseDouble(widthAttr);
958             }
959         } catch (NumberFormatException JavaDoc e) {
960             // If we can't derive width at compile time, just let it default
961
}
962
963         if (heightAttr == null) {
964             textheight = (int) Math.ceil(generator.getLFCLineHeight(fontInfo, fontInfo.getSize()));
965         }
966
967         // Look at text content. Measure width and height with current font spec.
968
LineMetrics lm;
969         lm = TextCompiler.getElementWidth(elt, fontInfo, generator);
970
971         // If the user didn't supply an explicit width, let's try to
972
// figure out something based on measuring the text content
973
// (taken either from elt.getText() or the "text" attribute).
974
if (widthAttr == null) {
975             // Check for 'text' attribute, and if found measure it. In
976
// this case we do not perform HTML whitespace normalization.
977
String JavaDoc textattr = elt.getAttributeValue("text");
978             if (textattr != null && textattr.length() != 0) {
979                 wd = TextCompiler.computeTextWidth(textattr, fontInfo, generator);
980             } else {
981                 if (!("".equals(lm.getText()))) {
982                     wd = lm.maxw;
983                     textheight = lm.nlines * (int) Math.ceil(generator.getLFCLineHeight(fontInfo, fontInfo.getSize()));
984                 }
985             }
986         }
987
988         // Check that the specified font
989
// exists, so the user doesn't get bitten later by a missing font.
990
if (wd != 0) {
991             TextCompiler.checkFontExists(generator, fontInfo);
992         }
993
994         // If there's text (a text or label attribute, or child content),
995
// add an attribute to carry its width.
996
if (wd != 0 && widthAttr == null) {
997             DecimalFormat JavaDoc format = new DecimalFormat JavaDoc("#.##");
998             StringBuffer JavaDoc width = new StringBuffer JavaDoc();
999             if (isInputTextElement) {
1000                // so cursor can be seen at end of line of inputtext
1001
wd += INPUT_TEXTWIDTH_FUDGE_FACTOR;
1002            }
1003            width = format.format(Math.ceil(wd), width, new FieldPosition JavaDoc(0));
1004            elt.setAttribute("textwidth", width.toString());
1005        }
1006
1007        if ((textheight != 0) && schema.hasTextContent(elt)) {
1008            DecimalFormat JavaDoc format = new DecimalFormat JavaDoc("#.##");
1009            StringBuffer JavaDoc height = new StringBuffer JavaDoc();
1010            height = format.format(Math.ceil(textheight), height, new FieldPosition JavaDoc(0));
1011            elt.setAttribute("textheight", height.toString());
1012        }
1013    }
1014
1015
1016    static void setFontInfo(FontInfo info, Element elt) {
1017        String JavaDoc face = elt.getAttributeValue("face");
1018        String JavaDoc size = elt.getAttributeValue("size");
1019
1020        if (face != null) {
1021            info.setName(face);
1022        }
1023        if (size != null) {
1024            info.setSize(size);
1025        }
1026    }
1027}
1028
1029
1030
Popular Tags