Kaynağa Gözat

New actions substitutor.

Squashed commit of the following:

commit 81c3cb41fe
Author: Chris Smith <chris@dmdirc.com>
Date:   Sun Feb 28 21:13:03 2010 +0000

    Fix a couple of escaping edge cases

    Change-Id: I8cd9c3493a932039b7bae704c522f79778946d55

commit cde2d943dc
Author: Chris Smith <chris@dmdirc.com>
Date:   Thu Feb 25 05:51:38 2010 +0000

    Working actions substitutor replacement

    Fixes issue 3804

    Change-Id: Id714c47d599815461826f403942bfe490ac587ff

commit f450ce592a
Author: Chris Smith <chris@dmdirc.com>
Date:   Thu Feb 25 00:01:55 2010 +0000

    Support for config and server substitutions

    Change-Id: I0b10332f340212d05ac478d4025ee557d2e66351

commit 4d2357a28f
Author: Chris Smith <chris@dmdirc.com>
Date:   Tue Feb 23 21:41:54 2010 +0000

    Implement word and component substitutions

    Change-Id: I120f50e6eec3dc968beff42d79c0171a5908155e

commit 0bb9fbb970
Author: Chris Smith <chris@dmdirc.com>
Date:   Mon Jan 25 21:32:00 2010 +0000

    Unit test for actions substitutor

Change-Id: I4d8b2084f7b78cf2b30f0409e1a7fdfe5a9ee5aa
Reviewed-on: http://gerrit.dmdirc.com/927
Automatic-Compile: DMDirc Local Commits <dmdirc@googlemail.com>
Reviewed-by: Gregory Holmes <greg@dmdirc.com>
tags/0.6.4rc1
Chris Smith 14 yıl önce
ebeveyn
işleme
dd61b6cd8a

+ 105
- 137
src/com/dmdirc/actions/ActionSubstitutor.java Dosyayı Görüntüle

@@ -27,14 +27,17 @@ import com.dmdirc.actions.interfaces.ActionComponent;
27 27
 import com.dmdirc.FrameContainer;
28 28
 import com.dmdirc.Precondition;
29 29
 import com.dmdirc.Server;
30
-import com.dmdirc.ServerState;
30
+import com.dmdirc.commandparser.CommandArguments;
31 31
 import com.dmdirc.config.ConfigManager;
32 32
 import com.dmdirc.config.IdentityManager;
33
-
34 33
 import com.dmdirc.ui.interfaces.Window;
34
+
35
+import java.util.Arrays;
35 36
 import java.util.HashMap;
36 37
 import java.util.Map;
37 38
 import java.util.Set;
39
+import java.util.regex.Matcher;
40
+import java.util.regex.Pattern;
38 41
 
39 42
 /**
40 43
  * Handles the substitution of variables into action targets and responses.
@@ -65,19 +68,6 @@ public class ActionSubstitutor {
65 68
         return IdentityManager.getGlobalConfig().getOptions("actions").keySet();
66 69
     }
67 70
     
68
-    /**
69
-     * Substitutes in config variables into the specified target.
70
-     *
71
-     * @param config The configuration manager to use for options
72
-     * @param target The StringBuilder to modify
73
-     * @since 0.6.3m2
74
-     */
75
-    private void doConfigSubstitutions(final ConfigManager config, final StringBuilder target) {
76
-        for (Map.Entry<String, String> option : config.getOptions("actions").entrySet()) {
77
-            doReplacement(target, "$" + option.getKey(), option.getValue());
78
-        }
79
-    }
80
-    
81 71
     /**
82 72
      * Retrieves a list of substitutions derived from argument and component
83 73
      * combinations, along with a corresponding friendly name for them.
@@ -103,33 +93,6 @@ public class ActionSubstitutor {
103 93
         return res;
104 94
     }
105 95
     
106
-    /**
107
-     * Substitutes in component-style substitutions.
108
-     *
109
-     * @param target The stringbuilder to be changed
110
-     * @param args The arguments passed for this action type
111
-     */
112
-    private void doComponentSubstitutions(final StringBuilder target, final Object ... args) {
113
-        int i = 0;
114
-        for (Class myClass : type.getType().getArgTypes()) {
115
-            if (args[i] != null) {
116
-                for (ActionComponent comp : ActionManager.getCompatibleComponents(myClass)) {
117
-                    final String needle = "${" + i + "." + comp.toString() + "}";
118
-
119
-                    if (target.indexOf(needle) > -1) {
120
-                        final Object replacement = comp.get(args[i]);
121
-
122
-                        if (replacement != null) {
123
-                            doReplacement(target, needle, replacement.toString());
124
-                        }
125
-                    }
126
-                }
127
-            }
128
-            
129
-            i++;
130
-        }
131
-    }
132
-    
133 96
     /**
134 97
      * Retrieves a list of server substitutions, if this action type supports
135 98
      * them.
@@ -152,38 +115,6 @@ public class ActionSubstitutor {
152 115
         return res;
153 116
     }
154 117
     
155
-    /**
156
-     * Substitutes in server substitutions.
157
-     *
158
-     * @param target The stringbuilder to be changed
159
-     * @param args The arguments passed for this action type
160
-     */
161
-    private void doServerSubstitutions(final StringBuilder target, final Object ... args) {
162
-        if (args.length > 0 && args[0] instanceof FrameContainer) {
163
-            final Server server = ((FrameContainer) args[0]).getServer();
164
-        
165
-            if (server != null) {
166
-                synchronized (server.getState()) {
167
-                    if (!server.getState().equals(ServerState.CONNECTED)) {
168
-                        return;
169
-                    }
170
-                    
171
-                    for (ActionComponent comp : ActionManager.getCompatibleComponents(Server.class)) {
172
-                        final String key = "${" + comp.toString() + "}";
173
-
174
-                        if (target.indexOf(key) > -1) {
175
-                            final Object res = comp.get(((FrameContainer) args[0]).getServer());
176
-
177
-                            if (res != null) {
178
-                                doReplacement(target, key, res.toString());
179
-                            }
180
-                        }
181
-                    }
182
-                }
183
-            }
184
-        }
185
-    }
186
-    
187 118
     /**
188 119
      * Returns true if this action type's first argument is a frame container,
189 120
      * or descendent of one.
@@ -212,42 +143,8 @@ public class ActionSubstitutor {
212 143
      */
213 144
     public boolean usesWordSubstitutions() {
214 145
         return type.getType().getArgTypes().length > 2
215
-                && type.getType().getArgTypes()[2] == String[].class;
216
-    }
217
-    
218
-    /**
219
-     * Substitutes in word substitutions.
220
-     *
221
-     * @param target The stringbuilder to be changed
222
-     * @param args The arguments passed for this action type
223
-     */
224
-    private void doWordSubstitutions(final StringBuilder target, final Object ... args) {
225
-        if (args.length > 1) {
226
-            String[] words = null;
227
-            
228
-            if (args.length > 2 && args[2] instanceof String[]) {
229
-                words = (String[]) args[2];
230
-            } else if (args.length > 2 && args[2] instanceof String) {
231
-                words = ((String) args[2]).split(" ");
232
-            } else if (args[1] instanceof String[]) {
233
-                words = (String[]) args[1];
234
-            } else if (args[1] instanceof String) {
235
-                words = ((String) args[1]).split(" ");
236
-            } else {
237
-                return;
238
-            }
239
-            
240
-            final StringBuffer compound = new StringBuffer();
241
-            for (int i = words.length - 1; i >= 0; i--) {
242
-                if (compound.length() > 0) {
243
-                    compound.insert(0, ' ');
244
-                }
245
-                compound.insert(0, words[i]);
246
-                
247
-                doReplacement(target, "$" + (i + 1) + "-", compound.toString());
248
-                doReplacement(target, "$" + (i + 1), words[i]);
249
-            }
250
-        }
146
+                && (type.getType().getArgTypes()[2] == String[].class
147
+                || type.getType().getArgTypes()[2] == String.class);
251 148
     }
252 149
     
253 150
     /**
@@ -268,13 +165,104 @@ public class ActionSubstitutor {
268 165
         }
269 166
 
270 167
         final StringBuilder res = new StringBuilder(target);
271
-        
272
-        doConfigSubstitutions(getConfigManager(args), res);
273
-        doServerSubstitutions(res, args);
274
-        doComponentSubstitutions(res, args);
275
-        doWordSubstitutions(res, args);
276
-        
277
-        return res.toString();
168
+
169
+        final Pattern bracesPattern = Pattern.compile("(?<!\\\\)((?:\\\\\\\\)*)"
170
+                + "(\\$\\{([^{}]*?)\\})");
171
+        final Pattern otherPattern = Pattern.compile("(?<!\\\\)((?:\\\\\\\\)*)(\\$("
172
+                + "[0-9]+(-([0-9]+)?)?|" // Word subs - $1, $1-, $1-2
173
+                + "[0-9]+(\\.([A-Z_]+))+|" // Component subs - 2.FOO_BAR
174
+                + "[a-z0-9A-Z_\\.]+" // Config/server subs
175
+                + "))");
176
+
177
+        Matcher bracesMatcher = bracesPattern.matcher(res);
178
+        Matcher otherMatcher = otherPattern.matcher(res);
179
+
180
+        boolean first;
181
+
182
+        while ((first = bracesMatcher.find()) || otherMatcher.find()) {
183
+            final Matcher matcher = first ? bracesMatcher : otherMatcher;
184
+
185
+            final String group = matcher.group(3);
186
+            final int start = matcher.start() + matcher.group(1).length(), end = matcher.end();
187
+            System.out.println(res + ": " + start + " ->" + end);
188
+
189
+            res.delete(start, end);
190
+            res.insert(start, getSubstitution(doSubstitution(group, args), args));
191
+            
192
+            bracesMatcher = bracesPattern.matcher(res);
193
+            otherMatcher = otherPattern.matcher(res);
194
+        }
195
+
196
+        return res.toString().replaceAll("\\\\(.)", "$1");
197
+    }
198
+
199
+    /**
200
+     * Retrieves the value which should be used for the specified substitution.
201
+     *
202
+     * @param substitution The substitution, without leading $
203
+     * @param args The arguments for the action
204
+     * @return The substitution to be used
205
+     */
206
+    private String getSubstitution(final String substitution, final Object ... args) {
207
+        final Pattern numberPattern = Pattern.compile("([0-9]+)(-([0-9]+)?)?");
208
+        final Matcher numberMatcher = numberPattern.matcher(substitution);
209
+
210
+        final Pattern compPattern = Pattern.compile("([0-9]+)\\.([A-Z_]+(\\.[A-Z_]+)*)");
211
+        final Matcher compMatcher = compPattern.matcher(substitution);
212
+
213
+        if (usesWordSubstitutions() && numberMatcher.matches()) {
214
+            final CommandArguments words = args[2] instanceof String ?
215
+                new CommandArguments((String) args[2]) :
216
+                new CommandArguments(Arrays.asList((String[]) args[2]));
217
+
218
+            int start, end;
219
+
220
+            start = end = Integer.parseInt(numberMatcher.group(1)) - 1;
221
+
222
+            if (numberMatcher.group(3) != null) {
223
+                end = Integer.parseInt(numberMatcher.group(3)) - 1;
224
+            } else if (numberMatcher.group(2) != null) {
225
+                end = words.getWords().length - 1;
226
+            }
227
+
228
+            return words.getWordsAsString(start, end);
229
+        }
230
+
231
+        if (compMatcher.matches()) {
232
+            final int argument = Integer.parseInt(compMatcher.group(1));
233
+
234
+            try {
235
+                final ActionComponentChain chain = new ActionComponentChain(
236
+                        type.getType().getArgTypes()[argument], compMatcher.group(2));
237
+                return chain.get(args[argument]).toString();
238
+            } catch (IllegalArgumentException ex) {
239
+                // TODO: Log the error nicely somewhere?
240
+                return substitution;
241
+            }
242
+        }
243
+
244
+        final ConfigManager manager = getConfigManager(args);
245
+
246
+        if (manager.hasOptionString("actions", substitution)) {
247
+            return manager.getOption("actions", substitution);
248
+        }
249
+
250
+        if (hasFrameContainer()) {
251
+            final Server server = ((FrameContainer) args[0]).getServer();
252
+
253
+            if (server != null) {
254
+                try {
255
+                    final ActionComponentChain chain = new ActionComponentChain(
256
+                        Server.class, substitution);
257
+                    return chain.get(server).toString();
258
+                } catch (IllegalArgumentException ex) {
259
+                    // TODO: Log the error nicely somewhere?
260
+                    return substitution;
261
+                }
262
+            }
263
+        }
264
+
265
+        return substitution;
278 266
     }
279 267
 
280 268
     /**
@@ -299,24 +287,4 @@ public class ActionSubstitutor {
299 287
         return IdentityManager.getGlobalConfig();
300 288
     }
301 289
     
302
-    /**
303
-     * Replaces all occurances of needle in haystack with replacement.
304
-     *
305
-     * @param haystack The stringbuilder that is to be modified
306
-     * @param needle The search string
307
-     * @param replacement The string to be substituted in
308
-     */
309
-    private void doReplacement(final StringBuilder haystack, final String needle,
310
-            final String replacement) {
311
-        int i = -1;
312
-        
313
-        do {
314
-            i = haystack.indexOf(needle);
315
-            
316
-            if (i != -1) {
317
-                haystack.replace(i, i + needle.length(), replacement);
318
-            }
319
-        } while (i != -1);
320
-    }
321
-    
322 290
 }

+ 151
- 0
test/com/dmdirc/actions/ActionSubstitutorTest.java Dosyayı Görüntüle

@@ -0,0 +1,151 @@
1
+/*
2
+ * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.actions;
24
+
25
+import com.dmdirc.Channel;
26
+import com.dmdirc.Server;
27
+import com.dmdirc.ServerState;
28
+import com.dmdirc.config.ConfigManager;
29
+import com.dmdirc.config.IdentityManager;
30
+import com.dmdirc.config.InvalidIdentityFileException;
31
+import com.dmdirc.parser.interfaces.ChannelClientInfo;
32
+
33
+import java.util.Arrays;
34
+import java.util.HashMap;
35
+import java.util.List;
36
+import java.util.Map;
37
+
38
+import org.junit.BeforeClass;
39
+import org.junit.Test;
40
+import org.junit.runner.RunWith;
41
+import org.junit.runners.Parameterized;
42
+import static org.junit.Assert.*;
43
+import static org.mockito.Mockito.*;
44
+
45
+@RunWith(Parameterized.class)
46
+public class ActionSubstitutorTest {
47
+
48
+    private static final Map<String, String> settings = new HashMap<String, String>();
49
+    
50
+    private final String input, expected;
51
+    private final Channel channel;
52
+    private final ActionSubstitutor substitutor;
53
+    private final Object[] args;
54
+
55
+    @BeforeClass
56
+    public static void setup() throws InvalidIdentityFileException {
57
+        IdentityManager.load();
58
+        ActionManager.init();
59
+
60
+        settings.put("alpha", "A");
61
+        settings.put("bravo", "$alpha");
62
+        settings.put("charlie", "${bravo}");
63
+        settings.put("delta", "${${bravo}${bravo}}");
64
+        settings.put("AA", "win!");
65
+    }
66
+
67
+    public ActionSubstitutorTest(final String input, final String expected) {
68
+        this.input = input;
69
+        this.expected = expected;
70
+
71
+        this.channel = mock(Channel.class);
72
+        
73
+        final ConfigManager manager = mock(ConfigManager.class);
74
+        final Server server = mock(Server.class);
75
+
76
+        final ChannelClientInfo clientInfo = mock(ChannelClientInfo.class);
77
+
78
+        when(channel.getServer()).thenReturn(server);
79
+        when(channel.getConfigManager()).thenReturn(manager);
80
+        when(server.getState()).thenReturn(ServerState.CONNECTED);
81
+        when(server.getAwayMessage()).thenReturn("foo");
82
+
83
+        when(manager.getOptions(eq("actions"))).thenReturn(settings);
84
+        for (Map.Entry<String, String> entry : settings.entrySet()) {
85
+            when(manager.hasOptionString("actions", entry.getKey())).thenReturn(true);
86
+            when(manager.getOption("actions", entry.getKey())).thenReturn(entry.getValue());
87
+        }
88
+
89
+        substitutor = new ActionSubstitutor(CoreActionType.CHANNEL_MESSAGE);
90
+
91
+        args = new Object[]{
92
+            channel,
93
+            clientInfo,
94
+            "1 2 3 fourth_word_here 5 6 7"
95
+        };
96
+    }
97
+
98
+    @Test
99
+    public void testSubstitution() {
100
+        assertEquals(expected, substitutor.doSubstitution(input, args));
101
+    }
102
+
103
+    @Parameterized.Parameters
104
+    public static List<String[]> data() {
105
+        final String[][] tests = {
106
+            // -- Existing behaviour -------------------------------------------
107
+            // ---- No subs at all ---------------------------------------------
108
+            {"no subs here!", "no subs here!"},
109
+            // ---- Config subs ------------------------------------------------
110
+            {"$alpha", "A"},
111
+            {"--$alpha--", "--A--"},
112
+            // ---- Word subs --------------------------------------------------
113
+            {"$1", "1"},
114
+            {"$4", "fourth_word_here"},
115
+            {"$5-", "5 6 7"},
116
+            // ---- Component subs ---------------------------------------------
117
+            {"${2.STRING_LENGTH}", "28"},
118
+            {"${2.STRING_STRING}", "1 2 3 fourth_word_here 5 6 7"},
119
+            // ---- Server subs ------------------------------------------------
120
+            {"${SERVER_MYAWAYREASON}", "foo"},
121
+            // ---- Combinations -----------------------------------------------
122
+            {"${1}${2.STRING_LENGTH}$alpha", "128A"},
123
+            {"$alpha$4${SERVER_MYAWAYREASON}", "Afourth_word_herefoo"},
124
+            
125
+            // -- New behaviour ------------------------------------------------
126
+            // ---- Config subs ------------------------------------------------
127
+            {"${alpha}", "A"},
128
+            {"\\$alpha", "$alpha"},
129
+            {"$bravo", "A"},
130
+            {"$charlie", "A"},
131
+            {"$delta", "win!"},
132
+            // ---- Word subs --------------------------------------------------
133
+            {"$5-6", "5 6"},
134
+            {"${5-6}", "5 6"},
135
+            {"${5-$6}", "5 6"},
136
+            {"${5-${${6}}}", "5 6"},
137
+            // ---- Component subs ---------------------------------------------
138
+            {"${2.STRING_STRING.STRING_LENGTH}", "28"},
139
+            // ---- Escaping ---------------------------------------------------
140
+            {"\\$1", "$1"},
141
+            {"\\$alpha $alpha", "$alpha A"},
142
+            {"\\$$1", "$1"},
143
+            {"\\\\$4", "\\fourth_word_here"},
144
+            {"\\\\${4}", "\\fourth_word_here"},
145
+            {"\\\\\\$4", "\\$4"},
146
+        };
147
+
148
+        return Arrays.asList(tests);
149
+    }
150
+
151
+}

Loading…
İptal
Kaydet