001: /*
002: * Copyright 2006 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.dev.cfg;
017:
018: import com.google.gwt.core.ext.Generator;
019: import com.google.gwt.core.ext.TreeLogger;
020: import com.google.gwt.core.ext.UnableToCompleteException;
021: import com.google.gwt.dev.js.JsParser;
022: import com.google.gwt.dev.js.JsParserException;
023: import com.google.gwt.dev.js.JsParserException.SourceDetail;
024: import com.google.gwt.dev.js.ast.JsExprStmt;
025: import com.google.gwt.dev.js.ast.JsFunction;
026: import com.google.gwt.dev.js.ast.JsProgram;
027: import com.google.gwt.dev.js.ast.JsStatement;
028: import com.google.gwt.dev.util.Empty;
029: import com.google.gwt.dev.util.Util;
030: import com.google.gwt.dev.util.xml.AttributeConverter;
031: import com.google.gwt.dev.util.xml.Schema;
032:
033: import java.io.IOException;
034: import java.io.StringReader;
035: import java.net.URL;
036: import java.util.HashMap;
037: import java.util.HashSet;
038: import java.util.List;
039: import java.util.Map;
040: import java.util.Set;
041:
042: // CHECKSTYLE_NAMING_OFF
043: /**
044: * Configures a module definition object using XML.
045: */
046: public class ModuleDefSchema extends Schema {
047: private final class BodySchema extends Schema {
048:
049: protected final String __define_property_1_name = null;
050:
051: protected final String __define_property_2_values = null;
052:
053: protected final String __entry_point_1_class = null;
054:
055: protected final String __extend_property_1_name = null;
056:
057: protected final String __extend_property_2_values = null;
058:
059: protected final String __generate_with_1_class = null;
060:
061: protected final String __inherits_1_name = null;
062:
063: protected final String __property_provider_1_name = null;
064:
065: protected final String __public_1_path = null;
066:
067: protected final String __public_2_includes = "";
068:
069: protected final String __public_3_excludes = "";
070:
071: protected final String __public_4_defaultexcludes = "yes";
072:
073: protected final String __public_5_casesensitive = "true";
074:
075: protected final String __replace_with_1_class = null;
076:
077: protected final String __script_1_src = null;
078:
079: protected final String __servlet_1_path = null;
080:
081: protected final String __servlet_2_class = null;
082:
083: protected final String __set_property_1_name = null;
084:
085: protected final String __set_property_2_value = null;
086:
087: protected final String __source_1_path = "";
088:
089: protected final String __source_2_includes = "";
090:
091: protected final String __source_3_excludes = "";
092:
093: protected final String __source_4_defaultexcludes = "yes";
094:
095: protected final String __source_5_casesensitive = "true";
096:
097: protected final String __stylesheet_1_src = null;
098:
099: protected final String __super _source_1_path = "";
100:
101: protected final String __super _source_2_includes = "";
102:
103: protected final String __super _source_3_excludes = "";
104:
105: protected final String __super _source_4_defaultexcludes = "yes";
106:
107: protected final String __super _source_5_casesensitive = "true";
108:
109: private Schema fChild;
110:
111: protected Schema __define_property_begin(PropertyName name,
112: PropertyValue[] values)
113: throws UnableToCompleteException {
114: if (moduleDef.getProperties().find(name.token) != null) {
115: // Disallow redefinition.
116: String msg = "Attempt to redefine property '"
117: + name.token + "'";
118: logger.log(TreeLogger.ERROR, msg, null);
119: throw new UnableToCompleteException();
120: }
121:
122: Property prop = moduleDef.getProperties()
123: .create(name.token);
124: for (int i = 0; i < values.length; i++) {
125: prop.addKnownValue(values[i].token);
126: }
127:
128: // No children.
129: return null;
130: }
131:
132: protected Schema __entry_point_begin(String className) {
133: moduleDef.addEntryPointTypeName(className);
134: return null;
135: }
136:
137: protected Schema __extend_property_begin(Property property,
138: PropertyValue[] values) {
139: for (int i = 0; i < values.length; i++) {
140: property.addKnownValue(values[i].token);
141: }
142:
143: // No children.
144: return null;
145: }
146:
147: protected Schema __fail_begin() {
148: RuleFail rule = new RuleFail();
149: moduleDef.getRules().prepend(rule);
150: return new ConditionSchema(rule.getRootCondition());
151: }
152:
153: protected Schema __generate_with_begin(Generator gen) {
154: RuleGenerateWith rule = new RuleGenerateWith(gen);
155: moduleDef.getRules().prepend(rule);
156: return new ConditionSchema(rule.getRootCondition());
157: }
158:
159: protected Schema __inherits_begin(String name)
160: throws UnableToCompleteException {
161: TreeLogger branch = logger.branch(TreeLogger.TRACE,
162: "Loading inherited module '" + name + "'", null);
163: loader.nestedLoad(branch, name, moduleDef);
164: return null;
165: }
166:
167: protected Schema __property_provider_begin(Property property) {
168: property.setProvider(new PropertyProvider(moduleDef,
169: property));
170: return fChild = new PropertyProviderBodySchema();
171: }
172:
173: protected void __property_provider_end(Property property)
174: throws UnableToCompleteException {
175: PropertyProviderBodySchema childSchema = ((PropertyProviderBodySchema) fChild);
176: String script = childSchema.getScript();
177: if (script == null) {
178: // This is a problem.
179: //
180: logger
181: .log(
182: TreeLogger.ERROR,
183: "Property providers must specify a JavaScript body",
184: null);
185: throw new UnableToCompleteException();
186: }
187:
188: int lineNumber = childSchema.getStartLineNumber();
189: JsFunction fn = parseJsBlock(lineNumber, script);
190:
191: property.getProvider().setBody(fn.getBody());
192: }
193:
194: protected Schema __public_begin(String path, String includes,
195: String excludes, String defaultExcludes,
196: String caseSensitive) {
197: return fChild = new IncludeExcludeSchema();
198: }
199:
200: protected void __public_end(String path, String includes,
201: String excludes, String defaultExcludes,
202: String caseSensitive) {
203: IncludeExcludeSchema childSchema = ((IncludeExcludeSchema) fChild);
204: foundAnyPublic = true;
205:
206: Set<String> includeSet = childSchema.getIncludes();
207: addDelimitedStringToSet(includes, "[ ,]", includeSet);
208: String[] includeList = includeSet
209: .toArray(new String[includeSet.size()]);
210:
211: Set<String> excludeSet = childSchema.getExcludes();
212: addDelimitedStringToSet(excludes, "[ ,]", excludeSet);
213: String[] excludeList = excludeSet
214: .toArray(new String[excludeSet.size()]);
215:
216: boolean doDefaultExcludes = toPrimitiveBoolean(defaultExcludes);
217: boolean doCaseSensitive = toPrimitiveBoolean(caseSensitive);
218:
219: addPublicPackage(modulePackageAsPath, path, includeList,
220: excludeList, doDefaultExcludes, doCaseSensitive);
221: }
222:
223: protected Schema __replace_with_begin(String className) {
224: RuleReplaceWith rule = new RuleReplaceWith(className);
225: moduleDef.getRules().prepend(rule);
226: return new ConditionSchema(rule.getRootCondition());
227: }
228:
229: /**
230: * @param src a partial or full url to a script file to inject
231: * @return <code>null</code> since there can be no children
232: */
233: protected Schema __script_begin(String src) {
234: return fChild = new ScriptReadyBodySchema();
235: }
236:
237: protected void __script_end(String src)
238: throws UnableToCompleteException {
239: ScriptReadyBodySchema childSchema = (ScriptReadyBodySchema) fChild;
240: String js = childSchema.getScriptReadyBlock();
241: if (js != null) {
242: logger
243: .log(
244: TreeLogger.WARN,
245: "Injected scripts no longer require an associated JavaScript block.",
246: null);
247: }
248:
249: // For consistency, we allow the ready functions to use $wnd even though
250: // they'll be running in the context of the host html window anyway.
251: // We make up the difference by injecting a local variable into the
252: // function we wrap around their code.
253: js = "var $wnd = window; " + js;
254:
255: int lineNumber = childSchema.getStartLineNumber();
256: JsFunction fn = parseJsBlock(lineNumber, js);
257: Script script = new Script(src, fn);
258: moduleDef.getScripts().append(script);
259: }
260:
261: protected Schema __servlet_begin(String path,
262: String servletClass) throws UnableToCompleteException {
263:
264: // Only absolute paths, although it is okay to have multiple slashes.
265: if (!path.startsWith("/")) {
266: logger
267: .log(
268: TreeLogger.ERROR,
269: "Servlet path '"
270: + path
271: + "' must begin with forward slash (e.g. '/foo')",
272: null);
273: throw new UnableToCompleteException();
274: }
275:
276: // Map the path within this module.
277: moduleDef.mapServlet(path, servletClass);
278:
279: return null;
280: }
281:
282: protected Schema __set_property_begin(Property prop,
283: PropertyValue value) {
284: prop.setActiveValue(value.token);
285:
286: // No children.
287: return null;
288: }
289:
290: /**
291: * Indicates which subdirectories contain translatable source without
292: * necessarily adding a sourcepath entry.
293: */
294: protected Schema __source_begin(String path, String includes,
295: String excludes, String defaultExcludes,
296: String caseSensitive) {
297: return fChild = new IncludeExcludeSchema();
298: }
299:
300: protected void __source_end(String path, String includes,
301: String excludes, String defaultExcludes,
302: String caseSensitive) {
303: addSourcePackage(path, includes, excludes, defaultExcludes,
304: caseSensitive, false);
305: }
306:
307: /**
308: * @param src a partial or full url to a stylesheet file to inject
309: * @return <code>null</code> since there can be no children
310: */
311: protected Schema __stylesheet_begin(String src) {
312: moduleDef.getStyles().append(src);
313: return null;
314: }
315:
316: /**
317: * Like adding a translatable source package, but such that it uses the
318: * module's package itself as its sourcepath root entry.
319: */
320: protected Schema __super _source_begin(String path,
321: String includes, String excludes,
322: String defaultExcludes, String caseSensitive) {
323: return fChild = new IncludeExcludeSchema();
324: }
325:
326: protected void __super _source_end(String path, String includes,
327: String excludes, String defaultExcludes,
328: String caseSensitive) {
329: addSourcePackage(path, includes, excludes, defaultExcludes,
330: caseSensitive, true);
331: }
332:
333: private void addDelimitedStringToSet(String delimited,
334: String delimiter, Set<String> toSet) {
335: if (delimited.length() > 0) {
336: String[] split = delimited.split(delimiter);
337: for (int i = 0; i < split.length; ++i) {
338: if (split[i].length() > 0) {
339: toSet.add(split[i]);
340: }
341: }
342: }
343: }
344:
345: private void addPublicPackage(String parentDir, String relDir,
346: String[] includeList, String[] excludeList,
347: boolean defaultExcludes, boolean caseSensitive) {
348: String normChildDir = normalizePathEntry(relDir);
349: if (normChildDir.startsWith("/")) {
350: logger.log(TreeLogger.WARN,
351: "Non-relative public package: " + normChildDir,
352: null);
353: return;
354: }
355: if (normChildDir.startsWith("./")
356: || normChildDir.indexOf("/./") >= 0) {
357: logger
358: .log(TreeLogger.WARN,
359: "Non-canonical public package: "
360: + normChildDir, null);
361: return;
362: }
363: if (normChildDir.startsWith("../")
364: || normChildDir.indexOf("/../") >= 0) {
365: logger
366: .log(TreeLogger.WARN,
367: "Non-canonical public package: "
368: + normChildDir, null);
369: return;
370: }
371: String fullDir = parentDir + normChildDir;
372: moduleDef.addPublicPackage(fullDir, includeList,
373: excludeList, defaultExcludes, caseSensitive);
374: }
375:
376: private void addSourcePackage(String relDir, String includes,
377: String excludes, String defaultExcludes,
378: String caseSensitive, boolean isSuperSource) {
379: IncludeExcludeSchema childSchema = ((IncludeExcludeSchema) fChild);
380: foundExplicitSourceOrSuperSource = true;
381:
382: Set<String> includeSet = childSchema.getIncludes();
383: addDelimitedStringToSet(includes, "[ ,]", includeSet);
384: String[] includeList = includeSet
385: .toArray(new String[includeSet.size()]);
386:
387: Set<String> excludeSet = childSchema.getExcludes();
388: addDelimitedStringToSet(excludes, "[ ,]", excludeSet);
389: String[] excludeList = excludeSet
390: .toArray(new String[excludeSet.size()]);
391:
392: boolean doDefaultExcludes = toPrimitiveBoolean(defaultExcludes);
393: boolean doCaseSensitive = toPrimitiveBoolean(caseSensitive);
394:
395: addSourcePackage(modulePackageAsPath, relDir, includeList,
396: excludeList, doDefaultExcludes, doCaseSensitive,
397: isSuperSource);
398: }
399:
400: private void addSourcePackage(String modulePackagePath,
401: String relDir, String[] includeList,
402: String[] excludeList, boolean defaultExcludes,
403: boolean caseSensitive, boolean isSuperSource) {
404: String normChildDir = normalizePathEntry(relDir);
405: if (normChildDir.startsWith("/")) {
406: logger.log(TreeLogger.WARN,
407: "Non-relative source package: " + normChildDir,
408: null);
409: return;
410: }
411: if (normChildDir.startsWith("./")
412: || normChildDir.indexOf("/./") >= 0) {
413: logger
414: .log(TreeLogger.WARN,
415: "Non-canonical source package: "
416: + normChildDir, null);
417: return;
418: }
419: if (normChildDir.startsWith("../")
420: || normChildDir.indexOf("/../") >= 0) {
421: logger
422: .log(TreeLogger.WARN,
423: "Non-canonical source package: "
424: + normChildDir, null);
425: return;
426: }
427:
428: String fullPackagePath = modulePackagePath + normChildDir;
429:
430: if (isSuperSource) {
431: /*
432: * Super source does not include the module package path as part of the
433: * logical class names.
434: */
435: moduleDef.addSuperSourcePackage(fullPackagePath,
436: includeList, excludeList, defaultExcludes,
437: caseSensitive);
438: } else {
439: /*
440: * Add the full package path to the include and exclude lists since the
441: * logical name of classes on the source path includes the package path
442: * but the include and exclude lists do not.
443: */
444: addPrefix(includeList, fullPackagePath);
445: addPrefix(excludeList, fullPackagePath);
446:
447: moduleDef.addSourcePackage(fullPackagePath,
448: includeList, excludeList, defaultExcludes,
449: caseSensitive);
450: }
451: }
452:
453: /**
454: * Normalizes a path entry such that it does not start with but does end
455: * with '/'.
456: */
457: private String normalizePathEntry(String path) {
458: path = path.trim();
459:
460: if (path.length() == 0) {
461: return "";
462: }
463:
464: path = path.replace('\\', '/');
465:
466: if (!path.endsWith("/")) {
467: path += "/";
468: }
469:
470: return path;
471: }
472: }
473:
474: private final class ConditionSchema extends Schema {
475:
476: protected final String __when_property_is_1_name = null;
477:
478: protected final String __when_property_is_2_value = null;
479:
480: protected final String __when_type_assignable_1_class = null;
481:
482: protected final String __when_type_is_1_class = null;
483:
484: private final CompoundCondition parentCondition;
485:
486: public ConditionSchema(CompoundCondition parentCondition) {
487: this .parentCondition = parentCondition;
488: }
489:
490: protected Schema __all_begin() {
491: CompoundCondition cond = new ConditionAll();
492: parentCondition.getConditions().add(cond);
493: return new ConditionSchema(cond);
494: }
495:
496: protected Schema __any_begin() {
497: CompoundCondition cond = new ConditionAny();
498: parentCondition.getConditions().add(cond);
499: return new ConditionSchema(cond);
500: }
501:
502: protected Schema __none_begin() {
503: CompoundCondition cond = new ConditionNone();
504: parentCondition.getConditions().add(cond);
505: return new ConditionSchema(cond);
506: }
507:
508: // We intentionally use the Property type here for tough-love on module
509: // writers. It prevents them from trying to create property providers for
510: // unknown properties.
511: //
512: protected Schema __when_property_is_begin(Property prop,
513: PropertyValue value) {
514: Condition cond = new ConditionWhenPropertyIs(
515: prop.getName(), value.token);
516: parentCondition.getConditions().add(cond);
517:
518: // No children allowed.
519: return null;
520: }
521:
522: protected Schema __when_type_assignable_begin(String className) {
523: Condition cond = new ConditionWhenTypeAssignableTo(
524: className);
525: parentCondition.getConditions().add(cond);
526:
527: // No children allowed.
528: return null;
529: }
530:
531: protected Schema __when_type_is_begin(String className) {
532: Condition cond = new ConditionWhenTypeIs(className);
533: parentCondition.getConditions().add(cond);
534:
535: // No children allowed.
536: return null;
537: }
538: }
539:
540: private final class IncludeExcludeSchema extends Schema {
541:
542: protected final String __exclude_1_name = null;
543:
544: protected final String __include_1_name = null;
545:
546: private final Set<String> excludes = new HashSet<String>();
547:
548: private final Set<String> includes = new HashSet<String>();
549:
550: public Set<String> getExcludes() {
551: return excludes;
552: }
553:
554: public Set<String> getIncludes() {
555: return includes;
556: }
557:
558: protected Schema __exclude_begin(String name) {
559: excludes.add(name);
560: return null;
561: }
562:
563: protected Schema __include_begin(String name) {
564: includes.add(name);
565: return null;
566: }
567: }
568:
569: /**
570: * Creates singleton instances of objects based on an attribute containing a
571: * class name.
572: */
573: private final class ObjAttrCvt extends AttributeConverter {
574:
575: private final Class fReqdSuperclass;
576:
577: public ObjAttrCvt(Class reqdSuperclass) {
578: fReqdSuperclass = reqdSuperclass;
579: }
580:
581: public Object convertToArg(Schema schema, int lineNumber,
582: String elemName, String attrName, String attrValue)
583: throws UnableToCompleteException {
584:
585: Object found = singletonsByName.get(attrValue);
586: if (found != null) {
587: // Found in the cache.
588: //
589: return found;
590: }
591:
592: try {
593: // Load the class.
594: //
595: ClassLoader cl = Thread.currentThread()
596: .getContextClassLoader();
597: Class clazz = cl.loadClass(attrValue);
598:
599: // Make sure it's compatible.
600: //
601: if (!fReqdSuperclass.isAssignableFrom(clazz)) {
602: Messages.INVALID_CLASS_DERIVATION.log(logger,
603: clazz, fReqdSuperclass, null);
604: throw new UnableToCompleteException();
605: }
606:
607: Object object = clazz.newInstance();
608: singletonsByName.put(attrValue, object);
609: return object;
610: } catch (ClassNotFoundException e) {
611: Messages.UNABLE_TO_LOAD_CLASS.log(logger, attrValue, e);
612: throw new UnableToCompleteException();
613: } catch (InstantiationException e) {
614: Messages.UNABLE_TO_CREATE_OBJECT.log(logger, attrValue,
615: e);
616: throw new UnableToCompleteException();
617: } catch (IllegalAccessException e) {
618: Messages.UNABLE_TO_CREATE_OBJECT.log(logger, attrValue,
619: e);
620: throw new UnableToCompleteException();
621: }
622: }
623: }
624:
625: /**
626: * Converts property names into their corresponding property objects.
627: */
628: private final class PropertyAttrCvt extends AttributeConverter {
629: public Object convertToArg(Schema schema, int line,
630: String elem, String attr, String value)
631: throws UnableToCompleteException {
632: // Find the named property.
633: //
634: Property prop = moduleDef.getProperties().find(value);
635:
636: if (prop != null) {
637: // Found it.
638: //
639: return prop;
640: } else {
641: // Property not defined. This is a problem.
642: //
643: Messages.PROPERTY_NOT_FOUND.log(logger, value, null);
644: throw new UnableToCompleteException();
645: }
646: }
647: }
648:
649: private static class PropertyName {
650: public final String token;
651:
652: public PropertyName(String token) {
653: this .token = token;
654: }
655: }
656:
657: /**
658: * Converts a string into a property name, validating it in the process.
659: */
660: private final class PropertyNameAttrCvt extends AttributeConverter {
661:
662: public Object convertToArg(Schema schema, int line,
663: String elem, String attr, String value)
664: throws UnableToCompleteException {
665: // Ensure each part of the name is valid.
666: //
667: String[] tokens = (value + ". ").split("\\.");
668: for (int i = 0; i < tokens.length - 1; i++) {
669: String token = tokens[i];
670: if (!Util.isValidJavaIdent(token)) {
671: Messages.PROPERTY_NAME_INVALID.log(logger, value,
672: null);
673: throw new UnableToCompleteException();
674: }
675: }
676:
677: // It is a valid name.
678: //
679: return new PropertyName(value);
680: }
681: }
682:
683: private class PropertyProviderBodySchema extends Schema {
684:
685: private StringBuffer script;
686:
687: private int startLineNumber = -1;
688:
689: public PropertyProviderBodySchema() {
690: }
691:
692: public void __text(String text) {
693: if (script == null) {
694: script = new StringBuffer();
695: startLineNumber = getLineNumber();
696: }
697: script.append(text);
698: }
699:
700: public String getScript() {
701: return script != null ? script.toString() : null;
702: }
703:
704: public int getStartLineNumber() {
705: return startLineNumber;
706: }
707: }
708:
709: private static class PropertyValue {
710: public final String token;
711:
712: public PropertyValue(String token) {
713: this .token = token;
714: }
715: }
716:
717: /**
718: * Converts a comma-separated string into an array of property value tokens.
719: */
720: private final class PropertyValueArrayAttrCvt extends
721: AttributeConverter {
722: public Object convertToArg(Schema schema, int line,
723: String elem, String attr, String value)
724: throws UnableToCompleteException {
725: String[] tokens = value.split(",");
726: PropertyValue[] values = new PropertyValue[tokens.length];
727:
728: // Validate each token as we copy it over.
729: //
730: for (int i = 0; i < tokens.length; i++) {
731: values[i] = (PropertyValue) propValueAttrCvt
732: .convertToArg(schema, line, elem, attr,
733: tokens[i]);
734: }
735:
736: return values;
737: }
738: }
739:
740: /**
741: * Converts a string into a property value, validating it in the process.
742: */
743: private final class PropertyValueAttrCvt extends AttributeConverter {
744: public Object convertToArg(Schema schema, int line,
745: String elem, String attr, String value)
746: throws UnableToCompleteException {
747:
748: String token = value.trim();
749: if (Util.isValidJavaIdent(token)) {
750: return new PropertyValue(token);
751: } else {
752: Messages.PROPERTY_VALUE_INVALID
753: .log(logger, token, null);
754: throw new UnableToCompleteException();
755: }
756: }
757: }
758:
759: private class ScriptReadyBodySchema extends Schema {
760:
761: private StringBuffer script;
762:
763: private int startLineNumber = -1;
764:
765: public ScriptReadyBodySchema() {
766: }
767:
768: public void __text(String text) {
769: if (script == null) {
770: script = new StringBuffer();
771: startLineNumber = getLineNumber();
772: }
773: script.append(text);
774: }
775:
776: public String getScriptReadyBlock() {
777: return script != null ? script.toString() : null;
778: }
779:
780: public int getStartLineNumber() {
781: return startLineNumber;
782: }
783: }
784:
785: private static final Map singletonsByName = new HashMap();
786:
787: private static void addPrefix(String[] strings, String prefix) {
788: for (int i = 0; i < strings.length; ++i) {
789: strings[i] = prefix + strings[i];
790: }
791: }
792:
793: /**
794: * Returns <code>true</code> if the string equals "true" or "yes" using a
795: * case insensitive comparison.
796: */
797: private static boolean toPrimitiveBoolean(String s) {
798: return "yes".equalsIgnoreCase(s) || "true".equalsIgnoreCase(s);
799: }
800:
801: private final BodySchema bodySchema;
802:
803: private boolean foundAnyPublic;
804:
805: private boolean foundExplicitSourceOrSuperSource;
806:
807: private final ObjAttrCvt genAttrCvt = new ObjAttrCvt(
808: Generator.class);
809: private final JsParser jsParser = new JsParser();
810: private final JsProgram jsPgm = new JsProgram();
811: private final ModuleDefLoader loader;
812: private final TreeLogger logger;
813: private final ModuleDef moduleDef;
814: private final String modulePackageAsPath;
815: private final URL moduleURL;
816: private final PropertyAttrCvt propAttrCvt = new PropertyAttrCvt();
817: private final PropertyNameAttrCvt propNameAttrCvt = new PropertyNameAttrCvt();
818: private final PropertyValueArrayAttrCvt propValueArrayAttrCvt = new PropertyValueArrayAttrCvt();
819: private final PropertyValueAttrCvt propValueAttrCvt = new PropertyValueAttrCvt();
820:
821: public ModuleDefSchema(TreeLogger logger, ModuleDefLoader loader,
822: URL moduleURL, String modulePackageAsPath,
823: ModuleDef toConfigure) {
824: this .logger = logger;
825: this .loader = loader;
826: this .moduleURL = moduleURL;
827: this .modulePackageAsPath = modulePackageAsPath;
828: assert (modulePackageAsPath.endsWith("/") || modulePackageAsPath
829: .equals(""));
830: this .moduleDef = toConfigure;
831: this .bodySchema = new BodySchema();
832:
833: registerAttributeConverter(PropertyName.class, propNameAttrCvt);
834: registerAttributeConverter(Property.class, propAttrCvt);
835: registerAttributeConverter(PropertyValue.class,
836: propValueAttrCvt);
837: registerAttributeConverter(PropertyValue[].class,
838: propValueArrayAttrCvt);
839: registerAttributeConverter(Generator.class, genAttrCvt);
840: }
841:
842: protected Schema __module_begin() {
843: return bodySchema;
844: }
845:
846: protected void __module_end() {
847: // Maybe infer source and public.
848: //
849: if (!foundExplicitSourceOrSuperSource) {
850: bodySchema.addSourcePackage(modulePackageAsPath, "client",
851: Empty.STRINGS, Empty.STRINGS, true, true, false);
852: }
853:
854: if (!foundAnyPublic) {
855: bodySchema.addPublicPackage(modulePackageAsPath, "public",
856: Empty.STRINGS, Empty.STRINGS, true, true);
857: }
858: }
859:
860: /**
861: * Parses handwritten JavaScript found in the module xml, logging an error
862: * message and throwing an exception if there's a problem.
863: *
864: * @param startLineNumber the start line number where the script was found;
865: * used to report errors
866: * @param script the JavaScript to wrap in "function() { script }" to parse
867: * @return the parsed function
868: * @throws UnableToCompleteException
869: */
870: private JsFunction parseJsBlock(int startLineNumber, String script)
871: throws UnableToCompleteException {
872: script = "function() { " + script + "}";
873: StringReader r = new StringReader(script);
874: List<JsStatement> stmts;
875: try {
876: stmts = jsParser
877: .parse(jsPgm.getScope(), r, startLineNumber);
878: } catch (IOException e) {
879: logger.log(TreeLogger.ERROR, "Error reading script source",
880: e);
881: throw new UnableToCompleteException();
882: } catch (JsParserException e) {
883: SourceDetail dtl = e.getSourceDetail();
884: if (dtl != null) {
885: StringBuffer sb = new StringBuffer();
886: sb.append(moduleURL.toExternalForm());
887: sb.append("(");
888: sb.append(dtl.getLine());
889: sb.append(", ");
890: sb.append(dtl.getLineOffset());
891: sb.append("): ");
892: sb.append(e.getMessage());
893: logger.log(TreeLogger.ERROR, sb.toString(), e);
894: } else {
895: logger.log(TreeLogger.ERROR,
896: "Error parsing JavaScript source", e);
897: }
898: throw new UnableToCompleteException();
899: }
900:
901: // Rip the body out of the parsed function and attach the JavaScript
902: // AST to the method.
903: //
904: JsFunction fn = (JsFunction) ((JsExprStmt) stmts.get(0))
905: .getExpression();
906: return fn;
907: }
908:
909: }
910: // CHECKSTYLE_NAMING_ON
|