/* * Copyright 2008-2017 by Emeric Vernat * * This file is part of Java Melody. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.bull.javamelody.swing; import java.awt.Toolkit; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.PlainDocument; /** * Document swing interne pour la saisie d'une date. * @author Emeric Vernat */ public class MDateDocument extends PlainDocument { private static final long serialVersionUID = 1L; // Les instances de formats suivants sont partagées pour tous les champs de date. // Note : Ce n'est pas gênant même pour un DateFormat car les traitements Swing se font toujours sur un seul thread. // format de base et 1er format pour la validation private static SimpleDateFormat dateFormat; // format alternatif de validation (sans les '/', puis juste dd et MM, puis juste dd) private static SimpleDateFormat alternateDateFormat; // format d'affichage (avec 4 chiffres pour l'année) private static SimpleDateFormat displayDateFormat; /** * Retourne la valeur de la propriété alternateDateFormat. * @return SimpleDateFormat */ public static SimpleDateFormat getAlternateDateFormat() { if (alternateDateFormat == null) { // NOPMD // pour construire ce dateFormat on enlève les '/' et '.' du pattern de dateFormat // ie on peut saisir en France 251202 au lieu de 25/12/02 ou 25/12/2002 final StringBuilder patternSb = new StringBuilder(getDateFormat().toPattern()); // note : il faut réévaluer pattern.length() chaque fois dans la condition // puisque la longueur diminue au fur et à mesure for (int i = 0; i < patternSb.length(); i++) { if (!Character.isLetter(patternSb.charAt(i))) { patternSb.deleteCharAt(i); } } final Locale locale = Locale.getDefault(); final String pattern = patternSb.toString(); final String pattern2 = pattern.replaceAll("y", ""); final String pattern3 = pattern2.replaceAll("M", ""); final SimpleDateFormat myAlternateDateFormat2 = new SimpleDateFormat(pattern2, locale); final SimpleDateFormat myAlternateDateFormat3 = new SimpleDateFormat(pattern3, locale); // CHECKSTYLE:OFF final SimpleDateFormat myAlternateDateFormat = new SimpleDateFormat(pattern, locale) { // CHECKSTYLE:ON private static final long serialVersionUID = 1L; @Override public Date parse(final String text) throws ParseException { try { return super.parse(text); } catch (final ParseException ex) { final Calendar myCalendar = Calendar.getInstance(); final int year = myCalendar.get(Calendar.YEAR); final int month = myCalendar.get(Calendar.MONTH); try { myCalendar.setTime(myAlternateDateFormat2.parse(text)); myCalendar.set(Calendar.YEAR, year); return myCalendar.getTime(); } catch (final ParseException ex1) { try { myCalendar.setTime(myAlternateDateFormat3.parse(text)); myCalendar.set(Calendar.YEAR, year); myCalendar.set(Calendar.MONTH, month); return myCalendar.getTime(); } catch (final ParseException ex2) { throw ex; } } } } }; // on n'accepte pas le 30/02 (qui serait alors le 02/03) myAlternateDateFormat.setLenient(false); myAlternateDateFormat2.setLenient(false); myAlternateDateFormat3.setLenient(false); alternateDateFormat = myAlternateDateFormat; // NOPMD } return alternateDateFormat; // NOPMD } /** * Retourne la valeur de la propriété dateFormat. <br/> * Ce dateFormat est le format court par défaut de java en fonction de la Locale par défaut (lenient est false). * @return SimpleDateFormat * @see #setDateFormat */ public static SimpleDateFormat getDateFormat() { if (dateFormat == null) { // NOPMD // ce dateFormat est bien une instance de SimpleDateFormat selon le source DateFormat.get final SimpleDateFormat myDateFormat = (SimpleDateFormat) DateFormat .getDateInstance(DateFormat.SHORT); // on n'accepte pas le 30/02 (qui serait alors le 02/03) myDateFormat.setLenient(false); dateFormat = myDateFormat; // NOPMD } return dateFormat; // NOPMD } /** * Retourne la valeur de la propriété displayDateFormat. * @return SimpleDateFormat */ public static SimpleDateFormat getDisplayDateFormat() { if (displayDateFormat == null) { // NOPMD String pattern = getDateFormat().toPattern(); // si le pattern contient seulement 2 chiffres pour l'année on en met 4 // parce que c'est plus clair à l'affichage // mais en saisie on peut toujours n'en mettre que deux qui seront alors interprétés (avec le siècle) if (pattern.indexOf("yy") != -1 && pattern.indexOf("yyyy") == -1) { pattern = new StringBuilder(pattern).insert(pattern.indexOf("yy"), "yy").toString(); } displayDateFormat = new SimpleDateFormat(pattern, Locale.getDefault()); // NOPMD } return displayDateFormat; // NOPMD } /** {@inheritDoc} */ @Override public void insertString(final int offset, final String string, final AttributeSet attributeSet) throws BadLocationException { if (string == null || string.length() == 0) { return; } // pour une date on n'accepte que les chiffres et '/' (et/ou '.', ' ', ':' selon pattern) char c; final String pattern = getDateFormat().toPattern(); final int stringLength = string.length(); for (int i = 0; i < stringLength; i++) { c = string.charAt(i); if (!Character.isDigit(c) && (Character.isLetter(c) || pattern.indexOf(c) == -1)) { Toolkit.getDefaultToolkit().beep(); return; } } // mais sinon on accepte tout, même un format incomplet super.insertString(offset, string, attributeSet); } /** * Définit la valeur de la propriété alternateDateFormat. * @param newAlternateDateFormat * SimpleDateFormat * @see #getAlternateDateFormat */ public static void setAlternateDateFormat(final SimpleDateFormat newAlternateDateFormat) { alternateDateFormat = newAlternateDateFormat; // NOPMD } /** * Définit la valeur de la propriété dateFormat (et réinitialise alternateDateFormat et displayDateFormat). <br/> * Il est possible notamment de fournir un format acceptant plusieurs syntaxes ou lançant une RuntimeException "Date invalide" pour l'afficher en rouge si la date n'est pas entre MDateField.getMinimumValue() et MDateField.getMaximumValue(). * @param newDateFormat * SimpleDateFormat * @see #getDateFormat */ public static void setDateFormat(final SimpleDateFormat newDateFormat) { dateFormat = newDateFormat; // NOPMD // force la reconstruction des autres formats setAlternateDateFormat(null); setDisplayDateFormat(null); } /** * Définit la valeur de la propriété displayDateFormat. * @param newDisplayDateFormat * SimpleDateFormat * @see #getDisplayDateFormat */ public static void setDisplayDateFormat(final SimpleDateFormat newDisplayDateFormat) { displayDateFormat = newDisplayDateFormat; // NOPMD } }