PatternModel.java
上传用户:zhengdagz
上传日期:2014-03-06
资源大小:1956k
文件大小:18k
源码类别:

xml/soap/webservice

开发平台:

Java

  1. /*
  2.  * $Id: PatternModel.java,v 1.17 2005/10/12 11:26:53 kleopatra Exp $
  3.  *
  4.  * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
  5.  * Santa Clara, California 95054, U.S.A. All rights reserved.
  6.  *
  7.  * This library is free software; you can redistribute it and/or
  8.  * modify it under the terms of the GNU Lesser General Public
  9.  * License as published by the Free Software Foundation; either
  10.  * version 2.1 of the License, or (at your option) any later version.
  11.  * 
  12.  * This library is distributed in the hope that it will be useful,
  13.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15.  * Lesser General Public License for more details.
  16.  * 
  17.  * You should have received a copy of the GNU Lesser General Public
  18.  * License along with this library; if not, write to the Free Software
  19.  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  20.  */
  21. package org.jdesktop.swingx;
  22. import java.beans.PropertyChangeListener;
  23. import java.beans.PropertyChangeSupport;
  24. import java.util.ArrayList;
  25. import java.util.Collections;
  26. import java.util.List;
  27. import java.util.regex.Pattern;
  28. import javax.swing.ActionMap;
  29. import javax.swing.UIManager;
  30. import org.jdesktop.swingx.action.AbstractActionExt;
  31. import org.jdesktop.swingx.action.BoundAction;
  32. /**
  33.  * Presentation Model for Find/Filter Widgets. 
  34.  * <p>
  35.  * 
  36.  * Compiles and holds a Pattern from rawText. There are different 
  37.  * predefined strategies to control the compilation:
  38.  * 
  39.  * <ul>
  40.  * <li> TODO: list and explain
  41.  * </ul> 
  42.  * 
  43.  * Holds state for controlling the match process
  44.  * for both find and filter (TODO - explain). 
  45.  * Relevant in all
  46.  * 
  47.  * <ul>
  48.  * <li> caseSensitive - 
  49.  * <li> empty - true if there's no searchString
  50.  * <li> incremental - a hint to clients to react immediately
  51.  *      to pattern changes.
  52.  * 
  53.  * </ul>
  54.  * 
  55.  * Relevant in find contexts:
  56.  * <ul>
  57.  * <li> backwards - search direction if used in a find context
  58.  * <li> wrapping - wrap over the end/start if not found
  59.  * <li> foundIndex - storage for last found index
  60.  * <li> autoAdjustFoundIndex - flag to indicate auto-incr/decr of foundIndex on setting.
  61.  *      Here the property correlates to !isIncremental() - to simplify batch vs.
  62.  *      incremental search ui.
  63.  * </ul>
  64.  * 
  65.  * 
  66.  * JW: Work-in-progress - Anchors will be factored into AnchoredSearchMode 
  67.  * <b>Anchors</b> By default, the scope of the pattern relative to strings
  68.  * being tested are unanchored, ie, the pattern will match any part of the
  69.  * tested string. Traditionally, special characters ('^' and '$') are used to
  70.  * describe patterns that match the beginning (or end) of a string. If those
  71.  * characters are included in the pattern, the regular expression will honor
  72.  * them. However, for ease of use, two properties are included in this model
  73.  * that will determine how the pattern will be evaluated when these characters
  74.  * are omitted.
  75.  * <p>
  76.  * The <b>StartAnchored</b> property determines if the pattern must match from
  77.  * the beginning of tested strings, or if the pattern can appear anywhere in the
  78.  * tested string. Likewise, the <b>EndAnchored</b> property determines if the
  79.  * pattern must match to the end of the tested string, or if the end of the
  80.  * pattern can appear anywhere in the tested string. The default values (false
  81.  * in both cases) correspond to the common database 'LIKE' operation, where the
  82.  * pattern is considered to be a match if any part of the tested string matches
  83.  * the pattern.
  84.  * 
  85.  * @author Jeanette Winzenburg
  86.  * @author David Hall
  87.  */
  88. public class PatternModel {
  89.     /**
  90.      * The prefix marker to find component related properties in the
  91.      * resourcebundle.
  92.      */
  93.     public static final String SEARCH_PREFIX = "Search.";
  94.     public static final String REGEX_UNCHANGED = "regex";
  95.     public static final String REGEX_ANCHORED = "anchored";
  96.     public static final String REGEX_WILDCARD = "wildcard";
  97.     public static final String REGEX_MATCH_RULES = "explicit";
  98.     public static final String MATCH_RULE_CONTAINS = "contains";
  99.     public static final String MATCH_RULE_EQUALS = "equals";
  100.     public static final String MATCH_RULE_ENDSWITH = "endsWith";
  101.     public static final String MATCH_RULE_STARTSWITH = "startsWith";
  102.     public static final String MATCH_BACKWARDS_ACTION_COMMAND = "backwardsSearch";
  103.     public static final String MATCH_WRAP_ACTION_COMMAND = "wrapSearch";
  104.     public static final String MATCH_CASE_ACTION_COMMAND = "matchCase";
  105.     public static final String MATCH_INCREMENTAL_ACTION_COMMAND = "matchIncremental";
  106.     private String rawText;
  107.     private boolean backwards;
  108.     private Pattern pattern;
  109.     private int foundIndex = -1;
  110.     private boolean caseSensitive;
  111.     private PropertyChangeSupport propertySupport;
  112.     private String regexCreatorKey;
  113.     private RegexCreator regexCreator;
  114.     private boolean wrapping;
  115.     private ActionMap actionMap;
  116.     private boolean incremental;
  117. //---------------------- misc. properties not directly related to Pattern.
  118.     
  119.     public int getFoundIndex() {
  120.         return foundIndex;
  121.     }
  122.     public void setFoundIndex(int foundIndex) {
  123.         int old = getFoundIndex();
  124.         updateFoundIndex(foundIndex);
  125.         firePropertyChange("foundIndex", old, getFoundIndex());
  126.     }
  127.     
  128.     /**
  129.      * 
  130.      * @param newFoundIndex
  131.      */
  132.     protected void updateFoundIndex(int newFoundIndex) {
  133.         if (newFoundIndex < 0) {
  134.             this.foundIndex = newFoundIndex;
  135.             return;
  136.         }
  137.         if (isAutoAdjustFoundIndex()) {
  138.             foundIndex = backwards ? newFoundIndex -1 : newFoundIndex + 1;
  139.         } else {
  140.             foundIndex = newFoundIndex;
  141.         }
  142.         
  143.     }
  144.     public boolean isAutoAdjustFoundIndex() {
  145.         return !isIncremental();
  146.     }
  147.     public boolean isBackwards() {
  148.         return backwards;
  149.     }
  150.     public void setBackwards(boolean backwards) {
  151.         boolean old = isBackwards();
  152.         this.backwards = backwards;
  153.         firePropertyChange("backwards", old, isBackwards());
  154.         setFoundIndex(getFoundIndex());
  155.     }
  156.     public boolean isWrapping() {
  157.         return wrapping;
  158.     }
  159.     
  160.     public void setWrapping(boolean wrapping) {
  161.         boolean old = isWrapping();
  162.         this.wrapping = wrapping;
  163.         firePropertyChange("wrapping", old, isWrapping());
  164.     }
  165.     public void setIncremental(boolean incremental) {
  166.         boolean old = isIncremental();
  167.         this.incremental = incremental;
  168.         firePropertyChange("incremental", old, isIncremental());
  169.     }
  170.     
  171.     public boolean isIncremental() {
  172.         return incremental;
  173.     }
  174.     public boolean isCaseSensitive() {
  175.         return caseSensitive;
  176.     }
  177.     public void setCaseSensitive(boolean caseSensitive) {
  178.         boolean old = isCaseSensitive();
  179.         this.caseSensitive = caseSensitive;
  180.         updatePattern(caseSensitive);
  181.         firePropertyChange("caseSensitive", old, isCaseSensitive());
  182.     }
  183.     public Pattern getPattern() {
  184.         return pattern;
  185.     }
  186.     public String getRawText() {
  187.         return rawText;
  188.     }
  189.     public void setRawText(String findText) {
  190.         String old = getRawText();
  191.         boolean oldEmpty = isEmpty();
  192.         this.rawText = findText;
  193.         updatePattern(createRegEx(findText));
  194.         firePropertyChange("rawText", old, getRawText());
  195.         firePropertyChange("empty", oldEmpty, isEmpty());
  196.     }
  197.     public boolean isEmpty() {
  198.         return isEmpty(getRawText());
  199.     }
  200.     /**
  201.      * returns a regEx for compilation into a pattern. Here: either a "contains"
  202.      * (== partial find) or null if the input was empty.
  203.      * 
  204.      * @param searchString
  205.      * @return null if the input was empty, or a regex according to the internal
  206.      *         rules
  207.      */
  208.     private String createRegEx(String searchString) {
  209.         if (isEmpty(searchString))
  210.             return null; //".*";
  211.         return getRegexCreator().createRegEx(searchString);
  212.     }
  213.     /**
  214.      * 
  215.      * @param s
  216.      * @return
  217.      */
  218.     private boolean isEmpty(String text) {
  219.         return (text == null) || (text.length() == 0);
  220.     }
  221.     private void updatePattern(String regEx) {
  222.         Pattern old = getPattern();
  223.         if (isEmpty(regEx)) {
  224.             pattern = null;
  225.         } else if ((old == null) || (!old.pattern().equals(regEx))) {
  226.             pattern = Pattern.compile(regEx, getFlags());
  227.         }
  228.         firePropertyChange("pattern", old, getPattern());
  229.     }
  230.     private int getFlags() {
  231.         return isCaseSensitive() ? 0 : getCaseInsensitiveFlag();
  232.     }
  233.     private int getCaseInsensitiveFlag() {
  234.         return Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE;
  235.     }
  236.     private void updatePattern(boolean caseSensitive) {
  237.         if (pattern == null)
  238.             return;
  239.         Pattern old = getPattern();
  240.         int flags = old.flags();
  241.         int flag = getCaseInsensitiveFlag();
  242.         if ((caseSensitive) && ((flags & flag) != 0)) {
  243.             pattern = Pattern.compile(pattern.pattern(), 0);
  244.         } else if (!caseSensitive && ((flags & flag) == 0)) {
  245.             pattern = Pattern.compile(pattern.pattern(), flag);
  246.         }
  247.         firePropertyChange("pattern", old, getPattern());
  248.     }
  249.     public void addPropertyChangeListener(PropertyChangeListener l) {
  250.         if (propertySupport == null) {
  251.             propertySupport = new PropertyChangeSupport(this);
  252.         }
  253.         propertySupport.addPropertyChangeListener(l);
  254.     }
  255.     public void removePropertyChangeListener(PropertyChangeListener l) {
  256.         if (propertySupport == null)
  257.             return;
  258.         propertySupport.removePropertyChangeListener(l);
  259.     }
  260.     protected void firePropertyChange(String name, Object oldValue,
  261.             Object newValue) {
  262.         if (propertySupport == null)
  263.             return;
  264.         propertySupport.firePropertyChange(name, oldValue, newValue);
  265.     }
  266.     /**
  267.      * Responsible for converting a "raw text" into a valid 
  268.      * regular expression in the context of a set of rules.
  269.      * 
  270.      */
  271.     public static class RegexCreator {
  272.         protected String matchRule;
  273.         private List rules;
  274.         public String getMatchRule() {
  275.             if (matchRule == null) {
  276.                 matchRule = getDefaultMatchRule();
  277.             }
  278.             return matchRule;
  279.         }
  280.         public boolean isAutoDetect() {
  281.             return false;
  282.         }
  283.         
  284.         public String createRegEx(String searchString) {
  285.             if (MATCH_RULE_CONTAINS.equals(getMatchRule())) {
  286.                 return createContainedRegEx(searchString);
  287.             }
  288.             if (MATCH_RULE_EQUALS.equals(getMatchRule())) {
  289.                 return createEqualsRegEx(searchString);
  290.             }
  291.             if (MATCH_RULE_STARTSWITH.equals(getMatchRule())){
  292.                 return createStartsAnchoredRegEx(searchString);
  293.             }
  294.             if (MATCH_RULE_ENDSWITH.equals(getMatchRule())) {
  295.                 return createEndAnchoredRegEx(searchString);
  296.             }
  297.             return searchString;
  298.         }
  299.         protected String createEndAnchoredRegEx(String searchString) {
  300.             return Pattern.quote(searchString) + "$";
  301.         }
  302.         protected String createStartsAnchoredRegEx(String searchString) {
  303.             return "^" + Pattern.quote(searchString);
  304.         }
  305.         protected String createEqualsRegEx(String searchString) {
  306.             return "^" + Pattern.quote(searchString) + "$";
  307.         }
  308.         protected String createContainedRegEx(String searchString) {
  309.             return Pattern.quote(searchString);
  310.         }
  311.         public void setMatchRule(String category) {
  312.             this.matchRule = category;
  313.         }
  314.         
  315.         protected String getDefaultMatchRule() {
  316.             return MATCH_RULE_CONTAINS;
  317.         }
  318.         public List getMatchRules() {
  319.             if (rules == null) {
  320.                 rules = createAndInitRules();
  321.             }
  322.             return rules;
  323.         }
  324.         private List createAndInitRules() {
  325.             if (!supportsRules()) return Collections.EMPTY_LIST;
  326.             List<String> list = new ArrayList<String>();
  327.             list.add(MATCH_RULE_CONTAINS);
  328.             list.add(MATCH_RULE_EQUALS);
  329.             list.add(MATCH_RULE_STARTSWITH);
  330.             list.add(MATCH_RULE_ENDSWITH);
  331.             return list;
  332.         }
  333.         private boolean supportsRules() {
  334.             return true;
  335.         }
  336.     }
  337.  
  338.     /**
  339.      * Support for anchored input.
  340.      * 
  341.      * PENDING: NOT TESTED - simply moved!
  342.      * Need to define requirements...
  343.      * 
  344.      */
  345.     public static class AnchoredSearchMode extends RegexCreator {
  346.         
  347.         public boolean isAutoDetect() {
  348.             return true;
  349.         }
  350.         
  351.         public String createRegEx(String searchExp) {
  352.           if (isAutoDetect()) {
  353.               StringBuffer buf = new StringBuffer(searchExp.length() + 4);
  354.               if (!hasStartAnchor(searchExp)) {
  355.                   if (isStartAnchored()) {
  356.                       buf.append("^");
  357.                   } 
  358.               }
  359.       
  360.               //PENDING: doesn't escape contained regex metacharacters...
  361.               buf.append(searchExp);
  362.       
  363.               if (!hasEndAnchor(searchExp)) {
  364.                   if (isEndAnchored()) {
  365.                       buf.append("$");
  366.                   } 
  367.               }
  368.       
  369.               return buf.toString();
  370.           }
  371.           return super.createRegEx(searchExp);
  372.         }
  373.         private boolean hasStartAnchor(String str) {
  374.             return str.startsWith("^");
  375.         }
  376.         private boolean hasEndAnchor(String str) {
  377.             int len = str.length();
  378.             if ((str.charAt(len - 1)) != '$')
  379.                 return false;
  380.             // the string "$" is anchored
  381.             if (len == 1)
  382.                 return true;
  383.             // scan backwards along the string: if there's an odd number
  384.             // of backslashes, then the last escapes the dollar and the
  385.             // pattern is not anchored. if there's an even number, then
  386.             // the dollar is unescaped and the pattern is anchored.
  387.             for (int n = len - 2; n >= 0; --n)
  388.                 if (str.charAt(n) != '\')
  389.                     return (len - n) % 2 == 0;
  390.             // The string is of the form "+$". If the length is an odd
  391.             // number (ie, an even number of '' and a '$') the pattern is
  392.             // anchored
  393.             return len % 2 != 0;
  394.         }
  395.       /**
  396.       * returns true if the pattern must match from the beginning of the string,
  397.       * or false if the pattern can match anywhere in a string.
  398.       */
  399.      public boolean isStartAnchored() {
  400.          return MATCH_RULE_EQUALS.equals(getMatchRule()) ||
  401.              MATCH_RULE_STARTSWITH.equals(getMatchRule());
  402.      }
  403.  //
  404. //     /**
  405. //      * sets the default interpretation of the pattern for strings it will later
  406. //      * be given. Setting this value to true will force the pattern to match from
  407. //      * the beginning of tested strings. Setting this value to false will allow
  408. //      * the pattern to match any part of a tested string.
  409. //      */
  410. //     public void setStartAnchored(boolean startAnchored) {
  411. //         boolean old = isStartAnchored();
  412. //         this.startAnchored = startAnchored;
  413. //         updatePattern(createRegEx(getRawText()));
  414. //         firePropertyChange("startAnchored", old, isStartAnchored());
  415. //     }
  416.  //
  417.      /**
  418.       * returns true if the pattern must match from the beginning of the string,
  419.       * or false if the pattern can match anywhere in a string.
  420.       */
  421.      public boolean isEndAnchored() {
  422.          return MATCH_RULE_EQUALS.equals(getMatchRule()) ||
  423.              MATCH_RULE_ENDSWITH.equals(getMatchRule());
  424.      }
  425.  //
  426. //     /**
  427. //      * sets the default interpretation of the pattern for strings it will later
  428. //      * be given. Setting this value to true will force the pattern to match the
  429. //      * end of tested strings. Setting this value to false will allow the pattern
  430. //      * to match any part of a tested string.
  431. //      */
  432. //     public void setEndAnchored(boolean endAnchored) {
  433. //         boolean old = isEndAnchored();
  434. //         this.endAnchored = endAnchored;
  435. //         updatePattern(createRegEx(getRawText()));
  436. //         firePropertyChange("endAnchored", old, isEndAnchored());
  437. //     }
  438.  //
  439. //     public boolean isStartEndAnchored() {
  440. //         return isEndAnchored() && isStartAnchored();
  441. //     }
  442. //     
  443. //     /**
  444. //      * sets the default interpretation of the pattern for strings it will later
  445. //      * be given. Setting this value to true will force the pattern to match the
  446. //      * end of tested strings. Setting this value to false will allow the pattern
  447. //      * to match any part of a tested string.
  448. //      */
  449. //     public void setStartEndAnchored(boolean endAnchored) {
  450. //         boolean old = isStartEndAnchored();
  451. //         this.endAnchored = endAnchored;
  452. //         this.startAnchored = endAnchored;
  453. //         updatePattern(createRegEx(getRawText()));
  454. //         firePropertyChange("StartEndAnchored", old, isStartEndAnchored());
  455. //     }
  456.     }
  457.     /**
  458.      * 
  459.      * @param mode
  460.      */
  461.     public void setRegexCreatorKey(String mode) {
  462.         if (getRegexCreatorKey().equals(mode)) return;
  463.         String old = getRegexCreatorKey();
  464.         regexCreatorKey = mode;
  465.         firePropertyChange("regexCreatorKey", old, getRegexCreatorKey());
  466.         
  467.     }
  468.     public String getRegexCreatorKey() {
  469.         if (regexCreatorKey == null) {
  470.             regexCreatorKey = getDefaultRegexCreatorKey();
  471.         }
  472.         return regexCreatorKey;
  473.     }
  474.     private String getDefaultRegexCreatorKey() {
  475.         return REGEX_MATCH_RULES;
  476.     }
  477.     public void setMatchRule(String category) {
  478.         if (getMatchRule().equals(category)) {
  479.             return;
  480.         }
  481.         String old = getMatchRule();
  482.         getRegexCreator().setMatchRule(category);
  483.         updatePattern(createRegEx(getRawText()));
  484.         firePropertyChange("matchRule", old, getMatchRule());
  485.         
  486.     }
  487.     public String getMatchRule() {
  488.         return getRegexCreator().getMatchRule();
  489.     }
  490.     private RegexCreator getRegexCreator() {
  491.         if (regexCreator == null) {
  492.             regexCreator = new RegexCreator();
  493.         }
  494.         return regexCreator;
  495.     }
  496.     public List getMatchRules() {
  497.         return getRegexCreator().getMatchRules();
  498.     }
  499.     
  500. }