Coverage Report - org.melati.poem.Column
 
Classes in this File Line Coverage Branch Coverage Complexity
Column
88%
162/184
68%
63/92
1.738
Column$1
100%
23/23
N/A
1.738
Column$2
100%
2/2
N/A
1.738
 
 1  
 /*
 2  
  * $Source$
 3  
  * $Revision$
 4  
  *
 5  
  * Copyright (C) 2000 William Chesters
 6  
  *
 7  
  * Part of Melati (http://melati.org), a framework for the rapid
 8  
  * development of clean, maintainable web applications.
 9  
  *
 10  
  * Melati is free software; Permission is granted to copy, distribute
 11  
  * and/or modify this software under the terms either:
 12  
  *
 13  
  * a) the GNU General Public License as published by the Free Software
 14  
  *    Foundation; either version 2 of the License, or (at your option)
 15  
  *    any later version,
 16  
  *
 17  
  *    or
 18  
  *
 19  
  * b) any version of the Melati Software License, as published
 20  
  *    at http://melati.org
 21  
  *
 22  
  * You should have received a copy of the GNU General Public License and
 23  
  * the Melati Software License along with this program;
 24  
  * if not, write to the Free Software Foundation, Inc.,
 25  
  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
 26  
  * GNU General Public License and visit http://melati.org to obtain the
 27  
  * Melati Software License.
 28  
  *
 29  
  * Feel free to contact the Developers of Melati (http://melati.org),
 30  
  * if you would like to work out a different arrangement than the options
 31  
  * outlined here.  It is our intention to allow Melati to be used by as
 32  
  * wide an audience as possible.
 33  
  *
 34  
  * This program is distributed in the hope that it will be useful,
 35  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 36  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 37  
  * GNU General Public License for more details.
 38  
  *
 39  
  * Contact details for copyright holder:
 40  
  *
 41  
  *     William Chesters <williamc At paneris.org>
 42  
  *     http://paneris.org/~williamc
 43  
  *     Obrechtstraat 114, 2517VX Den Haag, The Netherlands
 44  
  */
 45  
 
 46  
 package org.melati.poem;
 47  
 
 48  
 import java.io.PrintStream;
 49  
 import java.sql.PreparedStatement;
 50  
 import java.sql.ResultSet;
 51  
 import java.sql.SQLException;
 52  
 import java.util.Enumeration;
 53  
 
 54  
 import org.melati.poem.dbms.Dbms;
 55  
 import org.melati.poem.util.EmptyEnumeration;
 56  
 import org.melati.poem.util.StringUtils;
 57  
 
 58  
 /**
 59  
  * Abstract {@link Table} column which is extended by the generated classes.
 60  
  *
 61  
  * @author WilliamC At paneris.org
 62  
  * @param <T> The type of the Column
 63  
  * 
 64  
  */
 65  4723
 public abstract class Column<T> implements FieldAttributes<T> {
 66  4886
   @SuppressWarnings("rawtypes")
 67  
   private Table table = null;
 68  
   private String name;
 69  
   private String quotedName;
 70  
   private SQLPoemType<T> type;
 71  
   private DefinitionSource definitionSource;
 72  4886
   private ColumnInfo info = null;
 73  
 
 74  
   /**
 75  
    * Constructor.
 76  
    * @param table this column belongs to
 77  
    * @param name of this Column
 78  
    * @param type datatype
 79  
    * @param definitionSource where it is being defined from
 80  
    */
 81  
   public Column(
 82  
     Table<?> table,
 83  
     String name,
 84  
     SQLPoemType<T> type,
 85  4886
     DefinitionSource definitionSource) {
 86  4886
     this.table = table;
 87  4886
     this.name = name;
 88  4886
     this.quotedName = table.getDatabase().quotedName(name);
 89  4886
     this.type = type;
 90  4886
     this.definitionSource = definitionSource;
 91  4886
   }
 92  
 
 93  
   // 
 94  
   // ================
 95  
   //  Initialisation
 96  
   // ================
 97  
   // 
 98  
 
 99  
   /**
 100  
    * @return the underlying Dbms
 101  
    */
 102  
   Dbms dbms() {
 103  7338
     return getDatabase().getDbms();
 104  
   }
 105  
 
 106  
   <O> void unifyType(SQLPoemType<O> storeType, DefinitionSource source) {
 107  163
     PoemType<T> unified = dbms().canRepresent(storeType, type);
 108  163
     if (unified == null || !(unified instanceof SQLPoemType))
 109  0
       throw new TypeDefinitionMismatchException(this, storeType, source);
 110  
 
 111  163
     type = (SQLPoemType<T>) unified;
 112  163
   }
 113  
 
 114  
   void assertMatches(ResultSet colDesc)
 115  
       throws SQLException, TypeDefinitionMismatchException {
 116  4075
     PoemType<?> dbType = getDatabase().defaultPoemTypeOfColumnMetaData(colDesc);
 117  
 
 118  4075
     if (dbms().canRepresent(dbType, type) == null)
 119  0
       throw new TypeDefinitionMismatchException(
 120  
         this,
 121  
         dbType,
 122  
         DefinitionSource.sqlMetaData);
 123  4075
   }
 124  
 
 125  
   @SuppressWarnings("unchecked")
 126  
   void setColumnInfo(ColumnInfo columnInfo) {
 127  
     try {
 128  163
       unifyType(columnInfo.getType(), DefinitionSource.infoTables);
 129  163
       columnInfo.setColumn(this);
 130  163
       if (columnInfo.getDisplaylevel() == DisplayLevel.primary)
 131  11
         table.setDisplayColumn(this);
 132  163
       if (columnInfo.getSearchability() == Searchability.primary)
 133  9
         table.setSearchColumn(this);
 134  163
       info = columnInfo;
 135  163
       table.notifyColumnInfo(info);
 136  0
     } catch (Exception e) {
 137  0
       throw new UnexpectedExceptionPoemException(
 138  
         e,
 139  
         "Setting column info for " + name + " to " + columnInfo);
 140  163
     }
 141  163
   }
 142  
 
 143  
   protected DisplayLevel defaultDisplayLevel() {
 144  1022
     return DisplayLevel.summary;
 145  
   }
 146  
 
 147  
   protected Searchability defaultSearchability() {
 148  1552
     return Searchability.yes;
 149  
   }
 150  
 
 151  
   protected Integer defaultDisplayOrderPriority() {
 152  4079
     return null;
 153  
   }
 154  
 
 155  
   protected boolean defaultSortDescending() {
 156  4723
     return false;
 157  
   }
 158  
 
 159  
   protected String defaultDisplayName() {
 160  1586
     return StringUtils.capitalised(getName());
 161  
   }
 162  
 
 163  
   protected int defaultDisplayOrder() {
 164  31
     return 100;
 165  
   }
 166  
 
 167  
   protected String defaultDescription() {
 168  1331
     return null;
 169  
   }
 170  
 
 171  
   protected boolean defaultUserEditable() {
 172  3280
     return true;
 173  
   }
 174  
 
 175  
   protected boolean defaultUserCreateable() {
 176  3880
     return true;
 177  
   }
 178  
 
 179  
   protected boolean defaultIndexed() {
 180  4330
     return isTroidColumn();
 181  
   }
 182  
 
 183  
   protected boolean defaultUnique() {
 184  4168
     return isTroidColumn();
 185  
   }
 186  
 
 187  
   /**
 188  
    * @return the StandardIntegrityFix prevent
 189  
    */
 190  
   protected StandardIntegrityFix defaultIntegrityFix() {
 191  4523
     return StandardIntegrityFix.prevent;
 192  
   }
 193  
 
 194  
   protected int defaultWidth() {
 195  2843
     return 20;
 196  
   }
 197  
 
 198  
   protected int defaultHeight() {
 199  3148
     return 1;
 200  
   }
 201  
 
 202  
   protected int defaultPrecision() {
 203  4723
     return 22;
 204  
   }
 205  
 
 206  
   protected int defaultScale() {
 207  4723
     return 2;
 208  
   }
 209  
 
 210  
   protected String defaultRenderinfo() {
 211  4643
     return null;
 212  
   }
 213  
 
 214  
   @SuppressWarnings("unchecked")
 215  
   void createColumnInfo() throws PoemException {
 216  4799
     if (info == null) {
 217  4723
       info = (ColumnInfo)getDatabase().
 218  4723
           getColumnInfoTable().create(new Initialiser() {
 219  
         public void init(Persistent g) throws AccessPoemException {
 220  4723
           ColumnInfo i = (ColumnInfo)g;
 221  4723
           i.setName(getName());
 222  4723
           i.setDisplayname(defaultDisplayName());
 223  4723
           i.setDisplayorder(defaultDisplayOrder());
 224  4723
           i.setDescription(defaultDescription());
 225  4723
           i.setDisplaylevel(defaultDisplayLevel());
 226  4723
           i.setSearchability(defaultSearchability());
 227  4723
           i.setSortdescending(defaultSortDescending());
 228  4723
           i.setDisplayorderpriority(defaultDisplayOrderPriority());
 229  4723
           i.setTableinfoTroid(table.tableInfoID());
 230  4723
           i.setUsereditable(defaultUserEditable());
 231  4723
           i.setUsercreateable(defaultUserCreateable());
 232  4723
           i.setIndexed(defaultIndexed());
 233  4723
           i.setUnique(defaultUnique());
 234  4723
           i.setWidth(defaultWidth());
 235  4723
           i.setHeight(defaultHeight());
 236  4723
           i.setRenderinfo(defaultRenderinfo());
 237  4723
           i.setIntegrityfix(defaultIntegrityFix());
 238  4723
           i.setPrecision(defaultPrecision());
 239  4723
           i.setScale(defaultScale());
 240  4723
           getType().saveColumnInfo(i);
 241  4723
         }
 242  
       });
 243  
 
 244  
       // FIXME Repeating this in setColumnInfo(ColumnInfo) is a bad sign
 245  
 
 246  4723
       if (defaultDisplayLevel() == DisplayLevel.primary)
 247  427
         table.setDisplayColumn(this);
 248  4723
       if (defaultSearchability() == Searchability.primary)
 249  423
         table.setSearchColumn(this);
 250  
     }
 251  4799
   }
 252  
 
 253  
   void unifyWithIndex(String indexName, ResultSet index) throws SQLException, 
 254  
                               IndexUniquenessPoemException {
 255  603
     boolean indexUnique = !index.getBoolean("NON_UNIQUE");
 256  603
     if (indexUnique != getUnique()) 
 257  0
         throw new IndexUniquenessPoemException(
 258  
           this,
 259  
           indexName,
 260  0
           getUnique());
 261  603
   }
 262  
 
 263  
   // 
 264  
   // ===========
 265  
   //  Accessors
 266  
   // ===========
 267  
   // 
 268  
 
 269  
   /**
 270  
    * @return the Database our table is in
 271  
    */
 272  
   public final Database getDatabase() {
 273  16447
     return getTable().getDatabase();
 274  
   }
 275  
 
 276  
   /**
 277  
    * @return our Table
 278  
    */
 279  
   @SuppressWarnings("unchecked")
 280  
   public final Table<Persistent> getTable() {
 281  23480
     return table;
 282  
   }
 283  
 
 284  
   final void setTable(Table<?> table) {
 285  4881
     this.table = table;
 286  4881
   }
 287  
 
 288  
   /**
 289  
    * {@inheritDoc}
 290  
    * @see org.melati.poem.FieldAttributes#getName()
 291  
    */
 292  
   public final String getName() {
 293  19209
     return name;
 294  
   }
 295  
 
 296  
   /**
 297  
    * @return the name quoted appropriately for the DBMS
 298  
    */
 299  
   public final String quotedName() {
 300  10681
     return quotedName;
 301  
   }
 302  
 
 303  
   /**
 304  
    * @return the name in table.column notation
 305  
    */
 306  
   public final String fullQuotedName() {
 307  3387
     return table.quotedName() + "." + quotedName;
 308  
   }
 309  
 
 310  
   /**
 311  
    * Return a human readable name from the metadata.
 312  
    * 
 313  
    * {@inheritDoc}
 314  
    * @see org.melati.poem.FieldAttributes#getDisplayName()
 315  
    */
 316  
   public final String getDisplayName() {
 317  11
     return info.getDisplayname();
 318  
   }
 319  
 
 320  
   /**
 321  
    * {@inheritDoc}
 322  
    * @see org.melati.poem.FieldAttributes#getDescription()
 323  
    */
 324  
   public final String getDescription() {
 325  6868
     return info.getDescription();
 326  
   }
 327  
 
 328  
   /**
 329  
    * The troid (<TT>id</TT>) of the column's entry in the <TT>columninfo</TT>
 330  
    * table.  It will always have one (except during initialisation, which the
 331  
    * application programmer will never see).
 332  
    * @return the troid of our record in the columnInfo table
 333  
    */
 334  
   final Integer columnInfoID() {
 335  1015
     return info == null ? null : info.troid();
 336  
   }
 337  
 
 338  
   /**
 339  
    * @return the metadata record for this Column
 340  
    */
 341  
   public final ColumnInfo getColumnInfo() {
 342  89384
     return info;
 343  
   }
 344  
 
 345  
   /**
 346  
    * @return the defined or default DsiplayLevel
 347  
    */
 348  
   public DisplayLevel getDisplayLevel() {
 349  7
     return info == null ? defaultDisplayLevel() : info.getDisplaylevel();
 350  
   }
 351  
 
 352  
   /**
 353  
    * @param level the DisplayLevel to set
 354  
    */
 355  
   public void setDisplayLevel(DisplayLevel level) {
 356  4
     if (info != null)
 357  4
       info.setDisplaylevel(level);
 358  4
   }
 359  
 
 360  
   /**
 361  
    * @return our defined or default Searchabillity
 362  
    */
 363  
   public Searchability getSearchability() {
 364  7
     return info == null ? defaultSearchability() : info.getSearchability();
 365  
   }
 366  
 
 367  
   /**
 368  
    * @param searchability the Searchability to set
 369  
    */
 370  
   public void setSearchability(Searchability searchability) {
 371  2
     if (info != null)
 372  2
       info.setSearchability(searchability);
 373  2
   }
 374  
 
 375  
   /**
 376  
    * {@inheritDoc}
 377  
    * @see org.melati.poem.FieldAttributes#getUserEditable()
 378  
    */
 379  
   public final boolean getUserEditable() {
 380  1
     return !isTroidColumn()
 381  0
       && (info == null || info.getUsereditable().booleanValue());
 382  
   }
 383  
 
 384  
   /**
 385  
    * {@inheritDoc}
 386  
    * @see org.melati.poem.FieldAttributes#getUserCreateable()
 387  
    */
 388  
   public final boolean getUserCreateable() {
 389  1
     return !isTroidColumn()
 390  0
       && (info == null || info.getUsercreateable().booleanValue());
 391  
   }
 392  
 
 393  
   /**
 394  
    * @return the SQLPoemType of this Column
 395  
    */
 396  
   public final SQLPoemType<T> getSQLType() {
 397  5366
     return type;
 398  
   }
 399  
 
 400  
   /**
 401  
    * {@inheritDoc}
 402  
    * @see org.melati.poem.FieldAttributes#getType()
 403  
    */
 404  
   public final PoemType<T> getType() {
 405  307643
     return type;
 406  
   }
 407  
 
 408  
   /**
 409  
    * @return whether this is a Troid Column
 410  
    */
 411  
   public final boolean isTroidColumn() {
 412  18990
     return getType() instanceof TroidPoemType;
 413  
   }
 414  
 
 415  
   /**
 416  
    * A Deleted Column is a Column which signal whether 
 417  
    * the record has been soft-deleted.
 418  
    * @return whether this is a Deleted Column
 419  
    */
 420  
   public final boolean isDeletedColumn() {
 421  4241
     return getType() instanceof DeletedPoemType;
 422  
   }
 423  
 
 424  
   /**
 425  
    * {@inheritDoc}
 426  
    * @see org.melati.poem.FieldAttributes#getIndexed()
 427  
    */
 428  
   public final boolean getIndexed() {
 429  4199
     return getUnique() || info.getIndexed().booleanValue();
 430  
   }
 431  
 
 432  
   /**
 433  
    * @return whether this Column's values are unique in this table
 434  
    */
 435  
   public final boolean getUnique() {
 436  5356
     return isTroidColumn() || info.getUnique().booleanValue();
 437  
   }
 438  
 
 439  
   /**
 440  
    * @return the specified or default IntegrityFix
 441  
    */
 442  
   public IntegrityFix getIntegrityFix() {
 443  350
     IntegrityFix it = info.getIntegrityfix();
 444  350
     return it == null ? defaultIntegrityFix() : it;
 445  
   }
 446  
 
 447  
   /**
 448  
    * @param fix the IntegrityFix to set
 449  
    */
 450  
   public void setIntegrityFix(StandardIntegrityFix fix) {
 451  2
     info.setIntegrityfix(fix);
 452  2
   }
 453  
 
 454  
   /**
 455  
    * {@inheritDoc}
 456  
    * @see org.melati.poem.FieldAttributes#getRenderInfo()
 457  
    */
 458  
   public final String getRenderInfo() {
 459  1
     return info.getRenderinfo();
 460  
   }
 461  
 
 462  
   /**
 463  
    * {@inheritDoc}
 464  
    * @see org.melati.poem.FieldAttributes#getWidth()
 465  
    */
 466  
   public final int getWidth() {
 467  1
     return info.getWidth().intValue();
 468  
   }
 469  
 
 470  
   /**
 471  
    * {@inheritDoc}
 472  
    * @see org.melati.poem.FieldAttributes#getHeight()
 473  
    */
 474  
   public final int getHeight() {
 475  1
     return info.getHeight().intValue();
 476  
   }
 477  
 
 478  
   /**
 479  
    * @return the set or default DisplayOrderPriority
 480  
    */
 481  
   public final Integer getDisplayOrderPriority() {
 482  3209
     return info == null ? null : info.getDisplayorderpriority();
 483  
   }
 484  
 
 485  
   /**
 486  
    * Defaults to false.
 487  
    * @return whether this Column should be sorted in descending order 
 488  
    */
 489  
   public final boolean getSortDescending() {
 490  140
     return info.getSortdescending() == null
 491  
       ? false
 492  140
       : info.getSortdescending().booleanValue();
 493  
   }
 494  
 
 495  
   // 
 496  
   // ===========
 497  
   //  Utilities
 498  
   // ===========
 499  
   // 
 500  
 
 501  
   /**
 502  
    * {@inheritDoc}
 503  
    * @see java.lang.Object#toString()
 504  
    */
 505  
   public String toString() {
 506  77
     return table.getName()
 507  
       + "."
 508  
       + name
 509  
       + ": "
 510  77
       + getType().toString()
 511  
       + " (from "
 512  
       + definitionSource
 513  
       + ")";
 514  
   }
 515  
 
 516  
   /**
 517  
    * Print information about the structure of the Column to stdout.
 518  
    */
 519  
   public void dump() {
 520  1
     dump(System.out);
 521  1
   }
 522  
 
 523  
   /**
 524  
    * Print information to PrintStream. 
 525  
    * 
 526  
    * @param ps PrintStream to dump to
 527  
    */
 528  
   public void dump(PrintStream ps) {
 529  74
     ps.println(toString());
 530  74
   }
 531  
 
 532  
   /**
 533  
    * 
 534  
    * @param raw An object with an equivalent SQL type
 535  
    * @return the SQL euals clause that would capture equality with the raw
 536  
    */
 537  
   public String eqClause(Object raw) {
 538  3
     return fullQuotedName()
 539  2
       + (raw == null ? " IS NULL" : " = " + type.quotedRaw(raw));
 540  
   }
 541  
 
 542  4886
   private PreparedStatementFactory selectionWhereEq = null;
 543  
 
 544  
   private PreparedStatementFactory statementWhereEq() {
 545  5373
     if (selectionWhereEq == null)
 546  292
       selectionWhereEq =
 547  
         new PreparedStatementFactory(
 548  292
           getDatabase(),
 549  584
           getTable().selectionSQL(
 550  292
             getTable().quotedName(),
 551  292
             fullQuotedName()
 552  
               + " = "
 553  292
               + dbms().preparedStatementPlaceholder(getType()),
 554  
             null,
 555  
             false,
 556  
             true));
 557  
 
 558  5373
     return selectionWhereEq;
 559  
   }
 560  
 
 561  
   ResultSet resultSetWhereEq(Object raw) {
 562  2687
     SessionToken token = PoemThread.sessionToken();
 563  2687
     PreparedStatement ps =
 564  2687
       statementWhereEq().preparedStatement(token.transaction);
 565  2687
     type.setRaw(ps, 1, raw);
 566  2686
     return statementWhereEq().resultSet(token, ps);
 567  
   }
 568  
 
 569  
   /**
 570  
    * Not used in Melati or PanEris tree.
 571  
    * 
 572  
    * @param raw value 
 573  
    * @return an Enumeration of Integers 
 574  
    */
 575  
   /*
 576  
   Enumeration troidSelectionWhereEq(Object raw) {
 577  
     return new ResultSetEnumeration(resultSetWhereEq(raw)) {
 578  
       public Object mapped(ResultSet rs) throws SQLException {
 579  
         return new Integer(rs.getInt(1));
 580  
       }
 581  
     };
 582  
   }
 583  
   */
 584  
   
 585  
   /**
 586  
    * Get rows where column equal to value.
 587  
    * 
 588  
    * @param raw a raw value such as a String
 589  
    * @return an enumeration of Persistents
 590  
    */
 591  
   public Enumeration<Persistent> selectionWhereEq(Object raw) {
 592  2686
     return new ResultSetEnumeration<Persistent>(resultSetWhereEq(raw)) {
 593  
       public Object mapped(ResultSet rs) throws SQLException {
 594  1012
         return getTable().getObject(rs.getInt(1));
 595  
       }
 596  
     };
 597  
   }
 598  
 
 599  
   /**
 600  
    * Return the first one found or null if not found. 
 601  
    * 
 602  
    * @param raw Object of correct type for this Column
 603  
    * @return the first one found based upon default ordering
 604  
    */
 605  
   public Persistent firstWhereEq(Object raw) {
 606  1747
     Enumeration<Persistent> them = selectionWhereEq(raw);
 607  1747
     return them.hasMoreElements() ? (Persistent)them.nextElement() : null;
 608  
   }
 609  
 
 610  
   /**
 611  
    * Create a new CachedSelection of objects equal to this raw parameter. 
 612  
    * 
 613  
    * @param raw Object of correct type for this Column
 614  
    * @return a new CachedSelection of objects equal to raw.
 615  
    */
 616  
   @SuppressWarnings({ "rawtypes", "unchecked" })
 617  
   public CachedSelection cachedSelectionWhereEq(Object raw) {
 618  1
     return new CachedSelection(getTable(), eqClause(raw), null);
 619  
   }
 620  
 
 621  
   // 
 622  
   // =======================================
 623  
   //  Reading/setting the column in records
 624  
   // =======================================
 625  
   // 
 626  
 
 627  
  /**
 628  
   * Retrieves the field value, with locking,
 629  
   * for this {@link Column}.
 630  
   * 
 631  
   * @param g  the {@link Persistent} to read
 632  
   * @return the Object itself
 633  
   * @throws AccessPoemException 
 634  
   *         if the current <code>AccessToken</code> 
 635  
   *         does not confer read access rights
 636  
   */
 637  
   public abstract Object getRaw(Persistent g) throws AccessPoemException;
 638  
 
 639  
  /**
 640  
   * Retrieves the field value, without locking,
 641  
   * for this <code>Column</code>.
 642  
   * 
 643  
   * @param g  the {@link Persistent} to read
 644  
   * @return the Object without checks
 645  
   */
 646  
   public abstract Object getRaw_unsafe(Persistent g);
 647  
 
 648  
  /**
 649  
   * Sets the field value, with locking,
 650  
   * for this <code>Column</code>.
 651  
   * 
 652  
   * @param g  the {@link Persistent} to modify
 653  
   * @param raw the value to set the field to 
 654  
   * @throws AccessPoemException 
 655  
   *         if the current <code>AccessToken</code> 
 656  
   *         does not confer write access rights
 657  
   * @throws ValidationPoemException 
 658  
   *         if the raw value is not valid
 659  
   */
 660  
   public abstract void setRaw(Persistent g, Object raw)
 661  
     throws AccessPoemException, ValidationPoemException;
 662  
 
 663  
  /**
 664  
   * Sets the field value, without locking,
 665  
   * for this <code>Column</code>.
 666  
   * 
 667  
   * @param g  the {@link Persistent} to modify
 668  
   * @param raw the value to set the field to 
 669  
   */
 670  
   public abstract void setRaw_unsafe(Persistent g, Object raw);
 671  
 
 672  
  /**
 673  
   * Retrieves the field value, with locking and  access control 
 674  
   * for this <code>Column</code>.
 675  
   * 
 676  
   * @param g  the {@link Persistent} to modify
 677  
   * @return either the value or what is represented by the value
 678  
   * @throws AccessPoemException 
 679  
   *         if the current <code>AccessToken</code> 
 680  
   *         does not confer read access rights
 681  
   * @throws PoemException 
 682  
   *         if any problem occurs
 683  
   */
 684  
   public abstract Object getCooked(Persistent g)
 685  
     throws AccessPoemException, PoemException;
 686  
 
 687  
  /**
 688  
   * Sets the field value, with locking, access control 
 689  
   * and validation for this <code>Column</code>.
 690  
   * 
 691  
   * @param g  the {@link Persistent} to modify
 692  
   * @param cooked  the value to set
 693  
   * @throws AccessPoemException 
 694  
   *         if the current <code>AccessToken</code> 
 695  
   *         does not confer read access rights
 696  
   * @throws ValidationPoemException 
 697  
   *         if the value is not valid
 698  
   */
 699  
   public abstract void setCooked(Persistent g, Object cooked)
 700  
     throws AccessPoemException, ValidationPoemException;
 701  
 
 702  
   /**
 703  
    * Thrown when any unforeseen problem arises loading a {@link Column}.
 704  
    */
 705  
   public static class LoadException extends UnexpectedExceptionPoemException {
 706  
 
 707  
     private Column<?> column;
 708  
 
 709  
     /**
 710  
      * Constructor.
 711  
      * @param column Column relevant to
 712  
      * @param problem the Exception
 713  
      */
 714  
     public LoadException(Column<?> column, Exception problem) {
 715  
       super(problem);
 716  
       this.column = column;
 717  
     }
 718  
 
 719  
     /** @return Returns the message */
 720  
     public String getMessage() {
 721  
       return "An unexpected problem arose loading "
 722  
         + column
 723  
         + " from the "
 724  
         + "database:\n"
 725  
         + subException;
 726  
     }
 727  
 
 728  
   
 729  
     /**
 730  
      * @return Returns the column.
 731  
      */
 732  
      @SuppressWarnings("rawtypes")
 733  
     protected Column getColumn() {
 734  
       return column;
 735  
     }
 736  
   }
 737  
 
 738  
   /**
 739  
    * Load a Persistent field from a column of a ResultSet.
 740  
    * 
 741  
    * TODO Double validation
 742  
    * @param rs A <code>ResultSet</code>containing the value(s) to load
 743  
    * @param rsCol The index in the <tt>ResultSet</tt> of this {link column}
 744  
    * @param g The {@link Persistent} to load db values into
 745  
    * @throws LoadException
 746  
    */
 747  
   void load_unsafe(ResultSet rs, int rsCol, Persistent g)
 748  
     throws LoadException {
 749  
     try {
 750  16138
       setRaw_unsafe(g, type.getRaw(rs, rsCol));
 751  0
     } catch (Exception e) {
 752  0
       throw new LoadException(this, e);
 753  16138
     }
 754  16138
   }
 755  
 
 756  
   /**
 757  
    * Set value in a PreparedStatement which is to be used to save to database.
 758  
    *
 759  
    * TODO Double validation
 760  
    * @param g The {@link Persistent} containing unsaved values
 761  
    * @param ps <tt>PreparedStatement</tt> to save this column
 762  
    * @param psCol index of this Column in the PreparedStatement
 763  
    */
 764  
   void save_unsafe(Persistent g, PreparedStatement ps, int psCol) {
 765  
     try {
 766  136042
       type.setRaw(ps, psCol, getRaw_unsafe(g));
 767  0
     } catch (Exception e) {
 768  0
       throw new FieldContentsPoemException(this, e); 
 769  136042
     }
 770  136042
   }
 771  
 
 772  
   // 
 773  
   // ============
 774  
   //  Operations
 775  
   // ============
 776  
   // 
 777  
 
 778  
   /**
 779  
    * Return a Field of the same type as this Column from the Persistent.
 780  
    * @param g the Persistent
 781  
    * @return a Field
 782  
    */
 783  
   public abstract Field<T> asField(Persistent g);
 784  
 
 785  
   /**
 786  
    * Return a Field of the same type as this Column with default attributes.
 787  
    * @return the empty Field
 788  
    */
 789  
   public Field<T> asEmptyField() {
 790  2
     return new Field<T>((T) null, this);
 791  
   }
 792  
 
 793  
   /**
 794  
    * Thrown when any unforseen problem arises setting the value 
 795  
    * of a {@link Column}.
 796  
    */
 797  
   public static class SettingException extends NormalPoemException {
 798  
     /** The Persistent to which this Column belongs. */
 799  
     public Persistent persistent;
 800  
     /** The Column setting which caused the problem. */
 801  
     public Column<?> column;
 802  
     /** The description of the Column. */
 803  
     public String columnDesc;
 804  
 
 805  
     /**
 806  
      * Constructor.
 807  
      * @param persistent the Persistent with the problem
 808  
      * @param column he Column with the problem
 809  
      * @param trouble the problem
 810  
      */
 811  
     public SettingException(Persistent persistent, Column<?> column, Exception trouble) {
 812  
       super(trouble);
 813  
       this.persistent = persistent;
 814  
       this.column = column;
 815  
       columnDesc =
 816  
         "field `"
 817  
           + column.getDisplayName()
 818  
           + "' in object `"
 819  
           + persistent.displayString()
 820  
           + "' of type `"
 821  
           + column.getTable().getDisplayName()
 822  
           + "'";
 823  
     }
 824  
 
 825  
     /** @return the message */
 826  
     public String getMessage() {
 827  
       return "Unable to set " + columnDesc + "\n" + subException;
 828  
     }
 829  
   }
 830  
 
 831  
   /**
 832  
    * Set the value from its String representation, if possible.
 833  
    * 
 834  
    * Throws SettingException if the String value cannot be 
 835  
    * converted to the appropriate type 
 836  
    * @param g the Persistent to alter
 837  
    * @param rawString the String representation of the value to set
 838  
    */
 839  
   public void setRawString(Persistent g, String rawString) {
 840  
     Object raw;
 841  
     try {
 842  3
       raw = getType().rawOfString(rawString);
 843  1
     } catch (Exception e) {
 844  1
       throw new SettingException(g, this, e);
 845  2
     }
 846  2
     setRaw(g, raw);
 847  2
   }
 848  
 
 849  
   /**
 850  
    * Return an Enumeration of {@link Persistent}s from the 
 851  
    * Table this column refers to, if this is a reference column, 
 852  
    * otherwise the Empty Enumeration.
 853  
    * @param object A persistent of the type referred to by this column
 854  
    * @return an Enumeration {@link Persistent}s referencing this Column of the Persistent
 855  
    */
 856  
   public Enumeration<Persistent> referencesTo(Persistent object) {
 857  140
     if (getType() instanceof PersistentReferencePoemType)
 858  21
       if(((PersistentReferencePoemType) getType()).targetTable() == object.getTable()) { 
 859  3
         if (getType() instanceof ReferencePoemType)
 860  3
           return selectionWhereEq(object.troid());
 861  0
         else if (getType() instanceof StringKeyReferencePoemType)
 862  0
           return selectionWhereEq(
 863  0
               object.getField(
 864  0
                   ((StringKeyReferencePoemType)getType()).targetKeyName()).getRawString());
 865  0
         else throw new PoemBugPoemException("Unanticipated type " + getType());
 866  
       }
 867  137
     return new EmptyEnumeration<Persistent>();
 868  
   }
 869  
 
 870  
   /**
 871  
    * Ensures a row exists for which this column matches when compared with
 872  
    * the given {@link Persistent}.
 873  
    * 
 874  
    * The given object is used to create a new row if
 875  
    * necessary, in which case it will be assigned the next troid and
 876  
    * cached.
 877  
    * @param orCreate the Persistent to use as criteria and ensure
 878  
    * @return the existing or newly created Persistent
 879  
    */
 880  
   public Persistent ensure(Persistent orCreate) {
 881  1019
     Persistent there = firstWhereEq(getRaw_unsafe(orCreate));
 882  1019
     if (there == null) {
 883  379
       getTable().create(orCreate);
 884  379
       return orCreate;
 885  
     } else
 886  640
       return there;
 887  
   }
 888  
 
 889  
   
 890  
   /**
 891  
    * Find the next free value in an Integer column.
 892  
    * 
 893  
    * This is not used in Melati, but is used in Bibliomania. 
 894  
    * Throws AppBugPoemException if this Column is not an Integer column.
 895  
    * 
 896  
    * @param whereClause the SQL fragment to use
 897  
    * @return the incremented value 
 898  
    * @since 04/05/2000
 899  
    */
 900  
   public int firstFree(String whereClause) {
 901  4
     if (! (getType() instanceof IntegerPoemType)) 
 902  1
       throw new AppBugPoemException("firstFree called on a non Integer column");
 903  3
     getTable().readLock();
 904  3
     String querySelection = 
 905  
         quotedName
 906  
       + " + 1 "
 907  
       + "FROM "
 908  3
       + getTable().quotedName()
 909  
       + " AS t1 "
 910  
       + "WHERE "
 911  
       + (whereClause == null ? "" : "(t1." + whereClause + ") AND ")
 912  
       + "NOT EXISTS ("
 913  
       + "SELECT * FROM "
 914  3
       + getTable().quotedName()
 915  
       + " AS t2 "
 916  
       + "WHERE "
 917  
       + (whereClause == null ? "" : "(t2." + whereClause + ") AND ")
 918  
       + "t2."
 919  
       + quotedName
 920  
       + " = t1."
 921  
       + quotedName
 922  
       + " + 1) ";
 923  
 
 924  3
     String query = getDatabase().getDbms().selectLimit(querySelection, 1); 
 925  3
     ResultSet results = getDatabase().sqlQuery(query);
 926  
     try {
 927  3
       if (results.next())
 928  2
         return results.getInt(1);
 929  
       else
 930  1
         return 0;
 931  0
     } catch (SQLException e) {
 932  0
       throw new SQLSeriousPoemException(e);
 933  
     }
 934  
   }
 935  
 
 936  
   /**
 937  
    * @param colDescs from DatabaseMetaData.getColumns, with cursor at current row
 938  
    * @throws SQLException 
 939  
    */
 940  
   public void unifyWithMetadata(ResultSet colDescs) throws SQLException {
 941  4089
     if (info == null)
 942  39
       return;
 943  4050
     String remarks = colDescs.getString("REMARKS");
 944  4050
     if (getDescription() == null) {
 945  1242
       if (remarks != null && !remarks.trim().equals("")) {
 946  0
         info.setDescription(remarks);
 947  0
         getDatabase().log("Adding comment to column " + table + "." + name + 
 948  
             " from SQL metadata:" + remarks);
 949  
       }
 950  
     } else {
 951  2808
       if (!this.getDescription().equals(remarks)) {
 952  2808
         String sql = this.dbms().alterColumnAddCommentSQL(this, null); 
 953  2808
         if (sql != null)
 954  0
           this.getDatabase().modifyStructure(sql);          
 955  
       }
 956  
     }
 957  
       
 958  
     
 959  4050
   }
 960  
   
 961  
 }