001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.module.corebuilders;
011:
012: import java.util.*;
013:
014: import org.mmbase.bridge.Field;
015: import org.mmbase.util.*;
016: import org.mmbase.module.core.*;
017: import org.mmbase.core.CoreField;
018: import org.mmbase.core.event.Event;
019: import org.mmbase.core.event.NodeEvent;
020: import org.mmbase.core.util.Fields;
021: import org.mmbase.cache.*;
022: import org.mmbase.storage.search.implementation.BasicRelationStep;
023: import org.mmbase.storage.search.RelationStep;
024:
025: import org.mmbase.util.logging.Logger;
026: import org.mmbase.util.logging.Logging;
027:
028: /**
029: * TypeRel defines the allowed relations between two object types. Every relations also specifies a
030: * 'role', which is a reference to the RelDef table.
031: *
032: * Relations do principally have a 'source' and a 'destination' object type, but most functions of
033: * this class do ignore this distinction.
034: *
035: * TypeRel is a 'core' MMBase builder. You can get a reference to it via the MMBase instance.
036: *
037: * @author Daniel Ockeloen
038: * @author Pierre van Rooden
039: * @author Michiel Meeuwissen
040: * @version $Id: TypeRel.java,v 1.78 2008/02/20 17:12:28 michiel Exp $
041: * @see RelDef
042: * @see InsRel
043: * @see org.mmbase.module.core.MMBase
044: */
045: public class TypeRel extends MMObjectBuilder {
046:
047: private static final Logger log = Logging
048: .getLoggerInstance(TypeRel.class);
049:
050: /**
051: * Constant for {@link #contains}: return only typerels that exactly match.
052: */
053: public static final int STRICT = 0;
054:
055: /**
056: * Constant for {@link #contains}: return typerels where source/destination match with a
057: * builder or its descendants
058: */
059: public static final int INCLUDE_DESCENDANTS = 1;
060:
061: /**
062: * Constant for {@link #contains}: return typerels where source/destination match with a
063: * builder or its parents
064: */
065: public static final int INCLUDE_PARENTS = 2;
066:
067: /**
068: * Constant for {@link #contains}: return typerels where source/destination match with a
069: * builder, its descendants, or its parents
070: */
071: public static final int INCLUDE_PARENTS_AND_DESCENDANTS = 3;
072:
073: /**
074: * TypeRel should contain only a limited amount of nodes, so we can simply cache them all, and
075: * avoid all further querying.
076: */
077: protected TypeRelSet typeRelNodes; // for searching destinations
078:
079: protected TypeRelSet parentTypeRelNodes; // for caching typerels for 'parent'
080:
081: // builders
082:
083: public InverseTypeRelSet inverseTypeRelNodes; // for searching sources
084:
085: public boolean init() {
086: if (oType != -1)
087: return true;
088: super .init();
089: // during init not yet all builder are available so inhertiance is not
090: // yet possible
091: // This means that calls to getAllowedRelations do not consider
092: // inheritance during initializion of MMBase.
093: // This occurs e.g. in one of the Community-builders.
094: readCache(false);
095: return true;
096: }
097:
098: /**
099: * The TypeRel cache contains all TypeRels MMObjectNodes. Called after init by MMBase, and when
100: * something changes.
101: * @since MMBase-1.6.2
102: */
103: public void readCache() {
104: readCache(true);
105: }
106:
107: /**
108: * @since MMBase-1.6.2
109: */
110: private void readCache(boolean buildersInitialized) {
111: log.debug("Reading in typerels");
112: typeRelNodes = new TypeRelSet();
113: parentTypeRelNodes = new TypeRelSet();
114: inverseTypeRelNodes = new InverseTypeRelSet();
115:
116: TypeDef typeDef = mmb.getTypeDef();
117: typeDef.init();
118: // Find all typerel nodes
119: List<MMObjectNode> alltypes = getNodes();
120: for (MMObjectNode typerel : alltypes) {
121: addCacheEntry(typerel, buildersInitialized);
122: }
123: log.debug("Done reading typerel cache "
124: + (buildersInitialized ? "(considered inheritance)"
125: : "") + ": " + typeRelNodes);
126: }
127:
128: /**
129: * Addes one typerel cache entries, plus inherited relations (if builder are initialized)
130: * @return A Set with the added entries, which can be used for logging or so, or can be
131: * disregarded
132: * @since MMBase-1.6.2
133: */
134: protected TypeRelSet addCacheEntry(MMObjectNode typeRel,
135: boolean buildersInitialized) {
136:
137: TypeRelSet added = new TypeRelSet(); // store temporary, which will enable nice logging of what happened
138:
139: // Start to add the actual definition, this is then afterwards again,
140: // except if one of the builders could not be found
141: added.add(typeRel);
142:
143: RelDef reldef = mmb.getRelDef();
144:
145: MMObjectNode reldefNode = reldef.getNode(typeRel
146: .getIntValue("rnumber"));
147: if (reldefNode == null) {
148: throw new RuntimeException(
149: "Could not find reldef-node for rnumber= "
150: + typeRel.getIntValue("rnumber"));
151: }
152:
153: boolean bidirectional = (!InsRel.usesdir)
154: || (reldefNode.getIntValue("dir") > 1);
155:
156: inheritance: if (buildersInitialized) { // handle inheritance, which is
157: // not possible during
158: // initialization of MMBase.
159:
160: TypeDef typeDef = mmb.getTypeDef();
161:
162: String sourceBuilderName = typeDef.getValue(typeRel
163: .getIntValue("snumber"));
164: MMObjectBuilder sourceBuilder = sourceBuilderName != null ? mmb
165: .getBuilder(sourceBuilderName)
166: : null;
167:
168: String destinationBuilderName = typeDef.getValue(typeRel
169: .getIntValue("dnumber"));
170: MMObjectBuilder destinationBuilder = destinationBuilderName != null ? mmb
171: .getBuilder(destinationBuilderName)
172: : null;
173:
174: if (sourceBuilder == null) {
175: if (destinationBuilder == null) {
176: log
177: .warn("Both source and destination of "
178: + typeRel
179: + " are not active builders. Cannot follow descendants.");
180: } else {
181: log
182: .warn("The source of relation type "
183: + typeRel
184: + " is not an active builder. Cannot follow descendants.");
185: }
186: break inheritance;
187: }
188:
189: if (destinationBuilder == null) {
190: log
191: .warn("The destination of relation type "
192: + typeRel
193: + " is not an active builder. Cannot follow descendants.");
194: break inheritance;
195: }
196:
197: int rnumber = typeRel.getIntValue("rnumber");
198:
199: List<MMObjectBuilder> sources = sourceBuilder
200: .getDescendants();
201: sources.add(sourceBuilder);
202:
203: List<MMObjectBuilder> destinations = destinationBuilder
204: .getDescendants();
205: destinations.add(destinationBuilder);
206:
207: Iterator<MMObjectBuilder> i = sources.iterator();
208: while (i.hasNext()) {
209: MMObjectBuilder s = i.next();
210: Iterator<MMObjectBuilder> j = destinations.iterator();
211: while (j.hasNext()) {
212: MMObjectBuilder d = j.next();
213: MMObjectNode vnode = new VirtualTypeRelNode(s
214: .getNumber(), d.getNumber(), rnumber);
215: added.add(vnode);
216: }
217: }
218:
219: // seek all parents and store typerels for them
220: // this cache is used by contains(INCLUDE_PARENTS /
221: // INCLUDE_PARENTS_AND_DESCENDANTS));
222: MMObjectBuilder sourceParent = sourceBuilder;
223: while (sourceParent != null) {
224: MMObjectBuilder destinationParent = destinationBuilder;
225: while (destinationParent != null) {
226: MMObjectNode vnode = new VirtualTypeRelNode(
227: sourceParent.getNumber(), destinationParent
228: .getNumber(), rnumber);
229: parentTypeRelNodes.add(vnode);
230: destinationParent = destinationParent
231: .getParentBuilder();
232: }
233: sourceParent = sourceParent.getParentBuilder();
234: }
235: added.add(typeRel); // replaces the ones added in the 'inheritance'
236: // loop (so now not any more Virtual)
237: }
238: Iterator<MMObjectNode> i = added.iterator();
239: while (i.hasNext()) {
240: MMObjectNode node = i.next();
241: if (!node.isVirtual()) {
242: // make sure 'real' nodes replace virtual nodes. (real and virtual nodes are equal, so will not be added to set otherwise)
243: // This is especially essential whey you use STRICT in contains
244: typeRelNodes.remove(node);
245: if (bidirectional)
246: inverseTypeRelNodes.remove(node);
247: }
248: typeRelNodes.add(node);
249: if (bidirectional)
250: inverseTypeRelNodes.add(node);
251: }
252: if (log.isDebugEnabled()) {
253: log.debug("Added to typerelcache: " + added);
254: }
255: return added;
256: }
257:
258: /**
259: * Insert a new object (content provided) in the cloud, including an entry for the object alias
260: * (if provided). This method indirectly calls {@link #preCommit}. If the typerel node
261: * specified already exists (i.e. same snumber, dnumber,a nd rnumber fielfds), the typerel
262: * creation fails and returns -1.
263: * @param owner The administrator creating the node
264: * @param node The object to insert. The object need be of the same type as the current builder.
265: * @return An <code>int</code> value which is the new object's unique number, -1 if the insert
266: * failed.
267: */
268: public int insert(String owner, MMObjectNode node) {
269: int snumber = node.getIntValue("snumber");
270: int dnumber = node.getIntValue("dnumber");
271: int rnumber = node.getIntValue("rnumber");
272: if (contains(snumber, dnumber, rnumber, STRICT)) {
273: log.error("The typerel with snumber=" + snumber
274: + ", dnumber=" + dnumber + ", rnumber=" + rnumber
275: + " already exists");
276: throw new RuntimeException("The typerel with snumber="
277: + snumber + ", dnumber=" + dnumber + ", rnumber="
278: + rnumber + " already exists");
279: }
280: int res = super .insert(owner, node);
281: return res;
282: }
283:
284: /**
285: * Remove a node from the cloud.
286: * @param node The node to remove.
287: */
288: public void removeNode(MMObjectNode node) {
289: super .removeNode(node);
290: }
291:
292: /**
293: * Retrieves all relations which are 'allowed' for a specified node, that is, where the node is
294: * either allowed to be the source, or to be the destination (but where the corresponing
295: * relation definition is bidirectional). The allowed relations are determined by the type of
296: * the node
297: * @param node The node to retrieve the allowed relations of.
298: * @return An <code>Enumeration</code> of nodes containing the typerel relation data
299: */
300: public Enumeration<MMObjectNode> getAllowedRelations(
301: MMObjectNode node) {
302: return getAllowedRelations(node.getBuilder().getNumber());
303: }
304:
305: public Enumeration<MMObjectNode> getAllowedRelations(int otype) {
306: Set<MMObjectNode> res = getAllowedRelations(otype, 0, 0,
307: RelationStep.DIRECTIONS_BOTH);
308: return Collections.enumeration(res);
309: }
310:
311: /**
312: * Retrieves all relations which are 'allowed' between two specified nodes. No distinction
313: * between source / destination.
314: * @param node1 The first objectnode
315: * @param node2 The second objectnode
316: * @return An <code>Enumeration</code> of nodes containing the typerel relation data
317: */
318: public Enumeration<MMObjectNode> getAllowedRelations(
319: MMObjectNode node1, MMObjectNode node2) {
320: return getAllowedRelations(node1.getOType(), node2.getOType());
321: }
322:
323: /**
324: * An enumeration of all allowed relations between two builders. No distinction is made between
325: * source and destination.
326: *
327: */
328: public Enumeration<MMObjectNode> getAllowedRelations(int builder1,
329: int builder2) {
330: Set<MMObjectNode> res = getAllowedRelations(builder1, builder2,
331: 0, RelationStep.DIRECTIONS_BOTH);
332: return Collections.enumeration(res);
333: }
334:
335: /**
336: * A Set of all allowed relations of a certain role between two builders. No distinction between
337: * source and destination.
338: *
339: * @since MMBase-1.6.2
340: */
341: public Set<MMObjectNode> getAllowedRelations(int builder1,
342: int builder2, int role) {
343: return getAllowedRelations(builder1, builder2, role,
344: RelationStep.DIRECTIONS_BOTH);
345: }
346:
347: /**
348: * A Set of all allowed relations of a certain role between two builders. Distinction is made between
349: * source and destination depending on passed directionality.
350: *
351: * @since MMBase-1.6.2
352: */
353: public Set<MMObjectNode> getAllowedRelations(int builder1,
354: int builder2, int role, int directionality) {
355: Set<MMObjectNode> res = new HashSet<MMObjectNode>();
356: if (directionality != RelationStep.DIRECTIONS_SOURCE) {
357: res.addAll(typeRelNodes.getBySourceDestinationRole(
358: builder1, builder2, role));
359: }
360: if (directionality != RelationStep.DIRECTIONS_DESTINATION
361: && (directionality != RelationStep.DIRECTIONS_EITHER || res
362: .isEmpty())) {
363: res.addAll(inverseTypeRelNodes.getByDestinationSourceRole(
364: builder2, builder1, role));
365: }
366: return res;
367: }
368:
369: /**
370: * Retrieves all relations which are 'allowed' between two specified nodes.
371: * @param snum The first objectnode type (the source)
372: * @param dnum The second objectnode type (the destination)
373: * @return An <code>Enumeration</code> of nodes containing the reldef (not typerel!) sname
374: * field
375: */
376: protected Vector<String> getAllowedRelationsNames(int snum, int dnum) {
377: Vector<String> results = new Vector<String>();
378: for (Enumeration<MMObjectNode> e = getAllowedRelations(snum,
379: dnum); e.hasMoreElements();) {
380: MMObjectNode node = e.nextElement();
381: int rnumber = node.getIntValue("rnumber");
382: MMObjectNode snode = mmb.getRelDef().getNode(rnumber);
383: results.addElement(snode.getStringValue("sname"));
384: }
385: return results;
386: }
387:
388: /**
389: * Retrieves the identifying number of the relation definition that is 'allowed' between two
390: * specified node types. The results are dependent on there being only one type of relation
391: * between two node types (not enforced, thus unpredictable). Makes use of a typeRelNodes.
392: * @param snum The first objectnode type (the source)
393: * @param dnum The second objectnode type (the destination)
394: * @return the number of the found relation, or -1 if either no relation was found, or more than
395: * one was found.
396: */
397: public int getAllowedRelationType(int snum, int dnum) {
398: Set<MMObjectNode> set = new HashSet<MMObjectNode>(typeRelNodes
399: .getBySourceDestination(snum, dnum));
400: set.addAll(inverseTypeRelNodes.getByDestinationSource(dnum,
401: snum));
402:
403: if (set.size() != 1) {
404: return -1;
405: } else {
406: MMObjectNode n = set.iterator().next();
407: return n.getIntValue("rnumber");
408: }
409: }
410:
411: /**
412: * Returns the display string for this node It returns a commbination of objecttypes and
413: * rolename : "source->destination (role)".
414: * @param node Node from which to retrieve the data
415: * @return A <code>String</code> describing the content of the node
416: */
417: public String getGUIIndicator(MMObjectNode node) {
418: try {
419: String source = mmb.getTypeDef().getValue(
420: node.getIntValue("snumber"));
421: String destination = mmb.getTypeDef().getValue(
422: node.getIntValue("dnumber"));
423: MMObjectNode role = mmb.getRelDef().getNode(
424: node.getIntValue("rnumber"));
425: return source + "->" + destination + " ("
426: + (role != null ? role.getGUIIndicator() : "???")
427: + ")";
428: } catch (Exception e) {
429: log.warn(e);
430: }
431: return null;
432: }
433:
434: /**
435: * Returns the display string for a specified field. Returns, for snumber and dnumber, the name
436: * of the objecttype they represent, and for rnumber the display (GUI) string for the indicated
437: * relation definition.
438: * @param field The name of the field to retrieve
439: * @param node Node from which to retrieve the data
440: * @return A <code>String</code> describing the content of the field
441: */
442: public String getGUIIndicator(String field, MMObjectNode node) {
443: try {
444: if (field.equals("snumber")) {
445: return mmb.getTypeDef().getValue(
446: node.getIntValue("snumber"));
447: } else if (field.equals("dnumber")) {
448: return mmb.getTypeDef().getValue(
449: node.getIntValue("dnumber"));
450: } else if (field.equals("rnumber")) {
451: MMObjectNode reldef = mmb.getRelDef().getNode(
452: node.getIntValue("rnumber"));
453: return (reldef != null ? reldef.getGUIIndicator()
454: : "???");
455: }
456: } catch (Exception e) {
457: }
458: return null;
459: }
460:
461: /**
462: * Processes the BUILDER-typerel-ALLOWEDRELATIONSNAMES in the LIST command, and (possibly)
463: * returns a Vector containing requested data (based on the content of TYPE and NODE, which can
464: * be retrieved through tagger).
465: * @javadoc parameters
466: */
467: public Vector<String> getList(PageInfo sp, StringTagger tagger,
468: StringTokenizer tok) {
469: if (tok.hasMoreTokens()) {
470: String cmd = tok.nextToken(); //Retrieving command.
471: if (cmd.equals("ALLOWEDRELATIONSNAMES")) {
472: try {
473: String tmp = tagger.Value("TYPE");
474: int number1 = mmb.getTypeDef().getIntValue(tmp);
475: tmp = tagger.Value("NODE");
476: int number2 = Integer.parseInt(tmp);
477: MMObjectNode node = getNode(number2);
478: return getAllowedRelationsNames(number1, node
479: .getOType());
480: } catch (Exception e) {
481: log.error(e);
482: }
483: }
484: }
485: return null;
486: }
487:
488: /**
489: * Tests if a specific relation type is defined.
490: * <p>
491: * Note that this routine returns false both when a snumber/dnumber are swapped, and when a
492: * typecombo does not exist - it is not possible to derive whether one or the other has
493: * occurred.
494: * </p>
495: * @deprecated use {@link #contains}instead
496: * @param n1 The source type number.
497: * @param n2 The destination type number.
498: * @param r The relation definition (role) number, or -1 if the role does not matter
499: * @return <code>true</code> when the relation exists, false otherwise.
500: *
501: */
502: public boolean reldefCorrect(int n1, int n2, int r) {
503: return contains(n1, n2, r);
504: }
505:
506: /**
507: * Tests if a specific relation type is defined. The method also returns true if the typerel
508: * occurs as a virtual (derived) node.
509: * <p>
510: * Note that this routine returns false both when a snumber/dnumber are swapped, and when a
511: * typecombo does not exist - it is not possible to derive whether one or the other has
512: * occurred.
513: * <p>
514: *
515: * @param n1 The source type number.
516: * @param n2 The destination type number.
517: * @param r The relation definition (role) number, or -1 if the role does not matter
518: * @return <code>true</code> when the relation exists, false otherwise.
519: *
520: * @since MMBase-1.6.2
521: */
522: public boolean contains(int n1, int n2, int r) {
523: return contains(n1, n2, r, INCLUDE_DESCENDANTS);
524: }
525:
526: /**
527: * Tests if a specific relation type is defined.
528: * <p>
529: * Note that this routine returns false both when a snumber/dnumber are swapped, and when a
530: * typecombo does not exist - it is not possible to derive whether one or the other has
531: * occurred.
532: * <p>
533: *
534: * @param n1 The source type number.
535: * @param n2 The destination type number.
536: * @param r The relation definition (role) number, or -1 if the role does not matter r can only
537: * be -1 if virtual is <code>true</code>
538: * @param restriction if {@link #STRICT}, contains only returns true if the typerel occurs
539: * as-is in the database. if {@link #INCLUDE_DESCENDANTS}, contains returns true if the typerel
540: * occurs as a virtual (derived) node, where source or destination may also be descendants of
541: * the specified type. if {@link #INCLUDE_PARENTS}, contains returns true if the typerel occurs
542: * as a virtual (derived) node, where source or destination may also be parents of the specified
543: * type. if {@link #INCLUDE_PARENTS_AND_DESCENDANTS}, contains returns true if the typerel
544: * occurs as a virtual (derived) node, where source or destination may also be descendants or
545: * parents of the specified type.
546: * @return <code>true</code> when the relation exists, false otherwise.
547: *
548: * @since MMBase-1.6.2
549: */
550: public boolean contains(int n1, int n2, int r, int restriction) {
551: switch (restriction) {
552: case INCLUDE_DESCENDANTS:
553: return typeRelNodes.contains(new VirtualTypeRelNode(n1, n2,
554: r));
555: case INCLUDE_PARENTS:
556: return parentTypeRelNodes.contains(new VirtualTypeRelNode(
557: n1, n2, r));
558: case INCLUDE_PARENTS_AND_DESCENDANTS:
559: return typeRelNodes.contains(new VirtualTypeRelNode(n1, n2,
560: r))
561: || parentTypeRelNodes
562: .contains(new VirtualTypeRelNode(n1, n2, r));
563: case STRICT:
564: SortedSet<MMObjectNode> existingNodes = typeRelNodes
565: .getBySourceDestinationRole(n1, n2, r);
566: return (existingNodes.size() > 0 && !existingNodes.first()
567: .isVirtual());
568: default:
569: log.error("Unknown restriction " + restriction);
570: return false;
571: }
572: }
573:
574: /**
575: * Watch for changes on relation types and adjust our memory table accordingly
576: * @todo Should update artCache en relDefCorrectCache as wel
577: */
578: /*
579: * (non-Javadoc)
580: * @see org.mmbase.module.core.MMObjectBuilder#notify(org.mmbase.core.event.NodeEvent)
581: */
582: public void notify(NodeEvent event) {
583: if (log.isDebugEnabled()) {
584: log.debug("Changed " + event.getMachine() + " "
585: + event.getNodeNumber() + " "
586: + event.getBuilderName() + " "
587: + NodeEvent.newTypeToOldType(event.getType()));
588: }
589: if (tableName.equals(event.getBuilderName())) {
590: if (event.getType() == Event.TYPE_NEW) {
591: Set<MMObjectNode> newTypeRels = addCacheEntry(
592: getNode(event.getNodeNumber()), true);
593: log.service("Added to typerelcache: " + newTypeRels);
594: } else {
595: //something else changed in a typerel node? reread the complete typeRelNodes Set
596: readCache();
597: }
598: // also, clear all query-caches, because result may change by this. See MMB-348
599: for (Cache qc : CacheManager.getMap().values()) {
600: if (qc instanceof QueryResultCache) {
601: qc.clear();
602: }
603: }
604: }
605: super .notify(event);
606: }
607:
608: /**
609: * Optimize as relation step by considering restrictions of TypeRel. TypeRel defines which type
610: * of relations may be created, ergo can exist.
611: *
612: * @since MMBase-1.7
613: */
614: public boolean optimizeRelationStep(BasicRelationStep relationStep,
615: int sourceType, int destinationType, int roleInt,
616: int searchDir) {
617: // Determine in what direction(s) this relation can be followed:
618:
619: // Check directionality is requested and supported.
620: if (searchDir != RelationStep.DIRECTIONS_ALL && InsRel.usesdir) {
621: relationStep.setCheckedDirectionality(true);
622: }
623:
624: // this is a bit confusing, can the simple cases like explicit 'source'
625: // or 'destination' not be handled first?
626:
627: boolean sourceToDestination = searchDir != RelationStep.DIRECTIONS_SOURCE
628: && contains(sourceType, destinationType, roleInt,
629: INCLUDE_PARENTS_AND_DESCENDANTS);
630: boolean destinationToSource = searchDir != RelationStep.DIRECTIONS_DESTINATION
631: && contains(destinationType, sourceType, roleInt,
632: INCLUDE_PARENTS_AND_DESCENDANTS);
633:
634: if (destinationToSource && sourceToDestination
635: && (searchDir == RelationStep.DIRECTIONS_EITHER)) {
636: // support old
637: destinationToSource = false;
638: }
639:
640: if (destinationToSource) {
641: // there is a typed relation from destination to src
642: if (sourceToDestination) {
643: // there is ALSO a typed relation from src to destination - make
644: // a more complex query
645: relationStep
646: .setDirectionality(RelationStep.DIRECTIONS_BOTH);
647: } else {
648: // there is ONLY a typed relation from destination to src -
649: // optimized query
650: relationStep
651: .setDirectionality(RelationStep.DIRECTIONS_SOURCE);
652: }
653: } else {
654: if (sourceToDestination) {
655: // there is no typed relation from destination to src (assume a
656: // relation between src and destination) - optimized query
657: relationStep
658: .setDirectionality(RelationStep.DIRECTIONS_DESTINATION);
659: } else {
660: // no results possible, do something any way
661: if (searchDir == RelationStep.DIRECTIONS_SOURCE) {
662: // explicitely asked for source, it would be silly to try destination now
663: relationStep
664: .setDirectionality(RelationStep.DIRECTIONS_SOURCE);
665: } else {
666: // the 'normal' way
667: relationStep
668: .setDirectionality(RelationStep.DIRECTIONS_DESTINATION);
669: }
670: return false;
671: }
672: }
673: return true;
674: }
675:
676: /**
677: * Implements equals for a typerel node. Two nodes are equal if the snumber and dnumber fields
678: * are the same, and the rnumber fields are the same, or one of these is '-1' (don't care).
679: * @since MMBase-1.6.2
680: */
681: public boolean equals(MMObjectNode o1, MMObjectNode o2) {
682: if (o2.getBuilder() instanceof TypeRel) {
683: int r1 = o1.getIntValue("rnumber");
684: int r2 = o2.getIntValue("rnumber");
685: return o1.getIntValue("snumber") == o2
686: .getIntValue("snumber")
687: && o1.getIntValue("dnumber") == o2
688: .getIntValue("dnumber")
689: && (r1 == -1 || r2 == -1 || r1 == r2);
690: }
691: return false;
692: }
693:
694: /**
695: * Implements for MMObjectNode
696: * @since MMBase-1.6.2
697: */
698: public int hashCode(MMObjectNode o) {
699: int result = 0;
700: result = HashCodeUtil
701: .hashCode(result, o.getIntValue("snumber"));
702: result = HashCodeUtil
703: .hashCode(result, o.getIntValue("dnumber"));
704: result = HashCodeUtil
705: .hashCode(result, o.getIntValue("rnumber"));
706: return result;
707: }
708:
709: public String toString(MMObjectNode n) {
710: try {
711: int snumber = n.getIntValue("snumber");
712: int dnumber = n.getIntValue("dnumber");
713: int rnumber = n.getIntValue("rnumber");
714:
715: String sourceName = mmb.getTypeDef().getValue(snumber);
716: String destName = mmb.getTypeDef().getValue(dnumber);
717:
718: if (sourceName == null)
719: sourceName = "unknown builder '" + snumber + "'";
720: if (destName == null)
721: destName = "unknown builder '" + dnumber + "'";
722:
723: // unfilled should only happen during creation of the node.
724: String source = snumber > -1 ? sourceName : "[unfilled]";
725: String destination = dnumber > -1 ? destName : "[unfilled]";
726: MMObjectNode role = rnumber > -1 ? mmb.getRelDef().getNode(
727: rnumber) : null;
728: return source
729: + "->"
730: + destination
731: + " ("
732: + (role != null ? role.getStringValue("sname")
733: : "???") + ") "
734: + (isVirtual() ? "(virtual)" : "");
735: } catch (Exception e) {
736: log.warn(e);
737: }
738: return "typerel-node";
739: }
740:
741: /**
742: * Of course, virtual typerel nodes need a virtual typerel builder. Well 'of course', the reason
743: * is not quite obvious to me, it has to do with the bridge/temporarynodemanager which sometimes
744: * needs to know it.
745: *
746: * @since MMBase-1.6.2
747: */
748: static class VirtualTypeRel extends TypeRel {
749: static VirtualTypeRel virtualTypeRel = null;
750:
751: VirtualTypeRel(TypeRel t) {
752: mmb = t.getMMBase();
753: CoreField field = Fields.createField("snumber",
754: Field.TYPE_NODE, Field.TYPE_UNKNOWN,
755: Field.STATE_VIRTUAL, null);
756: field.finish();
757: addField(field);
758: field = Fields.createField("dnumber", Field.TYPE_NODE,
759: Field.TYPE_UNKNOWN, Field.STATE_VIRTUAL, null);
760: field.finish();
761: addField(field);
762: field = Fields.createField("rnumber", Field.TYPE_NODE,
763: Field.TYPE_UNKNOWN, Field.STATE_VIRTUAL, null);
764: field.finish();
765: addField(field);
766: tableName = "virtual_typerel";
767: virtual = true;
768: }
769:
770: static VirtualTypeRel getVirtualTypeRel(TypeRel t) {
771: if (virtualTypeRel == null)
772: virtualTypeRel = new VirtualTypeRel(t);
773: return virtualTypeRel;
774: }
775: }
776:
777: /**
778: * A TypeRelSet is a Set of typerel nodes. The TypeRel builder maintains such a Set of all
779: * typerel nodes for quick reference. TypeRelSets are also instantiated when doing queries on
780: * TypeRel like getAllowedRelations(MMObjectBuilder) etc.
781: *
782: * @since MMBase-1.6.2
783: */
784: protected class TypeRelSet extends TreeSet<MMObjectNode> {
785: protected TypeRelSet() {
786: super (new Comparator<MMObjectNode>() {
787: // sorted by source, destination, role
788: public int compare(MMObjectNode n1, MMObjectNode n2) {
789: int i1 = n1.getIntValue("snumber");
790: int i2 = n2.getIntValue("snumber");
791: if (i1 != i2)
792: return i1 - i2;
793:
794: i1 = n1.getIntValue("dnumber");
795: i2 = n2.getIntValue("dnumber");
796: if (i1 != i2)
797: return i1 - i2;
798:
799: i1 = n1.getIntValue("rnumber");
800: i2 = n2.getIntValue("rnumber");
801: if (i1 > 0 && i2 > 0 && i1 != i2)
802: return i1 - i2;
803:
804: return 0;
805: }
806: });
807: }
808:
809: // make sure only MMObjectNode's are added
810: public boolean add(MMObjectNode node) {
811: return super .add(node);
812: }
813:
814: // find some subsets:
815: SortedSet<MMObjectNode> getBySource(MMObjectBuilder source) {
816: return getBySourceDestinationRole(source.getNumber(), 0, 0);
817: }
818:
819: SortedSet<MMObjectNode> getBySource(int source) {
820: return getBySourceDestinationRole(source, 0, 0);
821: }
822:
823: SortedSet<MMObjectNode> getBySourceDestination(
824: MMObjectBuilder source, MMObjectBuilder destination) {
825: return getBySourceDestinationRole(source.getNumber(),
826: destination.getNumber(), 0);
827: }
828:
829: SortedSet<MMObjectNode> getBySourceDestination(int source,
830: int destination) {
831: return getBySourceDestinationRole(source, destination, 0);
832: }
833:
834: SortedSet<MMObjectNode> getBySourceDestinationRole(int source,
835: int destination, int role) {
836: // determine minimum value - corrects in case '-1' (common MMBase value for N.A.) is passed
837: int roleMin = role <= 0 ? 0 : role;
838: int destinationMin = destination <= 0 ? 0 : destination;
839: int sourceMin = source <= 0 ? 0 : source;
840:
841: // determine maximum value
842: int roleMax = role <= 0 ? 0 : role + 1; // i.e. source, destination, role
843: int destinationMax = role <= 0 ? destination + 1
844: : destination; // i.e. source, destination, 0
845: int sourceMax = (destination <= 0 && role <= 0) ? (source <= 0 ? 0
846: : source + 1)
847: : source; // i.e. source, 0, 0
848:
849: VirtualTypeRelNode fromTypeRelNode = new VirtualTypeRelNode(
850: sourceMin, destinationMin, roleMin);
851: VirtualTypeRelNode toTypeRelNode = new VirtualTypeRelNode(
852: sourceMax, destinationMax, roleMax);
853:
854: SortedSet<MMObjectNode> allowed = subSet(fromTypeRelNode,
855: toTypeRelNode);
856: return Collections.unmodifiableSortedSet(allowed);
857: }
858:
859: }
860:
861: /**
862: * An InverseTypeRelSet is a Set of typerel nodes. The TypeRel builder maintains such a Set of
863: * all typerel nodes for quick reference.
864: *
865: * @since MMBase-1.6.2
866: */
867: protected class InverseTypeRelSet extends TreeSet<MMObjectNode> {
868:
869: protected InverseTypeRelSet() {
870: super (new Comparator<MMObjectNode>() {
871: // sorted by destination, source, role
872: public int compare(MMObjectNode n1, MMObjectNode n2) {
873: int i1 = n1.getIntValue("dnumber");
874: int i2 = n2.getIntValue("dnumber");
875: if (i1 != i2)
876: return i1 - i2;
877:
878: i1 = n1.getIntValue("snumber");
879: i2 = n2.getIntValue("snumber");
880: if (i1 != i2)
881: return i1 - i2;
882:
883: i1 = n1.getIntValue("rnumber");
884: i2 = n2.getIntValue("rnumber");
885: if (i1 != -1 && i2 != -1 && i1 != i2)
886: return i1 - i2;
887: return 0;
888: }
889: });
890: }
891:
892: // make sure only MMObjectNode's are added
893: public boolean add(MMObjectNode object) {
894: return super .add(object);
895: }
896:
897: SortedSet<MMObjectNode> getByDestination(
898: MMObjectBuilder destination) {
899: return getByDestinationSourceRole(0, destination
900: .getNumber(), 0);
901: }
902:
903: SortedSet<MMObjectNode> getByDestination(int destination) {
904: return getByDestinationSourceRole(0, destination, 0);
905: }
906:
907: SortedSet<MMObjectNode> getByDestinationSource(
908: MMObjectBuilder source, MMObjectBuilder destination) {
909: return getByDestinationSourceRole(source.getNumber(),
910: destination.getNumber(), 0);
911: }
912:
913: SortedSet<MMObjectNode> getByDestinationSource(int source,
914: int destination) {
915: return getByDestinationSourceRole(source, destination, 0);
916: }
917:
918: SortedSet<MMObjectNode> getByDestinationSourceRole(int source,
919: int destination, int role) {
920: // determine minimum value - corrects in case '-1' (common MMBase value for N.A.) is passed
921: int roleMin = role <= 0 ? 0 : role;
922: int sourceMin = source <= 0 ? 0 : source;
923: int destinationMin = destination <= 0 ? 0 : destination;
924:
925: // determine maximum value
926: int roleMax = role <= 0 ? 0 : role + 1; // i.e. source, destination, role
927: int sourceMax = role <= 0 ? (source <= 0 ? 0 : source + 1)
928: : source; // i.e. source, destination, 0
929: int destinationMax = (source <= 0 && role <= 0) ? destination + 1
930: : destination; // i.e. 0, destination, 0
931:
932: return Collections.unmodifiableSortedSet(subSet(
933: new VirtualTypeRelNode(sourceMin, destinationMin,
934: roleMin), new VirtualTypeRelNode(sourceMax,
935: destinationMax, roleMax)));
936: }
937:
938: }
939:
940: /**
941: * A VirtualTypeRelNode is a MMObjectNode which is added to the typerelset with extensions of
942: * the actual builders specified. So these entries are not in the database.
943: *
944: * @since MMBase-1.6.2
945: */
946: protected class VirtualTypeRelNode extends VirtualNode {
947:
948: VirtualTypeRelNode(int snumber, int dnumber) { // only for use in lookups
949: // We don't use this-constructor because some jvm get confused then
950: super (VirtualTypeRel.getVirtualTypeRel(TypeRel.this ));
951: setValue("snumber", snumber);
952: setValue("dnumber", dnumber);
953: setValue("rnumber", -1);
954: }
955:
956: VirtualTypeRelNode(int snumber) { // only for use in lookups
957: // We don't use this-constructor because some jvm get confused then
958: super (VirtualTypeRel.getVirtualTypeRel(TypeRel.this ));
959: setValue("snumber", snumber);
960: setValue("dnumber", -1);
961: setValue("rnumber", -1);
962: }
963:
964: VirtualTypeRelNode(int snumber, int dnumber, int rnumber) {
965: super (VirtualTypeRel.getVirtualTypeRel(TypeRel.this ));
966: setValue("snumber", snumber);
967: setValue("dnumber", dnumber);
968: setValue("rnumber", rnumber);
969: values = Collections.unmodifiableMap(values); // make sure it is not changed any more!
970: }
971: }
972:
973: }
|