Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
WOComponent |
|
| 3.3559322033898304;3.356 |
1 | /* |
|
2 | Wotonomy: OpenStep design patterns for pure Java applications. |
|
3 | Copyright (C) 2000 Blacksmith, Inc. |
|
4 | ||
5 | This library is free software; you can redistribute it and/or |
|
6 | modify it under the terms of the GNU Lesser General Public |
|
7 | License as published by the Free Software Foundation; either |
|
8 | version 2.1 of the License, or (at your option) any later version. |
|
9 | ||
10 | This library is distributed in the hope that it will be useful, |
|
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
13 | Lesser General Public License for more details. |
|
14 | ||
15 | You should have received a copy of the GNU Lesser General Public |
|
16 | License along with this library; if not, see http://www.gnu.org |
|
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; // don't persist |
|
68 | protected boolean cachingEnabled; |
|
69 | protected WOElement template; |
|
70 | protected WOComponent parent; |
|
71 | ||
72 | /** |
|
73 | * Default constructor. Deprecated in latest spec. |
|
74 | */ |
|
75 | 0 | public WOComponent () |
76 | 0 | { |
77 | 0 | parent = null; |
78 | 0 | cachingEnabled = true; |
79 | 0 | template = null; |
80 | 0 | } |
81 | ||
82 | /** |
|
83 | * Constructor specifying a context. |
|
84 | */ |
|
85 | public WOComponent( WOContext aContext ) |
|
86 | { |
|
87 | 0 | this(); |
88 | 0 | context = aContext; |
89 | 0 | } |
90 | ||
91 | /** |
|
92 | * Returns the name of the component, which is usually just the class name. |
|
93 | */ |
|
94 | public String name () |
|
95 | { |
|
96 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | cachingEnabled = enabled; |
137 | 0 | } |
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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | NSArray languages = null; |
175 | 0 | WOContext context = context(); |
176 | 0 | if ( context != null ) |
177 | { |
|
178 | 0 | languages = context.request().browserLanguages(); |
179 | } |
|
180 | 0 | WOElement result = templateWithHTMLString( |
181 | 0 | readTemplateResource( aComponentName, aFramework, TEMPLATE_SUFFIX, languages ), |
182 | 0 | readTemplateResource( aComponentName, aFramework, DECLARATION_SUFFIX, languages ), |
183 | 0 | languages ); |
184 | 0 | if ( result == null ) |
185 | { |
|
186 | 0 | System.out.println( "WOComponent.templateWithName: failed for " + aComponentName ); |
187 | } |
|
188 | 0 | 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 | 0 | if ( anHTMLString == null ) return null; |
200 | 0 | WOElement result = null; |
201 | try |
|
202 | { |
|
203 | 0 | NSDictionary bindings = processDeclaration( aDeclaration ); |
204 | 0 | List elements = new LinkedList(); |
205 | 0 | int index = processTemplate( elements, anHTMLString, 0, bindings, aLanguageList ); |
206 | 0 | if ( index == -1 ) |
207 | { |
|
208 | 0 | if ( elements.size() == 1 ) |
209 | { |
|
210 | 0 | result = (WOElement) elements.get(0); |
211 | 0 | } |
212 | else |
|
213 | { |
|
214 | 0 | result = new WOParentElement( elements ); |
215 | } |
|
216 | 0 | } |
217 | else // entire template did not process |
|
218 | { |
|
219 | 0 | throw new RuntimeException( "No closing tag: " + anHTMLString.substring( index ) ); |
220 | } |
|
221 | } |
|
222 | 0 | catch ( Exception exc ) |
223 | { |
|
224 | 0 | exc.printStackTrace(); |
225 | 0 | } |
226 | 0 | 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 | 0 | } |
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 | 0 | context = aContext; |
247 | 0 | parent = aContext.parent(); |
248 | 0 | if ( template == null ) |
249 | { |
|
250 | 0 | template = templateWithName( name(), frameworkName() ); |
251 | } |
|
252 | 0 | if ( template != null ) |
253 | { |
|
254 | 0 | template.ensureAwakeInContext( aContext ); |
255 | } |
|
256 | 0 | awake(); |
257 | 0 | } |
258 | ||
259 | public void takeValuesFromRequest (WORequest aRequest, WOContext aContext) |
|
260 | { |
|
261 | 0 | if ( synchronizesVariablesWithBindings() ) |
262 | { |
|
263 | 0 | pullValuesFromParent(); |
264 | 0 | if ( template != null ) |
265 | { |
|
266 | 0 | template.takeValuesFromRequest( aRequest, aContext ); |
267 | } |
|
268 | 0 | pushValuesToParent(); |
269 | 0 | } |
270 | else |
|
271 | 0 | if ( template != null ) |
272 | { |
|
273 | 0 | template.takeValuesFromRequest( aRequest, aContext ); |
274 | } |
|
275 | 0 | } |
276 | ||
277 | public WOActionResults invokeAction (WORequest aRequest, WOContext aContext) |
|
278 | { |
|
279 | 0 | WOActionResults result = null; |
280 | 0 | if ( synchronizesVariablesWithBindings() ) |
281 | { |
|
282 | 0 | pullValuesFromParent(); |
283 | 0 | if ( template != null ) |
284 | { |
|
285 | 0 | result = template.invokeAction( aRequest, aContext ); |
286 | } |
|
287 | 0 | pushValuesToParent(); |
288 | 0 | } |
289 | else |
|
290 | 0 | if ( template != null ) |
291 | { |
|
292 | 0 | result = template.invokeAction( aRequest, aContext ); |
293 | } |
|
294 | 0 | return result; |
295 | } |
|
296 | ||
297 | public void appendToResponse (WOResponse aResponse, WOContext aContext) |
|
298 | { |
|
299 | 0 | if ( synchronizesVariablesWithBindings() ) |
300 | { |
|
301 | 0 | pullValuesFromParent(); |
302 | 0 | if ( template != null ) |
303 | { |
|
304 | 0 | template.appendToResponse( aResponse, aContext ); |
305 | } |
|
306 | 0 | pushValuesToParent(); |
307 | 0 | } |
308 | else |
|
309 | 0 | if ( template != null ) |
310 | { |
|
311 | 0 | template.appendToResponse( aResponse, aContext ); |
312 | } |
|
313 | 0 | context = null; |
314 | 0 | } |
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 | 0 | } |
324 | ||
325 | /** |
|
326 | * Generates a WOResponse and calls appendToResponse() on it. |
|
327 | */ |
|
328 | public WOResponse generateResponse () |
|
329 | { |
|
330 | 0 | WOResponse response = new WOResponse(); |
331 | 0 | WOContext context = context(); |
332 | 0 | appendToResponse( response, context ); // nulls out context |
333 | 0 | context.session().savePage( this ); //?is this the right place for this? |
334 | 0 | return response; |
335 | } |
|
336 | ||
337 | /** |
|
338 | * Returns this component's parent component, or null if none. |
|
339 | */ |
|
340 | public WOComponent parent() |
|
341 | { |
|
342 | 0 | 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 | 0 | WOActionResults result = parent().performAction( anAction ); |
352 | 0 | if ( synchronizesVariablesWithBindings() ) |
353 | { |
|
354 | 0 | pullValuesFromParent(); |
355 | } |
|
356 | 0 | return result; |
357 | } |
|
358 | ||
359 | /** |
|
360 | * Invokes the specified action on this component. |
|
361 | */ |
|
362 | WOActionResults performAction( String anAction ) |
|
363 | { |
|
364 | try |
|
365 | { |
|
366 | 0 | return (WOActionResults) NSSelector.invoke( anAction, this ); |
367 | } |
|
368 | 0 | catch ( NoSuchMethodException exc ) |
369 | { |
|
370 | // returns below |
|
371 | } |
|
372 | 0 | catch ( InvocationTargetException exc ) |
373 | { |
|
374 | 0 | Throwable t = exc.getTargetException(); |
375 | 0 | exc.printStackTrace(); |
376 | 0 | throw new RuntimeException( t.toString() ); |
377 | } |
|
378 | 0 | catch ( Exception exc ) |
379 | { |
|
380 | 0 | exc.printStackTrace(); |
381 | 0 | throw new RuntimeException( exc.toString() ); |
382 | 0 | } |
383 | 0 | 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 | 0 | if ( associations == null ) return; |
394 | String key; |
|
395 | 0 | Enumeration e = associations.keyEnumerator(); |
396 | 0 | while ( e.hasMoreElements() ) |
397 | { |
|
398 | 0 | key = e.nextElement().toString(); |
399 | 0 | takeValueForKey( valueForBinding( key ), key ); |
400 | 0 | } |
401 | 0 | } |
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 | 0 | if ( associations == null ) return; |
411 | String key; |
|
412 | 0 | Enumeration e = associations.keyEnumerator(); |
413 | 0 | while ( e.hasMoreElements() ) |
414 | { |
|
415 | 0 | key = e.nextElement().toString(); |
416 | 0 | setValueForBinding( valueForKey( key ), key ); |
417 | 0 | } |
418 | 0 | } |
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 | 0 | 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 | // does nothing |
|
438 | 0 | } |
439 | ||
440 | /** |
|
441 | * Returns the application containing this instance of the class. |
|
442 | */ |
|
443 | public WOApplication application () |
|
444 | { |
|
445 | 0 | return context.application(); |
446 | } |
|
447 | ||
448 | /** |
|
449 | * Returns whether a session has been created for this user. |
|
450 | */ |
|
451 | public boolean hasSession () |
|
452 | { |
|
453 | 0 | 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 | 0 | return context.session(); |
462 | } |
|
463 | ||
464 | /** |
|
465 | * Returns the current context for this component. |
|
466 | */ |
|
467 | public WOContext context () |
|
468 | { |
|
469 | 0 | 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 | 0 | 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 | // does nothing |
|
491 | 0 | } |
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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | WOAssociation assoc = |
533 | 0 | (WOAssociation)associations.objectForKey(aBinding); |
534 | 0 | if (assoc != null) |
535 | { |
|
536 | 0 | if ( assoc.isValueSettable() ) return true; |
537 | } |
|
538 | 0 | return false; |
539 | } |
|
540 | ||
541 | /** |
|
542 | * Returns whether this component has the specified binding. |
|
543 | */ |
|
544 | public boolean hasBinding (String aBinding) |
|
545 | { |
|
546 | 0 | if ( associations == null ) return false; |
547 | 0 | 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 | 0 | WOComponent parent = parent(); |
560 | 0 | if ( associations != null ) |
561 | { |
|
562 | 0 | WOAssociation assoc = |
563 | 0 | (WOAssociation)associations.objectForKey(aBinding); |
564 | 0 | if (assoc != null && parent != null) |
565 | { |
|
566 | 0 | return assoc.valueInComponent( parent ); |
567 | } |
|
568 | } |
|
569 | 0 | if ( parent != null ) |
570 | { |
|
571 | 0 | return parent.valueForKey( aBinding ); |
572 | } |
|
573 | 0 | 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 | 0 | if ( associations == null ) return; |
586 | ||
587 | 0 | WOComponent parent = parent(); |
588 | ||
589 | 0 | if ( associations != null ) |
590 | { |
|
591 | 0 | WOAssociation assoc = |
592 | 0 | (WOAssociation)associations.objectForKey(aBinding); |
593 | 0 | if (assoc != null && parent != null) |
594 | { |
|
595 | 0 | if ( assoc.isValueSettable() ) |
596 | { |
|
597 | 0 | assoc.setValue( aValue, parent ); |
598 | 0 | return; |
599 | } |
|
600 | } |
|
601 | } |
|
602 | 0 | if ( parent != null ) |
603 | { |
|
604 | 0 | parent.takeValueForKey( aValue, aBinding ); |
605 | } |
|
606 | 0 | } |
607 | ||
608 | public static void logString (String aString) |
|
609 | { |
|
610 | 0 | System.out.println( aString ); |
611 | 0 | } |
612 | ||
613 | public static void debugString (String aString) |
|
614 | { |
|
615 | 0 | System.err.println( aString ); |
616 | 0 | } |
617 | ||
618 | public Object valueForKeyPath (String aPath) |
|
619 | { |
|
620 | // currently key value coding support also handles keypaths |
|
621 | 0 | return valueForKey( aPath ); |
622 | } |
|
623 | ||
624 | public void takeValueForKeyPath (Object aValue, String aPath) |
|
625 | { |
|
626 | // currently key value coding support also handles keypaths |
|
627 | 0 | takeValueForKey( aValue, aPath ); |
628 | 0 | } |
629 | ||
630 | public NSDictionary valuesForKeys (List aKeyList) |
|
631 | { |
|
632 | 0 | throw new RuntimeException( "Not implemented yet." ); |
633 | } |
|
634 | ||
635 | public void takeValuesFromDictionary (Map aValueMap) |
|
636 | { |
|
637 | 0 | throw new RuntimeException( "Not implemented yet." ); |
638 | } |
|
639 | ||
640 | public Object valueForKey (String aKey) |
|
641 | { // System.out.println( "valueForKey: " + aKey + "->" + this ); |
|
642 | // handle "^" property keys |
|
643 | 0 | if ( aKey.startsWith( "^" ) ) |
644 | { |
|
645 | 0 | return valueForBinding( aKey.substring(1) ); |
646 | } |
|
647 | 0 | return EOKeyValueCodingSupport.valueForKey( this, aKey ); |
648 | } |
|
649 | ||
650 | public void takeValueForKey (Object aValue, String aKey) |
|
651 | { // System.out.println( "takeValueForKey: " + aKey + " : " + aValue + "->" + this ); |
|
652 | // handle "^" property keys |
|
653 | 0 | if ( aKey.startsWith( "^" ) ) |
654 | { |
|
655 | 0 | setValueForBinding( aValue, aKey.substring(1) ); |
656 | 0 | return; |
657 | } |
|
658 | 0 | EOKeyValueCodingSupport.takeValueForKey( this, aValue, aKey ); |
659 | 0 | } |
660 | ||
661 | public Object storedValueForKey (String aKey) |
|
662 | { |
|
663 | 0 | return EOKeyValueCodingSupport.storedValueForKey( this, aKey ); |
664 | } |
|
665 | ||
666 | public void takeStoredValueForKey (Object aValue, String aKey) |
|
667 | { |
|
668 | 0 | EOKeyValueCodingSupport.takeStoredValueForKey( this, aValue, aKey ); |
669 | 0 | } |
670 | ||
671 | public Object handleQueryWithUnboundKey (String aKey) |
|
672 | { |
|
673 | 0 | return EOKeyValueCodingSupport.handleQueryWithUnboundKey( this, aKey ); |
674 | } |
|
675 | ||
676 | public void handleTakeValueForUnboundKey (Object aValue, String aKey) |
|
677 | { |
|
678 | 0 | EOKeyValueCodingSupport.handleTakeValueForUnboundKey( this, aValue, aKey ); |
679 | 0 | } |
680 | ||
681 | public void unableToSetNullForKey (String aKey) |
|
682 | { |
|
683 | 0 | EOKeyValueCodingSupport.unableToSetNullForKey( this, aKey ); |
684 | 0 | } |
685 | ||
686 | public Object validateTakeValueForKeyPath (Object aValue, String aKey) |
|
687 | { |
|
688 | 0 | throw new RuntimeException( "Not implemented yet." ); |
689 | } |
|
690 | ||
691 | ||
692 | // Template Processing |
|
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 | { //System.out.println( "processTemplate: " + index ); |
|
704 | 0 | if ( template == null ) return -1; |
705 | ||
706 | 0 | int start = index; |
707 | ||
708 | 0 | while ( true ) |
709 | { |
|
710 | // search for start of next tag |
|
711 | 0 | start = template.indexOf( '<', start ); |
712 | ||
713 | 0 | if ( start == -1 ) |
714 | { |
|
715 | // if no tags, send output and return |
|
716 | 0 | elements.add( new WOStaticElement( template.substring( index ) ) ); |
717 | 0 | return -1; |
718 | } |
|
719 | ||
720 | // search for end of opening tag |
|
721 | 0 | int end = template.indexOf( ">", start + 1 ); |
722 | 0 | if ( end == -1 ) |
723 | { |
|
724 | // if no end to tag |
|
725 | 0 | throw new RuntimeException( "No end to tag: " |
726 | 0 | + template.substring( start ) ); |
727 | } |
|
728 | ||
729 | 0 | boolean hasBody = true; |
730 | 0 | if ( template.charAt( end - 1 ) == '/' ) |
731 | { |
|
732 | // tag is standalone - no body |
|
733 | 0 | end = end - 1; |
734 | 0 | hasBody = false; |
735 | } |
|
736 | ||
737 | // search for name of tag |
|
738 | 0 | int endName = start + 1; |
739 | 0 | while ( endName < end ) |
740 | { |
|
741 | 0 | if ( Character.isWhitespace( |
742 | 0 | template.charAt(endName) ) ) break; |
743 | 0 | endName++; |
744 | 0 | } |
745 | ||
746 | 0 | String name = template.substring( start + 1, endName ); |
747 | ||
748 | 0 | if ( name.toLowerCase().startsWith( OPEN_TAG ) ) |
749 | { |
|
750 | // add the contents before the tag |
|
751 | //System.out.println( index + " : " + start + " : " + hasBody ); |
|
752 | 0 | elements.add( new WOStaticElement( template.substring( index, start ) ) ); |
753 | ||
754 | // interesting tag; parse parameters |
|
755 | 0 | Map params = new HashMap( 5 ); // arbitrary init length |
756 | 0 | if ( endName < end ) |
757 | { |
|
758 | // delimit by whitespace |
|
759 | 0 | StringTokenizer tokens = new StringTokenizer( |
760 | 0 | template.substring( endName+1, end ) ); |
761 | int equals; |
|
762 | String token; |
|
763 | String value; |
|
764 | 0 | while ( tokens.hasMoreTokens() ) |
765 | { |
|
766 | 0 | token = tokens.nextToken(); |
767 | 0 | equals = token.indexOf( '=' ); |
768 | 0 | if ( equals != -1 ) |
769 | { |
|
770 | 0 | value = token.substring( equals+1 ); |
771 | ||
772 | 0 | if ( value.startsWith( "\"" ) ) |
773 | { |
|
774 | // handle spaces within parameter names |
|
775 | 0 | while ( ! value.endsWith( "\"" ) ) |
776 | { |
|
777 | 0 | value = value + " " + tokens.nextToken(); |
778 | 0 | } |
779 | ||
780 | // strip quotation marks |
|
781 | 0 | if ( value.endsWith( "\"" ) ) |
782 | { |
|
783 | 0 | value = value.substring( 1, value.length()-1 ); |
784 | } |
|
785 | } |
|
786 | ||
787 | // register key with specified value |
|
788 | 0 | params.put( |
789 | 0 | token.substring( 0, equals ).toLowerCase(), value ); |
790 | ||
791 | 0 | } |
792 | else |
|
793 | { |
|
794 | // no value found, register the key name |
|
795 | 0 | params.put( token.toLowerCase(), "" ); |
796 | } |
|
797 | 0 | } |
798 | } |
|
799 | ||
800 | 0 | index = end + (hasBody?1:2); |
801 | ||
802 | 0 | WOElement body = null; |
803 | 0 | if ( hasBody ) |
804 | { |
|
805 | 0 | List childElements = new LinkedList(); |
806 | ||
807 | 0 | index = processTemplate( |
808 | 0 | childElements, template, index, |
809 | 0 | bindings, aLanguageList ); |
810 | 0 | start = index; |
811 | ||
812 | 0 | if ( index == -1 ) |
813 | { |
|
814 | 0 | throw new RuntimeException( |
815 | 0 | "No closing tag found: " + template.substring( end ) ); |
816 | } |
|
817 | ||
818 | 0 | if ( childElements.size() == 1 ) |
819 | { |
|
820 | 0 | body = (WOElement) childElements.get(0); |
821 | 0 | } |
822 | else |
|
823 | { |
|
824 | 0 | body = new WOParentElement( childElements ); |
825 | } |
|
826 | } |
|
827 | ||
828 | 0 | WOElement element = null; |
829 | 0 | String nameProperty = (String) params.get( NAME_KEY ); |
830 | 0 | NSDictionary original = (NSDictionary) bindings.get( nameProperty ); |
831 | //System.out.println( nameProperty + " : " + associations ); |
|
832 | 0 | if ( original == null ) |
833 | { |
|
834 | 0 | original = NSDictionary.EmptyDictionary; |
835 | 0 | System.err.println( "No associations for: " + nameProperty ); |
836 | 0 | System.err.println( bindings ); |
837 | } |
|
838 | ||
839 | 0 | NSDictionary associations = new NSMutableDictionary( original ); |
840 | 0 | String elementClass = (String) associations.remove( WOApplication.ELEMENT_CLASS ); |
841 | ||
842 | 0 | WOApplication application = WOApplication.application(); |
843 | 0 | element = application.dynamicElementWithName( |
844 | 0 | elementClass, associations, body, aLanguageList ); |
845 | 0 | if ( element == null ) |
846 | { |
|
847 | // unable to create element: show assocs in static element |
|
848 | 0 | element = new WOStaticElement( associations.toString() ); |
849 | } |
|
850 | ||
851 | //System.out.println( element ); |
|
852 | 0 | elements.add( element ); |
853 | ||
854 | 0 | if ( !hasBody ) |
855 | { |
|
856 | 0 | start = end + 2; |
857 | } |
|
858 | 0 | } |
859 | else |
|
860 | 0 | if ( name.toLowerCase().startsWith( CLOSE_TAG ) ) |
861 | { |
|
862 | // add any contents before the tag |
|
863 | 0 | elements.add( new WOStaticElement( template.substring( index, start ) ) ); |
864 | ||
865 | // return end + name.length() + 1; // "<" + ">" - 1 = 1 |
|
866 | 0 | return end + (hasBody?1:2); // "<" + ">" - 1 = 1 |
867 | } |
|
868 | else |
|
869 | { |
|
870 | // tag not interesting: continue |
|
871 | 0 | start = end + (hasBody?1:2); |
872 | } |
|
873 | 0 | } |
874 | } |
|
875 | ||
876 | ||
877 | // Utility Methods |
|
878 | ||
879 | static private void rewriteTag( String tagName, |
|
880 | Map properties, String body, StringBuffer context ) |
|
881 | throws java.io.IOException |
|
882 | { |
|
883 | 0 | context.append( "<"+tagName ); |
884 | 0 | Iterator it = properties.keySet().iterator(); |
885 | String key; |
|
886 | 0 | while ( it.hasNext() ) |
887 | { |
|
888 | 0 | key = (String) it.next(); |
889 | 0 | context.append( " " + key + "=\"" + properties.get( key ) + "\"" ); |
890 | 0 | } |
891 | ||
892 | 0 | if ( body == null ) |
893 | { |
|
894 | 0 | context.append( "/>" ); |
895 | 0 | return; |
896 | } |
|
897 | ||
898 | 0 | context.append( ">" + body + "</" + tagName + ">" ); |
899 | 0 | } |
900 | ||
901 | private String justTheClassName() |
|
902 | { |
|
903 | 0 | String className = getClass().getName(); |
904 | 0 | int index = className.lastIndexOf( "." ); |
905 | 0 | if ( index == -1 ) return className; |
906 | 0 | return className.substring( index+1 ); |
907 | } |
|
908 | ||
909 | private String justTheResourcePath() |
|
910 | { |
|
911 | 0 | int last = -1; |
912 | 0 | char[] src = getClass().getName().toCharArray(); |
913 | 0 | char[] dst = new char[ src.length ]; |
914 | 0 | for ( int i = 0; i < src.length; i++ ) |
915 | { |
|
916 | 0 | if ( src[i] == '.' ) |
917 | { |
|
918 | 0 | dst[i] = '/'; |
919 | 0 | last = i; |
920 | 0 | } |
921 | else |
|
922 | { |
|
923 | 0 | dst[i] = src[i]; |
924 | } |
|
925 | } |
|
926 | 0 | if ( last == -1 ) return null; |
927 | 0 | return new String( dst, 0, last ); |
928 | } |
|
929 | ||
930 | private String readTemplateResource( String name, String framework, String suffix, NSArray languages ) |
|
931 | { |
|
932 | 0 | if ( name == null ) return null; |
933 | 0 | name = name + DIRECTORY_SUFFIX + '/' + name + suffix; |
934 | 0 | InputStream is = null; |
935 | 0 | if ( isCachingEnabled() ) |
936 | { |
|
937 | 0 | byte[] data = WOApplication.application().resourceManager().bytesForResourceNamed( |
938 | 0 | name, framework, languages ); |
939 | 0 | if ( data != null ) |
940 | { |
|
941 | 0 | is = new ByteArrayInputStream( data ); |
942 | } |
|
943 | 0 | } |
944 | else |
|
945 | { |
|
946 | 0 | is = WOApplication.application().resourceManager().inputStreamForResourceNamed( |
947 | 0 | name, framework, languages ); |
948 | } |
|
949 | 0 | if ( is == null ) |
950 | { |
|
951 | 0 | System.err.println( "No resources found for: " + name ); |
952 | 0 | return null; |
953 | } |
|
954 | ||
955 | // try to autodetect encoding |
|
956 | 0 | String encoding = "ISO8859_1"; |
957 | try |
|
958 | { |
|
959 | 0 | byte[] header = new byte[4]; |
960 | 0 | is = new PushbackInputStream( is, 4 ); |
961 | 0 | is.read( header ); |
962 | 0 | if ( header[0] < 33 || header[0] > 126 ) |
963 | { |
|
964 | // if any funny characters, presume UTF-16 |
|
965 | 0 | encoding = "UTF-16"; |
966 | 0 | if (!( header[1] < 33 || header[1] > 126 )) |
967 | { |
|
968 | // if second character is valid, presume UTF-8 |
|
969 | 0 | encoding = "UTF-8"; |
970 | } |
|
971 | } |
|
972 | // check byte-order-mark |
|
973 | 0 | if (header[0] == 0xef && header[1] == 0xbb && header[2] == 0xbf) // utf-8 |
974 | { |
|
975 | 0 | encoding = "UTF-8"; |
976 | 0 | } |
977 | else |
|
978 | 0 | if (header[0] == 0xfe && header[1] == 0xff) // utf-16 |
979 | { |
|
980 | 0 | encoding = "UTF-16"; |
981 | 0 | } |
982 | else |
|
983 | 0 | if (header[0] == 0 && header[1] == 0 && header[2] == 0xfe && header[3] == 0xff) // ucs-4 |
984 | { |
|
985 | 0 | encoding = "UCS-4"; //?? |
986 | 0 | } |
987 | else |
|
988 | 0 | if (header[0] == 0xff && header[1] == 0xfe) // ucs-2le, ucs-4le, and |
989 | { |
|
990 | 0 | encoding = "UCS-16le"; //?? |
991 | } |
|
992 | // put back the header |
|
993 | 0 | ((PushbackInputStream)is).unread( header ); |
994 | } |
|
995 | 0 | catch ( Throwable t ) |
996 | { |
|
997 | 0 | t.printStackTrace(); |
998 | 0 | System.err.println( |
999 | 0 | "Error while autodetecting encoding: should never happen" ); |
1000 | 0 | } |
1001 | ||
1002 | try |
|
1003 | { |
|
1004 | String line; |
|
1005 | 0 | StringBuffer buf = new StringBuffer(); |
1006 | 0 | BufferedReader r = new BufferedReader( new InputStreamReader( is, encoding ) ); |
1007 | 0 | while ( ( line = r.readLine() ) != null ) |
1008 | { |
|
1009 | 0 | buf.append( line ); |
1010 | 0 | buf.append( '\n' ); |
1011 | 0 | } |
1012 | 0 | is.close(); // release the resource |
1013 | 0 | return buf.toString(); |
1014 | } |
|
1015 | 0 | catch ( IOException exc ) |
1016 | { |
|
1017 | 0 | System.err.println( "Error while reading: " + name ); |
1018 | 0 | exc.printStackTrace(); |
1019 | 0 | return null; |
1020 | } |
|
1021 | } |
|
1022 | ||
1023 | // Declaration Parsing |
|
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 | 0 | NSMutableDictionary result = new NSMutableDictionary(); |
1033 | ||
1034 | // strip out comments |
|
1035 | 0 | StringBuffer stripped = new StringBuffer(); |
1036 | try |
|
1037 | { |
|
1038 | 0 | LineNumberReader reader = |
1039 | 0 | new LineNumberReader( new StringReader( content ) ); |
1040 | String line; |
|
1041 | 0 | while ( ( line = reader.readLine() ) != null ) |
1042 | { |
|
1043 | 0 | index = line.indexOf("//"); |
1044 | 0 | while (index > -1) { |
1045 | //(chochos) This used to truncate lines with quoted URLs |
|
1046 | //in them. We have to check that the "//" is not inside quotes. |
|
1047 | 0 | boolean quoted = false; |
1048 | 0 | if (index > 0) { |
1049 | 0 | for (int _position = 0; _position < index; _position++) |
1050 | 0 | if (line.charAt(_position) == '"') |
1051 | 0 | quoted = !quoted; |
1052 | } |
|
1053 | 0 | if (!quoted) { |
1054 | 0 | line = line.substring( 0, index ); |
1055 | 0 | index = -1; |
1056 | 0 | } else { |
1057 | //if we didn't truncate the line it's because the // |
|
1058 | //were quoted. let's look for more, and check if they're not quoted... |
|
1059 | 0 | index = line.indexOf("\"", index); |
1060 | 0 | if (index > 0) { |
1061 | 0 | index = line.indexOf("//", index); |
1062 | } |
|
1063 | } |
|
1064 | 0 | } |
1065 | 0 | stripped.append( line ); |
1066 | 0 | } |
1067 | } |
|
1068 | 0 | catch ( IOException exc ) |
1069 | { |
|
1070 | 0 | throw new RuntimeException( |
1071 | 0 | "Error while stripping comments from declaration: " + stripped ); |
1072 | 0 | } |
1073 | 0 | while ( (index = stripped.toString().indexOf( "/*" )) != -1 ) |
1074 | { |
|
1075 | 0 | int j = stripped.toString().indexOf( "*/", index+1 ); |
1076 | 0 | if ( j == -1 ) break; |
1077 | 0 | stripped.delete( index, j+2 ); |
1078 | 0 | } |
1079 | ||
1080 | String token; |
|
1081 | 0 | StringTokenizer tokens = new StringTokenizer( stripped.toString(), "{}", true ); |
1082 | 0 | while ( tokens.hasMoreTokens() ) |
1083 | { |
|
1084 | 0 | token = tokens.nextToken(); |
1085 | ||
1086 | // next token is the name and class |
|
1087 | String name, cl; |
|
1088 | 0 | index = token.indexOf( ":" ); |
1089 | 0 | if ( index > -1 ) |
1090 | { |
|
1091 | 0 | name = token.substring( 0, index ).trim(); |
1092 | 0 | cl = token.substring( index+1 ).trim(); |
1093 | 0 | } |
1094 | else |
|
1095 | { |
|
1096 | 0 | System.err.println( "Could not parse declaration:" ); |
1097 | 0 | System.err.println( content ); |
1098 | 0 | throw new RuntimeException( |
1099 | 0 | "Could not parse declaration: " + token ); |
1100 | } |
|
1101 | ||
1102 | // next token is the declaration for the name and class |
|
1103 | 0 | if ( ! tokens.hasMoreTokens() ) |
1104 | { |
|
1105 | 0 | System.err.println( "Could not find associations for declaration:" ); |
1106 | 0 | System.err.println( content ); |
1107 | 0 | throw new RuntimeException( |
1108 | 0 | "Could not find associations for declaration: " + name ); |
1109 | } |
|
1110 | ||
1111 | 0 | token = tokens.nextToken(); |
1112 | 0 | if ( token.equals( "{" ) ) |
1113 | { |
|
1114 | 0 | if ( !tokens.hasMoreTokens() ) throw new RuntimeException( |
1115 | 0 | "Error parsing declaration: expected { but found: '" + token + "'" ); |
1116 | 0 | token = tokens.nextToken(); |
1117 | } |
|
1118 | ||
1119 | 0 | NSMutableDictionary associations = new NSMutableDictionary(); |
1120 | ||
1121 | 0 | if ( !token.equals( "}" ) ) |
1122 | { |
|
1123 | String line, key, value; |
|
1124 | 0 | StringTokenizer lines = |
1125 | 0 | new StringTokenizer( token, ";" ); |
1126 | 0 | while ( lines.hasMoreElements() ) |
1127 | { |
|
1128 | 0 | line = lines.nextToken(); |
1129 | 0 | index = line.indexOf( "=" ); |
1130 | 0 | if ( index > -1 ) |
1131 | { |
|
1132 | 0 | if ( line.length() == index+ 1 ) line += " "; |
1133 | 0 | key = line.substring( 0, index ).trim(); |
1134 | 0 | value = line.substring( index+1 ).trim(); |
1135 | 0 | } |
1136 | else |
|
1137 | { |
|
1138 | // not a valid key: skip |
|
1139 | 0 | key = null; |
1140 | 0 | value = null; |
1141 | } |
|
1142 | ||
1143 | 0 | if ( key != null ) |
1144 | { |
|
1145 | // if in quotation marks |
|
1146 | 0 | if ( ( value.startsWith( "\"" ) ) && ( value.endsWith( "\"" ) ) ) |
1147 | { |
|
1148 | // it's a constant value association |
|
1149 | 0 | value = value.substring( 1, value.length()-1 ); |
1150 | 0 | associations.put( key, |
1151 | 0 | WOAssociation.associationWithValue( value ) ); |
1152 | 0 | } |
1153 | else |
|
1154 | 0 | if ( value.equalsIgnoreCase( "true" ) || value.equalsIgnoreCase( "false" ) ) |
1155 | { |
|
1156 | //HACK: needed to be compatible with woextensions |
|
1157 | // apparently true and false are allowed without quotes |
|
1158 | 0 | associations.put( key, |
1159 | 0 | WOAssociation.associationWithValue( value ) ); |
1160 | 0 | } |
1161 | else |
|
1162 | { |
|
1163 | //HACK: needed to be compatible with woextensions: |
|
1164 | // apparently a standalone integer is allowed without quotes. |
|
1165 | try |
|
1166 | { |
|
1167 | 0 | Integer.parseInt( value ); // does it parse? |
1168 | 0 | associations.put( key, |
1169 | 0 | WOAssociation.associationWithValue( value ) ); |
1170 | } |
|
1171 | 0 | catch ( NumberFormatException nfe ) |
1172 | { |
|
1173 | // did not parse: |
|
1174 | // it's a key path association |
|
1175 | 0 | associations.put( key, |
1176 | 0 | WOAssociation.associationWithKeyPath( value ) ); |
1177 | 0 | } |
1178 | } |
|
1179 | 0 | } |
1180 | } |
|
1181 | 0 | if ( tokens.hasMoreTokens() ) |
1182 | { |
|
1183 | 0 | token = tokens.nextToken(); |
1184 | 0 | if ( !token.equals( "}" ) ) throw new RuntimeException( |
1185 | 0 | "Error parsing declaration: expected } but found: '" + token + "'" ); |
1186 | } |
|
1187 | } |
|
1188 | 0 | associations.put( WOApplication.ELEMENT_CLASS, cl ); // store classname |
1189 | 0 | result.put( name, associations ); |
1190 | ||
1191 | 0 | } |
1192 | //System.out.println( "processDeclaration: " + result ); |
|
1193 | 0 | return result; |
1194 | } |
|
1195 | ||
1196 | } |
|
1197 | ||
1198 | /* |
|
1199 | * $Log$ |
|
1200 | * Revision 1.2 2006/02/19 01:44:02 cgruber |
|
1201 | * Add xmlrpc files |
|
1202 | * Remove jclark and replace with dom4j and javax.xml.sax stuff |
|
1203 | * Re-work dependencies and imports so it all compiles. |
|
1204 | * |
|
1205 | * Revision 1.1 2006/02/16 13:22:22 cgruber |
|
1206 | * Check in all sources in eclipse-friendly maven-enabled packages. |
|
1207 | * |
|
1208 | * Revision 1.32 2003/08/07 00:15:14 chochos |
|
1209 | * general cleanup (mostly removing unused imports) |
|
1210 | * |
|
1211 | * Revision 1.31 2003/07/24 00:23:21 chochos |
|
1212 | * fixed problem with parsing wod files that have //-type comments. Quotes URL's would be truncated. |
|
1213 | * |
|
1214 | * Revision 1.30 2003/03/28 15:33:11 mpowers |
|
1215 | * Now using a PushBackInputStream for auto detection of content encoding. |
|
1216 | * No longer relying on markSupported() since jar input streams don't have it. |
|
1217 | * |
|
1218 | * Revision 1.29 2003/03/03 16:41:52 mpowers |
|
1219 | * Bad characters in cvs log. |
|
1220 | * |
|
1221 | * Revision 1.28 2003/03/03 16:37:35 mpowers |
|
1222 | * Better handling for string encodings. |
|
1223 | * Now trying to autodetect unicode-formatted templates and declarations. |
|
1224 | * Now handlings block-style comments in declarations. |
|
1225 | * |
|
1226 | * Revision 1.27 2003/01/28 19:33:51 mpowers |
|
1227 | * Implemented the rest of WOResourceManager. |
|
1228 | * Implemented support for java-style i18n. |
|
1229 | * Components now use the resource manager to load templates. |
|
1230 | * |
|
1231 | * Revision 1.26 2003/01/24 20:13:22 mpowers |
|
1232 | * Now accepting immutable NSDictionary in constructor, not Map. |
|
1233 | * |
|
1234 | * Revision 1.25 2003/01/21 17:53:45 mpowers |
|
1235 | * Now correctly reporting error for missing bindings. |
|
1236 | * |
|
1237 | * Revision 1.24 2003/01/20 17:50:11 mpowers |
|
1238 | * Caught a loop condition when same declaration was used twice. |
|
1239 | * |
|
1240 | * Revision 1.23 2003/01/19 22:33:25 mpowers |
|
1241 | * Fixed problems with classpath and dynamic class loading. |
|
1242 | * Dynamic elements now pass on ensureAwakeInContext. |
|
1243 | * Parser how handles <standalone/> tags. |
|
1244 | * |
|
1245 | * Revision 1.22 2003/01/17 22:55:09 mpowers |
|
1246 | * Straighted out the parent binding issue (I think). |
|
1247 | * Fixes for woextensions compatibility. |
|
1248 | * |
|
1249 | * Revision 1.21 2003/01/17 20:34:57 mpowers |
|
1250 | * Better handling for components and parents in the context's element stack. |
|
1251 | * |
|
1252 | * Revision 1.19 2003/01/17 15:32:22 mpowers |
|
1253 | * Changes to better support generic elements and containers. |
|
1254 | * Now preserving newlines in templates. |
|
1255 | * |
|
1256 | * Revision 1.17 2003/01/16 22:47:30 mpowers |
|
1257 | * Compatibility changes to support compiling woextensions source. |
|
1258 | * (34 out of 56 classes compile!) |
|
1259 | * |
|
1260 | * Revision 1.15 2003/01/16 15:50:43 mpowers |
|
1261 | * More robust declaration parsing. |
|
1262 | * Subcomponents are now supported. |
|
1263 | * dynamicElementWithName can now return subcomponents. |
|
1264 | * |
|
1265 | * Revision 1.14 2003/01/15 19:50:49 mpowers |
|
1266 | * Fixed issues with WOSession and Serializable. |
|
1267 | * Can now persist sessions between classloaders (hot swap of class impls). |
|
1268 | * |
|
1269 | * Revision 1.13 2003/01/15 14:33:48 mpowers |
|
1270 | * Refactoring: element id handling is now confined to WOParentElement. |
|
1271 | * Other elements/components should not have to do element id incrementing. |
|
1272 | * |
|
1273 | * Revision 1.12 2003/01/14 16:05:12 mpowers |
|
1274 | * Removed extraneous printlns. |
|
1275 | * |
|
1276 | * Revision 1.11 2003/01/13 22:24:25 mpowers |
|
1277 | * Request-response cycle is working with session and page persistence. |
|
1278 | * |
|
1279 | * Revision 1.10 2003/01/10 19:33:28 mpowers |
|
1280 | * Added contextID for the component url generation. |
|
1281 | * |
|
1282 | * Revision 1.9 2003/01/10 19:16:40 mpowers |
|
1283 | * Implemented support for page caching. |
|
1284 | * |
|
1285 | * Revision 1.8 2003/01/09 21:16:48 mpowers |
|
1286 | * Bringing request-response cycle more into conformance. |
|
1287 | * |
|
1288 | * Revision 1.7 2003/01/09 16:13:55 mpowers |
|
1289 | * Implemented WOComponentRequestHandler: |
|
1290 | * Bringing the request-response cycle more into conformance. |
|
1291 | * |
|
1292 | * Revision 1.6 2002/12/20 22:56:33 mpowers |
|
1293 | * Reimplemented the template parsing again. |
|
1294 | * Nested components are now correctly parsed. |
|
1295 | * ElementID numbering is now working. |
|
1296 | * |
|
1297 | * Revision 1.3 2002/12/18 14:12:38 mpowers |
|
1298 | * Support for differentiated request handlers. |
|
1299 | * Support url generation for WOContext and WORequest. |
|
1300 | * |
|
1301 | * Revision 1.2 2002/11/07 18:52:33 mpowers |
|
1302 | * New components courtesy of ezamudio@nasoft.com. Many thanks! |
|
1303 | * |
|
1304 | * Revision 1.1.1.1 2000/12/21 15:53:01 mpowers |
|
1305 | * Contributing wotonomy. |
|
1306 | * |
|
1307 | * Revision 1.2 2000/12/20 16:25:49 michael |
|
1308 | * Added log to all files. |
|
1309 | * |
|
1310 | * |
|
1311 | */ |
|
1312 |