001: /*
002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: * $Id: OrdinalManager.java 3634 2007-01-08 21:42:24Z gbevin $
007: */
008: package com.uwyn.rife.cmf.dam;
009:
010: import com.uwyn.rife.database.*;
011:
012: import com.uwyn.rife.database.queries.Select;
013: import com.uwyn.rife.database.queries.Update;
014: import com.uwyn.rife.datastructures.EnumClass;
015: import com.uwyn.rife.tools.ExceptionUtils;
016: import com.uwyn.rife.tools.InnerClassException;
017: import java.sql.SQLException;
018: import java.util.logging.Logger;
019:
020: /**
021: * This class makes it possible to easily manage an integer ordinal column
022: * that is typically used to determine the order of the rows in a specific
023: * table.
024: * <p>The basic version manages the ordinals for the entire table, but it's
025: * also possible to create an <code>OrdinalManager</code> that uses several
026: * independent ranges of ordinals according to a restricting integer column.
027: * <p>For example, consider the following '<code>article</code>' table:
028: * <pre>id INT
029: *categoryId INT
030: *ordinal INT
031: *name VARCHAR(30)</pre>
032: * <p>with the following rows:
033: * <pre> id | categoryId | ordinal | name
034: *----+------------+---------+-----------------------
035: * 2 | 1 | 0 | some article
036: * 0 | 1 | 1 | another one
037: * 3 | 1 | 2 | boom boom
038: * 1 | 2 | 0 | this is yet an article
039: * 5 | 2 | 1 | an article for you
040: * 4 | 3 | 0 | our latest article
041: * 6 | 3 | 1 | important one</pre>
042: * <p>You can clearly see three independent <code>ordinal</code> ranges
043: * according to the <code>categoryId</code> column.
044: * <p>The <code>OrdinalManager</code> allows you to easily change the order of
045: * the articles by moving them up and down with the provided methods: {@link
046: * #move(Direction, int) move}, {@link #up(int) up} and {@link #down(int) down}.
047: * It's also possible to do more complex manipulations by using the lower
048: * level methods: {@link #free(int) free}, {@link #update(int, int) update},
049: * {@link #tighten() tighten} and {@link #obtainInsertOrdinal()
050: * obtainInsertOrdinal}.
051: *
052: * @author Geert Bevin (gbevin[remove] at uwyn dot com)
053: * @version $Revision: 3634 $
054: * @since 1.0
055: */
056: public class OrdinalManager implements Cloneable {
057: /**
058: * Has to be used to indicate an upwards direction for the {@link
059: * #move(Direction, int) move} method.
060: */
061: public static final Direction UP = new Direction("up");
062: /**
063: * Has to be used to indicate an downwards direction for the {@link
064: * #move(Direction, int) move} method.
065: */
066: public static final Direction DOWN = new Direction("down");
067:
068: private Datasource mDatasource = null;
069: private DbQueryManager mDbQueryManager = null;
070: private String mTable = null;
071: private String mOrdinalColumn = null;
072: private String mRestrictColumn = null;
073: private Update mFreeMoveOrdinal = null;
074: private Select mGetFinalOrdinal = null;
075: private Select mGetOrdinals = null;
076: private Select mGetFinalOrdinalRestricted = null;
077: private Update mFreeMoveOrdinalRestricted = null;
078: private Select mGetOrdinalsRestricted = null;
079:
080: /**
081: * Creates a new <code>OrdinalManager</code> that manages ordinals
082: * globally for the specified table.
083: *
084: * @param datasource the datasource where the table is accessible
085: * @param table the name of the table that will be managed
086: * @param ordinalColumn the name of the column that contains the integer
087: * ordinals
088: * @since 1.0
089: */
090: public OrdinalManager(Datasource datasource, String table,
091: String ordinalColumn) {
092: if (null == datasource)
093: throw new IllegalArgumentException(
094: "datasource can't be null");
095: if (null == table)
096: throw new IllegalArgumentException("table can't be null");
097: if (null == ordinalColumn)
098: throw new IllegalArgumentException(
099: "ordinalColumn can't be null");
100:
101: mDatasource = datasource;
102:
103: mDbQueryManager = new DbQueryManager(datasource);
104:
105: mTable = table;
106: mOrdinalColumn = ordinalColumn;
107:
108: mGetFinalOrdinal = new Select(datasource);
109: mGetFinalOrdinal.field(mOrdinalColumn).from(mTable).orderBy(
110: mOrdinalColumn, Select.DESC);
111:
112: mFreeMoveOrdinal = new Update(datasource);
113: mFreeMoveOrdinal.table(mTable).whereParameter(mOrdinalColumn,
114: "current", "=").fieldParameter(mOrdinalColumn, "new");
115:
116: mGetOrdinals = new Select(datasource);
117: mGetOrdinals.field(mOrdinalColumn).from(mTable).orderBy(
118: mOrdinalColumn, Select.ASC);
119: }
120:
121: /**
122: * Creates a new <code>OrdinalManager</code> that manages ordinals for the
123: * specified table in independent ranges according to a restricting
124: * integer column.
125: *
126: * @param datasource the datasource where the table is accessible
127: * @param table the name of the table that will be managed
128: * @param ordinalColumn the name of the column that contains the integer
129: * ordinals
130: * @param restrictColumn the name of the column whose values will
131: * partition the ordinals in independent ranges
132: * @since 1.0
133: */
134: public OrdinalManager(Datasource datasource, String table,
135: String ordinalColumn, String restrictColumn) {
136: this (datasource, table, ordinalColumn);
137:
138: if (null == restrictColumn)
139: throw new IllegalArgumentException(
140: "restrictColumn can't be null");
141:
142: mRestrictColumn = restrictColumn;
143:
144: mGetFinalOrdinalRestricted = new Select(mDatasource);
145: mGetFinalOrdinalRestricted.field(mOrdinalColumn).from(mTable)
146: .whereParameter(mRestrictColumn, "=").orderBy(
147: mOrdinalColumn, Select.DESC);
148:
149: mFreeMoveOrdinalRestricted = new Update(mDatasource);
150: mFreeMoveOrdinalRestricted.table(mTable).whereParameter(
151: mRestrictColumn, "=").whereParameterAnd(mOrdinalColumn,
152: "current", "=").fieldParameter(mOrdinalColumn, "new");
153:
154: mGetOrdinalsRestricted = new Select(mDatasource);
155: mGetOrdinalsRestricted.field(mOrdinalColumn).from(mTable)
156: .whereParameter(mRestrictColumn, "=").orderBy(
157: mOrdinalColumn, Select.ASC);
158:
159: }
160:
161: /**
162: * Retrieves the name of the table of this <code>OrdinalManager</code>.
163: *
164: * @return the name of the table
165: * @since 1.0
166: */
167: public String getTable() {
168: return mTable;
169: }
170:
171: /**
172: * Retrieves the name of the ordinal column.
173: *
174: * @return the name of the ordinal column
175: * @since 1.0
176: */
177: public String getOrdinalColumn() {
178: return mOrdinalColumn;
179: }
180:
181: /**
182: * Retrieves the name of the restricting column.
183: *
184: * @return the name of the restricting column; or
185: * <p><code>null</code> if this <code>OrdinalManager</code> manages the
186: * table globally
187: * @since 1.0
188: */
189: public String getRestrictColumn() {
190: return mRestrictColumn;
191: }
192:
193: /**
194: * Moves the position of a row with a specific ordinal within the entire
195: * table.
196: *
197: * @param direction the direction in which to move: {@link
198: * OrdinalManager#UP OrdinalManager.UP} or {@link OrdinalManager#DOWN
199: * OrdinalManager.DOWN}
200: * @param ordinal the ordinal of the row that has to be moved
201: * @return <code>true</code> if the move was executed successfully; or
202: * <p><code>false</code> if this wasn't the case
203: * @see #move(Direction, long, int)
204: * @since 1.0
205: */
206: public boolean move(Direction direction, final int ordinal) {
207: if (direction == UP) {
208: return up(ordinal);
209: } else if (direction == DOWN) {
210: return down(ordinal);
211: }
212:
213: return false;
214: }
215:
216: /**
217: * Moves the position of a row with a specific ordinal within the range
218: * restricted by the provided ID.
219: *
220: * @param direction the direction in which to move: {@link
221: * OrdinalManager#UP OrdinalManager.UP} or {@link OrdinalManager#DOWN
222: * OrdinalManager.DOWN}
223: * @param restrictId the restriction ID value
224: * @param ordinal the ordinal of the row that has to be moved
225: * @return <code>true</code> if the move was executed successfully; or
226: * <p><code>false</code> if this wasn't the case
227: * @see #move(Direction, int)
228: * @since 1.0
229: */
230: public boolean move(Direction direction, final long restrictId,
231: final int ordinal) {
232: if (direction == UP) {
233: return up(restrictId, ordinal);
234: } else if (direction == DOWN) {
235: return down(restrictId, ordinal);
236: }
237:
238: return false;
239: }
240:
241: /**
242: * Moves a row with a specific ordinal upwards within the entire table.
243: *
244: * @param ordinal the ordinal of the row that has to be moved
245: * @return <code>true</code> if the move was executed successfully; or
246: * <p><code>false</code> if this wasn't the case
247: * @see #up(long, int)
248: * @since 1.0
249: */
250: public boolean up(final int ordinal) {
251: Boolean result = mDbQueryManager
252: .inTransaction(new DbTransactionUser() {
253: public Boolean useTransaction()
254: throws InnerClassException {
255: if (!free(ordinal - 1)) {
256: rollback();
257: }
258: if (!update(ordinal + 1, ordinal - 1)) {
259: rollback();
260: }
261: if (!tighten()) {
262: rollback();
263: }
264:
265: return true;
266: }
267: });
268:
269: return null != result && result.booleanValue();
270: }
271:
272: /**
273: * Moves a row with a specific ordinal upwards within the range restricted
274: * by the provided ID.
275: *
276: * @param restrictId the restriction ID value
277: * @param ordinal the ordinal of the row that has to be moved
278: * @return <code>true</code> if the move was executed successfully; or
279: * <p><code>false</code> if this wasn't the case
280: * @see #up(int)
281: * @since 1.0
282: */
283: public boolean up(final long restrictId, final int ordinal) {
284: Boolean result = mDbQueryManager
285: .inTransaction(new DbTransactionUser() {
286: public Boolean useTransaction()
287: throws InnerClassException {
288: if (!free(restrictId, ordinal - 1)) {
289: rollback();
290: }
291: if (!update(restrictId, ordinal + 1,
292: ordinal - 1)) {
293: rollback();
294: }
295: if (!tighten(restrictId)) {
296: rollback();
297: }
298:
299: return true;
300: }
301: });
302:
303: return null != result && result.booleanValue();
304: }
305:
306: /**
307: * Moves a row with a specific ordinal downwards within the entire table.
308: *
309: * @param ordinal the ordinal of the row that has to be moved
310: * @return <code>true</code> if the move was executed successfully; or
311: * <p><code>false</code> if this wasn't the case
312: * @see #down(long, int)
313: * @since 1.0
314: */
315: public boolean down(final int ordinal) {
316: Boolean result = mDbQueryManager
317: .inTransaction(new DbTransactionUser() {
318: public Boolean useTransaction()
319: throws InnerClassException {
320: if (!free(ordinal + 2)) {
321: rollback();
322: }
323: if (!update(ordinal, ordinal + 2)) {
324: rollback();
325: }
326: if (!tighten()) {
327: rollback();
328: }
329:
330: return true;
331: }
332: });
333:
334: return null != result && result.booleanValue();
335: }
336:
337: /**
338: * Moves a row with a specific ordinal downwards within the range
339: * restricted by the provided ID.
340: *
341: * @param restrictId the restriction ID value
342: * @param ordinal the ordinal of the row that has to be moved
343: * @return <code>true</code> if the move was executed successfully; or
344: * <p><code>false</code> if this wasn't the case
345: * @see #down(int)
346: * @since 1.0
347: */
348: public boolean down(final long restrictId, final int ordinal) {
349: Boolean result = mDbQueryManager
350: .inTransaction(new DbTransactionUser() {
351: public Boolean useTransaction()
352: throws InnerClassException {
353: if (!free(restrictId, ordinal + 2)) {
354: rollback();
355: }
356: if (!update(restrictId, ordinal, ordinal + 2)) {
357: rollback();
358: }
359: if (!tighten(restrictId)) {
360: rollback();
361: }
362:
363: return true;
364: }
365: });
366:
367: return null != result && result.booleanValue();
368: }
369:
370: /**
371: * Moves a row with a specific ordinal to the location of another ordinal
372: * within the entire table.
373: *
374: * @param fromOrdinal the ordinal of the row that has to be moved
375: * @param toOrdinal the ordinal of the row where the from row has will be
376: * put above
377: * @return <code>true</code> if the move was executed successfully; or
378: * <p><code>false</code> if this wasn't the case
379: * @see #down(int)
380: * @since 1.0
381: */
382: public boolean move(final int fromOrdinal, final int toOrdinal) {
383: Boolean result = mDbQueryManager
384: .inTransaction(new DbTransactionUser() {
385: public Boolean useTransaction()
386: throws InnerClassException {
387: if (!free(toOrdinal)) {
388: rollback();
389: }
390: int real_from = fromOrdinal;
391: if (toOrdinal < fromOrdinal) {
392: real_from++;
393: }
394: if (!update(real_from, toOrdinal)) {
395: rollback();
396: }
397: if (!tighten()) {
398: rollback();
399: }
400:
401: return true;
402: }
403: });
404:
405: return null != result && result.booleanValue();
406: }
407:
408: /**
409: * Moves a row with a specific ordinal to the location of another ordinal
410: * within the range restricted by the provided ID.
411: *
412: * @param restrictId the restriction ID value
413: * @param fromOrdinal the ordinal of the row that has to be moved
414: * @param toOrdinal the ordinal of the row where the from row has will be
415: * put above
416: * @return <code>true</code> if the move was executed successfully; or
417: * <p><code>false</code> if this wasn't the case
418: * @see #down(int)
419: * @since 1.0
420: */
421: public boolean move(final long restrictId, final int fromOrdinal,
422: final int toOrdinal) {
423: if (fromOrdinal == toOrdinal) {
424: return true;
425: }
426:
427: Boolean result = mDbQueryManager
428: .inTransaction(new DbTransactionUser() {
429: public Boolean useTransaction()
430: throws InnerClassException {
431: if (!free(restrictId, toOrdinal)) {
432: rollback();
433: }
434: int real_from = fromOrdinal;
435: if (toOrdinal < fromOrdinal) {
436: real_from++;
437: }
438: if (!update(restrictId, real_from, toOrdinal)) {
439: rollback();
440: }
441: if (!tighten(restrictId)) {
442: rollback();
443: }
444:
445: return true;
446: }
447: });
448:
449: return null != result && result.booleanValue();
450: }
451:
452: /**
453: * Frees up a slot for the specified ordinal within the entire table.,
454: * this is done by incrementing everything after it by 1 to make space.
455: * <p>So for example issuing the method <code>free(1)</code> on the
456: * following table:
457: * <pre> id | ordinal | name
458: *----+---------+-----------------------
459: * 2 | 0 | some article
460: * 0 | 1 | another one
461: * 1 | 2 | this is yet an article</pre>
462: * <p>will result in:
463: * <pre> id | ordinal | name
464: *----+---------+-----------------------
465: * 2 | 0 | some article
466: * 0 | 2 | another one
467: * 1 | 3 | this is yet an article</pre>
468: *
469: * @param ordinal an integer representing the ordinal to free
470: * @return <code>true</code> if the slot was freed up correctly; or
471: * <p><code>false</code> if the operation wasn't possible
472: * @see #free(long, int)
473: * @since 1.0
474: */
475: public boolean free(final int ordinal) {
476: Boolean result = mDbQueryManager
477: .inTransaction(new DbTransactionUser() {
478: public Boolean useTransaction()
479: throws InnerClassException {
480: if (ordinal < 0) {
481: return false;
482: }
483:
484: int last_ordinal = mDbQueryManager
485: .executeGetFirstInt(mGetFinalOrdinal);
486:
487: for (int i = last_ordinal; i >= ordinal; i--) {
488: final int current_ordinal = i;
489:
490: mDbQueryManager.executeUpdate(
491: mFreeMoveOrdinal,
492: new DbPreparedStatementHandler() {
493: public void setParameters(
494: DbPreparedStatement statement) {
495: statement
496: .setInt("current",
497: current_ordinal)
498: .setInt(
499: "new",
500: current_ordinal + 1);
501: }
502: });
503: }
504:
505: return true;
506: }
507: });
508:
509: return null != result && result.booleanValue();
510: }
511:
512: /**
513: * Frees up a slot for the specified ordinal within the range restricted
514: * by the provided ID, this is done by incrementing everything after it by
515: * 1 to make space.
516: * <p>So for example issuing the method <code>free(2, 0)</code> on the
517: * following table:
518: * <pre> id | categoryId | ordinal | name
519: *----+------------+---------+-----------------------
520: * 2 | 1 | 0 | some article
521: * 0 | 1 | 1 | another one
522: * 3 | 1 | 2 | boom boom
523: * 1 | 2 | 0 | this is yet an article
524: * 5 | 2 | 1 | an article for you
525: * 4 | 3 | 0 | our latest article
526: * 6 | 3 | 1 | important one</pre>
527: * <p>will result into:
528: * <pre> id | categoryId | ordinal | name
529: *----+------------+---------+-----------------------
530: * 2 | 1 | 0 | some article
531: * 0 | 1 | 1 | another one
532: * 3 | 1 | 2 | boom boom
533: * 1 | 2 | 1 | this is yet an article
534: * 5 | 2 | 2 | an article for you
535: * 4 | 3 | 0 | our latest article
536: * 6 | 3 | 1 | important one</pre>
537: *
538: * @param restrictId the id by which to restrict with
539: * @param ordinal an int representation the ordinal to free
540: * @return <code>true</code> if the slot was freed up correctly; or
541: * <p><code>false</code> if the operation wasn't possible
542: * @see #free(int)
543: * @since 1.0
544: */
545: public boolean free(final long restrictId, final int ordinal) {
546: Boolean result = mDbQueryManager
547: .inTransaction(new DbTransactionUser() {
548: public Boolean useTransaction()
549: throws InnerClassException {
550: if (ordinal < 0) {
551: return false;
552: }
553:
554: int last_ordinal = mDbQueryManager
555: .executeGetFirstInt(
556: mGetFinalOrdinalRestricted,
557: new DbPreparedStatementHandler() {
558: public void setParameters(
559: DbPreparedStatement statement) {
560: statement
561: .setLong(
562: mRestrictColumn,
563: restrictId);
564: }
565: });
566:
567: for (int i = last_ordinal; i >= ordinal; i--) {
568: final int current_ordinal = i;
569:
570: mDbQueryManager.executeUpdate(
571: mFreeMoveOrdinalRestricted,
572: new DbPreparedStatementHandler() {
573: public void setParameters(
574: DbPreparedStatement statement) {
575: statement
576: .setLong(
577: mRestrictColumn,
578: restrictId)
579: .setInt("current",
580: current_ordinal)
581: .setInt(
582: "new",
583: current_ordinal + 1);
584: }
585: });
586: }
587:
588: return true;
589: }
590: });
591:
592: return null != result && result.booleanValue();
593: }
594:
595: /**
596: * Changes the ordinal of a certain row to a new value.
597: * <p>This simply updates the value of the ordinal column and doesn't
598: * execute any other logic.
599: *
600: * @param currentOrdinal the ordinal of the row that has to be updated
601: * @param newOrdinal the new ordinal value
602: * @return <code>true</code> if the update was executed successfully; or
603: * <p><code>false</code> if this wasn't the case
604: * @see #update(long, int, int)
605: * @since 1.0
606: */
607: public boolean update(final int currentOrdinal, final int newOrdinal) {
608: Boolean result = mDbQueryManager
609: .inTransaction(new DbTransactionUser() {
610: public Boolean useTransaction()
611: throws InnerClassException {
612: return 0 != mDbQueryManager.executeUpdate(
613: mFreeMoveOrdinal,
614: new DbPreparedStatementHandler() {
615: public void setParameters(
616: DbPreparedStatement statement) {
617: statement.setInt("current",
618: currentOrdinal).setInt(
619: "new", newOrdinal);
620: }
621: });
622:
623: }
624: });
625:
626: return null != result && result.booleanValue();
627: }
628:
629: /**
630: * Changes the ordinal of a certain row with a specific restriction ID to
631: * a new value.
632: * <p>This simply updates the value of the ordinal column and doesn't
633: * execute any other logic.
634: *
635: * @param restrictId the id by which to restrict with
636: * @param currentOrdinal the ordinal of the row that has to be updated
637: * @param newOrdinal the new ordinal value
638: * @return <code>true</code> if the update was executed successfully; or
639: * <p><code>false</code> if this wasn't the case
640: * @see #update(int, int)
641: * @since 1.0
642: */
643: public boolean update(final long restrictId,
644: final int currentOrdinal, final int newOrdinal) {
645: Boolean result = mDbQueryManager
646: .inTransaction(new DbTransactionUser() {
647: public Boolean useTransaction()
648: throws InnerClassException {
649: return 0 != mDbQueryManager.executeUpdate(
650: mFreeMoveOrdinalRestricted,
651: new DbPreparedStatementHandler() {
652: public void setParameters(
653: DbPreparedStatement statement) {
654: statement.setLong(
655: mRestrictColumn,
656: restrictId).setInt(
657: "current",
658: currentOrdinal).setInt(
659: "new", newOrdinal);
660: }
661: });
662:
663: }
664: });
665:
666: return null != result && result.booleanValue();
667: }
668:
669: /**
670: * Tightens the series of ordinal within the entire table so that no
671: * spaces are present in between the ordinals.
672: * <p>So for example issuing the method <code>tighten()</code> on the
673: * following table:
674: * <pre> id | ordinal | name
675: *----+---------+-----------------------
676: * 2 | 0 | some article
677: * 0 | 2 | another one
678: * 1 | 5 | this is yet an article</pre>
679: * <p>will result in:
680: * <pre> id | ordinal | name
681: *----+---------+-----------------------
682: * 2 | 0 | some article
683: * 0 | 1 | another one
684: * 1 | 2 | this is yet an article</pre>
685: *
686: * @return <code>true</code> if the tightening was executed correctly; or
687: * <p><code>false</code> if the operation wasn't possible
688: * @see #tighten(long)
689: * @since 1.0
690: */
691: public boolean tighten() {
692: mDbQueryManager
693: .inTransaction(new DbTransactionUserWithoutResult() {
694: public void useTransactionWithoutResult()
695: throws InnerClassException {
696: mDbQueryManager.executeQuery(mGetOrdinals,
697: new TightenResultSetHandler());
698: }
699: });
700:
701: return true;
702: }
703:
704: /**
705: * Tightens the series of ordinal within the range restricted by the
706: * provided ID so that no spaces are present in between the ordinals.
707: * <p>So for example issuing the method <code>tighten(2)</code> on the
708: * following table:
709: * <pre> id | categoryId | ordinal | name
710: *----+------------+---------+-----------------------
711: * 2 | 1 | 1 | some article
712: * 0 | 1 | 2 | another one
713: * 3 | 1 | 7 | boom boom
714: * 1 | 2 | 4 | this is yet an article
715: * 5 | 2 | 8 | an article for you
716: * 4 | 3 | 4 | our latest article
717: * 6 | 3 | 5 | important one</pre>
718: * <p>will result in:
719: * <pre> id | categoryId | ordinal | name
720: *----+------------+---------+-----------------------
721: * 2 | 1 | 1 | some article
722: * 0 | 1 | 2 | another one
723: * 3 | 1 | 7 | boom boom
724: * 1 | 2 | 0 | this is yet an article
725: * 5 | 2 | 1 | an article for you
726: * 4 | 3 | 4 | our latest article
727: * 6 | 3 | 5 | important one</pre>
728: *
729: * @param restrictId the id by which to restrict with
730: * @return <code>true</code> if the tightening was executed correctly; or
731: * <p><code>false</code> if the operation wasn't possible
732: * @see #tighten()
733: * @since 1.0
734: */
735: public boolean tighten(final long restrictId) {
736: final TightenResultSetHandlerRestricted handler = new TightenResultSetHandlerRestricted(
737: restrictId) {
738: public void setParameters(DbPreparedStatement statement) {
739: statement.setLong(mRestrictColumn, restrictId);
740: }
741: };
742:
743: mDbQueryManager
744: .inTransaction(new DbTransactionUserWithoutResult() {
745: public void useTransactionWithoutResult()
746: throws InnerClassException {
747: mDbQueryManager.executeQuery(
748: mGetOrdinalsRestricted, handler);
749: }
750: });
751:
752: return handler.isTightened();
753: }
754:
755: /**
756: * Returns the next freely available ordinal that can be used to insert a
757: * new row behind all the other rows in the entire table.
758: * <p>So for example issuing the method <code>obtainInsertOrdinal()</code>
759: * on the following table:
760: * <pre> id | ordinal | name
761: *----+---------+-----------------------
762: * 2 | 0 | some article
763: * 0 | 1 | another one
764: * 1 | 2 | this is yet an article</pre>
765: * <p>Will return the value <code>3</code>.
766: *
767: * @return the requested ordinal; or
768: * <p><code>0</code> if no ordinals are present within the table yet
769: * @see #obtainInsertOrdinal(long)
770: * @since 1.0
771: */
772: public int obtainInsertOrdinal() {
773: // return next ordinal to use
774: // +1 because mGetFinalOrdinal returns the last ordinal
775: // in the db series
776: return mDbQueryManager.executeGetFirstInt(mGetFinalOrdinal) + 1;
777: }
778:
779: /**
780: * Returns the next freely available ordinal that can be used to insert a
781: * new row behind all the other rows in the range restricted by the
782: * provided ID.
783: * <p>So for example issuing the method
784: * <code>obtainInsertOrdinal(3)</code> on the following table:
785: * <pre> id | categoryId | ordinal | name
786: *----+------------+---------+-----------------------
787: * 2 | 1 | 0 | some article
788: * 0 | 1 | 1 | another one
789: * 3 | 1 | 2 | boom boom
790: * 1 | 2 | 0 | this is yet an article
791: * 5 | 2 | 1 | an article for you
792: * 4 | 3 | 0 | our latest article
793: * 6 | 3 | 1 | important one</pre>
794: * <p>Will return the value <code>2</code>.
795: *
796: * @param restrictId the id by which to restrict with
797: * @return the requested ordinal; or
798: * <p><code>0</code> if no ordinals are present within the range yet
799: * @see #obtainInsertOrdinal()
800: * @since 1.0
801: */
802: public int obtainInsertOrdinal(final long restrictId) {
803: // return next ordinal to use
804: int ordinal = mDbQueryManager.executeGetFirstInt(
805: mGetFinalOrdinalRestricted,
806: new DbPreparedStatementHandler() {
807: public void setParameters(
808: DbPreparedStatement statement) {
809: statement.setLong(mRestrictColumn, restrictId);
810: }
811: });
812:
813: return ordinal + 1;
814: }
815:
816: /**
817: * Simply clones the instance with the default clone method. This creates
818: * a shallow copy of all fields and the clone will in fact just be another
819: * reference to the same underlying data. The independence of each cloned
820: * instance is consciously not respected since they rely on resources that
821: * can't be cloned.
822: *
823: * @since 1.0
824: */
825: public Object clone() {
826: try {
827: return super .clone();
828: } catch (CloneNotSupportedException e) {
829: ///CLOVER:OFF
830: // this should never happen
831: Logger.getLogger("com.uwyn.rife.cmf").severe(
832: ExceptionUtils.getExceptionStackTrace(e));
833: return null;
834: ///CLOVER:ON
835: }
836: }
837:
838: private class TightenResultSetHandler extends
839: DbPreparedStatementHandler {
840: private int mCount = 0;
841:
842: public TightenResultSetHandler() {
843: }
844:
845: public Object concludeResults(DbResultSet resultset)
846: throws SQLException {
847: while (resultset.next()) {
848: int ordinal = resultset.getInt(mOrdinalColumn);
849: if (ordinal != mCount) {
850: update(ordinal, mCount);
851: }
852: mCount++;
853: }
854:
855: return null;
856: }
857: }
858:
859: private class TightenResultSetHandlerRestricted extends
860: DbPreparedStatementHandler {
861: private int mCount = 0;
862: private long mRestrictId = -1;
863:
864: private boolean mTightened = false;
865:
866: public TightenResultSetHandlerRestricted(long restrictId) {
867: mRestrictId = restrictId;
868: }
869:
870: public Object concludeResults(DbResultSet resultset)
871: throws SQLException {
872: while (resultset.next()) {
873: mTightened = true;
874:
875: int ordinal = resultset.getInt(mOrdinalColumn);
876: if (ordinal != mCount) {
877: update(mRestrictId, ordinal, mCount);
878: }
879: mCount++;
880: }
881:
882: return null;
883: }
884:
885: public boolean isTightened() {
886: return mTightened;
887: }
888: }
889:
890: public static class Direction extends EnumClass<String> {
891: private Direction(String identifier) {
892: super (identifier);
893: }
894:
895: public static Direction getDirection(String name) {
896: return getMember(Direction.class, name);
897: }
898: }
899: }
|