File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\matrix\Combination.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
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 */
24 package hudson.matrix;
25
26 import hudson.Util;
27
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Set;
38 import java.util.StringTokenizer;
39 import java.util.TreeMap;
40 import static java.lang.Boolean.TRUE;
41
42 import groovy.lang.Binding;
43 import 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 */
53 public final class Combination extends TreeMap<String,String> implements Comparable<Combination> {
54
55 public Combination(AxisList axisList, List<String> values) {
56 for(int i=0; i<axisList.size(); i++)
57 super.put(axisList.get(i).getName(),values.get(i));
58 }
59
60 public Combination(AxisList axisList,String... values) {
61 this(axisList,Arrays.asList(values));
62 }
63
64 public Combination(Map<String,String> keyValuePairs) {
65 for (Map.Entry<String, String> e : keyValuePairs.entrySet())
66 super.put(e.getKey(),e.getValue());
67 }
68
69 public String get(Axis a) {
70 return get(a.getName());
71 }
72
73 /**
74 * Obtains the continuous unique index number of this {@link Combination}
75 * in the given {@link AxisList}.
76 */
77 public int toIndex(AxisList axis) {
78 int r = 0;
79 for (Axis a : axis) {
80 r *= a.size();
81 r += a.indexOf(get(a));
82 }
83 return r;
84 }
85
86 /**
87 * Obtains a number N such that "N%M==0" would create
88 * a reasonable sparse matrix for integer M.
89 *
90 * <p>
91 * This is bit like {@link #toIndex(AxisList)}, but instead
92 * of creating a continuous number (which often maps different
93 * values of the same axis to the same index in modulo N residue ring,
94 * we use a prime number P as the base. I think this guarantees the uniform
95 * distribution in any N smaller than 2P (but proof, anyone?)
96 */
97 private long toModuloIndex(AxisList axis) {
98 long r = 0;
99 for (Axis a : axis) {
100 r += a.indexOf(get(a));
101 r *= 31;
102 }
103 return r;
104 }
105
106 /**
107 * Evaluates the given Groovy expression with values bound from this combination.
108 *
109 * <p>
110 * For example, if this combination is a=X,b=Y, then expressions like <tt>a=="X"</tt> would evaluate to
111 * true.
112 */
113 public boolean evalGroovyExpression(AxisList axes, String expression) {
114 if(Util.fixEmptyAndTrim(expression)==null)
115 return true;
116
117 Binding binding = new Binding();
118 for (Map.Entry<String, String> e : entrySet())
119 binding.setVariable(e.getKey(),e.getValue());
120
121 binding.setVariable("index",toModuloIndex(axes));
122 binding.setVariable("uniqueId",toIndex(axes));
123
124 GroovyShell shell = new GroovyShell(binding);
125
126 Object result = shell.evaluate("use("+BooleanCategory.class.getName().replace('$','.')+") {"+expression+"}");
127 return TRUE.equals(result);
128 }
129
130 public int compareTo(Combination that) {
131 int d = this.size()-that.size();
132 if(d!=0) return d;
133
134 Iterator<Map.Entry<String,String>> itr = this.entrySet().iterator();
135 Iterator<Map.Entry<String,String>> jtr = that.entrySet().iterator();
136 while(itr.hasNext()) {
137 Map.Entry<String,String> i = itr.next();
138 Map.Entry<String,String> j = jtr.next();
139
140 d = i.getKey().compareTo(j.getKey());
141 if(d!=0) return d;
142 d = i.getValue().compareTo(j.getValue());
143 if(d!=0) return d;
144 }
145 return 0;
146 }
147
148 /**
149 * Works like {@link #toString()} but only include the given axes.
150 */
151 public String toString(Collection<Axis> subset) {
152 if(size()==1 && subset.size()==1)
153 return values().iterator().next();
154
155 StringBuilder buf = new StringBuilder();
156 for (Axis a : subset) {
157 if(buf.length()>0) buf.append(',');
158 buf.append(a.getName()).append('=').append(get(a));
159 }
160 if(buf.length()==0) buf.append("default"); // special case to avoid 0-length name.
161 return buf.toString();
162 }
163
164 /**
165 * Gets the values that correspond to the specified axes, in their order.
166 */
167 public List<String> values(Collection<? extends Axis> axes) {
168 List<String> r = new ArrayList<String>(axes.size());
169 for (Axis a : axes)
170 r.add(get(a));
171 return r;
172 }
173
174 /**
175 * Converts to the ID string representation:
176 * <tt>axisName=value,axisName=value,...</tt>
177 *
178 * @param sep1
179 * The separator between multiple axes.
180 * @param sep2
181 * The separator between axis name and value.
182 */
183 public String toString(char sep1, char sep2) {
184 StringBuilder buf = new StringBuilder();
185 for (Map.Entry<String,String> e : entrySet()) {
186 if(buf.length()>0) buf.append(sep1);
187 buf.append(e.getKey()).append(sep2).append(e.getValue());
188 }
189 if(buf.length()==0) buf.append("default"); // special case to avoid 0-length name.
190 return buf.toString();
191 }
192
193 @Override
194 public String toString() {
195 return toString(',','=');
196 }
197
198 /**
199 * Gets the 8 character-wide hash code for this combination
200 */
201 public String digest() {
202 return Util.getDigestOf(toString());
203 }
204
205 /**
206 * Reverse operation of {@link #toString()}.
207 */
208 public static Combination fromString(String id) {
209 if(id.equals("default"))
210 return new Combination(Collections.<String,String>emptyMap());
211
212 Map<String,String> m = new HashMap<String,String>();
213 StringTokenizer tokens = new StringTokenizer(id, ",");
214 while(tokens.hasMoreTokens()) {
215 String token = tokens.nextToken();
216 int idx = token.indexOf('=');
217 if(idx<0)
218 throw new IllegalArgumentException("Can't parse "+id);
219 m.put(token.substring(0,idx),token.substring(idx+1));
220 }
221 return new Combination(m);
222 }
223
224 /**
225 * Creates compact string representation suitable for display purpose.
226 *
227 * <p>
228 * The string is made compact by looking for {@link Axis} whose values
229 * are unique, and omit the axis name.
230 */
231 public String toCompactString(AxisList axes) {
232 Set<String> nonUniqueAxes = new HashSet<String>();
233 Map<String,Axis> axisByValue = new HashMap<String,Axis>();
234
235 for (Axis a : axes) {
236 for (String v : a.getValues()) {
237 Axis old = axisByValue.put(v,a);
238 if(old!=null) {
239 // these two axes have colliding values
240 nonUniqueAxes.add(old.getName());
241 nonUniqueAxes.add(a.getName());
242 }
243 }
244 }
245
246 StringBuilder buf = new StringBuilder();
247 for (Map.Entry<String,String> e : entrySet()) {
248 if(buf.length()>0) buf.append(',');
249 if(nonUniqueAxes.contains(e.getKey()))
250 buf.append(e.getKey()).append('=');
251 buf.append(e.getValue());
252 }
253 if(buf.length()==0) buf.append("default"); // special case to avoid 0-length name.
254 return buf.toString();
255 }
256
257 // read-only
258 @Override
259 public void clear() {
260 throw new UnsupportedOperationException();
261 }
262
263 @Override
264 public void putAll(Map<? extends String, ? extends String> map) {
265 throw new UnsupportedOperationException();
266 }
267
268 @Override
269 public String put(String key, String value) {
270 throw new UnsupportedOperationException();
271 }
272
273 @Override
274 public String remove(Object key) {
275 throw new UnsupportedOperationException();
276 }
277
278 /**
279 * Duck-typing for boolean expressions.
280 *
281 * @see Combination#evalGroovyExpression(AxisList,String)
282 */
283 public static final class BooleanCategory {
284 /**
285 * x -> y
286 */
287 public static Boolean implies(Boolean lhs, Boolean rhs) {
288 return !lhs || rhs;
289 }
290 }
291 }
292