PageRenderTime 33ms CodeModel.GetById 13ms app.highlight 16ms RepoModel.GetById 1ms app.codeStats 1ms

/hudson-core/src/main/java/hudson/matrix/Combination.java

http://github.com/hudson/hudson
Java | 319 lines | 183 code | 36 blank | 100 comment | 33 complexity | fb346aeb641f2edad73302c154e4aedd MD5 | raw file
  1/*
  2 * The MIT License
  3 * 
  4 * Copyright (c) 2004-2011, Oracle Corporation, Kohsuke Kawaguchi, Anton Kozak
  5 * 
  6 * Permission is hereby granted, free of charge, to any person obtaining a copy
  7 * of this software and associated documentation files (the "Software"), to deal
  8 * in the Software without restriction, including without limitation the rights
  9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 10 * copies of the Software, and to permit persons to whom the Software is
 11 * furnished to do so, subject to the following conditions:
 12 * 
 13 * The above copyright notice and this permission notice shall be included in
 14 * all copies or substantial portions of the Software.
 15 * 
 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 22 * THE SOFTWARE.
 23 */
 24package hudson.matrix;
 25
 26import hudson.Util;
 27
 28import java.util.ArrayList;
 29import java.util.Arrays;
 30import java.util.Collection;
 31import java.util.Collections;
 32import java.util.HashMap;
 33import java.util.HashSet;
 34import java.util.Iterator;
 35import java.util.List;
 36import java.util.Map;
 37import java.util.Set;
 38import java.util.StringTokenizer;
 39import java.util.TreeMap;
 40import static java.lang.Boolean.TRUE;
 41
 42import groovy.lang.Binding;
 43import groovy.lang.GroovyShell;
 44
 45/**
 46 * A particular combination of {@link Axis} values.
 47 *
 48 * For example, when axes are "x={1,2},y={3,4}", then
 49 * [x=1,y=3] is a combination (out of 4 possible combinations)
 50 *
 51 * @author Kohsuke Kawaguchi
 52 */
 53public final class Combination extends TreeMap<String,String> implements Comparable<Combination> {
 54
 55    protected static final String DELIM = ",";
 56
 57    public Combination(AxisList axisList, List<String> values) {
 58        for(int i=0; i<axisList.size(); i++)
 59            super.put(axisList.get(i).getName(),values.get(i));
 60    }
 61
 62    public Combination(AxisList axisList,String... values) {
 63        this(axisList,Arrays.asList(values));
 64    }
 65
 66    public Combination(Map<String,String> keyValuePairs) {
 67        for (Map.Entry<String, String> e : keyValuePairs.entrySet())
 68            super.put(e.getKey(),e.getValue());
 69    }
 70
 71    public String get(Axis a) {
 72        return get(a.getName());
 73    }
 74
 75    /**
 76     * Obtains the continuous unique index number of this {@link Combination}
 77     * in the given {@link AxisList}.
 78     */
 79    public int toIndex(AxisList axis) {
 80        int r = 0;
 81        for (Axis a : axis) {
 82            r *= a.size();
 83            r += a.indexOf(get(a));
 84        }
 85        return r;
 86    }
 87
 88    /**
 89     * Obtains a number N such that "N%M==0" would create
 90     * a reasonable sparse matrix for integer M.
 91     *
 92     * <p>
 93     * This is bit like {@link #toIndex(AxisList)}, but instead
 94     * of creating a continuous number (which often maps different
 95     * values of the same axis to the same index in modulo N residue ring,
 96     * we use a prime number P as the base. I think this guarantees the uniform
 97     * distribution in any N smaller than 2P (but proof, anyone?)
 98     */
 99    private long toModuloIndex(AxisList axis) {
100        long r = 0;
101        for (Axis a : axis) {
102            r += a.indexOf(get(a));
103            r *= 31;
104        }
105        return r;
106    }
107
108    /**
109     * Evaluates the given Groovy expression with values bound from this combination.
110     *
111     * <p>
112     * For example, if this combination is a=X,b=Y, then expressions like <tt>a=="X"</tt> would evaluate to
113     * true.
114     */
115    public boolean evalGroovyExpression(AxisList axes, String expression) {
116        if(Util.fixEmptyAndTrim(expression)==null)
117            return true;
118
119        Binding binding = new Binding();
120        for (Map.Entry<String, String> e : entrySet())
121            binding.setVariable(e.getKey(),e.getValue());
122
123        binding.setVariable("index",toModuloIndex(axes));
124        binding.setVariable("uniqueId",toIndex(axes));
125
126        GroovyShell shell = new GroovyShell(binding);
127
128        Object result = shell.evaluate("use("+BooleanCategory.class.getName().replace('$','.')+") {"+expression+"}");
129        return TRUE.equals(result);
130    }
131
132    public int compareTo(Combination that) {
133        int d = this.size()-that.size();
134        if(d!=0)    return d;
135
136        Iterator<Map.Entry<String,String>> itr = this.entrySet().iterator();
137        Iterator<Map.Entry<String,String>> jtr = that.entrySet().iterator();
138        while(itr.hasNext()) {
139            Map.Entry<String,String> i = itr.next();
140            Map.Entry<String,String> j = jtr.next();
141
142            d = i.getKey().compareTo(j.getKey());
143            if(d!=0)    return d;
144            d = i.getValue().compareTo(j.getValue());
145            if(d!=0)    return d;
146        }
147        return 0;
148    }
149
150    /**
151     * Works like {@link #toString()} but only include the given axes.
152     */
153    public String toString(Collection<Axis> subset) {
154        if(size()==1 && subset.size()==1)
155            return values().iterator().next();
156
157        StringBuilder buf = new StringBuilder();
158        for (Axis a : subset) {
159            if(buf.length()>0) buf.append(',');
160            buf.append(a.getName()).append('=').append(get(a));
161        }
162        if(buf.length()==0) buf.append("default"); // special case to avoid 0-length name.
163        return buf.toString();
164    }
165
166    /**
167     * Gets the values that correspond to the specified axes, in their order.
168     */
169    public List<String> values(Collection<? extends Axis> axes) {
170        List<String> r = new ArrayList<String>(axes.size());
171        for (Axis a : axes)
172            r.add(get(a));
173        return r;
174    }
175
176    /**
177     * Converts to the ID string representation:
178     * <tt>axisName=value,axisName=value,...</tt>
179     *
180     * @param sep1
181     *      The separator between multiple axes.
182     * @param sep2
183     *      The separator between axis name and value.
184     */
185    public String toString(char sep1, char sep2) {
186        return toString(sep1, sep2, false);
187    }
188
189    /**
190     * Converts to the ID string representation:
191     * <tt>axisName=value,axisName=value,...</tt>
192     *
193     * @param sep1 The separator between multiple axes.
194     * @param sep2 The separator between axis name and value.
195     * @param encodeValue true to encode value {@link Util#rawEncode(String)}
196     * @return string representation.
197     */
198    public String toString(char sep1, char sep2, boolean encodeValue) {
199        StringBuilder builder = new StringBuilder();
200        if (encodeValue) {
201            for (Map.Entry<String, String> e : entrySet()) {
202                if (builder.length() > 0) {
203                    builder.append(sep1);
204                }
205                builder.append(e.getKey()).append(sep2).append(Util.rawEncode(e.getValue()));
206            }
207        } else {
208            for (Map.Entry<String, String> e : entrySet()) {
209                if (builder.length() > 0) {
210                    builder.append(sep1);
211                }
212                builder.append(e.getKey()).append(sep2).append(e.getValue());
213            }
214        }
215        if (builder.length() == 0) {
216            builder.append("default"); // special case to avoid 0-length name.
217        }
218        return builder.toString();
219    }
220
221    @Override
222    public String toString() {
223        return toString(',','=');
224    }
225
226    /**
227     * Gets the 8 character-wide hash code for this combination
228     */
229    public String digest() {
230        return Util.getDigestOf(toString());
231    }
232
233    /**
234     * Reverse operation of {@link #toString()}.
235     */
236    public static Combination fromString(String id) {
237        if(id.equals("default"))
238            return new Combination(Collections.<String,String>emptyMap());
239
240        Map<String,String> m = new HashMap<String,String>();
241        StringTokenizer tokens = new StringTokenizer(id, DELIM);
242        while(tokens.hasMoreTokens()) {
243            String token = tokens.nextToken();
244            int idx = token.indexOf('=');
245            if(idx<0)
246                throw new IllegalArgumentException("Can't parse "+id);
247            m.put(token.substring(0,idx),token.substring(idx+1));
248        }
249        return new Combination(m);
250    }
251
252    /**
253     * Creates compact string representataion suitable for display purpose.
254     *
255     * <p>
256     * The string is made compact by looking for {@link Axis} whose values
257     * are unique, and omit the axis name.
258     */
259    public String toCompactString(AxisList axes) {
260        Set<String> nonUniqueAxes = new HashSet<String>();
261        Map<String,Axis> axisByValue = new HashMap<String,Axis>();
262
263        for (Axis a : axes) {
264            for (String v : a.getValues()) {
265                Axis old = axisByValue.put(v,a);
266                if(old!=null) {
267                    // these two axes have colliding values
268                    nonUniqueAxes.add(old.getName());
269                    nonUniqueAxes.add(a.getName());
270                }
271            }
272        }
273
274        StringBuilder buf = new StringBuilder();
275        for (Map.Entry<String,String> e : entrySet()) {
276            if(buf.length()>0) buf.append(',');
277            if(nonUniqueAxes.contains(e.getKey()))
278                buf.append(e.getKey()).append('=');
279            buf.append(e.getValue());
280        }
281        if(buf.length()==0) buf.append("default"); // special case to avoid 0-length name.
282        return buf.toString();
283    }
284
285    // read-only
286    @Override
287    public void clear() {
288        throw new UnsupportedOperationException();
289    }
290
291    @Override
292    public void putAll(Map<? extends String, ? extends String> map) {
293        throw new UnsupportedOperationException();
294    }
295
296    @Override
297    public String put(String key, String value) {
298        throw new UnsupportedOperationException();
299    }
300
301    @Override
302    public String remove(Object key) {
303        throw new UnsupportedOperationException();
304    }
305
306    /**
307     * Duck-typing for boolean expressions.
308     *
309     * @see Combination#evalGroovyExpression(AxisList,String)
310     */
311    public static final class BooleanCategory {
312        /**
313         * x -> y
314         */
315        public static Boolean implies(Boolean lhs, Boolean rhs) {
316            return !lhs || rhs;
317        }
318    }
319}