/* GASH 2 NumericDBField.java The GANYMEDE object storage system. Created: 2 July 1996 Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu ----------------------------------------------------------------------- Ganymede Directory Management System Copyright (C) 1996-2013 The University of Texas at Austin Ganymede is a registered trademark of The University of Texas at Austin Contact information Web site: http://www.arlut.utexas.edu/gash2 Author Email: ganymede_author@arlut.utexas.edu Email mailing list: ganymede@arlut.utexas.edu US Mail: Computer Science Division Applied Research Laboratories The University of Texas at Austin PO Box 8029, Austin TX 78713-8029 Telephone: (512) 835-3200 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package arlut.csd.ganymede.server; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.Vector; import arlut.csd.ganymede.common.ReturnVal; import arlut.csd.ganymede.rmi.num_field; import arlut.csd.Util.TranslationService; /*------------------------------------------------------------------------------ class NumericDBField ------------------------------------------------------------------------------*/ /** * <p>NumericDBField is a subclass of {@link * arlut.csd.ganymede.server.DBField DBField} for the storage and * handling of numeric fields in the {@link * arlut.csd.ganymede.server.DBStore DBStore} on the Ganymede * server.</p> * * <p>The Ganymede client talks to NumericDBFields through the * {@link arlut.csd.ganymede.rmi.num_field num_field} RMI interface.</p> */ public class NumericDBField extends DBField implements num_field { /** * TranslationService object for handling string localization in the * Ganymede server. */ static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.server.NumericDBField"); /** * <p>Receive constructor. Used to create a NumericDBField from a * {@link arlut.csd.ganymede.server.DBStore DBStore}/{@link arlut.csd.ganymede.server.DBJournal DBJournal} * DataInput stream.</p> */ NumericDBField(DBObject owner, DataInput in, DBObjectBaseField definition) throws IOException { super(owner, definition.getID()); this.value = null; receive(in, definition); } /** * <p>No-value constructor. Allows the construction of a * 'non-initialized' field, for use where the * {@link arlut.csd.ganymede.server.DBObjectBase DBObjectBase} * definition indicates that a given field may be present, * but for which no value has been stored in the * {@link arlut.csd.ganymede.server.DBStore DBStore}.</p> * * <p>Used to provide the client a template for 'creating' this * field if so desired.</p> */ NumericDBField(DBObject owner, DBObjectBaseField definition) { super(owner, definition.getID()); this.value = null; } /** * * Copy constructor. * */ public NumericDBField(DBObject owner, NumericDBField field) { super(owner, field.getID()); this.value = field.value; } /** * * Scalar value constructor. * */ public NumericDBField(DBObject owner, int value, DBObjectBaseField definition) { super(owner, definition.getID()); this.value = Integer.valueOf(value); } /** * * Vector value constructor. * */ public NumericDBField(DBObject owner, Vector values, DBObjectBaseField definition) { super(owner, definition.getID()); throw new IllegalArgumentException("vector constructor called on scalar field"); } @Override public Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } @Override void emit(DataOutput out) throws IOException { out.writeInt(((Integer) value).intValue()); } @Override void receive(DataInput in, DBObjectBaseField definition) throws IOException { value = Integer.valueOf(in.readInt()); } /** * <p>This method is used when the database is being dumped, to write * out this field to disk.</p> */ @Override synchronized void emitXML(XMLDumpContext xmlOut) throws IOException { xmlOut.startElementIndent(this.getXMLName()); emitIntXML(xmlOut, value()); xmlOut.endElement(this.getXMLName()); } public void emitIntXML(XMLDumpContext xmlOut, int value) throws IOException { xmlOut.write(java.lang.Integer.toString(value)); } // **** // // type-specific accessor methods // // **** public int value() { if (isVector()) { throw new IllegalArgumentException("scalar accessor called on vector field"); } return ((Integer) value).intValue(); } public int value(int index) { // numeric can't be a vector field throw new IllegalArgumentException("vector accessor called on scalar field"); } @Override public synchronized String getValueString() { if (value == null) { return ""; } return Integer.toString(this.value()); } /** * * For numbers, our default getValueString() encoding is adequate. * */ @Override public String getEncodingString() { return getValueString(); } /** * <p>Returns a String representing the change in value between this * field and orig. This String is intended for logging and email, * not for any sort of programmatic activity. The format of the * generated string is not defined, but is intended to be suitable * for inclusion in a log entry and in an email message.</p> * * <p>If there is no change in the field, null will be returned.</p> */ @Override public String getDiffString(DBField orig) { if (!(orig instanceof NumericDBField)) { throw new IllegalArgumentException("bad field comparison"); } if (this == orig) { return null; } NumericDBField origN = (NumericDBField) orig; if (origN.value() == this.value()) { return null; } StringBuilder result = new StringBuilder(); // "\tOld: {0,number,#}\n" result.append(ts.l("getDiffString.old", origN.getValueLocal())); // "\tNew: {0,number,#}\n" result.append(ts.l("getDiffString.new", this.getValueLocal())); return result.toString(); } // **** // // num_field specific methods // // **** /** * * Returns true if this field has max/min * limitations. * * @see arlut.csd.ganymede.rmi.num_field * */ public boolean limited() { DBEditObject eObj; /* -- */ if (!isEditable(true)) { throw new IllegalArgumentException("not applicable to a non-editable field/object"); } eObj = (DBEditObject) this.owner; return eObj.isIntLimited(this); } /** * * Returns the minimum acceptable value for this field if this field * has max/min limitations. * * @see arlut.csd.ganymede.rmi.num_field * */ public int getMinValue() { DBEditObject eObj; /* -- */ if (!isEditable(true)) { throw new IllegalArgumentException("not applicable to a non-editable field/object"); } eObj = (DBEditObject) this.owner; return eObj.minInt(this); } /** * * Returns the maximum acceptable value for this field if this field * has max/min limitations. * * @see arlut.csd.ganymede.rmi.num_field * */ public int getMaxValue() { DBEditObject eObj; /* -- */ if (!isEditable(true)) { throw new IllegalArgumentException("not applicable to a non-editable field/object"); } eObj = (DBEditObject) this.owner; return eObj.maxInt(this); } // **** // // Overridable methods for implementing intelligent behavior // // **** @Override public boolean verifyTypeMatch(Object o) { return ((o == null) || (o instanceof Integer)); } /** * Overridable method to verify that an object submitted to this * field has an appropriate value. * * This check is more limited than that of verifyNewValue().. all it * does is make sure that the object parameter passes the simple * value constraints of the field. verifyNewValue() does that plus * a bunch more, including calling to the DBEditObject hook for the * containing object type to see whether it happens to feel like * accepting the new value or not. * * verifyBasicConstraints() is used to double check for values that * are already in fields, in addition to being used as a likely * component of verifyNewValue() to verify new values. */ @Override public ReturnVal verifyBasicConstraints(Object o) { if (!verifyTypeMatch(o)) { // "Submitted value {0} is not a number! Major client error while trying to edit field {1} in object {2}." return Ganymede.createErrorDialog(this.getGSession(), ts.l("verifyBasicConstraints.error_title"), ts.l("verifyBasicConstraints.type_error", o, this.getName(), this.owner.getLabel())); } return null; } @Override public ReturnVal verifyNewValue(Object o) { DBEditObject eObj; Integer I; ReturnVal retVal; /* -- */ eObj = (DBEditObject) this.owner; if (o == null) { return eObj.verifyNewValue(this, null); // explicit for FindBugs } retVal = verifyBasicConstraints(o); if (!ReturnVal.didSucceed(retVal)) { return retVal; } I = (Integer) o; if (limited()) { if (getMinValue() > I.intValue()) { return Ganymede.createErrorDialog(this.getGSession(), ts.l("verifyNewValue.error_title"), ts.l("verifyNewValue.over_range", I, this.getName(), eObj.getLabel(), Integer.valueOf(this.getMinValue()))); } if (getMaxValue() < I.intValue()) { return Ganymede.createErrorDialog(this.getGSession(), ts.l("verifyNewValue.error_title"), ts.l("verifyNewValue.under_range", I, this.getName(), eObj.getLabel(), Integer.valueOf(this.getMinValue()))); } } // have our parent make the final ok on the value return eObj.verifyNewValue(this, o); } }