/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * ValueFormat.java * Creation date: (1/11/01 9:03:32 AM) * By: Luke Evans */ package org.openquark.gems.client.valueentry; import java.awt.Toolkit; import java.math.BigDecimal; import java.math.BigInteger; import java.text.ParseException; import java.util.Date; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.PlainDocument; import org.openquark.cal.compiler.PreludeTypeConstants; import org.openquark.cal.compiler.TypeExpr; import org.openquark.cal.module.Cal.Utilities.CAL_RelativeTime; import org.openquark.cal.module.Cal.Utilities.CAL_Time; import org.openquark.cal.valuenode.JTimeValueNode; import org.openquark.cal.valuenode.ListOfCharValueNode; import org.openquark.cal.valuenode.LiteralValueNode; import org.openquark.cal.valuenode.RelativeDateTimeValueNode; import org.openquark.cal.valuenode.RelativeDateValueNode; import org.openquark.cal.valuenode.RelativeTemporalValueNode; import org.openquark.cal.valuenode.RelativeTimeValueNode; import org.openquark.util.time.Time; import com.ibm.icu.text.DateFormat; import com.ibm.icu.util.TimeZone; /** * A document type accepting only input valid for various types of CAL constant * (e.g. Double, String, List(of Double, String)...). * Creation date: (1/11/01 9:03:32 AM) * @author Luke Evans */ class ValueFormat extends PlainDocument { private static final long serialVersionUID = 8382900701900352819L; // A flag to denote whether or not 'checking' is in effect. // If checking is not in effect, make sure you do not write // invalid gibberish into this document. private boolean isChecking; /** The ValueEntryPanel which uses this ValueFormat. */ private final ValueEntryPanel valueEntryPanel; /** * ValueFormat constructor. * @param valueEntryPanel The ValueEntryPanel which uses this ValueFormat. */ public ValueFormat(ValueEntryPanel valueEntryPanel) { this.isChecking = false; this.valueEntryPanel = valueEntryPanel; } /** * Get a value which is either a default value or a fixed up value. * If no change occurred (original value was okay), then returns a null. * Creation date: (1/12/2001 10:39:02 PM) * @return String */ public String getValidOrDefault() { String currentText; boolean changed = false; int len; try { currentText = getText(0, getLength()); } catch (BadLocationException e) { currentText = ""; } TypeExpr typeExpr = valueEntryPanel.getValueNode().getTypeExpr(); PreludeTypeConstants typeConstants = valueEntryPanel.valueEditorManager.getPreludeTypeConstants(); if (typeExpr.sameType(typeConstants.getDoubleType()) || typeExpr.sameType(typeConstants.getFloatType())) { // Remove trailing - len = currentText.length(); if (len > 0) { char lastChar = currentText.charAt(len - 1); if (lastChar == '-') { currentText = currentText.substring(0, len - 1); len--; changed = true; } } // Check the unique case where the user types in a '.' first // Then we want to show '0.' instead if (currentText.indexOf(".") == 0) { currentText = "0" + currentText; len++; changed = true; } // Remove trailing 'e' or 'E' if (len > 0) { char lastChar = currentText.charAt(len - 1); if (lastChar == 'e' || lastChar == 'E') { currentText = currentText.substring(0, len - 1); len--; changed = true; } } // If this is empty, set the text to zero if (len == 0) { currentText = "0.0"; len = 3; changed = true; } else { // If the value is a perfect whole number, then want the ending ".0". int indexOfPeriod = currentText.indexOf("."); if (indexOfPeriod == -1) { // Check for an 'e' within the value, or if the value doesn't end with a digit (eg. "Infinity"). int indexOfE = Math.max(currentText.indexOf('e'), currentText.indexOf('E')); boolean endsWithDigit = Character.isDigit(currentText.charAt(len - 1)); if (indexOfE < 0 && endsWithDigit) { currentText = currentText + ".0"; len += 2; changed = true; } } else if (indexOfPeriod == (currentText.length() - 1)) { currentText = currentText + "0"; len++; changed = true; } } } else if (typeExpr.sameType(typeConstants.getDecimalType())) { // Remove trailing - len = currentText.length(); if (len > 0) { char lastChar = currentText.charAt(len - 1); if (lastChar == '-') { currentText = currentText.substring(0, len - 1); len--; changed = true; } } // Check the unique case where the user types in a '.' first // Then we want to show '0.' instead if (currentText.indexOf(".") == 0) { currentText = "0" + currentText; len++; changed = true; } // Remove trailing 'e' or 'E' if (len > 0) { char lastChar = currentText.charAt(len - 1); if (lastChar == 'e' || lastChar == 'E') { currentText = currentText.substring(0, len - 1); len--; changed = true; } } // If this is empty, set the text to zero if (len == 0) { currentText = "0"; len = 1; changed = true; } } else if (typeExpr.sameType(typeConstants.getIntType()) || typeExpr.sameType(typeConstants.getIntegerType()) || typeExpr.sameType(typeConstants.getByteType()) || typeExpr.sameType(typeConstants.getShortType()) || typeExpr.sameType(typeConstants.getLongType())) { // Remove trailing - len = currentText.length(); if (len > 0) { char lastChar = currentText.charAt(len - 1); if (lastChar == '-') { currentText = currentText.substring(0, len - 1); len--; changed = true; } } // If this is empty, set the text to zero if (len == 0) { currentText = "0"; len = 1; changed = true; } } else if (typeExpr.sameType(typeConstants.getCharType())) { len = currentText.length(); if (len != 1) { // Default value is a space. currentText = " "; changed = true; } } else if (typeExpr.isNonParametricType(CAL_RelativeTime.TypeConstructors.RelativeDate)) { RelativeDateValueNode dateValueNode = (RelativeDateValueNode) valueEntryPanel.getValueNode(); // Check. If it's parseable, then it's valid (of course!). // If not parseable, then give a default value of the date in ValueNode. DateFormat dateFormat = RelativeTemporalValueNode.getDateFormat(DateFormat.FULL, -1); try { dateFormat.parse(currentText); } catch (ParseException pe) { currentText = dateFormat.format(dateValueNode.getDateValue()); changed = true; } } else if (typeExpr.isNonParametricType(CAL_RelativeTime.TypeConstructors.RelativeTime)) { RelativeTimeValueNode timeValueNode = (RelativeTimeValueNode) valueEntryPanel.getValueNode(); // Check. If it's parseable, then it's valid (of course!). // If not parseable, then give a default value of the time in ValueNode. DateFormat timeFormat = RelativeTemporalValueNode.getDateFormat(-1, DateFormat.MEDIUM); try { timeFormat.parse(currentText); } catch (ParseException pe) { currentText = timeFormat.format(timeValueNode.getTimeValue()); changed = true; } } else if (typeExpr.isNonParametricType(CAL_RelativeTime.TypeConstructors.RelativeDateTime)) { RelativeDateTimeValueNode dateTimeValueNode = (RelativeDateTimeValueNode) valueEntryPanel.getValueNode(); // Check. If it's parseable, then it's valid (of course!). // If not parseable, then give a default value of the date time in ValueNode. DateFormat dateTimeFormat = RelativeTemporalValueNode.getDateFormat(DateFormat.FULL, DateFormat.MEDIUM); try { dateTimeFormat.parse(currentText); } catch (ParseException pe) { currentText = dateTimeFormat.format(dateTimeValueNode.getDateTimeValue()); changed = true; } } else if (typeExpr.isNonParametricType(CAL_Time.TypeConstructors.Time)) { JTimeValueNode valueNode = (JTimeValueNode) valueEntryPanel.getValueNode(); // Check. If it's parseable, then it's valid (of course!). // If not parseable, then give a default value of the date time in ValueNode. DateFormat fmt=DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.LONG); fmt.setTimeZone(TimeZone.getDefault()); try { fmt.parse(currentText); } catch (ParseException pe) { currentText = fmt.format(valueNode.getJavaDate()); changed = true; } } else if (typeExpr.sameType(typeConstants.getCharListType())) { // Strings are valid. } else if (typeExpr.sameType(typeConstants.getStringType())) { // Strings are valid. } else { // All other types are assumed to be valid. No need for default. } // If we changed something, return the new string, else return null return changed ? currentText : null; } /** * Returns the ValueEntryPanel which uses this ValueFormat. * Note: In rare cases, a null can be returned if there are no such ValueEntryPanels. * (Eg: The TimeValueEditor uses a ValueFormat, but is not a ValueEntryPanel). * Creation date: (16/04/01 11:56:14 AM) * @return ValueEntryPanel */ ValueEntryPanel getValueEntryPanel() { return valueEntryPanel; } /** * Perform insertion edit operation. * Creation date: (1/12/2001 8:50:52 PM) * @param offs int the location * @param str String what to add * @param a AttributeSet the attributes * @exception BadLocationException Illegal location for insert. */ @Override public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { // Special check: If this document is not editable, do nothing. // Exception: if there is nothing in this document, then we must allow initialization. if (isChecking && !isEditable() && getLength() != 0) { return; } // Get pieces and formulate the proposed result String currentText = getText(0, getLength()); String beforeOffset = currentText.substring(0, offs); String afterOffset = currentText.substring(offs, currentText.length()); String proposedResult = beforeOffset + str + afterOffset; if (isChecking) { // If the proposed result is OK, allow the edit if (isValidTextChange(proposedResult)) { super.insertString(offs, str, a); updateValueNode(); } else { Toolkit.getDefaultToolkit().beep(); } } else { // No checking. Anything goes. super.insertString(offs, str, a); } } /** * Returns whether or not 'checking' is in effect. * If checking is not in effect, make sure you do not write * invalid gibberish into this document. * Creation date: (18/07/01 10:05:21 AM) * @return boolean */ public boolean isChecking() { return isChecking; } /** * Returns whether or not this Document is editable. * If it isn't, then any text change will be rejected. * Creation date: (11/04/01 10:28:35 AM) * @return boolean */ private boolean isEditable() { return valueEntryPanel.isEditable(); } /** * Check if this string image is a good char. * Creation date: (02/03/2001 8:51:00 AM) * @return boolean true if a good representation of a char * @param image String the image */ private static boolean isGoodChar(String image) { // An empty String (length 0) is okay. // A String with length 1 is always okay (since we allow any char). // A length > 1 is not okay. if (image.length() > 1) { return false; } else { return true; } } /** * Check if this string image is a good float representation * Creation date: (1/12/2001 9:08:55 PM) * @return boolean true if a good representation of a float * @param image String the image */ private static boolean isGoodDouble(String image) { // Image needs a bit of tweaking sometimes // If the last character is '-' or 'e' or image is empty, append '0' int len = image.length(); if (len == 0) { image = "0"; } else { char lastChar = image.charAt(len - 1); if (lastChar == '-' || lastChar == 'e' || lastChar == 'E') { image = image + '0'; } else if (lastChar == '.' && len == 1) { // If the user typed in '.' then we change it to "0." image = "0."; } } try { Double.parseDouble(image); return true; } catch (NumberFormatException e) { return false; } } /** * Check if this string image is a good integer representation. * Creation date: (1/12/2001 9:08:55 PM) * @return boolean true if a good representation of an integer * @param image String the image */ private static boolean isGoodInteger(String image) { // Image needs a bit of tweaking sometimes // If the image is empty, append '0' // If the last character is '-', append '0' int len = image.length(); if (len == 0) { image = "0"; } else { char lastChar = image.charAt(len - 1); if (lastChar == '-') { image = image + '0'; } } try { Integer.parseInt(image, 10); return true; } catch (NumberFormatException e) { return false; } } /** * Check if this string image is a good representation of a BigInteger. * We need a method separate from isGoodInteger, because Integer.parseInt * will reject values that won't fit into a long. * @param image String the image to check * @return boolean true if the image is a good representation of a BigInteger */ private static boolean isGoodBigInteger(String image) { // Image needs a bit of tweaking sometimes // If the image is empty, append '0' // If the last character is '-', append '0' int len = image.length(); if (len == 0) { image = "0"; } else { char lastChar = image.charAt(len - 1); if (lastChar == '-') { image = image + '0'; } } try { new BigInteger(image); return true; } catch (NumberFormatException e) { return false; } } /** * Checks to see if it's valid to change to the proposed text. * Note: Currently, only Double and Integer types are allowed to be changed. * Creation date: (27/2/2001 8:47:06 AM) * @return boolean true if a valid * @param text String the text to test */ public boolean isValidTextChange(String text) { boolean valid = false; TypeExpr typeExpr = valueEntryPanel.getValueNode().getTypeExpr(); PreludeTypeConstants typeConstants = valueEntryPanel.valueEditorManager.getPreludeTypeConstants(); if (typeExpr.sameType(typeConstants.getDoubleType()) || typeExpr.sameType(typeConstants.getFloatType()) || typeExpr.sameType(typeConstants.getDecimalType())) { //todoBI Float and Double should have different validation to take into account their ranges valid = isGoodDouble(text); } else if (typeExpr.sameType(typeConstants.getIntType()) || typeExpr.sameType(typeConstants.getByteType()) || typeExpr.sameType(typeConstants.getShortType()) || typeExpr.sameType(typeConstants.getLongType())) { //todoBI the integral types should have different validation to take into account their ranges valid = isGoodInteger(text); } else if (typeExpr.sameType(typeConstants.getIntegerType())) { valid = isGoodBigInteger(text); } else if (typeExpr.sameType(typeConstants.getCharType())) { valid = isGoodChar(text); } else if (typeExpr.sameType(typeConstants.getCharListType())) { // Strings are always good! valid = true; } else if (typeExpr.sameType(typeConstants.getStringType())) { // Strings are always good! valid = true; } else if (typeExpr.isNonParametricType(CAL_RelativeTime.TypeConstructors.RelativeDate) || typeExpr.isNonParametricType(CAL_RelativeTime.TypeConstructors.RelativeTime) || typeExpr.isNonParametricType(CAL_RelativeTime.TypeConstructors.RelativeDateTime) || typeExpr.isNonParametricType(CAL_Time.TypeConstructors.Time)) { // The specialized ValueEntryField should take care of validation. valid = true; } else { // All other types are currently not allowed to be edited. valid = false; } return valid; } /** * Perform deletion edit operation. * Creation date: (1/12/2001 8:57:39 PM) * @param offs int the location * @param len int the length to delete * @exception BadLocationException A bad location. */ @Override public void remove(int offs, int len) throws BadLocationException { // Special check: If this document is not editable, do nothing. if (isChecking && !isEditable()) { return; } // Get pieces and formulate the proposed result String currentText = getText(0, getLength()); String beforeOffset = currentText.substring(0, offs); String afterOffset = currentText.substring(len + offs, currentText.length()); String proposedResult = beforeOffset + afterOffset; if (isChecking) { // If the proposed result is OK, allow the edit if (isValidTextChange(proposedResult)) { super.remove(offs, len); updateValueNode(); } else { Toolkit.getDefaultToolkit().beep(); } } else { // No checking. Anything goes. super.remove(offs, len); } } /** * Sets the flag to denote whether or not 'checking' is in effect. * If checking is not in effect (checkingPolicy = false), make sure you do not write * invalid gibberish into this document. * Creation date: (27/02/01 8:40:17 AM) * @param checkingPolicy boolean */ public void setChecking(boolean checkingPolicy) { isChecking = checkingPolicy; } /** * Updates the ValueNode with the value in this document. * (Usually call this method upon a successful edit in checking mode). * Creation date: (27/02/01 9:27:45 AM) */ private void updateValueNode() { // First, check the string value to use. String newVal; newVal = getValidOrDefault(); if (newVal == null) { // The currentVal is completely valid. Use that. try { newVal = getText(0, getLength()); } catch (BadLocationException e) { newVal = ""; } } // Now, do the update to the valueNode. TypeExpr typeExpr = valueEntryPanel.getValueNode().getTypeExpr(); PreludeTypeConstants typeConstants = valueEntryPanel.valueEditorManager.getPreludeTypeConstants(); if (typeExpr.sameType(typeConstants.getCharType())) { // The first char should be the char we want as the value. Character charVal = new Character(newVal.charAt(0)); valueEntryPanel.replaceValueNode(new LiteralValueNode(charVal, typeExpr.copyTypeExpr()), true); } else if (typeExpr.sameType(typeConstants.getByteType())) { Double unRoundedVal = new Double(newVal); Byte byteVal = new Byte(unRoundedVal.byteValue()); valueEntryPanel.replaceValueNode(new LiteralValueNode(byteVal, typeExpr.copyTypeExpr()), true); } else if (typeExpr.sameType(typeConstants.getShortType())) { Double unRoundedVal = new Double(newVal); Short shortVal = new Short(unRoundedVal.shortValue()); valueEntryPanel.replaceValueNode(new LiteralValueNode(shortVal, typeExpr.copyTypeExpr()), true); } else if (typeExpr.sameType(typeConstants.getIntType())) { Double unRoundedVal = new Double(newVal); Integer integerVal = Integer.valueOf(unRoundedVal.intValue()); valueEntryPanel.replaceValueNode(new LiteralValueNode(integerVal, typeExpr.copyTypeExpr()), true); } else if (typeExpr.sameType(typeConstants.getIntegerType())) { BigDecimal unRoundedVal = new BigDecimal(newVal); BigInteger bigIntegerVal; // Math.round uses a rounding strategy that BigDecimal does not provide, so we have to do // a little bit of fiddling to round in an equivalent fashion. if(unRoundedVal.signum() >= 0) { bigIntegerVal = unRoundedVal.setScale(0, BigDecimal.ROUND_HALF_UP).toBigInteger(); } else { bigIntegerVal = unRoundedVal.setScale(0, BigDecimal.ROUND_HALF_DOWN).toBigInteger(); } valueEntryPanel.replaceValueNode(new LiteralValueNode(bigIntegerVal, typeExpr.copyTypeExpr()), true); } else if (typeExpr.sameType(typeConstants.getDecimalType())) { BigDecimal decimalVal = new BigDecimal(newVal); valueEntryPanel.replaceValueNode(new LiteralValueNode(decimalVal, typeExpr.copyTypeExpr()), true); } else if (typeExpr.sameType(typeConstants.getLongType())) { Double unRoundedVal = new Double(newVal); Long longVal = new Long(unRoundedVal.longValue()); valueEntryPanel.replaceValueNode(new LiteralValueNode(longVal, typeExpr.copyTypeExpr()), true); } else if (typeExpr.sameType(typeConstants.getFloatType())) { Float floatVal = new Float(newVal); valueEntryPanel.replaceValueNode(new LiteralValueNode(floatVal, typeExpr.copyTypeExpr()), true); } else if (typeExpr.sameType(typeConstants.getDoubleType())) { Double doubleVal = new Double(newVal); valueEntryPanel.replaceValueNode(new LiteralValueNode(doubleVal, typeExpr.copyTypeExpr()), true); } else if (typeExpr.sameType(typeConstants.getStringType())) { newVal = newVal.replace(ListOfCharValueNode.CHAR_RETURN_REPLACE, '\n'); valueEntryPanel.replaceValueNode(new LiteralValueNode(newVal, typeExpr.copyTypeExpr()), true); } else if (typeExpr.sameType(typeConstants.getCharListType())) { // First, must replace the return replacement chars with return. newVal = newVal.replace(ListOfCharValueNode.CHAR_RETURN_REPLACE, '\n'); valueEntryPanel.replaceValueNode(new ListOfCharValueNode(newVal, typeExpr.copyTypeExpr()), true); } else if (typeExpr.isNonParametricType(CAL_RelativeTime.TypeConstructors.RelativeDate)) { DateFormat dateFormat = RelativeTemporalValueNode.getDateFormat(DateFormat.FULL, -1); try { Date date = dateFormat.parse(newVal); valueEntryPanel.replaceValueNode(new RelativeDateValueNode(date, typeExpr.copyTypeExpr()), true); } catch (ParseException pe) { System.out.println("Error in updateValueNode: Could not parse the Text into a Date value."); } } else if (typeExpr.isNonParametricType(CAL_RelativeTime.TypeConstructors.RelativeTime)) { DateFormat timeFormat = RelativeTemporalValueNode.getDateFormat(-1, DateFormat.MEDIUM); try { Date date = timeFormat.parse(newVal); valueEntryPanel.replaceValueNode(new RelativeTimeValueNode(date, typeExpr.copyTypeExpr()), true); } catch (ParseException pe) { System.out.println("Error in updateValueNode: Could not parse the Text into a Date value."); } } else if (typeExpr.isNonParametricType(CAL_RelativeTime.TypeConstructors.RelativeDateTime)) { DateFormat dateTimeFormat = RelativeTemporalValueNode.getDateFormat(DateFormat.FULL, DateFormat.MEDIUM); try { Date date = dateTimeFormat.parse(newVal); valueEntryPanel.replaceValueNode(new RelativeDateTimeValueNode(date, typeExpr.copyTypeExpr()), true); } catch (ParseException pe) { System.out.println("Error in updateValueNode: Could not parse the Text into a Date value."); } } else if (typeExpr.isNonParametricType(CAL_Time.TypeConstructors.Time)) { DateFormat fmt=DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.LONG); fmt.setTimeZone(TimeZone.getDefault()); try { Date date = fmt.parse(newVal); Time time = Time.fromDate(date); valueEntryPanel.replaceValueNode(new JTimeValueNode(time, typeExpr.copyTypeExpr()), true); } catch (ParseException pe) { System.out.println("Error in updateValueNode: Could not parse the Text into a Date value."); } } else { throw new IllegalArgumentException("Error in updateValueNode:\nCurrently cannot handle this type."); } valueEntryPanel.valueChangedCheck(); } }