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