Changeset 4817 in josm


Ignore:
Timestamp:
18.01.2012 20:19:02 (4 months 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.