Ticket #244: josm-search.patch

File josm-search.patch, 8.4 KB (added by kleptog, 18 years ago)
  • src/org/openstreetmap/josm/actions/search/SearchCompiler.java

     
    55import java.io.PushbackReader;
    66import java.io.StringReader;
    77import java.util.Map.Entry;
     8import java.util.Stack;
    89
    910import org.openstreetmap.josm.data.osm.Node;
    1011import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1112import org.openstreetmap.josm.data.osm.Segment;
    1213import org.openstreetmap.josm.data.osm.Way;
     14import org.openstreetmap.josm.data.osm.User;
    1315
    1416/**
    1517 * Implements a google-like search.
     
    1820public class SearchCompiler {
    1921
    2022        boolean caseSensitive = false;
     23        private static enum Operator { OpParen, OpOr, OpAnd, OpNot };
    2124       
    2225        abstract public static class Match {
    2326                abstract public boolean match(OsmPrimitive osm);
     
    6770                @Override public String toString() {return "id="+id;}
    6871        }
    6972
     73        private static class UserMatch extends Match {
     74                private User user;
     75                public UserMatch(String username) {this.user = username.equals("")?null:User.get(username);}
     76                @Override public boolean match(OsmPrimitive osm) {
     77                        return osm.user == user;
     78                }
     79                @Override public String toString() {return "user="+(user==null?"(null)":user.name);}
     80        }
     81
    7082        private class KeyValue extends Match {
    7183                private String key;
    7284                private String value;
     
    150162
    151163        /**
    152164         * The token returned is <code>null</code> or starts with an identifier character:
    153          * - for an '-'. This will be the only character
     165         * - for an '-'. Similarly '(' and ')' are returned as is
    154166         * : for an key. The value is the next token
    155167         * | for "OR"
    156168         * ' ' for anything else.
     
    170182                        switch (c) {
    171183                        case '-':
    172184                                return "-";
     185                        case '(':
     186                                return "(";
     187                        case ')':
     188                                return ")";     
    173189                        case '"':
    174190                                s = new StringBuilder(" ");
    175191                                for (int nc = search.read(); nc != -1 && nc != '"'; nc = search.read())
     
    191207                                        return " "+s.toString();
    192208                                }
    193209                                c = (char)next;
    194                                 if (c == ' ' || c == '\t' || c == ':' || c == '"') {
     210                                if (c == ' ' || c == '\t' || c == ':' || c == '"' || c == '(' || c == ')' ) {
    195211                                        if (c == ':')
    196212                                                return ":"+s.toString();
    197213                                        search.unread(next);
     
    207223        }
    208224
    209225
    210         private boolean notKey = false;
    211226        private boolean notValue = false;
    212         private boolean or = false;
    213227        private String key = null;
    214228        String token = null;
     229        /* Takes a key and a possible value and builds it into a match object */
    215230        private Match build() {
    216231                String value = token.substring(1);
    217232                if (key == null) {
     
    227242                        return notValue ? new Not(c) : c;
    228243                }
    229244                Match c;
    230                 if (key.equals("type"))
     245                if (key.equals("type")) {
    231246                        c = new ExactType(value);
    232                 else if (key.equals("property")) {
     247                } else if (key.equals("property")) {
    233248                        String realKey = "", realValue = value;
    234249                        int eqPos = value.indexOf("=");
    235250                        if (eqPos != -1) {
     
    237252                                realValue = value.substring(eqPos+1);
    238253                        }
    239254                        c = new KeyValue(realKey, realValue, notValue);
     255                        notValue = false;
    240256                } else if (key.equals("id")) {
    241257                        try {
    242258                                c = new Id(Long.parseLong(value));
    243259                        } catch (NumberFormatException x) {
    244260                                c = new Id(0);
    245261                        }
    246                         if (notValue)
    247                                 c = new Not(c);
    248                 } else
     262                } else if (key.equals("user")) {
     263                        c = new UserMatch(value);
     264                } else {
    249265                        c = new KeyValue(key, value, notValue);
    250                 if (notKey)
    251                         return new Not(c);
     266                        notValue = false;
     267                }
     268                if (notValue)
     269                        c = new Not(c);
    252270                return c;
    253271        }
    254272
     273        /* Stacks for managing the operator precedence */
     274        private Stack<Operator> opstack;
     275        private Stack<Match>    argstack;
     276        /* Push the operator on the stack, but only after ensuring all giher precedence operators are applied */
     277        private void pushOp(Operator op)
     278        {
     279                while( !opstack.empty() && opstack.peek().compareTo(op) > 0)
     280                        applyOp();
     281                opstack.push(op);
     282        }
     283        /* A close parenthesis applies all operators until the open paren */
     284        private void closeParen()
     285        {
     286                while( !opstack.empty() && opstack.peek() != Operator.OpParen)
     287                        applyOp();
     288                if( opstack.empty() )
     289                        throw new Error( "Too many close parenthesis" );
     290                opstack.pop();
     291        }
     292        private void applyOp()
     293        {
     294                try {
     295                        switch( opstack.pop() )
     296                        {
     297                                case OpAnd:
     298                                        argstack.push( new And(argstack.pop(), argstack.pop()) );
     299                                        break;
     300                                case OpOr:
     301                                        argstack.push( new Or(argstack.pop(), argstack.pop()) );
     302                                        break;
     303                                case OpNot:
     304                                        argstack.push( new Not(argstack.pop()) );
     305                                        break;
     306                                default:
     307                                        throw new Error( "Unknown operator" );
     308                        }
     309                }
     310                catch( java.util.EmptyStackException e )
     311                {
     312                        throw new Error("Missing argument");
     313                }
     314        }
     315        /* The actual parser */
    255316        private Match parse(PushbackReader search) {
    256                 Match result = null;
     317                argstack = new Stack<Match>();
     318                opstack = new Stack<Operator>();
     319               
     320                pushOp( Operator.OpParen );
     321                boolean hadArg = false;
    257322                for (token = nextToken(search); token != null; token = nextToken(search)) {
     323//                      System.out.println( "token:["+token+"], opstack:"+opstack+", argstack:"+argstack+", hadArg:"+hadArg );
    258324                        if (token.equals("-"))
    259                                 notValue = true;
    260                         else if (token.equals("|")) {
    261                                 if (result == null)
    262                                         continue;
    263                                 or = true;
    264                                 notValue = false;
    265                         } else if (token.startsWith(":")) {
     325                        {
     326                                if( hadArg )
     327                                        pushOp(Operator.OpAnd);
     328                                hadArg = false;
     329                                if( key == null )
     330                                        pushOp(Operator.OpNot);
     331                                else
     332                                        notValue = true;
     333                        }
     334                        else if (token.equals("("))
     335                        {
     336                                if( hadArg )
     337                                        pushOp(Operator.OpAnd);
     338                                hadArg = false;
     339                                opstack.push(Operator.OpParen);
     340                        }
     341                        else if (token.equals(")"))
     342                        {
     343                                hadArg = true;
     344                                closeParen();
     345                        }
     346                        else if (token.equals("|"))
     347                        {
     348                                pushOp(Operator.OpOr);
     349                                hadArg = false;
     350                        }
     351                        else if (token.startsWith(":")) {
     352                                if( hadArg )
     353                                        pushOp(Operator.OpAnd);
     354                                hadArg = false;
    266355                                if (key == null) {
    267356                                        key = token.substring(1);
    268                                         notKey = notValue;
    269357                                        notValue = false;
    270358                                } else
    271359                                        key += token.substring(1);
    272360                        } else {
     361                                if( hadArg )
     362                                        pushOp(Operator.OpAnd);
    273363                                Match current = build();
    274                                 if (result == null)
    275                                         result = current;
    276                                 else
    277                                         result = or ? new Or(result, current) : new And(result, current);
    278                                         key = null;
    279                                         notKey = false;
    280                                         notValue = false;
    281                                         or = false;
    282                         }
     364                                argstack.push(current);
     365                                hadArg = true;
     366                                key = null;
     367                                notValue = false;
     368                        }
    283369                }
     370//              System.out.println( "End loop: opstack:"+opstack+", argstack:"+argstack+", hadArg:"+hadArg );
    284371                // if "key:" was the last search
    285372                if (key != null) {
     373                        if( hadArg )
     374                                pushOp(Operator.OpAnd);
    286375                        token = " ";
    287376                        Match current = build();
    288                         result = (result == null) ? current : new And(result, current);
     377                        argstack.push(current);
    289378                }
    290                 return result == null ? new Always() : result;
     379                closeParen();
     380//              System.out.println( "Finish: opstack:"+opstack+", argstack:"+argstack+", hadArg:"+hadArg );
     381                if( !opstack.empty() || argstack.size() > 1 )
     382                        throw new Error("Parse error");
     383                if( argstack.empty() )
     384                        return new Always();
     385                return argstack.pop();
    291386        }
    292387}
  • src/org/openstreetmap/josm/actions/search/SearchAction.java

     
    8585                }
    8686        }
    8787        Collection<OsmPrimitive> sel = Main.ds.getSelected();
    88         SearchCompiler.Match matcher = SearchCompiler.compile(search, caseSensitive);
    89         for (OsmPrimitive osm : Main.ds.allNonDeletedPrimitives()) {
     88        try {
     89                SearchCompiler.Match matcher = SearchCompiler.compile(search, caseSensitive);
     90        for (OsmPrimitive osm : Main.ds.allNonDeletedPrimitives()) {
    9091                if (mode == SearchMode.replace) {
    9192                        if (matcher.match(osm))
    9293                                sel.add(osm);
     
    9798                else if (mode == SearchMode.remove && osm.selected && matcher.match(osm))
    9899                        sel.remove(osm);
    99100        }
     101        } catch( Error e ) {
     102                JOptionPane.showMessageDialog(Main.parent, tr("Error in expression"));
     103                return;
     104        }
    100105        Main.ds.setSelected(sel);
    101106    }
    102107}