0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: package org.apache.wicket.markup.html.form;
0018:
0019: import java.text.Format;
0020: import java.text.SimpleDateFormat;
0021: import java.util.ArrayList;
0022: import java.util.Collections;
0023: import java.util.HashMap;
0024: import java.util.Iterator;
0025: import java.util.List;
0026: import java.util.Locale;
0027: import java.util.Map;
0028:
0029: import org.apache.wicket.Application;
0030: import org.apache.wicket.Component;
0031: import org.apache.wicket.Localizer;
0032: import org.apache.wicket.MarkupContainer;
0033: import org.apache.wicket.Page;
0034: import org.apache.wicket.WicketRuntimeException;
0035: import org.apache.wicket.markup.ComponentTag;
0036: import org.apache.wicket.model.IModel;
0037: import org.apache.wicket.util.convert.ConversionException;
0038: import org.apache.wicket.util.convert.IConverter;
0039: import org.apache.wicket.util.lang.Classes;
0040: import org.apache.wicket.util.string.PrependingStringBuffer;
0041: import org.apache.wicket.util.string.StringList;
0042: import org.apache.wicket.util.string.Strings;
0043: import org.apache.wicket.util.string.interpolator.MapVariableInterpolator;
0044: import org.apache.wicket.validation.IErrorMessageSource;
0045: import org.apache.wicket.validation.INullAcceptingValidator;
0046: import org.apache.wicket.validation.IValidatable;
0047: import org.apache.wicket.validation.IValidationError;
0048: import org.apache.wicket.validation.IValidator;
0049: import org.apache.wicket.validation.IValidatorAddListener;
0050: import org.apache.wicket.validation.ValidationError;
0051: import org.apache.wicket.version.undo.Change;
0052:
0053: /**
0054: * An HTML form component knows how to validate itself. Validators that
0055: * implement IValidator can be added to the component. They will be evaluated in
0056: * the order they were added and the first Validator that returns an error
0057: * message determines the error message returned by the component.
0058: * <p>
0059: * FormComponents are not versioned by default. If you need versioning for your
0060: * FormComponents, you will need to call Form.setVersioned(true), which will set
0061: * versioning on for the form and all form component children.
0062: * <p>
0063: * If this component is required and that fails, the error key that is used is
0064: * the "Required"; if the type conversion fails, it will use the key
0065: * "IConverter". The keys that can be used in both are:
0066: * <ul>
0067: * <li>${input}: the input the user did give</li>
0068: * <li>${name}: the name of the component that failed</li>
0069: * <li>${label}: the label of the component</li>
0070: * </ul>
0071: *
0072: * @author Jonathan Locke
0073: * @author Eelco Hillenius
0074: * @author Johan Compagner
0075: * @author Igor Vaynberg (ivaynberg)
0076: */
0077: public abstract class FormComponent extends LabeledWebMarkupContainer
0078: implements IFormVisitorParticipant {
0079: /**
0080: * Visitor for traversing form components
0081: */
0082: public static abstract class AbstractVisitor implements IVisitor {
0083: /**
0084: * @see org.apache.wicket.markup.html.form.FormComponent.IVisitor#formComponent(org.apache.wicket.markup.html.form.FormComponent)
0085: */
0086: public Object formComponent(IFormVisitorParticipant component) {
0087: if (component instanceof FormComponent) {
0088: onFormComponent((FormComponent) component);
0089: }
0090: return Component.IVisitor.CONTINUE_TRAVERSAL;
0091: }
0092:
0093: protected abstract void onFormComponent(
0094: FormComponent formComponent);
0095: }
0096:
0097: /**
0098: * Typesafe interface to code that is called when visiting a form component.
0099: */
0100: public static interface IVisitor {
0101: /**
0102: * Called when visiting a form component
0103: *
0104: * @param formComponent
0105: * The form component
0106: * @return component
0107: */
0108: public Object formComponent(
0109: IFormVisitorParticipant formComponent);
0110: }
0111:
0112: /**
0113: * {@link IErrorMessageSource} used for error messags against this form
0114: * components.
0115: *
0116: * @author ivaynberg
0117: */
0118: private class MessageSource implements IErrorMessageSource {
0119:
0120: /**
0121: * @see org.apache.wicket.validation.IErrorMessageSource#getMessage(java.lang.String)
0122: */
0123: public String getMessage(String key) {
0124: final FormComponent formComponent = FormComponent.this ;
0125:
0126: // retrieve prefix that will be used to construct message keys
0127: String prefix = formComponent.getValidatorKeyPrefix();
0128: if (Strings.isEmpty(prefix)) {
0129: prefix = "";
0130: }
0131:
0132: final Localizer localizer = formComponent.getLocalizer();
0133:
0134: String resource = prefix + getId() + "." + key;
0135:
0136: // First use the parent for resolving so that
0137: // form1.textfield1.Required can be used.
0138:
0139: // Note: It is important that the default value of "" is provided
0140: // to getString() not to throw a MissingResourceException or to
0141: // return a default string like "[Warning: String ..."
0142: String message = localizer.getString(resource,
0143: formComponent.getParent(), "");
0144:
0145: // If not found, than ...
0146: if (Strings.isEmpty(message)) {
0147: // Try a variation of the resource key
0148:
0149: resource = prefix + key;
0150:
0151: message = localizer.getString(resource, formComponent
0152: .getParent(), "");
0153: }
0154:
0155: if (Strings.isEmpty(message)) {
0156: // If still empty then use default
0157:
0158: resource = prefix + getId() + "." + key;
0159:
0160: // Note: It is important that the default value of "" is
0161: // provided
0162: // to getString() not to throw a MissingResourceException or to
0163: // return a default string like "[Warning: String ..."
0164: message = localizer.getString(resource, formComponent,
0165: "");
0166:
0167: // If not found, than ...
0168: if (Strings.isEmpty(message)) {
0169: // Try a variation of the resource key
0170:
0171: resource = prefix + key;
0172:
0173: message = localizer.getString(resource,
0174: formComponent, "");
0175: }
0176: }
0177:
0178: // convert empty string to null in case our default value of "" was
0179: // returned from localizer
0180: if (Strings.isEmpty(message)) {
0181: message = null;
0182: }
0183: return message;
0184: }
0185:
0186: /**
0187: * @see org.apache.wicket.validation.IErrorMessageSource#substitute(java.lang.String,
0188: * java.util.Map)
0189: */
0190: public String substitute(String string, Map vars)
0191: throws IllegalStateException {
0192: return new MapVariableInterpolator(string,
0193: addDefaultVars(vars), Application.get()
0194: .getResourceSettings()
0195: .getThrowExceptionOnMissingResource())
0196: .toString();
0197: }
0198:
0199: /**
0200: * Creates a new params map that additionaly contains the default input,
0201: * name, label parameters
0202: *
0203: * @param params
0204: * original params map
0205: * @return new params map
0206: */
0207: private Map addDefaultVars(Map params) {
0208: // create and fill the new params map
0209: final HashMap fullParams;
0210: if (params == null) {
0211: fullParams = new HashMap(6);
0212: } else {
0213: fullParams = new HashMap(params.size() + 6);
0214: fullParams.putAll(params);
0215: }
0216:
0217: // add the input param if not already present
0218: if (!fullParams.containsKey("input")) {
0219: fullParams.put("input", getInput());
0220: }
0221:
0222: // add the name param if not already present
0223: if (!fullParams.containsKey("name")) {
0224: fullParams.put("name", getId());
0225: }
0226:
0227: // add the label param if not already present
0228: if (!fullParams.containsKey("label")) {
0229: fullParams.put("label", getLabel());
0230: }
0231: return fullParams;
0232: }
0233:
0234: /**
0235: * @return value of label param for this form component
0236: */
0237: private Object getLabel() {
0238: final FormComponent fc = FormComponent.this ;
0239: Object label = null;
0240:
0241: // first try the label model ...
0242: if (fc.getLabel() != null) {
0243: label = fc.getLabel().getObject();
0244: }
0245: // ... then try a resource of format [form-component-id] with
0246: // default of '[form-component-id]'
0247: if (label == null) {
0248:
0249: label = fc.getLocalizer().getString(fc.getId(),
0250: fc.getParent(), fc.getId());
0251: }
0252: return label;
0253: }
0254: }
0255:
0256: /**
0257: * Change object to capture the required flag change
0258: *
0259: * @author Igor Vaynberg (ivaynberg)
0260: */
0261: private final class RequiredStateChange extends Change {
0262: private static final long serialVersionUID = 1L;
0263:
0264: private final boolean required = isRequired();
0265:
0266: /**
0267: * @see org.apache.wicket.version.undo.Change#undo()
0268: */
0269: public void undo() {
0270: setRequired(required);
0271: }
0272: }
0273:
0274: /**
0275: * Adapter that makes this component appear as {@link IValidatable}
0276: *
0277: * @author ivaynberg
0278: */
0279: private class ValidatableAdapter implements IValidatable {
0280:
0281: /**
0282: * @see org.apache.wicket.validation.IValidatable#error(org.apache.wicket.validation.IValidationError)
0283: */
0284: public void error(IValidationError error) {
0285: FormComponent.this .error(error);
0286: }
0287:
0288: /**
0289: * @see org.apache.wicket.validation.IValidatable#getValue()
0290: */
0291: public Object getValue() {
0292: return getConvertedInput();
0293: }
0294:
0295: public boolean isValid() {
0296: return FormComponent.this .isValid();
0297: }
0298:
0299: }
0300:
0301: /**
0302: * The value separator
0303: */
0304: public static String VALUE_SEPARATOR = ";";
0305:
0306: private static final String[] EMPTY_STRING_ARRAY = new String[] { "" };
0307:
0308: /**
0309: * Whether this form component should save and restore state between
0310: * sessions. This is false by default.
0311: */
0312: private static final short FLAG_PERSISTENT = FLAG_RESERVED2;
0313:
0314: /** Whether or not this component's value is required (non-empty) */
0315: private static final short FLAG_REQUIRED = FLAG_RESERVED3;
0316:
0317: private static final String NO_RAW_INPUT = "[-NO-RAW-INPUT-]";
0318:
0319: private static final long serialVersionUID = 1L;
0320:
0321: /**
0322: * Make empty strings null values boolean. Used by AbstractTextComponent
0323: * subclass.
0324: */
0325: protected static final short FLAG_CONVERT_EMPTY_INPUT_STRING_TO_NULL = FLAG_RESERVED1;
0326:
0327: /**
0328: * Visits any form components inside component if it is a container, or
0329: * component itself if it is itself a form component
0330: *
0331: * @param component
0332: * starting point of the traversal
0333: *
0334: * @param visitor
0335: * The visitor to call
0336: */
0337: public static final void visitFormComponentsPostOrder(
0338: Component component, final FormComponent.IVisitor visitor) {
0339: if (visitor == null) {
0340: throw new IllegalArgumentException(
0341: "Argument `visitor` cannot be null");
0342: }
0343:
0344: visitFormComponentsPostOrderHelper(component, visitor);
0345: }
0346:
0347: private static final Object visitFormComponentsPostOrderHelper(
0348: Component component, final FormComponent.IVisitor visitor) {
0349: if (component instanceof MarkupContainer) {
0350: final MarkupContainer container = (MarkupContainer) component;
0351: if (container.size() > 0) {
0352: boolean visitChildren = true;
0353: if (container instanceof IFormVisitorParticipant) {
0354: visitChildren = ((IFormVisitorParticipant) container)
0355: .processChildren();
0356: }
0357: if (visitChildren) {
0358: final Iterator children = container.iterator();
0359: while (children.hasNext()) {
0360: final Component child = (Component) children
0361: .next();
0362: Object value = visitFormComponentsPostOrderHelper(
0363: child, visitor);
0364: if (value == Component.IVisitor.STOP_TRAVERSAL) {
0365: return value;
0366: }
0367: }
0368: }
0369: }
0370: }
0371:
0372: if (component instanceof FormComponent) {
0373: final FormComponent fc = (FormComponent) component;
0374: return visitor.formComponent(fc);
0375: }
0376:
0377: return null;
0378: }
0379:
0380: private transient Object convertedInput;
0381:
0382: /**
0383: * Raw Input entered by the user or NO_RAW_INPUT if nothing is filled in.
0384: */
0385: private String rawInput = NO_RAW_INPUT;
0386:
0387: /**
0388: * Type that the raw input string will be converted to
0389: */
0390: private String typeName;
0391:
0392: /**
0393: * The list of validators for this form component as either an IValidator
0394: * instance or an array of IValidator instances.
0395: */
0396: private Object validators = null;
0397:
0398: /**
0399: * @see org.apache.wicket.Component#Component(String)
0400: */
0401: public FormComponent(final String id) {
0402: super (id);
0403: // the form decides whether form components are versioned or not
0404: // see Form.setVersioned
0405: setVersioned(false);
0406: }
0407:
0408: /**
0409: * @see org.apache.wicket.Component#Component(String, IModel)
0410: */
0411: public FormComponent(final String id, IModel model) {
0412: super (id, model);
0413: // the form decides whether form components are versioned or not
0414: // see Form.setVersioned
0415: setVersioned(false);
0416: }
0417:
0418: /**
0419: * Adds a validator to this form component.
0420: *
0421: * @param validator
0422: * The validator
0423: * @return This
0424: * @throws IllegalArgumentException
0425: * if validator is null
0426: * @see IValidator
0427: * @see IValidatorAddListener
0428: */
0429: public final FormComponent add(final IValidator validator) {
0430: if (validator == null) {
0431: throw new IllegalArgumentException(
0432: "validator argument cannot be null");
0433: }
0434:
0435: // add the validator
0436: validators_add(validator);
0437:
0438: // see whether the validator listens for add events
0439: if (validator instanceof IValidatorAddListener) {
0440: ((IValidatorAddListener) validator).onAdded(this );
0441: }
0442:
0443: // return this for chaining
0444: return this ;
0445: }
0446:
0447: /**
0448: * Checks if the form component's 'required' requirement is met. This method
0449: * should typically only be called when {@link #isRequired()} returns true.
0450: *
0451: * @return true if the 'required' requirement is met, false otherwise
0452: */
0453: public boolean checkRequired() {
0454: if (isRequired()) {
0455: final String input = getInput();
0456:
0457: // when null, check whether this is natural for that component, or
0458: // whether - as is the case with text fields - this can only happen
0459: // when the component was disabled
0460: if (input == null && !isInputNullable()) {
0461: // this value must have come from a disabled field
0462: // do not perform validation
0463: return true;
0464: }
0465:
0466: // peform validation by looking whether the value is null or empty
0467: return !Strings.isEmpty(input);
0468: }
0469: return true;
0470: }
0471:
0472: /**
0473: * Clears the user input.
0474: */
0475: public final void clearInput() {
0476: rawInput = NO_RAW_INPUT;
0477: }
0478:
0479: /**
0480: * Reports a validation error against this form component.
0481: *
0482: * The actual error is reported by creating a
0483: * {@link ValidationErrorFeedback} object that holds both the validation
0484: * error and the generated error message - so a custom feedback panel can
0485: * have access to both.
0486: *
0487: * @param error
0488: * validation error
0489: */
0490: public void error(IValidationError error) {
0491: if (error == null) {
0492: throw new IllegalArgumentException(
0493: "Argument [[error]] cannot be null");
0494: }
0495: String message = error.getErrorMessage(new MessageSource());
0496:
0497: if (message == null) {
0498: // XXX maybe make message source remember tried resource keys so a
0499: // more detailederror message can be created - like show which keys
0500: // were tried
0501: message = "Could not locate error message for error: "
0502: + error.toString();
0503: }
0504: error(new ValidationErrorFeedback(error, message));
0505: }
0506:
0507: /**
0508: * Gets the converted input. The converted input is set earlier though the
0509: * implementation of {@link #convertInput()}.
0510: *
0511: * @return value of input possibly converted into an appropriate type
0512: */
0513: public final Object getConvertedInput() {
0514: return convertedInput;
0515: }
0516:
0517: /**
0518: * Sets the converted input. This method is typically not called by clients,
0519: * unless they override {@link #convertInput()}, in which case they should
0520: * call this method to update the input for this component instance.
0521: *
0522: * @param convertedInput
0523: * the converted input
0524: */
0525: public final void setConvertedInput(Object convertedInput) {
0526: this .convertedInput = convertedInput;
0527: }
0528:
0529: /**
0530: * @return The parent form for this form component
0531: */
0532: public Form getForm() {
0533: // Look for parent form
0534: final Form form = (Form) findParent(Form.class);
0535: if (form == null) {
0536: throw new WicketRuntimeException(
0537: "Could not find Form parent for " + this );
0538: }
0539: return form;
0540: }
0541:
0542: /**
0543: * Gets the request parameter for this component as a string.
0544: *
0545: * @return The value in the request for this component
0546: */
0547: public String getInput() {
0548: String[] input = getInputAsArray();
0549: if (input == null || input.length == 0) {
0550: return null;
0551: } else {
0552: return input[0];
0553: }
0554: }
0555:
0556: /**
0557: * Gets the request parameters for this component as strings.
0558: *
0559: * @return The values in the request for this component
0560: */
0561: public String[] getInputAsArray() {
0562: String[] values = getRequest().getParameters(getInputName());
0563: if (!isInputNullable()) {
0564: if (values != null && values.length == 1
0565: && values[0] == null) {
0566: // we the key got passed in (otherwise values would be null),
0567: // but the value was set to null.
0568: // As the servlet spec isn't clear on what to do with 'empty'
0569: // request values - most return an empty string, but some null -
0570: // we have to workaround here and deliberately set to an empty
0571: // string if the the component is not nullable (text components)
0572: return EMPTY_STRING_ARRAY;
0573: }
0574: }
0575: return values;
0576: }
0577:
0578: /**
0579: * Gets the string to be used for the <tt>name</tt> attribute of the form
0580: * element. Generated using the path from the form to the component,
0581: * excluding the form itself. Override it if you want even a smaller name.
0582: * E.g. if you know for sure that the id is unique within a form.
0583: *
0584: * @return The string to use as the form element's name attribute
0585: */
0586: public String getInputName() {
0587: String id = getId();
0588: final PrependingStringBuffer inputName = new PrependingStringBuffer(
0589: id.length());
0590: Component c = this ;
0591: while (true) {
0592: inputName.prepend(id);
0593: c = c.getParent();
0594: if (c == null
0595: || (c instanceof Form && ((Form) c).isRootForm())
0596: || c instanceof Page) {
0597: break;
0598: }
0599: inputName.prepend(Component.PATH_SEPARATOR);
0600: id = c.getId();
0601: }
0602:
0603: // having input name "submit" causes problems with javascript, so we
0604: // create a unique string to replace it by prepending a path separator
0605: if (inputName.equals("submit")) {
0606: inputName.prepend(Component.PATH_SEPARATOR);
0607: }
0608: return inputName.toString();
0609: }
0610:
0611: /**
0612: * Use hasRawInput() to check if this component has raw input because null
0613: * can mean 2 things: It doesn't have rawinput or the rawinput is really
0614: * null.
0615: *
0616: * @return The raw form input that is stored for this formcomponent
0617: */
0618: public final String getRawInput() {
0619: return rawInput == NO_RAW_INPUT ? null : rawInput;
0620: }
0621:
0622: /**
0623: * @return the type to use when updating the model for this form component
0624: */
0625: public final Class getType() {
0626: return typeName == null ? null : Classes.resolveClass(typeName);
0627: }
0628:
0629: /**
0630: * @see Form#getValidatorKeyPrefix()
0631: * @return prefix used when constructing validator key messages
0632: */
0633: public String getValidatorKeyPrefix() {
0634: Form form = (Form) findParent(Form.class);
0635: if (form != null) {
0636: return getForm().getValidatorKeyPrefix();
0637: }
0638: return null;
0639: }
0640:
0641: /**
0642: * Gets an unmodifiable list of validators for this FormComponent.
0643: *
0644: * @return List of validators
0645: */
0646: public final List getValidators() {
0647: final int size = validators_size();
0648: if (size == 0) {
0649: return Collections.EMPTY_LIST;
0650: } else {
0651: final List list = new ArrayList(size);
0652: for (int i = 0; i < size; i++) {
0653: list.add(validators_get(i));
0654: }
0655: return Collections.unmodifiableList(list);
0656: }
0657: }
0658:
0659: /**
0660: * Gets current value for a form component, which can be either input data
0661: * entered by the user, or the component's model object if no input was
0662: * provided.
0663: *
0664: * @return The value
0665: */
0666: public final String getValue() {
0667: if (NO_RAW_INPUT.equals(rawInput)) {
0668: return getModelValue();
0669: } else {
0670: if (getEscapeModelStrings() && rawInput != null) {
0671: return Strings.escapeMarkup(rawInput).toString();
0672: }
0673: return rawInput;
0674: }
0675: }
0676:
0677: /**
0678: * Returns whether this component has raw input. Raw input is unconverted
0679: * input straight from the client.
0680: *
0681: * @return boolean whether this component has raw input.
0682: */
0683: public final boolean hasRawInput() {
0684: return rawInput != NO_RAW_INPUT;
0685: }
0686:
0687: /**
0688: * Used by Form to tell the FormComponent that a new user input is available
0689: */
0690: public final void inputChanged() {
0691: if (isVisibleInHierarchy() && isEnabled()) {
0692: // Get input as String array
0693: final String[] input = getInputAsArray();
0694:
0695: // If there is any input
0696: if (input != null && input.length > 0 && input[0] != null) {
0697: // join the values together with ";", for example, "id1;id2;id3"
0698: rawInput = StringList.valueOf(input).join(
0699: VALUE_SEPARATOR);
0700: } else if (isInputNullable()) {
0701: // no input
0702: rawInput = null;
0703: } else {
0704: rawInput = NO_RAW_INPUT;
0705: }
0706: }
0707: }
0708:
0709: /**
0710: * Indicate that validation of this form component failed.
0711: */
0712: public final void invalid() {
0713: onInvalid();
0714: }
0715:
0716: /**
0717: * Gets whether this component's input can be null. By default, components
0718: * that do not get input will have null values passed in for input. However,
0719: * component TextField is an example (possibly the only one) that never gets
0720: * a null passed in, even if the field is left empty UNLESS it had attribute
0721: * <code>disabled="disabled"</code> set.
0722: *
0723: * @return True if this component's input can be null. Returns true by
0724: * default.
0725: */
0726: public boolean isInputNullable() {
0727: return true;
0728: }
0729:
0730: /**
0731: * @return True if this component encodes data in a multipart form submit
0732: */
0733: public boolean isMultiPart() {
0734: return false;
0735: }
0736:
0737: /**
0738: * @return True if this component supports persistence AND it has been asked
0739: * to persist itself with setPersistent().
0740: */
0741: public final boolean isPersistent() {
0742: return supportsPersistence() && getFlag(FLAG_PERSISTENT);
0743: }
0744:
0745: /**
0746: * @return whether or not this component's value is required
0747: */
0748: public boolean isRequired() {
0749: return getFlag(FLAG_REQUIRED);
0750: }
0751:
0752: /**
0753: * Gets whether this component is 'valid'. Valid in this context means that
0754: * no validation errors were reported the last time the form component was
0755: * processed. This variable not only is convenient for 'business' use, but
0756: * is also nescesarry as we don't want the form component models updated
0757: * with invalid input.
0758: *
0759: * @return valid whether this component is 'valid'
0760: */
0761: public final boolean isValid() {
0762: class IsValidVisitor implements IVisitor {
0763: boolean valid = true;
0764:
0765: public Object formComponent(
0766: IFormVisitorParticipant formComponent) {
0767: final FormComponent fc = (FormComponent) formComponent;
0768: if (fc.hasErrorMessage()) {
0769: valid = false;
0770: return Component.IVisitor.STOP_TRAVERSAL;
0771: }
0772: return Component.IVisitor.CONTINUE_TRAVERSAL;
0773: }
0774: }
0775: IsValidVisitor tmp = new IsValidVisitor();
0776: visitFormComponentsPostOrder(this , tmp);
0777: return tmp.valid;
0778: }
0779:
0780: /**
0781: * @see org.apache.wicket.markup.html.form.IFormVisitorParticipant#processChildren(boolean)
0782: */
0783: public boolean processChildren() {
0784: return true;
0785: }
0786:
0787: /**
0788: * This method will retrieve the request parameter, validate it, and if
0789: * valid update the model. These are the same steps as would be performed by
0790: * the form.
0791: *
0792: * This is useful when a formcomponent is used outside a form.
0793: *
0794: */
0795: public final void processInput() {
0796: inputChanged();
0797: validate();
0798: if (hasErrorMessage()) {
0799: invalid();
0800: } else {
0801: valid();
0802: updateModel();
0803: }
0804: }
0805:
0806: /**
0807: * The value will be made available to the validator property by means of
0808: * ${label}. It does not have any specific meaning to FormComponent itself.
0809: *
0810: * @param labelModel
0811: * @return this for chaining
0812: */
0813: public FormComponent setLabel(IModel labelModel) {
0814: setLabelInternal(labelModel);
0815: return this ;
0816: }
0817:
0818: /**
0819: * Sets the value for a form component this value will be split the string
0820: * with {@link FormComponent#VALUE_SEPARATOR} and calls
0821: * setModelValue(String[]) with that.
0822: *
0823: * @param value
0824: * The value
0825: *
0826: * @deprecated call or override setModelValue(String[])
0827: */
0828: public void setModelValue(final String value) {
0829: setModelValue(value.split(VALUE_SEPARATOR));
0830: }
0831:
0832: /**
0833: * Sets the value for a form component.
0834: *
0835: * @param value
0836: * The value
0837: */
0838: public void setModelValue(final String[] value) {
0839: convertedInput = convertValue(value);
0840: updateModel();
0841: }
0842:
0843: /**
0844: * Sets whether this component is to be persisted.
0845: *
0846: * @param persistent
0847: * True if this component is to be persisted.
0848: * @return this for chaining
0849: */
0850: public final FormComponent setPersistent(final boolean persistent) {
0851: if (supportsPersistence()) {
0852: setFlag(FLAG_PERSISTENT, persistent);
0853: } else {
0854: throw new UnsupportedOperationException("FormComponent "
0855: + getClass() + " does not support cookies");
0856: }
0857: return this ;
0858: }
0859:
0860: /**
0861: * Sets the required flag
0862: *
0863: * @param required
0864: * @return this for chaining
0865: */
0866: public final FormComponent setRequired(final boolean required) {
0867: if (!required && getType() != null && getType().isPrimitive()) {
0868: throw new WicketRuntimeException(
0869: "FormComponent can't be not required when the type is primitive class: "
0870: + this );
0871: }
0872: if (required != isRequired()) {
0873: addStateChange(new RequiredStateChange());
0874: }
0875: setFlag(FLAG_REQUIRED, required);
0876: return this ;
0877: }
0878:
0879: /**
0880: * Sets the type that will be used when updating the model for this
0881: * component. If no type is specified String type is assumed.
0882: *
0883: * @param type
0884: * @return this for chaining
0885: */
0886: public final FormComponent setType(Class type) {
0887: typeName = type == null ? null : type.getName();
0888: if (type != null && type.isPrimitive()) {
0889: setRequired(true);
0890: }
0891: return this ;
0892: }
0893:
0894: /**
0895: * Updates this components' model from the request, it expect that the
0896: * object is already converted through the convert() call. By default it
0897: * just does this:
0898: *
0899: * <pre>
0900: * setModelObject(getConvertedInput());
0901: * </pre>
0902: *
0903: * DO NOT CALL THIS METHOD DIRECTLY UNLESS YOU ARE SURE WHAT YOU ARE DOING.
0904: * USUALLY UPDATING YOUR MODEL IS HANDLED BY THE FORM, NOT DIRECTLY BY YOU.
0905: */
0906: public void updateModel() {
0907: setModelObject(getConvertedInput());
0908: }
0909:
0910: /**
0911: * Called to indicate that the user input is valid.
0912: */
0913: public final void valid() {
0914: clearInput();
0915:
0916: onValid();
0917: }
0918:
0919: /**
0920: * Performs full validation of the form component, which consists of calling
0921: * validateRequired(), validateTypeConversion(), and validateValidators().
0922: * This method should only be used if the form component needs to be fully
0923: * validated outside the form process.
0924: */
0925: public final void validate() {
0926: validateRequired();
0927: if (isValid()) {
0928: convertInput();
0929:
0930: if (isValid() && isRequired()
0931: && getConvertedInput() == null && isInputNullable()) {
0932: reportRequiredError();
0933: }
0934:
0935: if (isValid()) {
0936: validateValidators();
0937: }
0938: }
0939: }
0940:
0941: /**
0942: * @param validator
0943: * The validator to add to the validators Object (which may be an
0944: * array of IValidators or a single instance, for efficiency)
0945: */
0946: private void validators_add(final IValidator validator) {
0947: if (validators == null) {
0948: validators = validator;
0949: } else {
0950: // Get current list size
0951: final int size = validators_size();
0952:
0953: // Create array that holds size + 1 elements
0954: final IValidator[] validators = new IValidator[size + 1];
0955:
0956: // Loop through existing validators copying them
0957: for (int i = 0; i < size; i++) {
0958: validators[i] = validators_get(i);
0959: }
0960:
0961: // Add new validator to the end
0962: validators[size] = validator;
0963:
0964: // Save new validator list
0965: this .validators = validators;
0966: }
0967: }
0968:
0969: /**
0970: * Gets validator from validators Object (which may be an array of
0971: * IValidators or a single instance, for efficiency) at the given index
0972: *
0973: * @param index
0974: * The index of the validator to get
0975: * @return The validator
0976: */
0977: private IValidator validators_get(int index) {
0978: if (validators == null) {
0979: throw new IndexOutOfBoundsException();
0980: }
0981: if (validators instanceof IValidator[]) {
0982: return ((IValidator[]) validators)[index];
0983: }
0984: return (IValidator) validators;
0985: }
0986:
0987: /**
0988: * @return The number of validators in the validators Object (which may be
0989: * an array of IValidators or a single instance, for efficiency)
0990: */
0991: private int validators_size() {
0992: if (validators == null) {
0993: return 0;
0994: }
0995: if (validators instanceof IValidator[]) {
0996: return ((IValidator[]) validators).length;
0997: }
0998: return 1;
0999: }
1000:
1001: /**
1002: * Converts and validates the conversion of the raw input string into the
1003: * object specified by {@link FormComponent#getType()} and records any
1004: * errors. Converted value is available through
1005: * {@link FormComponent#getConvertedInput()}
1006: */
1007: protected void convertInput() {
1008: if (typeName == null) {
1009: try {
1010: convertedInput = convertValue(getInputAsArray());
1011: } catch (ConversionException e) {
1012: ValidationError error = new ValidationError();
1013: if (e.getResourceKey() != null) {
1014: error.addMessageKey(e.getResourceKey());
1015: }
1016: if (e.getTargetType() != null) {
1017: error.addMessageKey("ConversionError."
1018: + Classes.simpleName(e.getTargetType()));
1019: }
1020: error.addMessageKey("ConversionError");
1021:
1022: final Locale locale = e.getLocale();
1023: if (locale != null) {
1024: error.setVariable("locale", locale);
1025: }
1026: error.setVariable("exception", e);
1027: Format format = e.getFormat();
1028: if (format instanceof SimpleDateFormat) {
1029: error.setVariable("format",
1030: ((SimpleDateFormat) format)
1031: .toLocalizedPattern());
1032: }
1033:
1034: error((IValidationError) error);
1035: }
1036: } else {
1037: final IConverter converter = getConverter(getType());
1038: try {
1039: convertedInput = converter.convertToObject(getInput(),
1040: getLocale());
1041: } catch (ConversionException e) {
1042: ValidationError error = new ValidationError();
1043: if (e.getResourceKey() != null) {
1044: error.addMessageKey(e.getResourceKey());
1045: }
1046: String simpleName = Classes.simpleName(getType());
1047: error.addMessageKey("IConverter." + simpleName);
1048: error.addMessageKey("IConverter");
1049:
1050: error.setVariable("type", simpleName);
1051: final Locale locale = e.getLocale();
1052: if (locale != null) {
1053: error.setVariable("locale", locale);
1054: }
1055: error.setVariable("exception", e);
1056: Format format = e.getFormat();
1057: if (format instanceof SimpleDateFormat) {
1058: error.setVariable("format",
1059: ((SimpleDateFormat) format)
1060: .toLocalizedPattern());
1061: }
1062:
1063: error((IValidationError) error);
1064: }
1065: }
1066: }
1067:
1068: /**
1069: * Subclasses should overwrite this if the conversion is not done through
1070: * the type field and the IConverter. <strong>WARNING: this method may be
1071: * removed in future versions.</strong>
1072: *
1073: * If conversion fails then a ConversionException should be thrown
1074: *
1075: * @param value
1076: * The value can be the getInput() or through a cookie
1077: *
1078: * @return The converted value. default returns just the given value
1079: * @throws ConversionException
1080: * If input can't be converted
1081: */
1082: protected Object convertValue(String[] value)
1083: throws ConversionException {
1084: return value != null && value.length > 0 && value[0] != null ? value[0]
1085: .trim()
1086: : null;
1087: }
1088:
1089: /**
1090: * @see org.apache.wicket.Component#getBehaviors(java.lang.Class)
1091: */
1092: protected List getBehaviors(Class type) {
1093: // List
1094: return super .getBehaviors(type);
1095: }
1096:
1097: /**
1098: * @return Value to return when model value is needed
1099: */
1100: protected String getModelValue() {
1101: return getModelObjectAsString();
1102: }
1103:
1104: /**
1105: * Gets the request parameter for this component as an int.
1106: *
1107: * @return The value in the request for this component
1108: */
1109: protected final int inputAsInt() {
1110: final String string = getInput();
1111: try {
1112: return Integer.parseInt(string);
1113: } catch (NumberFormatException e) {
1114: throw new IllegalArgumentException(
1115: exceptionMessage("Internal error. Request string '"
1116: + string + "' not a valid integer"));
1117: }
1118: }
1119:
1120: /**
1121: * Gets the request parameter for this component as an int, using the given
1122: * default in case no corresponding request parameter was found.
1123: *
1124: * @param defaultValue
1125: * Default value to return if request does not have an integer
1126: * for this component
1127: * @return The value in the request for this component
1128: */
1129: protected final int inputAsInt(final int defaultValue) {
1130: final String string = getInput();
1131: if (string != null) {
1132: try {
1133: return Integer.parseInt(string);
1134: } catch (NumberFormatException e) {
1135: throw new IllegalArgumentException(
1136: exceptionMessage("Request string '" + string
1137: + "' is not a valid integer"));
1138: }
1139: } else {
1140: return defaultValue;
1141: }
1142: }
1143:
1144: /**
1145: * Gets the request parameters for this component as ints.
1146: *
1147: * @return The values in the request for this component
1148: */
1149: protected final int[] inputAsIntArray() {
1150: final String[] strings = getInputAsArray();
1151: if (strings != null) {
1152: final int[] ints = new int[strings.length];
1153: for (int i = 0; i < strings.length; i++) {
1154: ints[i] = Integer.parseInt(strings[i]);
1155: }
1156: return ints;
1157: }
1158: return null;
1159: }
1160:
1161: /**
1162: * @see org.apache.wicket.Component#internalOnModelChanged()
1163: */
1164: protected void internalOnModelChanged() {
1165: // If the model for this form component changed, we should make it
1166: // valid again because there can't be any invalid input for it anymore.
1167: valid();
1168: }
1169:
1170: /**
1171: * Processes the component tag.
1172: *
1173: * @param tag
1174: * Tag to modify
1175: * @see org.apache.wicket.Component#onComponentTag(ComponentTag)
1176: */
1177: protected void onComponentTag(final ComponentTag tag) {
1178: tag.put("name", getInputName());
1179:
1180: if (!isEnabled() || !isEnableAllowed()) {
1181: onDisabled(tag);
1182: }
1183:
1184: super .onComponentTag(tag);
1185: }
1186:
1187: /**
1188: * Sets the temporary converted input value to null.
1189: *
1190: * @see org.apache.wicket.Component#onDetach()
1191: */
1192: protected void onDetach() {
1193: super .onDetach();
1194: convertedInput = null;
1195: }
1196:
1197: /**
1198: * Called by {@link #onComponentTag(ComponentTag)} when the component is
1199: * disabled. By default, this method will add a disabled="disabled"
1200: * attribute to the tag. Components may override this method to tweak the
1201: * tag as they think is fit.
1202: *
1203: * @param tag
1204: * the tag that is being rendered
1205: */
1206: protected void onDisabled(final ComponentTag tag) {
1207: tag.put("disabled", "disabled");
1208: }
1209:
1210: /**
1211: * Handle invalidation
1212: */
1213: protected void onInvalid() {
1214: }
1215:
1216: /**
1217: * Handle validation
1218: */
1219: protected void onValid() {
1220: }
1221:
1222: /**
1223: * @return True if this type of FormComponent can be persisted.
1224: */
1225: protected boolean supportsPersistence() {
1226: return false;
1227: }
1228:
1229: /**
1230: * Checks if the raw input value is not null if this component is required.
1231: */
1232: protected final void validateRequired() {
1233: if (!checkRequired()) {
1234: reportRequiredError();
1235: }
1236: }
1237:
1238: /**
1239: * Reports required error against this component
1240: */
1241: private void reportRequiredError() {
1242: error((IValidationError) new ValidationError()
1243: .addMessageKey("Required"));
1244: }
1245:
1246: /**
1247: * Validates this component using the component's validators.
1248: */
1249: protected final void validateValidators() {
1250: final int size = validators_size();
1251:
1252: final IValidatable validatable = new ValidatableAdapter();
1253:
1254: int i = 0;
1255: IValidator validator = null;
1256:
1257: boolean isNull = getConvertedInput() == null;
1258:
1259: try {
1260: for (i = 0; i < size; i++) {
1261: validator = validators_get(i);
1262:
1263: if (isNull == false
1264: || validator instanceof INullAcceptingValidator) {
1265: validator.validate(validatable);
1266: }
1267: if (!isValid()) {
1268: break;
1269: }
1270: }
1271: } catch (Exception e) {
1272: throw new WicketRuntimeException("Exception '" + e
1273: + "' occurred during validation "
1274: + validator.getClass().getName() + " on component "
1275: + getPath(), e);
1276: }
1277: }
1278:
1279: }
|