Author: tchemit Date: 2008-10-15 18:38:36 +0000 (Wed, 15 Oct 2008) New Revision: 920 Added: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/BeanValidator.java lutinjaxx/trunk/jaxx-core/src/site/fr/rst/examples/Validation.jaxx.rst lutinjaxx/trunk/jaxx-core/src/site/fr/rst/examples/images/Validation-screenshot.jaxx.png Removed: lutinjaxx/trunk/jaxx-core/src/site/fr/rst/examples/LabelStyles2.jaxx.rst lutinjaxx/trunk/maven-jaxx-plugin/src/examples/LabelStyle2/ Modified: lutinjaxx/trunk/jaxx-core/pom.xml lutinjaxx/trunk/jaxx-core/src/site/fr/rst/exemples.rst lutinjaxx/trunk/maven-jaxx-plugin/src/test/java/org/codelutin/jaxx/BuildExamples.java Log: introduce BeanValidator + example for validation Modified: lutinjaxx/trunk/jaxx-core/pom.xml =================================================================== --- lutinjaxx/trunk/jaxx-core/pom.xml 2008-10-15 18:35:54 UTC (rev 919) +++ lutinjaxx/trunk/jaxx-core/pom.xml 2008-10-15 18:38:36 UTC (rev 920) @@ -43,6 +43,16 @@ <artifactId>javahelp</artifactId> </dependency> + <dependency> + <groupId>com.opensymphony</groupId> + <artifactId>xwork</artifactId> + </dependency> + + <dependency> + <groupId>org.swinglabs</groupId> + <artifactId>jxlayer</artifactId> + </dependency> + </dependencies> <!-- ************************************************************* --> Added: lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/BeanValidator.java =================================================================== --- lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/BeanValidator.java (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/main/java/jaxx/runtime/BeanValidator.java 2008-10-15 18:38:36 UTC (rev 920) @@ -0,0 +1,494 @@ +/* *##% + * Copyright (C) 2002-2008 Code Lutin, Benjamin Poussin + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + *##%*/ + +package jaxx.runtime; + + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ValidationAwareSupport; +import com.opensymphony.xwork2.config.Configuration; +import com.opensymphony.xwork2.config.ConfigurationManager; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.ValueStackFactory; +import com.opensymphony.xwork2.validator.ActionValidatorManager; +import com.opensymphony.xwork2.validator.DelegatingValidatorContext; +import com.opensymphony.xwork2.validator.ValidationException; +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JList; +import javax.swing.ListModel; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jdesktop.jxlayer.JXLayer; +import org.jdesktop.jxlayer.plaf.AbstractLayerUI; + +/** + * <p> + * Permet d'ajouter facilement le support de la validation des champs d'un + * bean et de le relier a une interface graphique. + * Utilise xwork pour la validation et JXLayer pour la visualisation. + * + * <p> + * Le mieux pour son integration dans Jaxx est de faire de la generation pour + * force la compilation du code suivant: + * + * <pre> + * myValidor.getBean().get<field>(); + * </pre> + * + * et ceci pour chaque field ajoute a la map fieldRepresentation. De cette + * facon meme si le champs field est en texte on a une verification de son + * existance a la compilation. + * + * <p> + * La representation en tag pourrait etre + * <pre> + * + * <validator id="myValidator" beanClass="{Personne.class}" errorList="$list"> + * <field name="name" component="$name"/> + * <field name="firstName" component="$firstName"/> + * <field name="birthDate" component="$birthDate"/> + * </validator> + * + * <validator beanClass="{Personne.class}" autoField="true" errorList="$list"> + * <fieldRepresentation name="name" component="$lastName"/> + * </validator> + * + * </pre> + * + * dans le premier exemple on fait un mapping explicite des champs, mais on voit + * que le nom du composant graphique est le meme que celui du champs. Pour eviter + * de longue saisie, il est possible d'utiliser le flag <b>autoField</b> + * qui pour chaque champs du ayant une methode get du bean recherche un composant + * avec cet Id. Il est aussi possible de surcharge un champs explicitement + * comme ici name, dans le cas ou le composant qui porterait ce nom serait + * utilise pour autre chose. + * + * <p> + * Il faut un handler particulier pour ce composant car les attributs + * <b>beanClass</b> et <b>autoField</b> ne sont present que dans le XML jaxx et + * servent a la generation. Il faut aussi prendre en compte les elements + * fieldRepresentation fils du tag validator. + * + * <p> + * Voici ce que pourrait etre le code genere par jaxx + * <pre> + * // declaration du bean + * BeanValidator<beanClass> $myValidator; + * + * // init du bean + * protected void createMyValidator() { + * $myValidator = new BeanValidator<beanClass>(); + * + * // genere seulement si autoField = true + * for (Method m : beanClass.getMethod()) { + * if (m.getName().startsWith("get")) { + * String fieldName = m.getName().substring(3).toLowerCase(); + * $myValidator.setFieldRepresentation(fieldName, $objectMap.get(fieldName)); + * } + * } + * + * // pour chaque tag fieldRepresentation + * myValidator.setFieldRepresentation("name", $lastName); + * + * // si beanClass est specifie et n'est pas Object, on force l'acces au champs + * // pour validation a la compilation + * $myValidator.getBean().getName(); + * + * $objectMap.put("myValidator", $myValidator); + * } + * </pre> + * + * @author poussin + * @version $Revision$ + * + * Last update: $Date$ + * by : $Author$ + */ +public class BeanValidator<T> { + + /** to use log facility, just put in your code: log.info(\"...\"); */ + static private Log log = LogFactory.getLog(BeanValidator.class); + + protected PropertyChangeSupport pcs = new PropertyChangeSupport(this); + + protected ValidationAwareSupport validationSupport = new ValidationAwareSupport(); + protected DelegatingValidatorContext validationContext = + new DelegatingValidatorContext(validationSupport); + + protected transient ActionValidatorManager validator = null; + + /** indique si le bean a ete modifie depuis sont arrive */ + protected boolean changed = false; + /** le bean a surveiller */ + protected T bean = null; + /** l'objet qui recoit les notifications de modification du bean */ + protected Listener l = new Listener(); + /** permet de faire le lien en un champs du bean et l'objet qui permet de l'editer */ + protected Map<String, JComponent> fieldRepresentation = new HashMap<String, JComponent>(); + /** Objet servant a afficher toutes les erreurs */ + protected JList errorList = null; + + public BeanValidator() { + } + + /** + * Permet de modifier l'objet permettant d'afficher toutes les errors + * @param errorList si null arrete d'afficher les errors + */ + public void setErrorList(JList errorList) { + this.errorList = errorList; + } + + public JList getErrorList() { + return errorList; + } + + /** + * Permet d'indiquer le composant graphique responsable de l'affichage + * d'un attribut du bean + * @param fieldname + * @param c + */ + public void setFieldRepresentation(String fieldname, JComponent c) { + JComponent old = fieldRepresentation.put(fieldname, c); + setErrorRepresentation(fieldname, old, c); + } + + public void setFieldRepresentation(Map<String, JComponent> fieldRepresentation) { + for(Map.Entry<String, JComponent> e : fieldRepresentation.entrySet()) { + setFieldRepresentation(e.getKey(), e.getValue()); + } + } + + + + public JComponent getFieldRepresentation(String fieldname) { + return fieldRepresentation.get(fieldname); + } + + + /** + * Retourne vrai si l'objet bean a ete modifie depuis le dernier + * {@link #setBean} + * @return + */ + public boolean isChanged() { + return changed; + } + + /** + * Permet de force la remise a false de l'etat de changement du bean + * @param changed + */ + public void setChanged(boolean changed) { + boolean oldChanged = this.changed; + this.changed = changed; + pcs.firePropertyChange("changed", oldChanged, changed); + } + + public T getBean() { + return bean; + } + + public void setBean(T bean) { + T oldBean = this.bean; + if (this.bean != null) { + try { + Method method = this.bean.getClass().getMethod("removePropertyChangeListener", PropertyChangeListener.class); + method.invoke(this.bean, l); + } catch (Exception eee) { + log.info("Can't register as listener", eee); + } + } + this.bean = bean; + if (this.bean != null) { + try { + Method method = this.bean.getClass().getMethod("addPropertyChangeListener", PropertyChangeListener.class); + method.invoke(this.bean, l); + } catch (Exception eee) { + log.info("Can't register as listener", eee); + } + } + validate(); + pcs.firePropertyChange("bean", oldBean, bean); + } + + protected ActionValidatorManager getValidator() { + if (validator == null) { + ConfigurationManager confManager = new ConfigurationManager(); + Configuration conf = confManager.getConfiguration(); + + ValueStackFactory vsf = conf.getContainer().getInstance( + ValueStackFactory.class); + ValueStack vs = vsf.createValueStack(); + ActionContext context = new ActionContext(vs.getContext()); + ActionContext.setContext(context); + + validator = conf.getContainer().getInstance( + ActionValidatorManager.class, "no-annotations"); + } + return validator; + } + + /** + * il faut eviter le code re-intrant (durant une validation, une autre est + * demandee). Pour cela on fait la validation dans un thread, et tant + * que la premiere validation n'est pas fini, on ne repond pas aux + * solicitations. + * Cette method est public pour permettre de force une validation par + * programmation, ce qui est utile par exemple si le bean ne supporte + * pas les {@link PropertyChangeListener} + */ + public void validate() { + // on ne valide que si il y a un bean et que le resultat de la validation + // pourra etre affiche quelque part + if (bean != null && (getErrorList() != null || fieldRepresentation.size() != 0)) { + try { + + validationSupport.clearErrorsAndMessages(); + getValidator().validate(bean, null, validationContext); + + if(log.isDebugEnabled()) { + log.debug("Action errors: " + validationContext.getActionErrors()); + log.debug("Action messages: " + validationContext.getActionMessages()); + log.debug("Field errors: " + validationContext.getFieldErrors()); + } + + System.out.println(validationContext.getFieldErrors()); + + // TODO: trouver autre chose + // On est obliger de le refaire ici car avec jaxx, lors du + // setFieldRepresentation le composant n'a pas encore de pere :( + for (String fieldname : fieldRepresentation.keySet()) { + JComponent c = fieldRepresentation.get(fieldname); + setErrorRepresentation(fieldname, null, c); + } + } catch (ValidationException eee) { + log.warn("Error during validation", eee); + } + } + } + + protected void setErrorRepresentation(String fieldname, JComponent old, JComponent c) { + if (old != c) { + if (old != null) { + // suppression du jxlayer sous l'ancien composant + Container container = old.getParent(); + if (container instanceof JXLayer) { + JXLayer<JComponent> jx = (JXLayer<JComponent>)container; + jx.setUI(null); + } + } + if (c != null) { + // ajout du jxlayer sous ce composant + Container container = c.getParent(); + if (container instanceof JXLayer) { + IconValidationUI ui = new IconValidationUI(fieldname, validationSupport.getFieldErrors()); +// TranslucentValidationUI ui = new TranslucentValidationUI(fieldname, validationSupport.getFieldErrors()); + ui.setEnabled(true); + + JXLayer<JComponent> jx = (JXLayer<JComponent>)container; + jx.setUI(ui); + } + } + } + } + + /** + * Permet d'afficher les messages d'error dans L'ui + */ + protected ListModel getListModel() { + // FIXME a implanter + return null; + } + + protected class Listener implements PropertyChangeListener { + + public void propertyChange(PropertyChangeEvent evt) { + validate(); + setChanged(true); + } + + } + + /** + * recherche les composants portant le meme nom que les champs de la classe + * clazz. Cette methode est statique pour pouvoir eventuellement l'utiliser + * dans un autre context (je pense par exemple a la generation jaxx). + * + * <p> + * Si la recherche echoue pour quelque raison que se soit, aucune exception + * n'est leve, et la map retournee est tout simplement vide ou incomplete + * + * @param clazz la classe ou recherche les champs + * @param container le container ou rechercher les composants d'edition + * @return + */ + public static Map<String, JComponent> lookingForEditor(Class clazz, Container container) { + Map<String, JComponent> result = new HashMap<String, JComponent>(); + try { + // looking for all component with name set + Map<String, JComponent> allNamedComponent = new HashMap<String, JComponent>(); + List<Container> todo = new LinkedList<Container>(); + todo.add(container); + while (todo.size() > 0) { + for (ListIterator<Container> i = todo.listIterator(); i.hasNext();) { + Container parent = i.next(); + i.remove(); + for (Component c : parent.getComponents()) { + if (c instanceof Container) { + i.add((Container) c); + String name = c.getName(); + if (c instanceof JComponent && + name != null && !"".equals(name)) { + allNamedComponent.put(name, (JComponent)c); + } + } + } + } + } + + // looking for all properties on class + BeanInfo info = Introspector.getBeanInfo(clazz); + PropertyDescriptor[] props = info.getPropertyDescriptors(); + + // find if one properties have same name that component + for (PropertyDescriptor prop : props) { + String name = prop.getName(); + if (allNamedComponent.containsKey(name)) { + result.put(name, allNamedComponent.get(name)); + } + } + + } catch (IntrospectionException eee) { + log.warn("Can't introspect bean", eee); + } + + System.out.println("Result: " + result); + + return result; + } + + public static class TranslucentValidationUI extends AbstractLayerUI<JComponent> { + + protected String field = null; + protected Map resultValidation = null; + public TranslucentValidationUI(String field, Map resultValidation) { + this.field = field; + this.resultValidation = resultValidation; + } + + @Override + protected void paintLayer(Graphics2D g2, JXLayer<JComponent> l) { + // paints the layer as is + super.paintLayer(g2, l); + + // to be in sync with the view if the layer has a border + Insets layerInsets = l.getInsets(); + g2.translate(layerInsets.left, layerInsets.top); + + JComponent view = l.getView(); + // To prevent painting on view's border + Insets insets = view.getInsets(); + g2.clip(new Rectangle(insets.left, insets.top, + view.getWidth() - insets.left - insets.right, + view.getHeight() - insets.top - insets.bottom)); + + g2.setColor(!resultValidation.containsKey(field) ? + Color.GREEN : Color.RED); + g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .2f)); + g2.fillRect(0, 0, l.getWidth(), l.getHeight()); + } + } + + public static class IconValidationUI extends AbstractLayerUI<JComponent> { + + // The red icon to be shown at the layer's corner + private final static BufferedImage INVALID_ICON; + + static { + int width = 7; + int height = 8; + INVALID_ICON = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2 = (Graphics2D) INVALID_ICON.getGraphics(); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); + g2.setColor(Color.RED); + g2.fillRect(0, 0, width, height); + g2.setColor(Color.WHITE); + g2.drawLine(0, 0, width, height); + g2.drawLine(0, height, width, 0); + g2.dispose(); + } + + protected String field = null; + protected Map resultValidation = null; + public IconValidationUI(String field, Map resultValidation) { + this.field = field; + this.resultValidation = resultValidation; + } + + @Override + public void installUI(JComponent c) { + super.installUI(c); + c.setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 3)); + } + + @Override + public void uninstallUI(JComponent c) { + super.uninstallUI(c); + c.setBorder(null); + } + + @Override + protected void paintLayer(Graphics2D g2, JXLayer<JComponent> l) { + super.paintLayer(g2, l); + + // There is no need to take insets into account for this painter + if (resultValidation.containsKey(field)) { + g2.drawImage(INVALID_ICON, l.getWidth() - INVALID_ICON.getWidth() - 1, 0, null); +// g2.drawImage(INVALID_ICON, 0, 0, null); + } + } + } + +} Deleted: lutinjaxx/trunk/jaxx-core/src/site/fr/rst/examples/LabelStyles2.jaxx.rst =================================================================== --- lutinjaxx/trunk/jaxx-core/src/site/fr/rst/examples/LabelStyles2.jaxx.rst 2008-10-15 18:35:54 UTC (rev 919) +++ lutinjaxx/trunk/jaxx-core/src/site/fr/rst/examples/LabelStyles2.jaxx.rst 2008-10-15 18:38:36 UTC (rev 920) @@ -1,28 +0,0 @@ -===================== -Examples/LabelStyles2 -===================== - -This example program creates a number of components which are used to control the appearance of a JLabel. -Everything is performed through data binding; there are no script tags or explicit event handlers anywhere. - -Screen shot ------------ - -.. image:: images/LabelStyle-screenshot.gif - -Set it in action ----------------- - -|webstart| - -To run this example in `Java Web Start`_, click the `following link`_. - - -Source code ------------ - -.. _Java Web Start: http://java.sun.com/products/javawebstart/ - -.. |webstart| image:: images/webstart.gif - -.. _following link: http://buix.labs.libre-entreprise.org/lutinjaxx/maven-jaxx-plugin/examples/l... Copied: lutinjaxx/trunk/jaxx-core/src/site/fr/rst/examples/Validation.jaxx.rst (from rev 915, lutinjaxx/trunk/jaxx-core/src/site/fr/rst/examples/LabelStyles2.jaxx.rst) =================================================================== --- lutinjaxx/trunk/jaxx-core/src/site/fr/rst/examples/Validation.jaxx.rst (rev 0) +++ lutinjaxx/trunk/jaxx-core/src/site/fr/rst/examples/Validation.jaxx.rst 2008-10-15 18:38:36 UTC (rev 920) @@ -0,0 +1,28 @@ +===================== +Examples/LabelStyles2 +===================== + +This example program creates a number of components which are used to control the appearance of a JLabel. +Everything is performed through data binding; there are no script tags or explicit event handlers anywhere. + +Screen shot +----------- + +.. image:: images/Validation-screenshot.gif + +Set it in action +---------------- + +|webstart| + +To run this example in `Java Web Start`_, click the `following link`_. + + +Source code +----------- + +.. _Java Web Start: http://java.sun.com/products/javawebstart/ + +.. |webstart| image:: images/webstart.png + +.. _following link: http://buix.labs.libre-entreprise.org/lutinjaxx/maven-jaxx-plugin/examples/l... Property changes on: lutinjaxx/trunk/jaxx-core/src/site/fr/rst/examples/Validation.jaxx.rst ___________________________________________________________________ Name: svn:mergeinfo + Added: lutinjaxx/trunk/jaxx-core/src/site/fr/rst/examples/images/Validation-screenshot.jaxx.png =================================================================== (Binary files differ) Property changes on: lutinjaxx/trunk/jaxx-core/src/site/fr/rst/examples/images/Validation-screenshot.jaxx.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Modified: lutinjaxx/trunk/jaxx-core/src/site/fr/rst/exemples.rst =================================================================== --- lutinjaxx/trunk/jaxx-core/src/site/fr/rst/exemples.rst 2008-10-15 18:35:54 UTC (rev 919) +++ lutinjaxx/trunk/jaxx-core/src/site/fr/rst/exemples.rst 2008-10-15 18:38:36 UTC (rev 920) @@ -15,10 +15,14 @@ * Counter_ - simple data binding and scripting example. Based on the XUL Grand Coding Challenge. + * Validation_ - simple data validation with xworks and jxlayer. + .. _Components: examples/Components.jaxx.html .. _LabelStyles: examples/LabelStyles.jaxx.html .. _Calculator: examples/Calculator.jaxx.html -.. _Counter: examples/Counter.jaxx.html \ No newline at end of file +.. _Counter: examples/Counter.jaxx.html + +.. _Validation: examples/Validation.jaxx.html \ No newline at end of file Modified: lutinjaxx/trunk/maven-jaxx-plugin/src/test/java/org/codelutin/jaxx/BuildExamples.java =================================================================== --- lutinjaxx/trunk/maven-jaxx-plugin/src/test/java/org/codelutin/jaxx/BuildExamples.java 2008-10-15 18:35:54 UTC (rev 919) +++ lutinjaxx/trunk/maven-jaxx-plugin/src/test/java/org/codelutin/jaxx/BuildExamples.java 2008-10-15 18:38:36 UTC (rev 920) @@ -88,6 +88,10 @@ buildExample(); } + public void testValidation() throws Exception { + buildExample(); + } + protected void buildExample() throws VerificationException, IOException { verifier.executeGoals(Arrays.asList("jaxx:generate", "package")); verifier.verifyErrorFreeLog();
participants (1)
-
tchemit@users.labs.libre-entreprise.org