001: /*
002: *
003: * JMoney - A Personal Finance Manager
004: * Copyright (c) 2004 Nigel Westbury <westbury@users.sourceforge.net>
005: *
006: *
007: * This program is free software; you can redistribute it and/or modify
008: * it under the terms of the GNU General Public License as published by
009: * the Free Software Foundation; either version 2 of the License, or
010: * (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
020: *
021: */
022:
023: package net.sf.jmoney.model2;
024:
025: import java.util.Collection;
026: import java.util.HashMap;
027: import java.util.HashSet;
028: import java.util.Map;
029: import java.util.Set;
030: import java.util.Vector;
031:
032: import org.eclipse.core.runtime.CoreException;
033: import org.eclipse.core.runtime.IConfigurationElement;
034: import org.eclipse.core.runtime.IExtensionRegistry;
035: import org.eclipse.core.runtime.Platform;
036:
037: /**
038: * A <code>PropertySet</code> contains information about a
039: * property set. A property set is a set of properties
040: * that either:
041: * <UL>
042: * <LI>form the base set of properties in a data model object
043: * </LI>
044: * <LI>or are the properties added to a data model object
045: * by a derived class (such property sets are known as
046: * derived property sets)
047: * </LI>
048: * <LI>or are the properties added to a data model object by
049: * a plug-in (such property sets are know as extension
050: * property sets)
051: * </LI>
052: * </UL>
053: * The <code>getBasePropertySet</code> and <code>isExtension</code> methods
054: * can be called to determine in which of the above three categories
055: * a property set lies.
056: *
057: * @see <a href="propertySets.html">Property Set Documentation</a>
058: * @see <a href="extendingDatamodel.html#propertySets">Property Set Documentation</a>
059: * @param E the type of the implementation object, which must be
060: * either an ExtendableObject or an ExtensionObject
061: * @author Nigel Westbury
062: */
063: public abstract class PropertySet<E> {
064:
065: protected String propertySetId;
066:
067: protected Class<E> classOfObject;
068:
069: protected Vector<PropertyAccessor> properties = new Vector<PropertyAccessor>();
070:
071: /**
072: * These arrays are built on first use and then cached.
073: */
074: private Vector<ScalarPropertyAccessor<?>> scalarProperties1 = null;
075: private Vector<ListPropertyAccessor<?>> listProperties1 = null;
076:
077: boolean isExtension;
078:
079: static Vector<PropertySet> allPropertySets = new Vector<PropertySet>();
080: static Set<String> allPropertySetIds = new HashSet<String>();
081:
082: /**
083: * Maps property set id to the property set
084: */
085: protected static Map<String, ExtendablePropertySet<?>> allExtendablePropertySetsMap = new HashMap<String, ExtendablePropertySet<?>>();
086: protected static Map<String, ExtensionPropertySet<?>> allExtensionPropertySetsMap = new HashMap<String, ExtensionPropertySet<?>>();
087:
088: /**
089: * Map extendable classes to property sets.
090: */
091: protected static Map<Class<? extends ExtendableObject>, ExtendablePropertySet> classToPropertySetMap = new HashMap<Class<? extends ExtendableObject>, ExtendablePropertySet>();
092:
093: protected PropertySet() {
094: // Add to our list of all property sets
095: allPropertySets.add(this );
096: }
097:
098: /**
099: * This method is called after all the properties in this property set have
100: * been set. It completes the initialization of this object.
101: *
102: * This cannot be done in the constructor because there may be circular references
103: * between property sets, properties in those property sets, and property sets for
104: * the objects referenced by those properties.
105: *
106: * @param propertySetId
107: *
108: */
109: public void initProperties(String propertySetId) {
110: /*
111: * Check that the property set id is unique.
112: */
113: if (allPropertySetIds.contains(propertySetId)) {
114: throw new MalformedPluginException(
115: "More than one property set has an id of "
116: + propertySetId);
117: }
118: this .propertySetId = propertySetId;
119: allPropertySetIds.add(propertySetId);
120: }
121:
122: /**
123: * Loads the property sets.
124: * All property sets (both base and extensions) are added to the
125: * net.sf.jmoney.fields extension point.
126: */
127: public static void init() {
128: // Load the property set extensions.
129: IExtensionRegistry registry = Platform.getExtensionRegistry();
130:
131: // TODO: They may be not much point in processing extendable classes before extension
132: // classes. Eclipse, I believe, will always iterate extension info from a plug-in
133: // before extensions from plug-ins that depend on that plug-in, so we don't have the
134: // problem of the extendable not being processed before the extension.
135: // We do have other problems, however, which have required a second pass thru
136: // the property sets.
137:
138: for (IConfigurationElement element : registry
139: .getConfigurationElementsFor("net.sf.jmoney.fields")) {
140: if (element.getName().equals("extendable-property-set")) {
141: try {
142: Object listener = element
143: .createExecutableExtension("info-class");
144: if (!(listener instanceof IPropertySetInfo)) {
145: throw new MalformedPluginException(
146: "Plug-in "
147: + element.getContributor()
148: .getName()
149: + " extends the net.sf.jmoney.fields extension point. "
150: + "However, the class specified by the info-class attribute "
151: + "("
152: + listener.getClass().getName()
153: + ") "
154: + "does not implement the IPropertySetInfo interface. "
155: + "This interface must be implemented by all classes referenced "
156: + "by the info-class attribute.");
157: }
158:
159: IPropertySetInfo pageListener = (IPropertySetInfo) listener;
160:
161: String fullPropertySetId = element
162: .getNamespaceIdentifier();
163: String id = element.getAttribute("id");
164: if (id != null && id.length() != 0) {
165: fullPropertySetId = fullPropertySetId + '.'
166: + id;
167: }
168:
169: String basePropertySetId = element
170: .getAttribute("base-property-set");
171: if (basePropertySetId != null
172: && basePropertySetId.length() == 0) {
173: basePropertySetId = null;
174: }
175: registerExtendablePropertySet(fullPropertySetId,
176: basePropertySetId, pageListener);
177: } catch (CoreException e) {
178: if (e.getStatus().getException() instanceof ClassNotFoundException) {
179: ClassNotFoundException e2 = (ClassNotFoundException) e
180: .getStatus().getException();
181: throw new MalformedPluginException(
182: "Plug-in "
183: + element.getContributor()
184: .getName()
185: + " extends the net.sf.jmoney.fields extension point. "
186: + "However, the class specified by the info-class attribute "
187: + "("
188: + e2.getMessage()
189: + ") "
190: + "could not be found. "
191: + "The info-class attribute must specify a class that implements the "
192: + "IPropertySetInfo interface.");
193: }
194: e.printStackTrace();
195: }
196: }
197: }
198:
199: for (IConfigurationElement element : registry
200: .getConfigurationElementsFor("net.sf.jmoney.fields")) {
201: if (element.getName().equals("extension-property-set")) {
202: try {
203: Object listener = element
204: .createExecutableExtension("info-class");
205: if (listener instanceof IPropertySetInfo) {
206: IPropertySetInfo pageListener = (IPropertySetInfo) listener;
207:
208: String fullPropertySetId = element
209: .getNamespaceIdentifier();
210: String id = element.getAttribute("id");
211: if (id != null && id.length() != 0) {
212: fullPropertySetId = fullPropertySetId + '.'
213: + id;
214: }
215:
216: String extendablePropertySetId = element
217: .getAttribute("extendable-property-set");
218: if (extendablePropertySetId != null) {
219: registerExtensionPropertySet(
220: fullPropertySetId,
221: extendablePropertySetId,
222: pageListener);
223: } else {
224: // TODO plug-in error
225: }
226: }
227: } catch (CoreException e) {
228: e.printStackTrace();
229: }
230: }
231: }
232:
233: /*
234: * Check for property sets that have been created (because
235: * other property sets depended on them) but that have no entry
236: * in a plugin.xml file.
237: */
238: for (PropertySet propertySet : PropertySet.allPropertySets) {
239: if (propertySet.getId() == null) {
240: throw new MalformedPluginException(
241: "The property set for "
242: + propertySet.getImplementationClass()
243: .getName()
244: + " has not been registered in the plugin.xml file.");
245: }
246: }
247:
248: /*
249: * After all property information has been registered, make a second
250: * pass through the extendable objects. In this pass we do processing of
251: * extendable property sets that requires the complete set of extension
252: * property sets to be available and complete.
253: */
254: for (ExtendablePropertySet propertySet : PropertySet
255: .getAllExtendablePropertySets()) {
256: propertySet.initPropertiesPass2();
257: }
258: }
259:
260: /**
261: *
262: * @param propertySetId
263: * @param basePropertySetId If this property set is derived from
264: * another property set then the id of the base property set,
265: * otherwise null.
266: * @param propertySetInfo Null if property set data is found in
267: * the datastore but no plug-in defined a property set
268: * with this id.
269: * @param class1
270: * @return
271: */
272: static private void registerExtendablePropertySet(
273: final String propertySetId, final String basePropertySetId,
274: IPropertySetInfo propertySetInfo) {
275:
276: // Set up the list of properties.
277: // This is done by calling the registerProperties
278: // method of the supplied interface.
279: PropertySet propertySet = propertySetInfo.registerProperties();
280: propertySet.initProperties(propertySetId);
281: }
282:
283: /**
284: *
285: * @param propertySetId
286: * @param propertySetInfo Null if property set data is found in
287: * the datastore but no plug-in defined a property set
288: * with this id.
289: * @return
290: */
291: static private void registerExtensionPropertySet(
292: final String propertySetId,
293: final String extendablePropertySetId,
294: IPropertySetInfo propertySetInfo) {
295: // Set up the list of properties.
296: // This is done by calling the registerProperties
297: // method of the supplied interface.
298: PropertySet propertySet = propertySetInfo.registerProperties();
299: propertySet.initProperties(propertySetId);
300: }
301:
302: /**
303: * This method is called when a property set id in plugin.xml references
304: * an extendable property set. The property set object is
305: * returned.
306: */
307: static public ExtendablePropertySet getExtendablePropertySet(
308: String propertySetId) throws PropertySetNotFoundException {
309: ExtendablePropertySet propertySet = allExtendablePropertySetsMap
310: .get(propertySetId);
311: if (propertySet == null) {
312: throw new PropertySetNotFoundException(propertySetId);
313: }
314: return propertySet;
315: }
316:
317: /**
318: * This method is called when one plug-in wants to access a property
319: * in another plug-in's property set. Callers must be able to handle
320: * the case where the requested property set is not found. The plug-in
321: * must catch PropertySetNotFoundException and supply appropriate behavior
322: * (not an error from the user's perspective).
323: */
324: static public ExtensionPropertySet getExtensionPropertySet(
325: String propertySetId) throws PropertySetNotFoundException {
326: ExtensionPropertySet propertySet = allExtensionPropertySetsMap
327: .get(propertySetId);
328: if (propertySet == null) {
329: throw new PropertySetNotFoundException(propertySetId);
330: }
331: return propertySet;
332: }
333:
334: /**
335: * This method will find the PropertySet object, given the class of an
336: * implementation object. The given class must be an implementation of ExtendableObject
337: * (The class may not be an implementation of an extension
338: * property set).
339: * <P>
340: * This method should be called when we have an object, but we do not know
341: * exactly of what derived class the object is. By calling this method,
342: * we can get the actual set of properties for this object.
343: * For example, if one wants to display the properties for
344: * a CapitalAccount object, then call this method to get the property
345: * set for the actual object and you will then see properties for
346: * this particular object (bank account properties if the object
347: * is a bank account, credit card account properties if the
348: * object is a credit card account and so on).
349: */
350: static public <E extends ExtendableObject> ExtendablePropertySet<? extends E> getPropertySet(
351: Class<E> propertySetClass) {
352: return classToPropertySetMap.get(propertySetClass);
353: }
354:
355: @Override
356: public String toString() {
357: return propertySetId;
358: }
359:
360: /**
361: * @return The globally unique id of the property set.
362: */
363: public String getId() {
364: return propertySetId;
365: }
366:
367: /**
368: * Returns the implementation class for this property set.
369: *
370: * The implementation class for a property set is a class that
371: * implements getters and setters for all the properties in
372: * the property set. Implementation classes for property sets
373: * have a few other rules they must follow too. For example,
374: * certain constructors must be provided and they must extend
375: * either ExtendableObject or ExtensionObject.
376: * See the documentation on property set implementation classes
377: * for further information.
378: *
379: * @see doc on implemetation classes
380: * @return the implementation class
381: */
382: public Class<E> getImplementationClass() {
383: return classOfObject;
384: }
385:
386: /**
387: * Get the property accessor for a property in a
388: * property set.
389: *
390: * This method looks in only in the given property set
391: * (it will not look in base property sets or extension
392: * property sets).
393: *
394: * @param name The local name of the property. This name does not
395: * include the dotted prefix.
396: */
397: public PropertyAccessor getProperty(String name)
398: throws PropertyNotFoundException {
399: for (PropertyAccessor propertyAccessor : properties) {
400: if (propertyAccessor.getLocalName().equals(name)) {
401: return propertyAccessor;
402: }
403: }
404: throw new PropertyNotFoundException(propertySetId, name);
405: }
406:
407: /**
408: * Gets a list of all extension property sets.
409: *
410: * @return the collection of all property sets
411: */
412: static public Collection<ExtensionPropertySet<?>> getAllExtensionPropertySets() {
413: return allExtensionPropertySetsMap.values();
414: }
415:
416: /**
417: * Gets a list of all extendable property sets.
418: *
419: * @return the collection of all property sets
420: */
421: static public Collection<ExtendablePropertySet<?>> getAllExtendablePropertySets() {
422: return allExtendablePropertySetsMap.values();
423: }
424:
425: /**
426: * @return An iterator that iterates over all properties
427: * in this property set, returning, for each property,
428: * the PropertyAccessor object for that property.
429: */
430: public Collection<PropertyAccessor> getProperties1() {
431: return properties;
432: }
433:
434: public Collection<ScalarPropertyAccessor<?>> getScalarProperties1() {
435: if (scalarProperties1 == null) {
436: scalarProperties1 = new Vector<ScalarPropertyAccessor<?>>();
437: for (PropertyAccessor propertyAccessor : properties) {
438: if (propertyAccessor instanceof ScalarPropertyAccessor) {
439: scalarProperties1
440: .add((ScalarPropertyAccessor<?>) propertyAccessor);
441: }
442: }
443: }
444:
445: return scalarProperties1;
446: }
447:
448: public Collection<ListPropertyAccessor<?>> getListProperties1() {
449: if (listProperties1 == null) {
450: listProperties1 = new Vector<ListPropertyAccessor<?>>();
451: for (PropertyAccessor propertyAccessor : properties) {
452: if (propertyAccessor instanceof ListPropertyAccessor) {
453: listProperties1
454: .add((ListPropertyAccessor<?>) propertyAccessor);
455: }
456: }
457: }
458:
459: return listProperties1;
460: }
461:
462: /**
463: * @return
464: */
465: public boolean isExtension() {
466: return isExtension;
467: }
468:
469: public static <E2 extends ExtendableObject> ExtendablePropertySet<E2> addBaseAbstractPropertySet(
470: Class<E2> classOfImplementationObject, String description) {
471: return new ExtendablePropertySet<E2>(
472: classOfImplementationObject, description, null, null);
473: }
474:
475: public static <E2 extends ExtendableObject> ExtendablePropertySet<E2> addBaseFinalPropertySet(
476: Class<E2> classOfImplementationObject, String description,
477: IExtendableObjectConstructors<E2> constructors) {
478: return new ExtendablePropertySet<E2>(
479: classOfImplementationObject, description, null,
480: constructors);
481: }
482:
483: public static <E extends ExtendableObject> ExtendablePropertySet<E> addDerivedAbstractPropertySet(
484: Class<E> classOfImplementationObject, String description,
485: ExtendablePropertySet<? super E> basePropertySet) {
486: return new ExtendablePropertySet<E>(
487: classOfImplementationObject, description,
488: basePropertySet, null);
489: }
490:
491: public static <E extends ExtendableObject> ExtendablePropertySet<E> addDerivedFinalPropertySet(
492: Class<E> classOfImplementationObject, String description,
493: ExtendablePropertySet<? super E> basePropertySet,
494: IExtendableObjectConstructors<E> constructors) {
495: return new ExtendablePropertySet<E>(
496: classOfImplementationObject, description,
497: basePropertySet, constructors);
498: }
499:
500: public static <E extends ExtensionObject> ExtensionPropertySet<E> addExtensionPropertySet(
501: Class<E> classOfImplementationObject,
502: ExtendablePropertySet<?> extendablePropertySet,
503: IExtensionObjectConstructors<E> constructors) {
504: return new ExtensionPropertySet<E>(classOfImplementationObject,
505: extendablePropertySet, constructors);
506: }
507:
508: public <V> ScalarPropertyAccessor<V> addProperty(String name,
509: String displayName, Class<V> classOfValue, int weight,
510: int minimumWidth,
511: IPropertyControlFactory<V> propertyControlFactory,
512: IPropertyDependency<E> propertyDependency) {
513: if (propertyControlFactory == null) {
514: throw new MalformedPluginException(
515: "No IPropertyControlFactory object has been specified for property "
516: + name
517: + ". This is needed even if the property is not editable. (Though the method that gets the"
518: + " control may return null if the property is not editable).");
519: }
520:
521: ScalarPropertyAccessor<V> accessor = new ScalarPropertyAccessor<V>(
522: classOfValue, this , name, displayName, weight,
523: minimumWidth, propertyControlFactory,
524: propertyDependency);
525: properties.add(accessor);
526: return accessor;
527: }
528:
529: public <V extends ExtendableObject> ReferencePropertyAccessor<V> addProperty(
530: String name,
531: String displayName,
532: Class<V> classOfValue,
533: int weight,
534: int minimumWidth,
535: final IReferenceControlFactory<E, V> propertyControlFactory,
536: IPropertyDependency<E> propertyDependency) {
537: if (propertyControlFactory == null) {
538: throw new MalformedPluginException(
539: "No IPropertyControlFactory object has been specified for property "
540: + name
541: + ". This is needed even if the property is not editable. (Though the method that gets the"
542: + " control may return null if the property is not editable).");
543: }
544:
545: ReferencePropertyAccessor<V> accessor = new ReferencePropertyAccessor<V>(
546: classOfValue, this , name, displayName, weight,
547: minimumWidth, propertyControlFactory,
548: propertyDependency) {
549: @Override
550: public IObjectKey invokeObjectKeyField(
551: ExtendableObject parentObject) {
552: return propertyControlFactory
553: .getObjectKey(getImplementationObject(parentObject));
554: }
555: };
556:
557: properties.add(accessor);
558: return accessor;
559: }
560:
561: public <E2 extends ExtendableObject> ListPropertyAccessor<E2> addPropertyList(
562: String name, String displayName,
563: ExtendablePropertySet<E2> elementPropertySet,
564: final IListGetter<E, E2> listGetter) {
565: ListPropertyAccessor<E2> accessor = new ListPropertyAccessor<E2>(
566: this , name, displayName, elementPropertySet) {
567: @Override
568: public ObjectCollection<E2> getElements(
569: ExtendableObject parentObject) {
570: return listGetter
571: .getList(getImplementationObject(parentObject));
572: }
573: };
574:
575: properties.add(accessor);
576: return accessor;
577: }
578:
579: /**
580: * Given an extendable object, return the Java object that contains the
581: * getters and setters for this property set.
582: *
583: * If this property set is an extendable property set then this method
584: * should return the passed object as is. If this property set is an
585: * extension property set then this method should get the appropriate
586: * extension object.
587: *
588: * @param extendableObject
589: * @return
590: */
591: protected abstract E getImplementationObject(
592: ExtendableObject extendableObject);
593: }
|