1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.wotonomy.web;
20
21 import java.io.BufferedReader;
22 import java.io.ByteArrayInputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.InputStreamReader;
26 import java.io.LineNumberReader;
27 import java.io.PushbackInputStream;
28 import java.io.StringReader;
29 import java.lang.reflect.InvocationTargetException;
30 import java.util.Enumeration;
31 import java.util.HashMap;
32 import java.util.Iterator;
33 import java.util.LinkedList;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.StringTokenizer;
37
38 import net.wotonomy.control.EOKeyValueCodingSupport;
39 import net.wotonomy.foundation.NSArray;
40 import net.wotonomy.foundation.NSDictionary;
41 import net.wotonomy.foundation.NSMutableDictionary;
42 import net.wotonomy.foundation.NSSelector;
43
44 /***
45 * Pure java implementation of WOComponent.
46 *
47 * @author michael@mpowers.net
48 * @author $Author: cgruber $
49 * @version $Revision: 905 $
50 */
51 public class WOComponent
52 extends WOElement
53 implements WOActionResults,
54 net.wotonomy.control.EOKeyValueCodingAdditions,
55 net.wotonomy.control.EOKeyValueCoding
56 {
57 WOElement rootElement;
58
59 private static final String DIRECTORY_SUFFIX = ".wo";
60 private static final String TEMPLATE_SUFFIX = ".html";
61 private static final String DECLARATION_SUFFIX = ".wod";
62
63 private static final String OPEN_TAG = "webobject";
64 private static final String CLOSE_TAG = "/webobject";
65 private static final String NAME_KEY = "name";
66
67 protected transient WOContext context;
68 protected boolean cachingEnabled;
69 protected WOElement template;
70 protected WOComponent parent;
71
72 /***
73 * Default constructor. Deprecated in latest spec.
74 */
75 public WOComponent ()
76 {
77 parent = null;
78 cachingEnabled = true;
79 template = null;
80 }
81
82 /***
83 * Constructor specifying a context.
84 */
85 public WOComponent( WOContext aContext )
86 {
87 this();
88 context = aContext;
89 }
90
91 /***
92 * Returns the name of the component, which is usually just the class name.
93 */
94 public String name ()
95 {
96 return justTheClassName();
97 }
98
99 /***
100 * Returns the system-dependent file path to the current component
101 * directory, including the ".wo" extension.
102 */
103 public String path ()
104 {
105 throw new RuntimeException( "Not implemented yet." );
106 }
107
108 /***
109 * Returns the URL for this component, relative to the server's
110 * document root on the server's file system.
111 * This is not an http url.
112 */
113 public String baseURL ()
114 {
115 throw new RuntimeException( "Not implemented yet." );
116 }
117
118 /***
119 * Returns the name of the framework that contains this component,
120 * or null if the component does not belong to a framework.
121 * This currently returns the package path of the class, or
122 * null if it does not belong to a package.
123 */
124 public String frameworkName ()
125 {
126 return justTheResourcePath();
127 }
128
129 /***
130 * Sets whether templates are cached. If true, templates will
131 * only be read once per application lifetime. Otherwise, templates
132 * will be read each time this class is instantiated. Defaults to false.
133 */
134 public void setCachingEnabled (boolean enabled)
135 {
136 cachingEnabled = enabled;
137 }
138
139 /***
140 * Returns whether templates are cached. If true, templates are
141 * read once per application lifetime. Otherwise, templates are
142 * read each time this class is instantiated.
143 */
144 public boolean isCachingEnabled ()
145 {
146 return cachingEnabled && WOApplication.application().isCachingEnabled();
147 }
148
149 /***
150 * Returns the root of the tree of elements produced by parsing
151 * the templates in the component directory for this component.
152 */
153 public WOElement template()
154 {
155 return template;
156 }
157
158 /***
159 * Returns the root of the tree of elements produced by parsing
160 * the templates in the component directory for the named component.
161 * @deprecated Use template() instead.
162 */
163 public WOElement templateWithName(String aComponentName)
164 {
165 return templateWithName( aComponentName, null );
166 }
167
168 /***
169 * Returns the root of the tree of elements produced by parsing
170 * the templates in the component directory for the named component.
171 */
172 WOElement templateWithName(String aComponentName, String aFramework)
173 {
174 NSArray languages = null;
175 WOContext context = context();
176 if ( context != null )
177 {
178 languages = context.request().browserLanguages();
179 }
180 WOElement result = templateWithHTMLString(
181 readTemplateResource( aComponentName, aFramework, TEMPLATE_SUFFIX, languages ),
182 readTemplateResource( aComponentName, aFramework, DECLARATION_SUFFIX, languages ),
183 languages );
184 if ( result == null )
185 {
186 System.out.println( "WOComponent.templateWithName: failed for " + aComponentName );
187 }
188 return result;
189 }
190
191 /***
192 * Returns the root of the tree of elements produced by parsing
193 * the specfified HTML string and bindings declaration string.
194 * Note: language list is currently ignored.
195 */
196 public static WOElement templateWithHTMLString (
197 String anHTMLString, String aDeclaration, List aLanguageList)
198 {
199 if ( anHTMLString == null ) return null;
200 WOElement result = null;
201 try
202 {
203 NSDictionary bindings = processDeclaration( aDeclaration );
204 List elements = new LinkedList();
205 int index = processTemplate( elements, anHTMLString, 0, bindings, aLanguageList );
206 if ( index == -1 )
207 {
208 if ( elements.size() == 1 )
209 {
210 result = (WOElement) elements.get(0);
211 }
212 else
213 {
214 result = new WOParentElement( elements );
215 }
216 }
217 else
218 {
219 throw new RuntimeException( "No closing tag: " + anHTMLString.substring( index ) );
220 }
221 }
222 catch ( Exception exc )
223 {
224 exc.printStackTrace();
225 }
226 return result;
227 }
228
229 /***
230 * Called at the beginning of a request-response cycle.
231 * Override to perform any necessary initialization.
232 * This implementation does nothing.
233 */
234 public void awake ()
235 {
236 }
237
238 /***
239 * Package access only. Called to initialize the component with
240 * the proper context before the start of the request-response cycle.
241 * If the context has a current component, that component becomes
242 * this component's parent.
243 */
244 void ensureAwakeInContext (WOContext aContext)
245 {
246 context = aContext;
247 parent = aContext.parent();
248 if ( template == null )
249 {
250 template = templateWithName( name(), frameworkName() );
251 }
252 if ( template != null )
253 {
254 template.ensureAwakeInContext( aContext );
255 }
256 awake();
257 }
258
259 public void takeValuesFromRequest (WORequest aRequest, WOContext aContext)
260 {
261 if ( synchronizesVariablesWithBindings() )
262 {
263 pullValuesFromParent();
264 if ( template != null )
265 {
266 template.takeValuesFromRequest( aRequest, aContext );
267 }
268 pushValuesToParent();
269 }
270 else
271 if ( template != null )
272 {
273 template.takeValuesFromRequest( aRequest, aContext );
274 }
275 }
276
277 public WOActionResults invokeAction (WORequest aRequest, WOContext aContext)
278 {
279 WOActionResults result = null;
280 if ( synchronizesVariablesWithBindings() )
281 {
282 pullValuesFromParent();
283 if ( template != null )
284 {
285 result = template.invokeAction( aRequest, aContext );
286 }
287 pushValuesToParent();
288 }
289 else
290 if ( template != null )
291 {
292 result = template.invokeAction( aRequest, aContext );
293 }
294 return result;
295 }
296
297 public void appendToResponse (WOResponse aResponse, WOContext aContext)
298 {
299 if ( synchronizesVariablesWithBindings() )
300 {
301 pullValuesFromParent();
302 if ( template != null )
303 {
304 template.appendToResponse( aResponse, aContext );
305 }
306 pushValuesToParent();
307 }
308 else
309 if ( template != null )
310 {
311 template.appendToResponse( aResponse, aContext );
312 }
313 context = null;
314 }
315
316 /***
317 * Called at the end of a request-response cycle.
318 * Override to perform any necessary clean-up.
319 * This implementation does nothing.
320 */
321 public void sleep ()
322 {
323 }
324
325 /***
326 * Generates a WOResponse and calls appendToResponse() on it.
327 */
328 public WOResponse generateResponse ()
329 {
330 WOResponse response = new WOResponse();
331 WOContext context = context();
332 appendToResponse( response, context );
333 context.session().savePage( this );
334 return response;
335 }
336
337 /***
338 * Returns this component's parent component, or null if none.
339 */
340 public WOComponent parent()
341 {
342 return parent;
343 }
344
345 /***
346 * Invokes the specified action on this component's parent.
347 * Variables will be synchronized when this method returns.
348 */
349 public WOActionResults performParentAction(String anAction)
350 {
351 WOActionResults result = parent().performAction( anAction );
352 if ( synchronizesVariablesWithBindings() )
353 {
354 pullValuesFromParent();
355 }
356 return result;
357 }
358
359 /***
360 * Invokes the specified action on this component.
361 */
362 WOActionResults performAction( String anAction )
363 {
364 try
365 {
366 return (WOActionResults) NSSelector.invoke( anAction, this );
367 }
368 catch ( NoSuchMethodException exc )
369 {
370
371 }
372 catch ( InvocationTargetException exc )
373 {
374 Throwable t = exc.getTargetException();
375 exc.printStackTrace();
376 throw new RuntimeException( t.toString() );
377 }
378 catch ( Exception exc )
379 {
380 exc.printStackTrace();
381 throw new RuntimeException( exc.toString() );
382 }
383 return null;
384 }
385
386 /***
387 * Called before each phase of the request-response cycle,
388 * if synchronizesVariablesWithBindings is true and the
389 * component is not stateless.
390 */
391 public void pullValuesFromParent()
392 {
393 if ( associations == null ) return;
394 String key;
395 Enumeration e = associations.keyEnumerator();
396 while ( e.hasMoreElements() )
397 {
398 key = e.nextElement().toString();
399 takeValueForKey( valueForBinding( key ), key );
400 }
401 }
402
403 /***
404 * Called after each phase of the request-response cycle,
405 * if synchronizesVariablesWithBindings is true and the
406 * component is not stateless.
407 */
408 public void pushValuesToParent()
409 {
410 if ( associations == null ) return;
411 String key;
412 Enumeration e = associations.keyEnumerator();
413 while ( e.hasMoreElements() )
414 {
415 key = e.nextElement().toString();
416 setValueForBinding( valueForKey( key ), key );
417 }
418 }
419
420 /***
421 * Returns whether this component should be considered stateless.
422 * Stateless components are shared between sessions to conserve memory.
423 * This implementation returns false; override to return true.
424 */
425 public boolean isStateless()
426 {
427 return false;
428 }
429
430 /***
431 * Called only on stateless components to tell themselves to reset
432 * themselves for another invocation using a different context.
433 * This implementation does nothing.
434 */
435 public void reset()
436 {
437
438 }
439
440 /***
441 * Returns the application containing this instance of the class.
442 */
443 public WOApplication application ()
444 {
445 return context.application();
446 }
447
448 /***
449 * Returns whether a session has been created for this user.
450 */
451 public boolean hasSession ()
452 {
453 return context.hasSession();
454 }
455
456 /***
457 * Returns the current session object, creating it if it doesn't exist.
458 */
459 public WOSession session ()
460 {
461 return context.session();
462 }
463
464 /***
465 * Returns the current context for this component.
466 */
467 public WOContext context ()
468 {
469 return context;
470 }
471
472 /***
473 * Returns a new WOComponent with the specified name.
474 * If null, returns the component named "Main".
475 * If the named component doesn't exist, returns null.
476 */
477 public WOComponent pageWithName (String aName)
478 {
479 return application().pageWithName( aName, context() );
480 }
481
482 /***
483 * Called when exceptions are raised by assigning values
484 * to this object. This implementation does nothing, but
485 * subclasses may override to do something useful.
486 */
487 public void validationFailedWithException (
488 Throwable anException, Object aValue, String aPath)
489 {
490
491 }
492
493 /***
494 * Called on the component that represents the requested page.
495 * Override to return logging information specific to your
496 * component. This implementation returns the component's name.
497 */
498 public String descriptionForResponse (
499 WOResponse aResponse, WOContext aContext)
500 {
501 return name();
502 }
503
504 /***
505 * Returns true if this component should get and set values
506 * in its parent. This implementation returns true.
507 * Override to create a component that does not automatically
508 * synchronize bindings with its parent, useful if you wish
509 * to handle synchronization manually.
510 */
511 public boolean synchronizesVariablesWithBindings ()
512 {
513 return true;
514 }
515
516 /***
517 * Returns whether this component has a readable value that maps
518 * to the specified binding. This implementation calls
519 * hasBinding(aBinding).
520 */
521 public boolean canGetValueForBinding(String aBinding)
522 {
523 return hasBinding( aBinding );
524 }
525
526 /***
527 * Returns whether this component has a writable value that maps
528 * to the specified binding.
529 */
530 public boolean canSetValueForBinding(String aBinding)
531 {
532 WOAssociation assoc =
533 (WOAssociation)associations.objectForKey(aBinding);
534 if (assoc != null)
535 {
536 if ( assoc.isValueSettable() ) return true;
537 }
538 return false;
539 }
540
541 /***
542 * Returns whether this component has the specified binding.
543 */
544 public boolean hasBinding (String aBinding)
545 {
546 if ( associations == null ) return false;
547 return associations.containsKey( aBinding );
548 }
549
550 /***
551 * Returns the value for the specified binding for this component.
552 * The parent component is expected to have set the binding for
553 * this component. If no such binding exists, the binding is
554 * treated as a property is and obtained using valueForKey.
555 * If the property is not found, this method returns null.
556 */
557 public Object valueForBinding (String aBinding)
558 {
559 WOComponent parent = parent();
560 if ( associations != null )
561 {
562 WOAssociation assoc =
563 (WOAssociation)associations.objectForKey(aBinding);
564 if (assoc != null && parent != null)
565 {
566 return assoc.valueInComponent( parent );
567 }
568 }
569 if ( parent != null )
570 {
571 return parent.valueForKey( aBinding );
572 }
573 return null;
574 }
575
576 /***
577 * Sets the value for the specified binding for this component.
578 * The parent component is expected to have set the binding
579 * for this component. If no such binding exists, the binding
580 * is treated as a property and is set using takeValueForKey.
581 * If the property is not found, this method fails silently.
582 */
583 public void setValueForBinding (Object aValue, String aBinding)
584 {
585 if ( associations == null ) return;
586
587 WOComponent parent = parent();
588
589 if ( associations != null )
590 {
591 WOAssociation assoc =
592 (WOAssociation)associations.objectForKey(aBinding);
593 if (assoc != null && parent != null)
594 {
595 if ( assoc.isValueSettable() )
596 {
597 assoc.setValue( aValue, parent );
598 return;
599 }
600 }
601 }
602 if ( parent != null )
603 {
604 parent.takeValueForKey( aValue, aBinding );
605 }
606 }
607
608 public static void logString (String aString)
609 {
610 System.out.println( aString );
611 }
612
613 public static void debugString (String aString)
614 {
615 System.err.println( aString );
616 }
617
618 public Object valueForKeyPath (String aPath)
619 {
620
621 return valueForKey( aPath );
622 }
623
624 public void takeValueForKeyPath (Object aValue, String aPath)
625 {
626
627 takeValueForKey( aValue, aPath );
628 }
629
630 public NSDictionary valuesForKeys (List aKeyList)
631 {
632 throw new RuntimeException( "Not implemented yet." );
633 }
634
635 public void takeValuesFromDictionary (Map aValueMap)
636 {
637 throw new RuntimeException( "Not implemented yet." );
638 }
639
640 public Object valueForKey (String aKey)
641 {
642
643 if ( aKey.startsWith( "^" ) )
644 {
645 return valueForBinding( aKey.substring(1) );
646 }
647 return EOKeyValueCodingSupport.valueForKey( this, aKey );
648 }
649
650 public void takeValueForKey (Object aValue, String aKey)
651 {
652
653 if ( aKey.startsWith( "^" ) )
654 {
655 setValueForBinding( aValue, aKey.substring(1) );
656 return;
657 }
658 EOKeyValueCodingSupport.takeValueForKey( this, aValue, aKey );
659 }
660
661 public Object storedValueForKey (String aKey)
662 {
663 return EOKeyValueCodingSupport.storedValueForKey( this, aKey );
664 }
665
666 public void takeStoredValueForKey (Object aValue, String aKey)
667 {
668 EOKeyValueCodingSupport.takeStoredValueForKey( this, aValue, aKey );
669 }
670
671 public Object handleQueryWithUnboundKey (String aKey)
672 {
673 return EOKeyValueCodingSupport.handleQueryWithUnboundKey( this, aKey );
674 }
675
676 public void handleTakeValueForUnboundKey (Object aValue, String aKey)
677 {
678 EOKeyValueCodingSupport.handleTakeValueForUnboundKey( this, aValue, aKey );
679 }
680
681 public void unableToSetNullForKey (String aKey)
682 {
683 EOKeyValueCodingSupport.unableToSetNullForKey( this, aKey );
684 }
685
686 public Object validateTakeValueForKeyPath (Object aValue, String aKey)
687 {
688 throw new RuntimeException( "Not implemented yet." );
689 }
690
691
692
693
694 /***
695 * Takes a template string and a location to begin parsing,
696 * looking only for interesting tags, and calling itself recursively
697 * as necessary. Returns the index to resume parsing, or -1 if done.
698 */
699 static private int processTemplate(
700 List elements, String template, int index,
701 Map bindings, List aLanguageList )
702 throws java.io.IOException
703 {
704 if ( template == null ) return -1;
705
706 int start = index;
707
708 while ( true )
709 {
710
711 start = template.indexOf( '<', start );
712
713 if ( start == -1 )
714 {
715
716 elements.add( new WOStaticElement( template.substring( index ) ) );
717 return -1;
718 }
719
720
721 int end = template.indexOf( ">", start + 1 );
722 if ( end == -1 )
723 {
724
725 throw new RuntimeException( "No end to tag: "
726 + template.substring( start ) );
727 }
728
729 boolean hasBody = true;
730 if ( template.charAt( end - 1 ) == '/' )
731 {
732
733 end = end - 1;
734 hasBody = false;
735 }
736
737
738 int endName = start + 1;
739 while ( endName < end )
740 {
741 if ( Character.isWhitespace(
742 template.charAt(endName) ) ) break;
743 endName++;
744 }
745
746 String name = template.substring( start + 1, endName );
747
748 if ( name.toLowerCase().startsWith( OPEN_TAG ) )
749 {
750
751
752 elements.add( new WOStaticElement( template.substring( index, start ) ) );
753
754
755 Map params = new HashMap( 5 );
756 if ( endName < end )
757 {
758
759 StringTokenizer tokens = new StringTokenizer(
760 template.substring( endName+1, end ) );
761 int equals;
762 String token;
763 String value;
764 while ( tokens.hasMoreTokens() )
765 {
766 token = tokens.nextToken();
767 equals = token.indexOf( '=' );
768 if ( equals != -1 )
769 {
770 value = token.substring( equals+1 );
771
772 if ( value.startsWith( "\"" ) )
773 {
774
775 while ( ! value.endsWith( "\"" ) )
776 {
777 value = value + " " + tokens.nextToken();
778 }
779
780
781 if ( value.endsWith( "\"" ) )
782 {
783 value = value.substring( 1, value.length()-1 );
784 }
785 }
786
787
788 params.put(
789 token.substring( 0, equals ).toLowerCase(), value );
790
791 }
792 else
793 {
794
795 params.put( token.toLowerCase(), "" );
796 }
797 }
798 }
799
800 index = end + (hasBody?1:2);
801
802 WOElement body = null;
803 if ( hasBody )
804 {
805 List childElements = new LinkedList();
806
807 index = processTemplate(
808 childElements, template, index,
809 bindings, aLanguageList );
810 start = index;
811
812 if ( index == -1 )
813 {
814 throw new RuntimeException(
815 "No closing tag found: " + template.substring( end ) );
816 }
817
818 if ( childElements.size() == 1 )
819 {
820 body = (WOElement) childElements.get(0);
821 }
822 else
823 {
824 body = new WOParentElement( childElements );
825 }
826 }
827
828 WOElement element = null;
829 String nameProperty = (String) params.get( NAME_KEY );
830 NSDictionary original = (NSDictionary) bindings.get( nameProperty );
831
832 if ( original == null )
833 {
834 original = NSDictionary.EmptyDictionary;
835 System.err.println( "No associations for: " + nameProperty );
836 System.err.println( bindings );
837 }
838
839 NSDictionary associations = new NSMutableDictionary( original );
840 String elementClass = (String) associations.remove( WOApplication.ELEMENT_CLASS );
841
842 WOApplication application = WOApplication.application();
843 element = application.dynamicElementWithName(
844 elementClass, associations, body, aLanguageList );
845 if ( element == null )
846 {
847
848 element = new WOStaticElement( associations.toString() );
849 }
850
851
852 elements.add( element );
853
854 if ( !hasBody )
855 {
856 start = end + 2;
857 }
858 }
859 else
860 if ( name.toLowerCase().startsWith( CLOSE_TAG ) )
861 {
862
863 elements.add( new WOStaticElement( template.substring( index, start ) ) );
864
865
866 return end + (hasBody?1:2);
867 }
868 else
869 {
870
871 start = end + (hasBody?1:2);
872 }
873 }
874 }
875
876
877
878
879 static private void rewriteTag( String tagName,
880 Map properties, String body, StringBuffer context )
881 throws java.io.IOException
882 {
883 context.append( "<"+tagName );
884 Iterator it = properties.keySet().iterator();
885 String key;
886 while ( it.hasNext() )
887 {
888 key = (String) it.next();
889 context.append( " " + key + "=\"" + properties.get( key ) + "\"" );
890 }
891
892 if ( body == null )
893 {
894 context.append( "/>" );
895 return;
896 }
897
898 context.append( ">" + body + "</" + tagName + ">" );
899 }
900
901 private String justTheClassName()
902 {
903 String className = getClass().getName();
904 int index = className.lastIndexOf( "." );
905 if ( index == -1 ) return className;
906 return className.substring( index+1 );
907 }
908
909 private String justTheResourcePath()
910 {
911 int last = -1;
912 char[] src = getClass().getName().toCharArray();
913 char[] dst = new char[ src.length ];
914 for ( int i = 0; i < src.length; i++ )
915 {
916 if ( src[i] == '.' )
917 {
918 dst[i] = '/';
919 last = i;
920 }
921 else
922 {
923 dst[i] = src[i];
924 }
925 }
926 if ( last == -1 ) return null;
927 return new String( dst, 0, last );
928 }
929
930 private String readTemplateResource( String name, String framework, String suffix, NSArray languages )
931 {
932 if ( name == null ) return null;
933 name = name + DIRECTORY_SUFFIX + '/' + name + suffix;
934 InputStream is = null;
935 if ( isCachingEnabled() )
936 {
937 byte[] data = WOApplication.application().resourceManager().bytesForResourceNamed(
938 name, framework, languages );
939 if ( data != null )
940 {
941 is = new ByteArrayInputStream( data );
942 }
943 }
944 else
945 {
946 is = WOApplication.application().resourceManager().inputStreamForResourceNamed(
947 name, framework, languages );
948 }
949 if ( is == null )
950 {
951 System.err.println( "No resources found for: " + name );
952 return null;
953 }
954
955
956 String encoding = "ISO8859_1";
957 try
958 {
959 byte[] header = new byte[4];
960 is = new PushbackInputStream( is, 4 );
961 is.read( header );
962 if ( header[0] < 33 || header[0] > 126 )
963 {
964
965 encoding = "UTF-16";
966 if (!( header[1] < 33 || header[1] > 126 ))
967 {
968
969 encoding = "UTF-8";
970 }
971 }
972
973 if (header[0] == 0xef && header[1] == 0xbb && header[2] == 0xbf)
974 {
975 encoding = "UTF-8";
976 }
977 else
978 if (header[0] == 0xfe && header[1] == 0xff)
979 {
980 encoding = "UTF-16";
981 }
982 else
983 if (header[0] == 0 && header[1] == 0 && header[2] == 0xfe && header[3] == 0xff)
984 {
985 encoding = "UCS-4";
986 }
987 else
988 if (header[0] == 0xff && header[1] == 0xfe)
989 {
990 encoding = "UCS-16le";
991 }
992
993 ((PushbackInputStream)is).unread( header );
994 }
995 catch ( Throwable t )
996 {
997 t.printStackTrace();
998 System.err.println(
999 "Error while autodetecting encoding: should never happen" );
1000 }
1001
1002 try
1003 {
1004 String line;
1005 StringBuffer buf = new StringBuffer();
1006 BufferedReader r = new BufferedReader( new InputStreamReader( is, encoding ) );
1007 while ( ( line = r.readLine() ) != null )
1008 {
1009 buf.append( line );
1010 buf.append( '\n' );
1011 }
1012 is.close();
1013 return buf.toString();
1014 }
1015 catch ( IOException exc )
1016 {
1017 System.err.println( "Error while reading: " + name );
1018 exc.printStackTrace();
1019 return null;
1020 }
1021 }
1022
1023
1024
1025 /***
1026 * Parses the declarations in the specified content and returns a map of element names
1027 * to maps of attribute names to WOAssociations.
1028 */
1029 private static NSDictionary processDeclaration( String content )
1030 {
1031 int index;
1032 NSMutableDictionary result = new NSMutableDictionary();
1033
1034
1035 StringBuffer stripped = new StringBuffer();
1036 try
1037 {
1038 LineNumberReader reader =
1039 new LineNumberReader( new StringReader( content ) );
1040 String line;
1041 while ( ( line = reader.readLine() ) != null )
1042 {
1043 index = line.indexOf("//");
1044 while (index > -1) {
1045
1046
1047 boolean quoted = false;
1048 if (index > 0) {
1049 for (int _position = 0; _position < index; _position++)
1050 if (line.charAt(_position) == '"')
1051 quoted = !quoted;
1052 }
1053 if (!quoted) {
1054 line = line.substring( 0, index );
1055 index = -1;
1056 } else {
1057
1058
1059 index = line.indexOf("\"", index);
1060 if (index > 0) {
1061 index = line.indexOf("//", index);
1062 }
1063 }
1064 }
1065 stripped.append( line );
1066 }
1067 }
1068 catch ( IOException exc )
1069 {
1070 throw new RuntimeException(
1071 "Error while stripping comments from declaration: " + stripped );
1072 }
1073 while ( (index = stripped.toString().indexOf( "/*" )) != -1 )
1074 {
1075 int j = stripped.toString().indexOf( "*/", index+1 );
1076 if ( j == -1 ) break;
1077 stripped.delete( index, j+2 );
1078 }
1079
1080 String token;
1081 StringTokenizer tokens = new StringTokenizer( stripped.toString(), "{}", true );
1082 while ( tokens.hasMoreTokens() )
1083 {
1084 token = tokens.nextToken();
1085
1086
1087 String name, cl;
1088 index = token.indexOf( ":" );
1089 if ( index > -1 )
1090 {
1091 name = token.substring( 0, index ).trim();
1092 cl = token.substring( index+1 ).trim();
1093 }
1094 else
1095 {
1096 System.err.println( "Could not parse declaration:" );
1097 System.err.println( content );
1098 throw new RuntimeException(
1099 "Could not parse declaration: " + token );
1100 }
1101
1102
1103 if ( ! tokens.hasMoreTokens() )
1104 {
1105 System.err.println( "Could not find associations for declaration:" );
1106 System.err.println( content );
1107 throw new RuntimeException(
1108 "Could not find associations for declaration: " + name );
1109 }
1110
1111 token = tokens.nextToken();
1112 if ( token.equals( "{" ) )
1113 {
1114 if ( !tokens.hasMoreTokens() ) throw new RuntimeException(
1115 "Error parsing declaration: expected { but found: '" + token + "'" );
1116 token = tokens.nextToken();
1117 }
1118
1119 NSMutableDictionary associations = new NSMutableDictionary();
1120
1121 if ( !token.equals( "}" ) )
1122 {
1123 String line, key, value;
1124 StringTokenizer lines =
1125 new StringTokenizer( token, ";" );
1126 while ( lines.hasMoreElements() )
1127 {
1128 line = lines.nextToken();
1129 index = line.indexOf( "=" );
1130 if ( index > -1 )
1131 {
1132 if ( line.length() == index+ 1 ) line += " ";
1133 key = line.substring( 0, index ).trim();
1134 value = line.substring( index+1 ).trim();
1135 }
1136 else
1137 {
1138
1139 key = null;
1140 value = null;
1141 }
1142
1143 if ( key != null )
1144 {
1145
1146 if ( ( value.startsWith( "\"" ) ) && ( value.endsWith( "\"" ) ) )
1147 {
1148
1149 value = value.substring( 1, value.length()-1 );
1150 associations.put( key,
1151 WOAssociation.associationWithValue( value ) );
1152 }
1153 else
1154 if ( value.equalsIgnoreCase( "true" ) || value.equalsIgnoreCase( "false" ) )
1155 {
1156
1157
1158 associations.put( key,
1159 WOAssociation.associationWithValue( value ) );
1160 }
1161 else
1162 {
1163
1164
1165 try
1166 {
1167 Integer.parseInt( value );
1168 associations.put( key,
1169 WOAssociation.associationWithValue( value ) );
1170 }
1171 catch ( NumberFormatException nfe )
1172 {
1173
1174
1175 associations.put( key,
1176 WOAssociation.associationWithKeyPath( value ) );
1177 }
1178 }
1179 }
1180 }
1181 if ( tokens.hasMoreTokens() )
1182 {
1183 token = tokens.nextToken();
1184 if ( !token.equals( "}" ) ) throw new RuntimeException(
1185 "Error parsing declaration: expected } but found: '" + token + "'" );
1186 }
1187 }
1188 associations.put( WOApplication.ELEMENT_CLASS, cl );
1189 result.put( name, associations );
1190
1191 }
1192
1193 return result;
1194 }
1195
1196 }
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312