001: /*--------------------------------------------------------------------------*
002: | Copyright (C) 2006 Christopher Kohlhaas |
003: | |
004: | This program is free software; you can redistribute it and/or modify |
005: | it under the terms of the GNU General Public License as published by the |
006: | Free Software Foundation. A copy of the license has been included with |
007: | these distribution in the COPYING file, if not go to www.fsf.org . |
008: | |
009: | As a special exception, you are granted the permissions to link this |
010: | program with every library, which license fulfills the Open Source |
011: | Definition as published by the Open Source Initiative (OSI). |
012: *--------------------------------------------------------------------------*/
013: package org.rapla.entities.domain.internal;
014:
015: import java.text.SimpleDateFormat;
016: import java.util.Date;
017:
018: import org.rapla.components.util.Assert;
019: import org.rapla.components.util.DateTools;
020: import org.rapla.components.util.Tools;
021: import org.rapla.entities.RaplaType;
022: import org.rapla.entities.domain.Appointment;
023: import org.rapla.entities.domain.AppointmentBlockArray;
024: import org.rapla.entities.domain.Repeating;
025: import org.rapla.entities.domain.Reservation;
026: import org.rapla.entities.storage.Mementable;
027: import org.rapla.entities.storage.internal.SimpleEntity;
028:
029: public class AppointmentImpl extends SimpleEntity implements
030: Appointment, Mementable, java.io.Serializable {
031: // Don't forget to increase the serialVersionUID when you change the fields
032: private static final long serialVersionUID = 1;
033:
034: private Date start;
035: private Date end;
036: private RepeatingImpl repeating;
037: private boolean isWholeDaysSet = false;
038: /** set DE (DebugDisabled) to false for debuging output. You must change in code
039: because this flag is final for efficience reasons.*/
040: public final static boolean DE = true;
041: public final static String BUG = null;
042: public static String DD = null;
043:
044: final public RaplaType getRaplaType() {
045: return TYPE;
046: }
047:
048: private AppointmentImpl() {
049: }
050:
051: public AppointmentImpl(Date start, Date end) {
052: this .start = start;
053: this .end = end;
054: }
055:
056: void setParent(Reservation parent) {
057: getReferenceHandler().put("parent", parent);
058: if (parent != null)
059: setOwner(parent.getOwner());
060: }
061:
062: public void removeParent() {
063: getReferenceHandler().removeId("parent");
064: setOwner(null);
065: }
066:
067: public Date getStart() {
068: return start;
069: }
070:
071: public Date getEnd() {
072: return end;
073: }
074:
075: public void setReadOnly(boolean enable) {
076: super .setReadOnly(enable);
077: if (repeating != null)
078: repeating.setReadOnly(enable);
079: }
080:
081: public void move(Date newStart) {
082: long diff = this .end.getTime() - this .start.getTime();
083: move(newStart, new Date(newStart.getTime() + diff));
084: }
085:
086: public void move(Date start, Date end) {
087: checkWritable();
088: this .start = start;
089: this .end = end;
090: }
091:
092: public String toString() {
093: if (start != null && end != null)
094: return f(start.getTime(), end.getTime())
095: + ((repeating != null) ? (" [" + repeating
096: .toString())
097: + "]" : "");
098: else
099: return start + "-" + end;
100: }
101:
102: public Reservation getReservation() {
103: return (Reservation) getReferenceHandler().get("parent");
104: }
105:
106: public boolean isWholeDaysSet() {
107: return isWholeDaysSet;
108: }
109:
110: public void setWholeDays(boolean enable) {
111: checkWritable();
112: if (enable) {
113: if (start.getTime() != DateTools.cutDate(start.getTime()))
114: this .start = DateTools.cutDate(this .start);
115: if (start.getTime() != DateTools.cutDate(end.getTime()))
116: this .end = DateTools.fillDate(this .end);
117: }
118: isWholeDaysSet = enable;
119: }
120:
121: public int compareTo(Object obj) {
122: if (!(obj instanceof Appointment))
123: throw new ClassCastException("Appointment Expected");
124: Appointment a2 = (Appointment) obj;
125: Date start2 = a2.getStart();
126: Date end2 = a2.getEnd();
127: if (start.before(start2))
128: return -1;
129: if (start.after(start2))
130: return 1;
131: if (getEnd().before(end2))
132: return -1;
133: if (getEnd().after(end2))
134: return 1;
135: return 0;
136: }
137:
138: transient Date maxDate;
139:
140: /** returns the largest date that covers the appointment
141: and null if the appointments repeats forever.
142: */
143: public Date getMaxEnd() {
144: long end = (this .end != null) ? this .end.getTime() : 0;
145: if (repeating != null)
146: if (repeating.getEnd() != null)
147: end = Math.max(end, repeating.getEnd().getTime());
148: else
149: end = 0;
150: if (end == 0)
151: return null;
152:
153: // cache max date object
154: if (maxDate == null || maxDate.getTime() != end)
155: maxDate = new Date(end);
156: return maxDate;
157: }
158:
159: public Repeating getRepeating() {
160: return repeating;
161: }
162:
163: public void setRepeatingEnabled(boolean enableRepeating) {
164: checkWritable();
165: if (this .repeating == null) {
166: if (enableRepeating) {
167: this .repeating = new RepeatingImpl(Repeating.WEEKLY,
168: this );
169: }
170: } else {
171: if (!enableRepeating) {
172: this .repeating = null;
173: }
174: }
175: }
176:
177: public boolean isRepeatingEnabled() {
178: return repeating != null;
179: }
180:
181: public Date getFirstDifference(Appointment a2, Date maxDate) {
182: AppointmentBlockArray blocks1 = new AppointmentBlockArray();
183: createBlocks(start, maxDate, blocks1);
184: AppointmentBlockArray blocks2 = new AppointmentBlockArray();
185: a2.createBlocks(a2.getStart(), maxDate, blocks2);
186: // System.out.println("block sizes " + blocks1.size() + ", " + blocks2.size() );
187: for (int i = 0; i < blocks1.size(); i++) {
188: long a1Start = blocks1.getStartAt(i);
189: long a1End = blocks1.getEndAt(i);
190: if (i >= blocks2.size()) {
191: return new Date(a1Start);
192: }
193: long a2Start = blocks2.getStartAt(i);
194: long a2End = blocks2.getEndAt(i);
195: //System.out.println("a1Start " + a1Start + " a1End " + a1End);
196: //System.out.println("a2Start " + a2Start + " a2End " + a2End);
197: if (a1Start != a2Start)
198: return new Date(Math.min(a1Start, a2Start));
199:
200: if (a1End != a2End)
201: return new Date(Math.min(a1End, a2End));
202: }
203: if (blocks2.size() > blocks1.size()) {
204: return new Date(blocks2.getStartAt(blocks1.size()));
205: }
206: return null;
207: }
208:
209: public Date getLastDifference(Appointment a2, Date maxDate) {
210: AppointmentBlockArray blocks1 = new AppointmentBlockArray();
211: createBlocks(start, maxDate, blocks1);
212: AppointmentBlockArray blocks2 = new AppointmentBlockArray();
213: a2.createBlocks(a2.getStart(), maxDate, blocks2);
214: if (blocks2.size() > blocks1.size()) {
215: return new Date(blocks2.getEndAt(blocks1.size()));
216: }
217: if (blocks1.size() > blocks2.size()) {
218: return new Date(blocks1.getEndAt(blocks2.size()));
219: }
220: for (int i = blocks1.size() - 1; i >= 0; i--) {
221: long a1Start = blocks1.getStartAt(i);
222: long a1End = blocks1.getEndAt(i);
223: long a2Start = blocks2.getStartAt(i);
224: long a2End = blocks2.getEndAt(i);
225: if (a1End != a2End)
226: return new Date(Math.max(a1End, a2End));
227:
228: if (a1Start != a2Start)
229: return new Date(Math.max(a1Start, a2Start));
230: }
231: return null;
232: }
233:
234: public boolean matches(Appointment a2) {
235: if (!Tools.equalsOrBothNull(this .start, a2.getStart()))
236: return false;
237:
238: if (!Tools.equalsOrBothNull(this .end, a2.getEnd()))
239: return false;
240:
241: Repeating r1 = this .repeating;
242: Repeating r2 = a2.getRepeating();
243:
244: // No repeatings. The two appointments match
245: if (r1 == null && r2 == null) {
246: return true;
247: } else if (r1 == null || r2 == null) {
248: // one repeating is null the other not so the appointments don't match
249: return false;
250: }
251:
252: if (!r1.getType().equals(r2.getType()))
253: return false;
254:
255: if (r1.getInterval() != r2.getInterval())
256: return false;
257:
258: if (!Tools.equalsOrBothNull(r1.getEnd(), r2.getEnd()))
259: return false;
260:
261: // The repeatings match regulary, so we must test the exceptions
262: Date[] e1 = r1.getExceptions();
263: Date[] e2 = r2.getExceptions();
264: if (e1.length != e2.length) {
265: //System.out.println("Exception-length don't match");
266: return false;
267: }
268:
269: for (int i = 0; i < e1.length; i++)
270: if (!e1[i].equals(e2[i])) {
271: //System.out.println("Exception " + e1[i] + " doesn't match " + e2[i]);
272: return false;
273: }
274:
275: // Even the repeatings match, so we can return true
276: return true;
277: }
278:
279: public void createBlocks(Date start, Date end,
280: AppointmentBlockArray blocks) {
281: createBlocks(start, end, blocks, true);
282: }
283:
284: public void createBlocks(Date start, Date end,
285: AppointmentBlockArray blocks, boolean excludeExceptions) {
286: Assert.notNull(blocks);
287: Assert.notNull(start, "You must set a startDate");
288: Assert.notNull(end, "You must set an endDate");
289: processBlocks(start.getTime(), end.getTime(), blocks,
290: excludeExceptions);
291: }
292:
293: /* returns true if there is at least one block in an array. If the passed blocks array is not null it will contain all blocks
294: * that overlap the start,end period after a call.*/
295: private boolean processBlocks(long start, long end,
296: AppointmentBlockArray blocks, boolean excludeExceptions) {
297: boolean checkOnly = (blocks == null);
298: long c1 = start;
299: long c2 = end;
300: long s = this .start.getTime();
301: long e = this .end.getTime();
302: // if there is no repeating
303: if (repeating == null) {
304: if (s < c2 && e > c1) {
305: if (!checkOnly)
306: blocks.add(s, e, this , false);
307: return true;
308: }
309: return false;
310: }
311:
312: DD = DE ? BUG
313: : print("s = appointmentstart, e = appointmentend, c1 = intervalstart c2 = intervalend");
314: DD = DE ? BUG : print("s:" + n(s) + " e:" + n(e) + " c2:"
315: + n(c2) + " c1:" + n(c1));
316: if (s < c2 && e > c1
317: && (!repeating.isException(s) || !excludeExceptions)) {
318: if (checkOnly) {
319: return true;
320: } else {
321: blocks.add(s, e, this , repeating.isException(s));
322: }
323: }
324:
325: long l = repeating.getIntervalLength(s);
326: //System.out.println( "l in days " + l / DateTools.MILLISECONDS_PER_DAY );
327: Assert.isTrue(l > 0);
328: long timeFromStart = l;
329: if (repeating.isFixedIntervalLength()) {
330: timeFromStart = Math.max(l, ((c1 - e) / l) * l);
331: }
332: int maxNumber = repeating.getNumber();
333: long maxEnding = Long.MAX_VALUE;
334: if (maxNumber >= 0) {
335: maxEnding = repeating.getEnd().getTime();
336: }
337: DD = DE ? BUG
338: : print("l = repeatingInterval (in minutes), x = stepcount");
339: DD = DE ? BUG : print("Maxend " + f(maxEnding));
340: long currentPos = s + timeFromStart;
341: DD = DE ? BUG : print(" currentPos:" + n(currentPos) + " c2-s:"
342: + n(c2 - s) + " c1-e:" + n(c1 - e));
343: long blockLength = Math.max(0, e - s);
344: while (currentPos <= c2
345: && (maxNumber < 0 || currentPos <= maxEnding)) {
346: DD = DE ? BUG : print(" current pos:" + f(currentPos));
347: if ((currentPos + blockLength > c1) && (currentPos < c2)) {
348: boolean isException = repeating.isException(currentPos);
349: if ((!isException || !excludeExceptions)) {
350: if (checkOnly) {
351: return true;
352: } else {
353: blocks.add(currentPos,
354: currentPos + blockLength, this ,
355: isException);
356: }
357: }
358: }
359: currentPos += repeating.getIntervalLength(currentPos);
360:
361: }
362: return false;
363: }
364:
365: public boolean overlaps(Date start, Date end) {
366: return overlaps(start, end, true);
367: }
368:
369: public boolean overlaps(Date start, Date end,
370: boolean excludeExceptions) {
371: if (start == null && end == null)
372: return true;
373: if (start == null)
374: start = this .start;
375: if (end == null) {
376: // there must be an overlapp because there can't be infinity exceptions
377: if (getMaxEnd() == null)
378: return true;
379: end = getMaxEnd();
380: }
381:
382: if (getMaxEnd() != null && getMaxEnd().before(start))
383: return false;
384:
385: if (this .start.after(end))
386: return false;
387:
388: boolean overlaps = processBlocks(start.getTime(),
389: end.getTime(), null, excludeExceptions);
390: return overlaps;
391: }
392:
393: public boolean overlaps(long start, long end,
394: boolean excludeExceptions) {
395: if (getMaxEnd() != null && getMaxEnd().getTime() < start)
396: return false;
397:
398: if (this .start.getTime() > end)
399: return false;
400:
401: boolean overlaps = processBlocks(start, end, null,
402: excludeExceptions);
403: return overlaps;
404: }
405:
406: private static Date getOverlappingEnd(Repeating r1, Repeating r2) {
407: Date maxEnd = null;
408: if (r1.getEnd() != null)
409: maxEnd = r1.getEnd();
410: if (r2.getEnd() != null)
411: if (maxEnd != null && r2.getEnd().before(maxEnd))
412: maxEnd = r2.getEnd();
413: return maxEnd;
414: }
415:
416: public boolean overlaps(Appointment a2) {
417: if (a2 == this )
418: return true;
419: Date start2 = a2.getStart();
420: Date end2 = a2.getEnd();
421: long s1 = this .start.getTime();
422: long s2 = start2.getTime();
423: long e1 = this .end.getTime();
424: long e2 = a2.getEnd().getTime();
425: RepeatingImpl r1 = this .repeating;
426: RepeatingImpl r2 = (RepeatingImpl) a2.getRepeating();
427: DD = DE ? BUG : print("Testing overlap of");
428: DD = DE ? BUG : print(" A1: " + toString());
429: DD = DE ? BUG : print(" A2: " + a2.toString());
430:
431: if (r1 == null && r2 == null) {
432: if (e2 <= s1 || e1 <= s2)
433: return false;
434: return true;
435: }
436: if (r1 == null) {
437: return a2.overlaps(this .start, this .end);
438: }
439: if (r2 == null) {
440: return overlaps(start2, end2);
441: }
442:
443: // So both appointments have a repeating
444:
445: // If r2 has no exceptions we can check if a1 overlaps the first appointment of a2
446: if (overlaps(start2, end2) && !r2.isException(start2.getTime())) {
447: DD = DE ? BUG
448: : print("Primitive overlap for " + getReservation()
449: + " with " + a2.getReservation());
450: return true;
451: }
452:
453: // Check if appointments could overlap because of the end-dates of an repeating
454: Date end = getOverlappingEnd(r1, r2);
455: if (end != null && (end.getTime() <= s1 || end.getTime() <= s2))
456: return false;
457:
458: // We cant compare the fixed interval length here so we have to compare the blocks
459: if (!r1.isFixedIntervalLength()) {
460: return overlapsHard((AppointmentImpl) a2);
461: }
462: if (!r2.isFixedIntervalLength()) {
463: return ((AppointmentImpl) a2).overlapsHard(this );
464: }
465: // O.K. we found 2 Candidates for the hard way
466: long l1 = r1.getFixedIntervalLength();
467: long l2 = r2.getFixedIntervalLength();
468: // The greatest common divider of the two intervals
469: long gcd = gcd(l1, l2);
470: long startx1 = Math.max(0, (s2 - e1)) / l1;
471: long startx2 = Math.max(0, (s1 - e2)) / l2;
472:
473: DD = DE ? BUG
474: : print("l? = intervalsize for A?, x? = stepcount for A? ");
475: long max_x1 = l2 / gcd + startx1;
476: if (end != null && (end.getTime() - s1) / l1 + startx1 < max_x1)
477: max_x1 = (end.getTime() - s1) / l1 + startx1;
478: long max_x2 = l1 / gcd + startx2;
479: if (end != null && (end.getTime() - s2) / l2 + startx2 < max_x2)
480: max_x2 = (end.getTime() - s2) / l2 + startx2;
481: long x1 = startx1;
482: long x2 = startx2;
483:
484: DD = DE ? BUG : print("l1: " + n(l1) + " l2: " + n(l2)
485: + " gcd: " + n(gcd) + " start_x1: " + startx1
486: + " start_x2: " + startx2 + " max_x1: " + max_x1
487: + " max_x2: " + max_x2);
488: boolean overlaps = false;
489: long current1 = x1 * l1;
490: long current2 = x2 * l2;
491: long maxEnd1 = max_x1 * l1;
492: long maxEnd2 = max_x2 * l2;
493:
494: while (current1 <= maxEnd1 && current2 <= maxEnd2) {
495: // DD=DE?BUG: print("x1: " + x1 + " x2:" + x2);
496: DD = DE ? BUG : print(" A1: "
497: + f(s1 + current1, e1 + current1));
498: DD = DE ? BUG : print(" A2: "
499: + f(s2 + current2, e2 + current2));
500: if ((s1 + current1) < (e2 + current2)
501: && (e1 + current1) > (s2 + current2)) {
502: if (!isException(s1 + current1, s2 + current2, r2)) {
503: overlaps = true;
504: break;
505: }
506: }
507: if ((s1 + current1) < (s2 + current2))
508: current1 += l1;
509: else
510: current2 += l2;
511: }
512: if (overlaps)
513: DD = DE ? BUG : print("Appointments overlap");
514: else
515: DD = DE ? BUG : print("Appointments don't overlap");
516: return overlaps;
517: }
518:
519: private boolean overlapsHard(AppointmentImpl a2) {
520: RepeatingImpl r2 = (RepeatingImpl) a2.getRepeating();
521: AppointmentBlockArray array = new AppointmentBlockArray();
522: Date maxEnd = r2.getEnd();
523: if (maxEnd == null) {
524: // overlaps will be checked two 250 weeks (5 years) from now on
525: maxEnd = new Date(a2.getStart().getTime()
526: + DateTools.MILLISECONDS_PER_WEEK * 250);
527: }
528: createBlocks(getStart(), maxEnd, array);
529: for (int i = 0; i < array.size(); i++) {
530: long start = array.getStartAt(i);
531: long end = array.getEndAt(i);
532: if (a2.overlaps(start, end, true)) {
533: return true;
534: }
535: }
536: return false;
537: }
538:
539: /** the greatest common divider of a and b (Euklids Algorithm) */
540: public static long gcd(long a, long b) {
541: return (b == 0) ? a : gcd(b, a % b);
542: }
543:
544: /* Prueft im Abstand von "gap" millisekunden das Intervall von start bis ende
545: auf Ausnahmen. Gibt es fuer einen Punkt keine Ausnahme wird false zurueckgeliefert.
546: */
547: private boolean isException(long s1, long s2, RepeatingImpl r2) {
548: RepeatingImpl r1 = repeating;
549: Date end = getOverlappingEnd(r1, r2);
550: if (end == null)
551: return false;
552:
553: if ((!r1.hasExceptions() && !r2.hasExceptions()))
554: return false;
555:
556: long l1 = r1.getFixedIntervalLength();
557: long l2 = r2.getFixedIntervalLength();
558: long gap = (l1 * l2) / gcd(l1, l2);
559: Date[] exceptions1 = r1.getExceptions();
560: Date[] exceptions2 = r2.getExceptions();
561: DD = DE ? BUG : print(" Testing Exceptions for overlapp "
562: + f(s1) + " with " + f(s2) + " gap " + n(gap));
563: int i1 = 0;
564: int i2 = 0;
565: long x = 0;
566: if (exceptions1.length > i1)
567: DD = DE ? BUG : print("Exception a1: "
568: + fe(exceptions1[i1].getTime()));
569: if (exceptions2.length > i2)
570: DD = DE ? BUG : print("Exception a2: "
571: + fe(exceptions2[i2].getTime()));
572: while (s1 + x * gap < end.getTime()) {
573: DD = DE ? BUG : print("Looking for exception for gap " + x
574: + " s1: " + fe(s1 + x * gap) + " s2: "
575: + fe(s2 + x * gap));
576: while ((i1 < exceptions1.length)
577: && (exceptions1[i1].getTime()
578: + DateTools.MILLISECONDS_PER_DAY <= s1 + x
579: * gap)) {
580: i1++;
581: if (exceptions1.length > i1) {
582: DD = DE ? BUG : print("Exception a1: "
583: + fe(exceptions1[i1].getTime()));
584: }
585: }
586: while ((exceptions2.length > i2)
587: && (exceptions2[i2].getTime()
588: + DateTools.MILLISECONDS_PER_DAY <= s2 + x
589: * gap)) {
590: i2++;
591: if (exceptions2.length > i2) {
592: DD = DE ? BUG : print("Exception a2: "
593: + fe(exceptions2[i2].getTime()));
594: }
595: }
596: if ((exceptions1.length == i1)
597: || (s1 + x * gap >= exceptions1[i1].getTime()
598: + DateTools.MILLISECONDS_PER_DAY)) {
599: DD = DE ? BUG
600: : print("Exception from a1 doesnt match ");
601: if ((exceptions2.length == i2)
602: || (s2 + x * gap >= exceptions2[i2].getTime()
603: + DateTools.MILLISECONDS_PER_DAY)) {
604: DD = DE ? BUG
605: : print("Exception from a2 doesnt match. !");
606: return false;
607: } else {
608: DD = DE ? BUG : print("Exception from a2 matches!");
609: }
610: } else {
611: DD = DE ? BUG : print("Exception from a1 matches!");
612: }
613: DD = DE ? BUG : print("Exception found for gap " + x);
614: x++;
615: }
616: DD = DE ? BUG
617: : print("Exceptions found for every gap. No overlapping. ");
618: return true;
619: }
620:
621: private static String print(String string) {
622: if (string != null)
623: System.out.println(string);
624: return string;
625: }
626:
627: /* cuts the milliseconds and seconds. Usefull for debugging output.*/
628: private long n(long n) {
629: return n / (1000 * 60);
630: }
631:
632: /* Formats milliseconds as date. Usefull for debugging output.*/
633: static String f(long n) {
634: SimpleDateFormat format = new SimpleDateFormat(
635: "E yyyy-MM-dd HH:mm");
636: format.setTimeZone(DateTools.getTimeZone());
637: return format.format(new Date(n));
638: }
639:
640: /* Formats milliseconds as date without time. Usefull for debugging output.*/
641: static String fe(long n) {
642: SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
643: format.setTimeZone(DateTools.getTimeZone());
644: return format.format(new Date(n));
645: }
646:
647: /* Formats 2 dates in milliseconds as appointment. Usefull for debugging output.*/
648: static String f(long s, long e) {
649: Date start = new Date(s);
650: Date end = new Date(e);
651: SimpleDateFormat formatLong = new SimpleDateFormat(
652: "E yyyy-MM-dd HH:mm");
653: formatLong.setTimeZone(DateTools.getTimeZone());
654: SimpleDateFormat formatTime = new SimpleDateFormat("HH:mm");
655: formatTime.setTimeZone(DateTools.getTimeZone());
656: if (DateTools.isSameDay(s, e)) {
657: return formatLong.format(start) + "-"
658: + formatTime.format(end);
659: } else {
660: return formatLong.format(start) + "-"
661: + formatLong.format(end);
662: }
663: }
664:
665: static private void copy(AppointmentImpl source,
666: AppointmentImpl dest) {
667: dest.isWholeDaysSet = source.isWholeDaysSet;
668: dest.start = source.start;
669: dest.end = source.end;
670: dest.repeating = (RepeatingImpl) ((source.repeating != null) ? source.repeating
671: .clone()
672: : null);
673: if (dest.repeating != null)
674: dest.repeating.setAppointment(dest);
675: }
676:
677: public void copy(Object obj) {
678: super .copy((AppointmentImpl) obj);
679: copy((AppointmentImpl) obj, this );
680: }
681:
682: public Object deepClone() {
683: AppointmentImpl clone = new AppointmentImpl();
684: super .deepClone(clone);
685: copy(this , clone);
686: return clone;
687: }
688:
689: public Object clone() {
690: AppointmentImpl clone = new AppointmentImpl();
691: super.clone(clone);
692: copy(this, clone);
693: return clone;
694: }
695:
696: }
|