Changeset 4817 in josm for trunk/src


Ignore:
Timestamp:
2012-01-18T20:19:02+01:00 (8 years ago)
Author:
bastiK
Message:

applied #7178 - Allow plugins to register search operators (patch by joshdoe)

Location:
trunk/src/org/openstreetmap/josm
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/search/PushbackTokenizer.java

    r4346 r4817  
    4848
    4949    public enum Token {
    50         NOT(marktr("<not>")), OR(marktr("<or>")), LEFT_PARENT(marktr("<left parent>")),
     50        NOT(marktr("<not>")), OR(marktr("<or>")), XOR(marktr("<xor>")), LEFT_PARENT(marktr("<left parent>")),
    5151        RIGHT_PARENT(marktr("<right parent>")), COLON(marktr("<colon>")), EQUALS(marktr("<equals>")),
    5252        KEY(marktr("<key>")), QUESTION_MARK(marktr("<question mark>")),
     
    7474    }
    7575
    76     private static final List<Character> specialChars = Arrays.asList(new Character[] {'"', ':', '(', ')', '|', '=', '?'});
     76    private static final List<Character> specialChars = Arrays.asList(new Character[] {'"', ':', '(', ')', '|', '^', '=', '?'});
    7777    private static final List<Character> specialCharsQuoted = Arrays.asList(new Character[] {'"'});
    7878
     
    102102     * : for an key. The value is the next token
    103103     * | for "OR"
     104     * ^ for "XOR"
    104105     * ' ' for anything else.
    105106     * @return The next token in the stream.
     
    134135            getChar();
    135136            return Token.OR;
     137        case '^':
     138            getChar();
     139            return Token.XOR;
    136140        case '&':
    137141            getChar();
     
    156160            if ("or".equalsIgnoreCase(currentText))
    157161                return Token.OR;
    158             if ("and".equalsIgnoreCase(currentText))
     162            else if ("xor".equalsIgnoreCase(currentText))
     163                return Token.XOR;
     164            else if ("and".equalsIgnoreCase(currentText))
    159165                return nextToken();
    160166            // try parsing number
  • trunk/src/org/openstreetmap/josm/actions/search/SearchCompiler.java

    r4696 r4817  
    88import java.io.StringReader;
    99import java.text.Normalizer;
     10import java.util.Arrays;
    1011import java.util.Collection;
    1112import java.util.Date;
     13import java.util.HashMap;
     14import java.util.Map;
    1215import java.util.regex.Matcher;
    1316import java.util.regex.Pattern;
     
    5558    private static String  rxErrorMsgNoPos = marktr("The regex \"{0}\" had a parse error, full error:\n\n{1}");
    5659    private PushbackTokenizer tokenizer;
     60    private static Map<String, SimpleMatchFactory> simpleMatchFactoryMap = new HashMap<String, SimpleMatchFactory>();
     61    private static Map<String, UnaryMatchFactory> unaryMatchFactoryMap = new HashMap<String, UnaryMatchFactory>();
     62    private static Map<String, BinaryMatchFactory> binaryMatchFactoryMap = new HashMap<String, BinaryMatchFactory>();
    5763
    5864    public SearchCompiler(boolean caseSensitive, boolean regexSearch, PushbackTokenizer tokenizer) {
     
    6066        this.regexSearch = regexSearch;
    6167        this.tokenizer = tokenizer;
    62     }
    63 
     68
     69        /* register core match factories at first instance, so plugins should
     70         * never be able to generate a NPE
     71         */
     72        if (simpleMatchFactoryMap.isEmpty()) {
     73            addMatchFactory(new CoreSimpleMatchFactory());
     74        }
     75        if (unaryMatchFactoryMap.isEmpty()) {
     76            addMatchFactory(new CoreUnaryMatchFactory());
     77        }
     78
     79    }
     80
     81    /**
     82     * Add (register) MatchFactory with SearchCompiler
     83     * @param factory
     84     */
     85    public static void addMatchFactory(MatchFactory factory) {
     86        for (String keyword : factory.getKeywords()) {
     87            // TODO: check for keyword collisions
     88            if (factory instanceof SimpleMatchFactory) {
     89                simpleMatchFactoryMap.put(keyword, (SimpleMatchFactory)factory);
     90            } else if (factory instanceof UnaryMatchFactory) {
     91                unaryMatchFactoryMap.put(keyword, (UnaryMatchFactory)factory);
     92            } else if (factory instanceof BinaryMatchFactory) {
     93                binaryMatchFactoryMap.put(keyword, (BinaryMatchFactory)factory);
     94            } else
     95                throw new AssertionError("Unknown match factory");
     96        }
     97    }
     98
     99    public class CoreSimpleMatchFactory implements SimpleMatchFactory {
     100        private Collection<String> keywords = Arrays.asList("id", "version",
     101                "changeset", "nodes", "tags", "areasize", "modified", "selected",
     102                "incomplete", "untagged", "closed", "new", "indownloadarea",
     103                "allindownloadarea", "inview", "allinview", "timestamp");
     104
     105        @Override
     106        public Match get(String keyword, PushbackTokenizer tokenizer) throws ParseError {
     107            if ("id".equals(keyword))
     108                return new Id(tokenizer);
     109            else if ("version".equals(keyword))
     110                return new Version(tokenizer);
     111            else if ("changeset".equals(keyword))
     112                return new ChangesetId(tokenizer);
     113            else if ("nodes".equals(keyword))
     114                return new NodeCountRange(tokenizer);
     115            else if ("tags".equals(keyword))
     116                return new TagCountRange(tokenizer);
     117            else if ("areasize".equals(keyword))
     118                return new AreaSize(tokenizer);
     119            else if ("modified".equals(keyword))
     120                return new Modified();
     121            else if ("selected".equals(keyword))
     122                return new Selected();
     123            else if ("incomplete".equals(keyword))
     124                return new Incomplete();
     125            else if ("untagged".equals(keyword))
     126                return new Untagged();
     127            else if ("closed".equals(keyword))
     128                return new Closed();
     129            else if ("new".equals(keyword))
     130                return new New();
     131            else if ("indownloadedarea".equals(keyword))
     132                return new InDataSourceArea(false);
     133            else if ("allindownloadedarea".equals(keyword))
     134                return new InDataSourceArea(true);
     135            else if ("inview".equals(keyword))
     136                return new InView(false);
     137            else if ("allinview".equals(keyword))
     138                return new InView(true);
     139            else if ("timestamp".equals(keyword)) {
     140                String rangeS = " " + tokenizer.readTextOrNumber() + " "; // add leading/trailing space in order to get expected split (e.g. "a--" => {"a", ""})
     141                String[] rangeA = rangeS.split("/");
     142                if (rangeA.length == 1)
     143                    return new KeyValue(keyword, rangeS, regexSearch, caseSensitive);
     144                else if (rangeA.length == 2) {
     145                    String rangeA1 = rangeA[0].trim();
     146                    String rangeA2 = rangeA[1].trim();
     147                    long minDate = DateUtils.fromString(rangeA1.isEmpty() ? "1980" : rangeA1).getTime(); // if min timestap is empty: use lowest possible date
     148                    long maxDate = rangeA2.isEmpty() ? new Date().getTime() : DateUtils.fromString(rangeA2).getTime(); // if max timestamp is empty: use "now"
     149                    return new TimestampRange(minDate, maxDate);
     150                } else
     151                    /*
     152                     * I18n: Don't translate timestamp keyword
     153                     */ throw new ParseError(tr("Expecting <i>min</i>/<i>max</i> after ''timestamp''"));
     154            } else
     155                return null;
     156        }
     157
     158        @Override
     159        public Collection<String> getKeywords() {
     160            return keywords;
     161        }
     162    }
     163
     164    public static class CoreUnaryMatchFactory implements UnaryMatchFactory {
     165        private static Collection<String> keywords = Arrays.asList("parent", "child");
     166
     167        @Override
     168        public UnaryMatch get(String keyword, Match matchOperand, PushbackTokenizer tokenizer) {
     169            if ("parent".equals(keyword))
     170                return new Parent(matchOperand);
     171            else if ("child".equals(keyword))
     172                return new Child(matchOperand);
     173            return null;
     174        }
     175
     176        @Override
     177        public Collection<String> getKeywords() {
     178            return keywords;
     179        }
     180    }
     181
     182    /**
     183     * Classes implementing this interface can provide Match operators.
     184     */
     185    private interface MatchFactory {
     186        public Collection<String> getKeywords();
     187    }
     188
     189    public interface SimpleMatchFactory extends MatchFactory {
     190        public Match get(String keyword, PushbackTokenizer tokenizer) throws ParseError;
     191    }
     192
     193    public interface UnaryMatchFactory extends MatchFactory {
     194        public UnaryMatch get(String keyword, Match matchOperand, PushbackTokenizer tokenizer) throws ParseError;
     195    }
     196
     197    public interface BinaryMatchFactory extends MatchFactory {
     198        public BinaryMatch get(String keyword, Match lhs, Match rhs, PushbackTokenizer tokenizer) throws ParseError;
     199    }
     200
     201    /**
     202     * Base class for all search operators.
     203     */
    64204    abstract public static class Match {
     205
    65206        abstract public boolean match(OsmPrimitive osm);
    66207
     
    88229    }
    89230
     231    /**
     232     * A unary search operator which may take data parameters.
     233     */
     234    abstract public static class UnaryMatch extends Match {
     235
     236        protected final Match match;
     237
     238        public UnaryMatch(Match match) {
     239            if (match == null) {
     240                // "operator" (null) should mean the same as "operator()"
     241                // (Always). I.e. match everything
     242                this.match = new Always();
     243            } else {
     244                this.match = match;
     245            }
     246        }
     247
     248        public Match getOperand() {
     249            return match;
     250        }
     251    }
     252
     253    /**
     254     * A binary search operator which may take data parameters.
     255     */
     256    abstract public static class BinaryMatch extends Match {
     257
     258        protected final Match lhs;
     259        protected final Match rhs;
     260
     261        public BinaryMatch(Match lhs, Match rhs) {
     262            this.lhs = lhs;
     263            this.rhs = rhs;
     264        }
     265
     266        public Match getLhs() {
     267            return lhs;
     268        }
     269
     270        public Match getRhs() {
     271            return rhs;
     272        }
     273    }
     274
     275    /**
     276     * Matches every OsmPrimitive.
     277     */
    90278    public static class Always extends Match {
    91279        public static Always INSTANCE = new Always();
     
    95283    }
    96284
     285    /**
     286     * Never matches any OsmPrimitive.
     287     */
    97288    public static class Never extends Match {
    98289        @Override
     
    102293    }
    103294
    104     public static class Not extends Match {
    105         private final Match match;
    106         public Not(Match match) {this.match = match;}
     295    /**
     296     * Inverts the match.
     297     */
     298    public static class Not extends UnaryMatch {
     299        public Not(Match match) {super(match);}
    107300        @Override public boolean match(OsmPrimitive osm) {
    108301            return !match.match(osm);
     
    114307    }
    115308
     309    /**
     310     * Matches if the value of the corresponding key is ''yes'', ''true'', ''1'' or ''on''.
     311     */
    116312    private static class BooleanMatch extends Match {
    117313        private final String key;
     
    132328    }
    133329
    134     public static class And extends Match {
    135         private final Match lhs;
    136         private final Match rhs;
    137         public And(Match lhs, Match rhs) {this.lhs = lhs; this.rhs = rhs;}
     330    /**
     331     * Matches if both left and right expressions match.
     332     */
     333    public static class And extends BinaryMatch {
     334    public And(Match lhs, Match rhs) {super(lhs, rhs);}
    138335        @Override public boolean match(OsmPrimitive osm) {
    139336            return lhs.match(osm) && rhs.match(osm);
    140337        }
    141         @Override public String toString() {return lhs+" && "+rhs;}
    142         public Match getLhs() {
    143             return lhs;
    144         }
    145         public Match getRhs() {
    146             return rhs;
    147         }
    148     }
    149 
    150     public static class Or extends Match {
    151         private final Match lhs;
    152         private final Match rhs;
    153         public Or(Match lhs, Match rhs) {this.lhs = lhs; this.rhs = rhs;}
     338        @Override public String toString() {
     339            return lhs + " && " + rhs;
     340        }
     341    }
     342
     343    /**
     344     * Matches if the left OR the right expression match.
     345     */
     346    public static class Or extends BinaryMatch {
     347    public Or(Match lhs, Match rhs) {super(lhs, rhs);}
    154348        @Override public boolean match(OsmPrimitive osm) {
    155349            return lhs.match(osm) || rhs.match(osm);
    156350        }
    157         @Override public String toString() {return lhs+" || "+rhs;}
    158         public Match getLhs() {
    159             return lhs;
    160         }
    161         public Match getRhs() {
    162             return rhs;
    163         }
    164     }
    165 
     351        @Override public String toString() {
     352            return lhs + " || " + rhs;
     353        }
     354    }
     355
     356    /**
     357     * Matches if the left OR the right expression match, but not both.
     358     */
     359    public static class Xor extends BinaryMatch {
     360    public Xor(Match lhs, Match rhs) {super(lhs, rhs);}
     361        @Override public boolean match(OsmPrimitive osm) {
     362            return lhs.match(osm) ^ rhs.match(osm);
     363        }
     364        @Override public String toString() {
     365            return lhs + " ^ " + rhs;
     366        }
     367    }
     368
     369    /**
     370     * Matches objects with the given object ID.
     371     */
    166372    private static class Id extends Match {
    167373        private long id;
     
    169375            this.id = id;
    170376        }
     377        public Id(PushbackTokenizer tokenizer) throws ParseError {
     378            this(tokenizer.readNumber(tr("Primitive id expected")));
     379        }
    171380        @Override public boolean match(OsmPrimitive osm) {
    172381            return id == 0?osm.isNew():osm.getUniqueId() == id;
     
    175384    }
    176385
     386    /**
     387     * Matches objects with the given changeset ID.
     388     */
    177389    private static class ChangesetId extends Match {
    178390        private long changesetid;
    179391        public ChangesetId(long changesetid) {this.changesetid = changesetid;}
     392        public ChangesetId(PushbackTokenizer tokenizer) throws ParseError {
     393            this(tokenizer.readNumber(tr("Changeset id expected")));
     394        }
    180395        @Override public boolean match(OsmPrimitive osm) {
    181396            return osm.getChangesetId() == changesetid;
     
    184399    }
    185400
     401    /**
     402     * Matches objects with the given version number.
     403     */
    186404    private static class Version extends Match {
    187405        private long version;
    188406        public Version(long version) {this.version = version;}
     407        public Version(PushbackTokenizer tokenizer) throws ParseError {
     408            this(tokenizer.readNumber(tr("Version expected")));
     409        }
    189410        @Override public boolean match(OsmPrimitive osm) {
    190411            return osm.getVersion() == version;
     
    193414    }
    194415
     416    /**
     417     * Matches objects with the given key-value pair.
     418     */
    195419    private static class KeyValue extends Match {
    196420        private final String key;
     
    287511    }
    288512
     513    /**
     514     * Matches objects with the exact given key-value pair.
     515     */
    289516    public static class ExactKeyValue extends Match {
    290517
     
    415642    }
    416643
     644    /**
     645     * Match a string in any tags (key or value), with optional regex and case insensitivity.
     646     */
    417647    private static class Any extends Match {
    418648        private final String search;
     
    478708    }
    479709
     710    // TODO: change how we handle this
    480711    private static class ExactType extends Match {
    481712        private final Class<?> type;
     
    497728    }
    498729
     730    /**
     731     * Matches objects last changed by the given username.
     732     */
    499733    private static class UserMatch extends Match {
    500734        private String user;
     
    519753    }
    520754
     755    /**
     756     * Matches objects with the given relation role (i.e. "outer").
     757     */
    521758    private static class RoleMatch extends Match {
    522759        private String role;
     
    549786    }
    550787
     788    /**
     789     * Matches objects with properties in a certain range.
     790     */
    551791    private abstract static class CountRange extends Match {
    552792
     
    557797            this.minCount = Math.min(minCount, maxCount);
    558798            this.maxCount = Math.max(minCount, maxCount);
     799        }
     800
     801        public CountRange(Range range) {
     802            this(range.getStart(), range.getEnd());
    559803        }
    560804
     
    579823
    580824
    581 
     825    /**
     826     * Matches ways with a number of nodes in given range
     827     */
    582828    private static class NodeCountRange extends CountRange {
    583 
    584         public NodeCountRange(long minCount, long maxCount) {
    585             super(minCount, maxCount);
     829        public NodeCountRange(Range range) {
     830            super(range);
     831        }
     832
     833        public NodeCountRange(PushbackTokenizer tokenizer) throws ParseError {
     834            this(tokenizer.readRange(tr("Range of numbers expected")));
    586835        }
    587836
     
    600849    }
    601850
     851    /**
     852     * Matches objects with a number of tags in given range
     853     */
    602854    private static class TagCountRange extends CountRange {
    603 
    604         public TagCountRange(long minCount, long maxCount) {
    605             super(minCount, maxCount);
     855        public TagCountRange(Range range) {
     856            super(range);
     857        }
     858
     859        public TagCountRange(PushbackTokenizer tokenizer) throws ParseError {
     860            this(tokenizer.readRange(tr("Range of numbers expected")));
    606861        }
    607862
     
    617872    }
    618873
     874    /**
     875     * Matches objects with a timestamp in given range
     876     */
    619877    private static class TimestampRange extends CountRange {
    620878
     
    635893    }
    636894
     895    /**
     896     * Matches objects that are new (i.e. have not been uploaded to the server)
     897     */
    637898    private static class New extends Match {
    638899        @Override public boolean match(OsmPrimitive osm) {
     
    644905    }
    645906
     907    /**
     908     * Matches all objects that have been modified, created, or undeleted
     909     */
    646910    private static class Modified extends Match {
    647911        @Override public boolean match(OsmPrimitive osm) {
     
    651915    }
    652916
     917    /**
     918     * Matches all objects currently selected
     919     */
    653920    private static class Selected extends Match {
    654921        @Override public boolean match(OsmPrimitive osm) {
     
    658925    }
    659926
     927    /**
     928     * Match objects that are incomplete, where only id and type are known.
     929     * Typically some members of a relation are incomplete until they are
     930     * fetched from the server.
     931     */
    660932    private static class Incomplete extends Match {
    661933        @Override public boolean match(OsmPrimitive osm) {
     
    665937    }
    666938
     939    /**
     940     * Matches objects that don't have any interesting tags (i.e. only has source,
     941     * FIXME, etc.). The complete list of uninteresting tags can be found here:
     942     * org.openstreetmap.josm.data.osm.OsmPrimitive.getUninterestingKeys()
     943     */
    667944    private static class Untagged extends Match {
    668945        @Override public boolean match(OsmPrimitive osm) {
     
    672949    }
    673950
     951    /**
     952     * Matches ways which are closed (i.e. first and last node are the same)
     953     */
    674954    private static class Closed extends Match {
    675955        @Override public boolean match(OsmPrimitive osm) {
     
    679959    }
    680960
    681     public static class Parent extends Match {
    682         private final Match child;
     961    /**
     962     * Matches objects if they are parents of the expression
     963     */
     964    public static class Parent extends UnaryMatch {
    683965        public Parent(Match m) {
    684             if (m == null) {
    685                 // "parent" (null) should mean the same as "parent()"
    686                 // (Always). I.e. match everything
    687                 child = new Always();
    688             } else {
    689                 child = m;
    690             }
     966            super(m);
    691967        }
    692968        @Override public boolean match(OsmPrimitive osm) {
     
    695971            if (osm instanceof Way) {
    696972                for (Node n : ((Way)osm).getNodes()) {
    697                     isParent |= child.match(n);
     973                    isParent |= match.match(n);
    698974                }
    699975            } else if (osm instanceof Relation) {
    700976                for (RelationMember member : ((Relation)osm).getMembers()) {
    701                     isParent |= child.match(member.getMember());
     977                    isParent |= match.match(member.getMember());
    702978                }
    703979            }
    704980            return isParent;
    705981        }
    706         @Override public String toString() {return "parent(" + child + ")";}
    707         public Match getChild() {
    708             return child;
    709         }
    710     }
    711 
    712     public static class Child extends Match {
    713         private final Match parent;
     982        @Override public String toString() {return "parent(" + match + ")";}
     983    }
     984
     985    /**
     986     * Matches objects if they are children of the expression
     987     */
     988    public static class Child extends UnaryMatch {
    714989
    715990        public Child(Match m) {
    716             // "child" (null) should mean the same as "child()"
    717             // (Always). I.e. match everything
    718             if (m == null) {
    719                 parent = new Always();
    720             } else {
    721                 parent = m;
    722             }
     991            super(m);
    723992        }
    724993
     
    726995            boolean isChild = false;
    727996            for (OsmPrimitive p : osm.getReferrers()) {
    728                 isChild |= parent.match(p);
     997                isChild |= match.match(p);
    729998            }
    730999            return isChild;
    7311000        }
    732         @Override public String toString() {return "child(" + parent + ")";}
    733 
    734         public Match getParent() {
    735             return parent;
    736         }
    737     }
    738 
    739     /**
    740      * Matches on the area of a closed way.
     1001        @Override public String toString() {return "child(" + match + ")";}
     1002    }
     1003
     1004    /**
     1005     * Matches if the size of the area is within the given range
    7411006     *
    7421007     * @author Ole Jørgen Brønner
    7431008     */
    744     private static class Area extends CountRange {
    745 
    746         public Area(long minCount, long maxCount) {
    747             super(minCount, maxCount);
     1009    private static class AreaSize extends CountRange {
     1010
     1011        public AreaSize(Range range) {
     1012            super(range);
     1013        }
     1014
     1015        public AreaSize(PushbackTokenizer tokenizer) throws ParseError {
     1016            this(tokenizer.readRange(tr("Range of numbers expected")));
    7481017        }
    7491018
     
    7581027        @Override
    7591028        protected String getCountString() {
    760             return "area";
    761         }
    762     }
    763 
    764     /**
    765      * Matches data within bounds.
     1029            return "areasize";
     1030        }
     1031    }
     1032
     1033    /**
     1034     * Matches objects within the given bounds.
    7661035     */
    7671036    private abstract static class InArea extends Match {
     
    7971066
    7981067    /**
    799      * Matches data in source area ("downloaded area").
     1068     * Matches objects within source area ("downloaded area").
    8001069     */
    8011070    private static class InDataSourceArea extends InArea {
     
    8121081
    8131082    /**
    814      * Matches data in current map view.
     1083     * Matches objects within current map view.
    8151084     */
    8161085    private static class InView extends InArea {
     
    8431112    }
    8441113
     1114    /**
     1115     * Parse search string.
     1116     *
     1117     * @return match determined by search string
     1118     * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError
     1119     */
    8451120    public Match parse() throws ParseError {
    8461121        Match m = parseExpression();
     
    8521127    }
    8531128
     1129    /**
     1130     * Parse expression. This is a recursive method.
     1131     *
     1132     * @return match determined by parsing expression
     1133     * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError
     1134     */
    8541135    private Match parseExpression() throws ParseError {
    8551136        Match factor = parseFactor();
    8561137        if (factor == null)
     1138            // empty search string
    8571139            return null;
    8581140        if (tokenizer.readIfEqual(Token.OR))
    8591141            return new Or(factor, parseExpression(tr("Missing parameter for OR")));
     1142        else if (tokenizer.readIfEqual(Token.XOR))
     1143            return new Xor(factor, parseExpression(tr("Missing parameter for XOR")));
    8601144        else {
    8611145            Match expression = parseExpression();
    8621146            if (expression == null)
     1147                // reached end of search string, no more recursive calls
    8631148                return factor;
    8641149            else
     1150                // the default operator is AND
    8651151                return new And(factor, expression);
    8661152        }
    8671153    }
    8681154
     1155    /**
     1156     * Parse expression, showing the specified error message if parsing fails.
     1157     *
     1158     * @param errorMessage to display if parsing error occurs
     1159     * @return
     1160     * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError
     1161     */
    8691162    private Match parseExpression(String errorMessage) throws ParseError {
    8701163        Match expression = parseExpression();
     
    8751168    }
    8761169
     1170    /**
     1171     * Parse next factor (a search operator or search term).
     1172     *
     1173     * @return match determined by parsing factor string
     1174     * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError
     1175     */
    8771176    private Match parseFactor() throws ParseError {
    8781177        if (tokenizer.readIfEqual(Token.LEFT_PARENT)) {
     
    8811180                throw new ParseError(Token.RIGHT_PARENT, tokenizer.nextToken());
    8821181            return expression;
    883         } else if (tokenizer.readIfEqual(Token.NOT))
     1182        } else if (tokenizer.readIfEqual(Token.NOT)) {
    8841183            return new Not(parseFactor(tr("Missing operator for NOT")));
    885         else if (tokenizer.readIfEqual(Token.KEY)) {
     1184        } else if (tokenizer.readIfEqual(Token.KEY)) {
     1185            // factor consists of key:value or key=value
    8861186            String key = tokenizer.getText();
    8871187            if (tokenizer.readIfEqual(Token.EQUALS))
    8881188                return new ExactKeyValue(regexSearch, key, tokenizer.readTextOrNumber());
    8891189            else if (tokenizer.readIfEqual(Token.COLON)) {
    890                 if ("id".equals(key))
    891                     return new Id(tokenizer.readNumber(tr("Primitive id expected")));
    892                 else if ("tags".equals(key)) {
    893                     Range range = tokenizer.readRange(tr("Range of numbers expected"));
    894                     return new TagCountRange(range.getStart(), range.getEnd());
    895                 } else if ("nodes".equals(key)) {
    896                     Range range = tokenizer.readRange(tr("Range of numbers expected"));
    897                     return new NodeCountRange(range.getStart(), range.getEnd());
    898                 } else if ("areasize".equals(key)) {
    899                     Range range = tokenizer.readRange(tr("Range of numbers expected"));
    900                     return new Area(range.getStart(), range.getEnd());
    901                 } else if ("timestamp".equals(key)) {
    902                     String rangeS = " " + tokenizer.readTextOrNumber() + " "; // add leading/trailing space in order to get expected split (e.g. "a--" => {"a", ""})
    903                     String[] rangeA = rangeS.split("/");
    904                     if (rangeA.length == 1) {
    905                         return new KeyValue(key, rangeS, regexSearch, caseSensitive);
    906                     } else if (rangeA.length == 2) {
    907                         String rangeA1 = rangeA[0].trim();
    908                         String rangeA2 = rangeA[1].trim();
    909                         long minDate = DateUtils.fromString(rangeA1.isEmpty() ? "1980" : rangeA1).getTime(); // if min timestap is empty: use lowest possible date
    910                         long maxDate = rangeA2.isEmpty() ? new Date().getTime() : DateUtils.fromString(rangeA2).getTime(); // if max timestamp is empty: use "now"
    911                         return new TimestampRange(minDate, maxDate);
    912                     } else {
    913                         /* I18n: Don't translate timestamp keyword */ throw new ParseError(tr("Expecting <i>min</i>/<i>max</i> after ''timestamp''"));
    914                     }
    915                 } else if ("changeset".equals(key))
    916                     return new ChangesetId(tokenizer.readNumber(tr("Changeset id expected")));
    917                 else if ("version".equals(key))
    918                     return new Version(tokenizer.readNumber(tr("Version expected")));
    919                 else
    920                     return parseKV(key, tokenizer.readTextOrNumber());
     1190                // see if we have a Match that takes a data parameter
     1191                SimpleMatchFactory factory = simpleMatchFactoryMap.get(key);
     1192                if (factory != null)
     1193                    return factory.get(key, tokenizer);
     1194
     1195                UnaryMatchFactory unaryFactory = unaryMatchFactoryMap.get(key);
     1196                if (unaryFactory != null)
     1197                    return unaryFactory.get(key, parseFactor(), tokenizer);
     1198
     1199                // key:value form where value is a string (may be OSM key search)
     1200                return parseKV(key, tokenizer.readTextOrNumber());
    9211201            } else if (tokenizer.readIfEqual(Token.QUESTION_MARK))
    9221202                return new BooleanMatch(key, false);
    923             else if ("new".equals(key))
    924                 return new New();
    925             else if ("modified".equals(key))
    926                 return new Modified();
    927             else if ("incomplete".equals(key))
    928                 return new Incomplete();
    929             else if ("untagged".equals(key))
    930                 return new Untagged();
    931             else if ("selected".equals(key))
    932                 return new Selected();
    933             else if ("closed".equals(key))
    934                 return new Closed();
    935             else if ("child".equals(key))
    936                 return new Child(parseFactor());
    937             else if ("parent".equals(key))
    938                 return new Parent(parseFactor());
    939             else if ("indownloadedarea".equals(key))
    940                 return new InDataSourceArea(false);
    941             else if ("allindownloadedarea".equals(key))
    942                 return new InDataSourceArea(true);
    943             else if ("inview".equals(key))
    944                 return new InView(false);
    945             else if ("allinview".equals(key))
    946                 return new InView(true);
    947             else
     1203            else {
     1204                SimpleMatchFactory factory = simpleMatchFactoryMap.get(key);
     1205                if (factory != null)
     1206                    return factory.get(key, null);
     1207
     1208                UnaryMatchFactory unaryFactory = unaryMatchFactoryMap.get(key);
     1209                if (unaryFactory != null)
     1210                    return unaryFactory.get(key, parseFactor(), null);
     1211
     1212                // match string in any key or value
    9481213                return new Any(key, regexSearch, caseSensitive);
     1214            }
    9491215        } else
    9501216            return null;
  • trunk/src/org/openstreetmap/josm/tools/template_engine/ContextSwitchTemplate.java

    r4725 r4817  
    184184    private Match transform(Match m, int searchExpressionPosition) throws ParseError {
    185185        if (m instanceof Parent) {
    186             Match child = transform(((Parent) m).getChild(), searchExpressionPosition);
     186            Match child = transform(((Parent) m).getOperand(), searchExpressionPosition);
    187187            return new ParentSet(child);
    188188        } else if (m instanceof Child) {
    189             Match parent = transform(((Child) m).getParent(), searchExpressionPosition);
     189            Match parent = transform(((Child) m).getOperand(), searchExpressionPosition);
    190190            return new ChildSet(parent);
    191191        } else if (m instanceof And) {
Note: See TracChangeset for help on using the changeset viewer.