001: package org.osbl.client.wings.form;
002:
003: import java.awt.event.*;
004: import java.util.*;
005:
006: import javax.swing.*;
007:
008: import org.conform.*;
009: import org.conform.format.Format;
010: import org.conform.format.NoFormat;
011: import org.conform.wings.devel.MetaViewer;
012: import org.conform.wings.*;
013: import org.osbl.client.action.ObjectActionEvent;
014: import org.wings.*;
015:
016: import org.osbl.client.action.ObjectAction;
017: import org.osbl.client.wings.form.layouter.*;
018: import org.osbl.client.wings.*;
019: import org.osbl.client.wings.shell.*;
020: import org.osbl.client.ClientServiceProvider;
021: import org.osbl.plugin.*;
022:
023: /**
024: * An abstract implementation of generic ObjectForm functionality.
025: * It assumes, that the UI is built from one or more BeanMetas following a descriptive layout specification.
026: * Databinding is accomplished using BeanDatas.
027: */
028: public abstract class GenericObjectForm implements ObjectForm,
029: ObjectContextAware, VisibilityListener {
030: private static final NoFormat NO_FORMAT = new NoFormat();
031:
032: protected DelegateEnvironment environment = new DelegateEnvironment();
033:
034: protected int viewMode = EDIT;
035: protected boolean documentLook;
036: protected Object object;
037: protected SortedMap<String, BeanMeta> metas = new TreeMap<String, BeanMeta>(
038: PATH_COMPARATOR);
039: protected SortedMap<String, BeanData> datas = new TreeMap<String, BeanData>(
040: PATH_COMPARATOR);
041: protected SComponent component;
042:
043: private Map<ObjectAction, DelegateObjectAction> actionDelegates = new HashMap<ObjectAction, DelegateObjectAction>();
044: private ValidationStatus validationStatus = new ValidationStatus(
045: environment);
046: protected ChangeStatus changeStatus = new ChangeStatus(environment);
047: private Map<Integer, Modifier> variations = new HashMap<Integer, Modifier>();
048: private ComponentProvider componentProvider;
049:
050: private Map<SComponent, BeanData> dataByComponent = new HashMap<SComponent, BeanData>();
051: private Set<BeanData> initializedDatas = new HashSet<BeanData>();
052: protected Layouter layouter;
053: protected ObjectContext context;
054:
055: protected GenericObjectForm() {
056: addViewModeVariation(VIEW, READONLY_VARIATION);
057: }
058:
059: public Environment getEnvironment() {
060: getComponent();
061: return environment;
062: }
063:
064: public void setViewMode(int viewMode) {
065: if (component != null)
066: throw new IllegalStateException(
067: "Must be called before the conform's component has been built!");
068:
069: this .viewMode = viewMode;
070: }
071:
072: public void addViewModeVariation(int viewMode, Modifier modifier) {
073: variations.put(viewMode, modifier);
074: }
075:
076: public void setDocumentLook(boolean documentLook) {
077: this .documentLook = documentLook;
078: }
079:
080: /**
081: * Lazily construct the details component according to the provided Layouter and BeanMeta.
082: * @return the component showing the object
083: */
084: public SComponent getComponent() {
085: if (component == null) {
086: layouter = initLayout(createLayoutInstruction());
087: layouter.addVisibilityListener(this );
088: component = layouter.build(getComponentProvider());
089: environment.setContentPane(component);
090: installTools();
091: initializeForm();
092:
093: if (object != null)
094: visibilityChanged(layouter.getVisibles());
095: }
096: return component;
097: }
098:
099: protected void installTools() {
100: PluginManager pluginManager = (PluginManager) ClientServiceProvider
101: .getInstance().getService("ClientPluginManager");
102: final ExtensionPoint extensionPoint = new ExtensionPoint(
103: "org.osbl.client.wings.form.formTools", FormTool.class);
104: final List<Extension> list = pluginManager
105: .getExtensions(extensionPoint);
106:
107: for (Extension extension : list) {
108: final Class<FormTool> toolClass = (Class<FormTool>) extension
109: .getImplementation();
110: try {
111: FormTool tool = toolClass.newInstance();
112: tool.setForm(this );
113: environment.addTool(tool);
114: } catch (Exception e) {
115: throw new RuntimeException(e);
116: }
117: }
118: }
119:
120: /**
121: * Perform initialization tasks.
122: * Override this method, if you want to perform initialization tasks on some UI elements after construction. This
123: * is the place, where PropertyChangeListeners can be registered on BeanDatas.
124: */
125: protected void initializeForm() {
126: }
127:
128: protected ComponentProvider getComponentProvider() {
129: if (componentProvider == null)
130: componentProvider = new AbstractComponentProvider() {
131: protected BeanMeta getBeanMeta(String path) {
132: return GenericObjectForm.this .getBeanMeta(path);
133: }
134:
135: protected BeanData getBeanData(String path) {
136: return GenericObjectForm.this .getBeanData(path);
137: }
138:
139: protected SComponent createCustomComponent(String key) {
140: return GenericObjectForm.this
141: .createCustomComponent(key);
142: }
143:
144: protected SLabel createCustomLabel(String key) {
145: return GenericObjectForm.this
146: .createCustomLabel(key);
147: }
148:
149: protected SComponent createComponent(String bean,
150: String property) {
151: SComponent component = super .createComponent(bean,
152: property);
153: dataByComponent.put(component, getBeanData(bean));
154: return component;
155: }
156:
157: protected SComponent createReadOnlyComponent(
158: String bean, String property) {
159: SComponent component = super
160: .createReadOnlyComponent(bean, property);
161: dataByComponent.put(component, getBeanData(bean));
162: return component;
163: }
164: };
165: return componentProvider;
166: }
167:
168: public void visibilityChanged(VisibilityEvent event) {
169: List<SComponent> visibleComponents = event.getVisibles();
170: visibilityChanged(visibleComponents);
171: focus();
172: }
173:
174: protected void visibilityChanged(List<SComponent> visibleComponents) {
175: Map<SComponent, BeanData> visibleDataByComponent = new HashMap<SComponent, BeanData>(
176: dataByComponent);
177: visibleDataByComponent.keySet().retainAll(visibleComponents);
178: Collection<BeanData> visibleDatas = new HashSet<BeanData>(
179: visibleDataByComponent.values());
180:
181: for (BeanData data : visibleDatas)
182: initData(data);
183: }
184:
185: public void setApplicability(Instruction instruction,
186: boolean applicable) {
187: layouter.setApplicability(instruction, applicable);
188: }
189:
190: /**
191: * Retrieve or lazily create a BeanMeta for the respective object.
192: * A details implementation might display complex object graphs. Then the path for the root of the object graph is
193: * named ROOT and child objects are addressed using dot notation.
194: * @param path addresses an object in the object graph.
195: * @return a BeanMeta for the respective object / subobject
196: */
197: public final BeanMeta getBeanMeta(String path) {
198: if (path == null)
199: throw new IllegalArgumentException(
200: "Use ComponentProvider.ROOT instead of null!");
201:
202: BeanMeta meta = metas.get(path);
203: if (meta == null) {
204: meta = createBeanMeta(path);
205: Modifier modifier = variations.get(viewMode);
206: if (modifier != null)
207: modifier.modify(meta);
208: metas.put(path, meta);
209: environment.addDevelopmentAction(new ViewerAction(meta));
210: }
211: return meta;
212: }
213:
214: /**
215: * Retrieve or lazily create a BeanData for the respective object.
216: * A details implementation might display complex object graphs. Then the path for the root of the object graph is
217: * named ROOT and child objects are addressed using dot notation.
218: * @param path addresses an object in the object graph.
219: * @return a BeanMeta for the respective object / subobject
220: */
221: public final BeanData getBeanData(String path) {
222: if (path == null)
223: throw new IllegalArgumentException(
224: "Use ROOT instead of null!");
225:
226: if (!ComponentProvider.ROOT.equals(path)) {
227: int pos = path.lastIndexOf('.');
228: getBeanData(pos > -1 ? path.substring(0, pos)
229: : ComponentProvider.ROOT);
230: }
231:
232: BeanData data = datas.get(path);
233: if (data == null) {
234: data = createBeanData(path);
235: datas.put(path, data);
236: addForValidation(data);
237: addForChanges(data);
238: }
239:
240: return data;
241: }
242:
243: private void addForChanges(BeanData data) {
244: data.addPropertyChangeListener(changeStatus);
245: }
246:
247: private void addForValidation(Data data) {
248: data.addValidationListener(validationStatus);
249:
250: if (data instanceof BeanData) {
251: BeanData beanData = (BeanData) data;
252: DeferredValidationListener deferredValidationListener = new DeferredValidationListener(
253: beanData);
254: beanData
255: .addPropertyChangeListener(deferredValidationListener);
256: }
257: }
258:
259: /**
260: * Are there any unresolved validations issues pending?
261: * @return true, if there are pending issues, false otherwise
262: */
263: public boolean hasValidationIssues() {
264: activateValidation();
265: return validationStatus.getIssues().size() != 0;
266: }
267:
268: /**
269: * Activate validation.
270: * Validation issues will be published.
271: */
272: protected void activateValidation() {
273: for (Data data : datas.values()) {
274: if (data instanceof BeanData
275: && ((BeanData) data).getBeanMeta().isApplicable())
276: data.setIssuePublishingActive(true);
277: }
278: }
279:
280: /**
281: * Activate validation.
282: * Validation issues will not be published.
283: */
284: protected void deactivateValidation() {
285: for (Data data : datas.values()) {
286: data.setIssuePublishingActive(false);
287: }
288: }
289:
290: public ValidationStatus getValidationStatus() {
291: return validationStatus;
292: }
293:
294: public ChangeStatus getChangeStatus() {
295: return changeStatus;
296: }
297:
298: public boolean hasChanges() {
299: return changeStatus.hasChanges();
300: }
301:
302: public void clearChanges() {
303: changeStatus.clearChanges();
304: }
305:
306: /**
307: * Retrieve the subobject, addressed by the specified path.
308: * A details implementation might display complex object graphs. Then the path for the root of the object graph is
309: * named ROOT and child objects are addressed using dot notation.
310: * @param path addresses an object in the object graph.
311: * @return the object / subobject of the object graph
312: */
313: protected abstract Object getObject(String path);
314:
315: /**
316: * Set the subobject, addressed by the specified path.
317: * A details implementation might display complex object graphs. Then the path for the root of the object graph is
318: * named ROOT and child objects are addressed using dot notation.
319: * @param path addresses an object in the object graph.
320: * @param object the object / subobject of the object graph
321: */
322: protected abstract void setObject(String path, Object object);
323:
324: public Object getObject() {
325: return object;
326: }
327:
328: public void setObject(Object object) {
329: deactivateValidation();
330: clearDatas();
331:
332: this .object = object;
333:
334: BeanData data = datas.get(ComponentProvider.ROOT);
335: if (object == null) {
336: this .object = data.createInstance();
337: data.setValue(this .object);
338: data.initialize();
339: } else {
340: data.setValue(this .object);
341: }
342:
343: if (layouter != null)
344: visibilityChanged(layouter.getVisibles());
345:
346: focus();
347:
348: environment.setTitle(getBeanMeta(ComponentProvider.ROOT)
349: .getType().getName()
350: + ".editor.title", titleArguments());
351:
352: clearChanges();
353: }
354:
355: protected void focus() {
356: for (SComponent component : layouter.getFocusableComponents()) {
357: if (isWritable(component)
358: && component.isRecursivelyVisible()) {
359: component.requestFocus();
360: break;
361: }
362: }
363: }
364:
365: private boolean isWritable(SComponent component) {
366: if (component instanceof STextComponent) {
367: STextComponent textComponent = (STextComponent) component;
368: return textComponent.isEditable();
369: }
370: return component.isEnabled();
371: }
372:
373: protected String titleArguments() {
374: Format format = getBeanMeta(ComponentProvider.ROOT).getFormat();
375: if (format == null)
376: format = NO_FORMAT;
377: return format.format(getObject());
378: }
379:
380: public Class getType() {
381: return getBeanMeta(ComponentProvider.ROOT).getType();
382: }
383:
384: protected void clearDatas() {
385: for (Map.Entry<String, BeanData> entry : datas.entrySet()) {
386: BeanData data = entry.getValue();
387: data.setValue(null);
388: }
389: initializedDatas.clear();
390: }
391:
392: public void initData(BeanData data) {
393: if (initializedDatas.contains(data))
394: return;
395:
396: initializedDatas.add(data);
397:
398: changeStatus.setActive(false);
399:
400: String path = null;
401: for (Map.Entry<String, BeanData> entry : datas.entrySet()) {
402: if (entry.getValue() == data) {
403: path = entry.getKey();
404: break;
405: }
406: }
407: assert path != null;
408:
409: Object object = getObject(path);
410: if (object == null) {
411: object = data.createInstance();
412: data.setValue(object);
413: data.initialize();
414: setObject(path, object);
415: } else {
416: try {
417: data.setValue(object);
418: } catch (ValidationException e) {
419: System.err.println("object " + object
420: + " has validation issues on initialization ("
421: + data.getMeta().getType() + ")");
422: e.printStackTrace(System.err);
423: }
424: }
425:
426: changeStatus.setActive(true);
427: }
428:
429: /**
430: * Construct a descriptive layout specification.
431: * @return the layout
432: * @param instruction layout instructions
433: */
434: protected Layouter initLayout(Instruction instruction) {
435: if (instruction instanceof Page) {
436: return documentLook ? new DocumentLayouter(
437: (Page) instruction) : new FormLayouter(
438: (Page) instruction);
439: } else if (instruction instanceof PageSet) {
440: return documentLook ? new DocumentLayouter(
441: (PageSet) instruction) : new FormLayouter(
442: (PageSet) instruction);
443: }
444: throw new IllegalArgumentException("Page or PageSet expected");
445: }
446:
447: protected abstract Instruction createLayoutInstruction();
448:
449: protected abstract BeanMeta createBeanMeta(String path);
450:
451: protected BeanData createBeanData(String path) {
452: return new DefaultBeanData(getBeanMeta(path));
453: }
454:
455: protected SComponent createCustomComponent(String key) {
456: throw new UnsupportedOperationException("No component for '"
457: + key + "'. Maybe you should override this method?");
458: }
459:
460: protected SLabel createCustomLabel(String key) {
461: return new SLabel(Client.getInstance().getResourceProvider()
462: .getMessage(key));
463: }
464:
465: public void addObjectAction(ObjectAction objectAction) {
466: DelegateObjectAction action = new DelegateObjectAction(
467: objectAction);
468: actionDelegates.put(objectAction, action);
469: environment.addControl(new XButton(action));
470: }
471:
472: public void removeObjectAction(ObjectAction objectAction) {
473: throw new UnsupportedOperationException(
474: "Remove is not implemented.");
475: }
476:
477: public ObjectAction[] getObjectActions() {
478: Set<ObjectAction> objectActions = actionDelegates.keySet();
479: return objectActions.toArray(new ObjectAction[objectActions
480: .size()]);
481: }
482:
483: protected void updateLabelAndEditor(String beanName,
484: String propertyName) {
485: updateLabel(beanName, propertyName);
486: updateEditorComponent(beanName, propertyName);
487: }
488:
489: protected void updateEditorComponent(String beanName,
490: String propertyName) {
491: BeanMeta beanMeta = getBeanMeta(beanName);
492: PropertyMeta propertyMeta = beanMeta.getProperty(propertyName);
493: SComponent component = getComponentProvider()
494: .getEditorComponent(beanName, propertyName);
495: ComponentFactory.INSTANCE.configureComponent(propertyMeta,
496: component, false);
497: }
498:
499: protected void updateLabel(String beanName, String propertyName) {
500: BeanMeta beanMeta = getBeanMeta(beanName);
501: PropertyMeta propertyMeta = beanMeta.getProperty(propertyName);
502: SLabel component = getComponentProvider().getLabel(beanName,
503: propertyName);
504: ComponentFactory.INSTANCE.configureLabel(propertyMeta,
505: component, false);
506: }
507:
508: public ObjectContext getContext() {
509: if (context == null) {
510: System.out.println("new context: " + context);
511: context = new ObjectContext();
512: }
513: return context;
514: }
515:
516: public void setContext(ObjectContext context) {
517: this .context = context;
518: }
519:
520: protected static class DelegateObjectAction extends AbstractAction {
521: ObjectForm form;
522: ObjectAction objectAction;
523:
524: public DelegateObjectAction(ObjectAction objectAction) {
525: this .objectAction = objectAction;
526: }
527:
528: public void setForm(ObjectForm form) {
529: this .form = form;
530: }
531:
532: public void actionPerformed(ActionEvent e) {
533: ObjectActionEvent objectEvent = new ObjectActionEvent(form,
534: form.getObject());
535: objectAction.actionPerformed(objectEvent);
536: }
537:
538: public Object getValue(String key) {
539: return objectAction.getValue(key);
540: }
541:
542: public void putValue(String key, Object value) {
543: objectAction.putValue(key, value);
544: }
545:
546: public void setEnabled(boolean b) {
547: objectAction.setEnabled(b);
548: }
549:
550: public boolean isEnabled() {
551: return objectAction.isEnabled();
552: }
553: }
554:
555: private static final Comparator<? super String> PATH_COMPARATOR = new Comparator<String>() {
556: public int compare(String o1, String o2) {
557: if (o1 == null)
558: return -1;
559: if (o2 == null)
560: return 1;
561: return o1.compareTo(o2);
562: }
563: };
564:
565: private static final Modifier READONLY_VARIATION = new Modifier() {
566: public void modify(BeanMeta beanMeta) throws ModifierException {
567: for (int i = 0; i < beanMeta.getProperties().length; i++) {
568: PropertyMeta propertyMeta = beanMeta.getProperties()[i];
569: propertyMeta.setWritable(false);
570: }
571: }
572: };
573:
574: class ViewerAction extends AbstractAction {
575: BeanMeta beanMeta;
576:
577: public ViewerAction(BeanMeta beanMeta) {
578: this .beanMeta = beanMeta;
579: putValue(Action.NAME, "Meta: "
580: + beanMeta.getType().getSimpleName());
581: }
582:
583: public void actionPerformed(ActionEvent e) {
584: XOptionPane.showInputDialog(component, beanMeta.getName(),
585: "Meta", new MetaViewer(beanMeta),
586: new ActionListener() {
587: public void actionPerformed(ActionEvent e) {
588: }
589: });
590: }
591: }
592: }
|