/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
- /*
- * The MIT License
- *
- * Copyright (c) 2004-2011, Oracle Corporation, Kohsuke Kawaguchi, Anton Kozak
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
- package hudson.matrix;
- import hudson.Util;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import java.util.StringTokenizer;
- import java.util.TreeMap;
- import static java.lang.Boolean.TRUE;
- import groovy.lang.Binding;
- import groovy.lang.GroovyShell;
- /**
- * A particular combination of {@link Axis} values.
- *
- * For example, when axes are "x={1,2},y={3,4}", then
- * [x=1,y=3] is a combination (out of 4 possible combinations)
- *
- * @author Kohsuke Kawaguchi
- */
- public final class Combination extends TreeMap<String,String> implements Comparable<Combination> {
- protected static final String DELIM = ",";
- public Combination(AxisList axisList, List<String> values) {
- for(int i=0; i<axisList.size(); i++)
- super.put(axisList.get(i).getName(),values.get(i));
- }
- public Combination(AxisList axisList,String... values) {
- this(axisList,Arrays.asList(values));
- }
- public Combination(Map<String,String> keyValuePairs) {
- for (Map.Entry<String, String> e : keyValuePairs.entrySet())
- super.put(e.getKey(),e.getValue());
- }
- public String get(Axis a) {
- return get(a.getName());
- }
- /**
- * Obtains the continuous unique index number of this {@link Combination}
- * in the given {@link AxisList}.
- */
- public int toIndex(AxisList axis) {
- int r = 0;
- for (Axis a : axis) {
- r *= a.size();
- r += a.indexOf(get(a));
- }
- return r;
- }
- /**
- * Obtains a number N such that "N%M==0" would create
- * a reasonable sparse matrix for integer M.
- *
- * <p>
- * This is bit like {@link #toIndex(AxisList)}, but instead
- * of creating a continuous number (which often maps different
- * values of the same axis to the same index in modulo N residue ring,
- * we use a prime number P as the base. I think this guarantees the uniform
- * distribution in any N smaller than 2P (but proof, anyone?)
- */
- private long toModuloIndex(AxisList axis) {
- long r = 0;
- for (Axis a : axis) {
- r += a.indexOf(get(a));
- r *= 31;
- }
- return r;
- }
- /**
- * Evaluates the given Groovy expression with values bound from this combination.
- *
- * <p>
- * For example, if this combination is a=X,b=Y, then expressions like <tt>a=="X"</tt> would evaluate to
- * true.
- */
- public boolean evalGroovyExpression(AxisList axes, String expression) {
- if(Util.fixEmptyAndTrim(expression)==null)
- return true;
- Binding binding = new Binding();
- for (Map.Entry<String, String> e : entrySet())
- binding.setVariable(e.getKey(),e.getValue());
- binding.setVariable("index",toModuloIndex(axes));
- binding.setVariable("uniqueId",toIndex(axes));
- GroovyShell shell = new GroovyShell(binding);
- Object result = shell.evaluate("use("+BooleanCategory.class.getName().replace('$','.')+") {"+expression+"}");
- return TRUE.equals(result);
- }
- public int compareTo(Combination that) {
- int d = this.size()-that.size();
- if(d!=0) return d;
- Iterator<Map.Entry<String,String>> itr = this.entrySet().iterator();
- Iterator<Map.Entry<String,String>> jtr = that.entrySet().iterator();
- while(itr.hasNext()) {
- Map.Entry<String,String> i = itr.next();
- Map.Entry<String,String> j = jtr.next();
- d = i.getKey().compareTo(j.getKey());
- if(d!=0) return d;
- d = i.getValue().compareTo(j.getValue());
- if(d!=0) return d;
- }
- return 0;
- }
- /**
- * Works like {@link #toString()} but only include the given axes.
- */
- public String toString(Collection<Axis> subset) {
- if(size()==1 && subset.size()==1)
- return values().iterator().next();
- StringBuilder buf = new StringBuilder();
- for (Axis a : subset) {
- if(buf.length()>0) buf.append(',');
- buf.append(a.getName()).append('=').append(get(a));
- }
- if(buf.length()==0) buf.append("default"); // special case to avoid 0-length name.
- return buf.toString();
- }
- /**
- * Gets the values that correspond to the specified axes, in their order.
- */
- public List<String> values(Collection<? extends Axis> axes) {
- List<String> r = new ArrayList<String>(axes.size());
- for (Axis a : axes)
- r.add(get(a));
- return r;
- }
- /**
- * Converts to the ID string representation:
- * <tt>axisName=value,axisName=value,...</tt>
- *
- * @param sep1
- * The separator between multiple axes.
- * @param sep2
- * The separator between axis name and value.
- */
- public String toString(char sep1, char sep2) {
- return toString(sep1, sep2, false);
- }
- /**
- * Converts to the ID string representation:
- * <tt>axisName=value,axisName=value,...</tt>
- *
- * @param sep1 The separator between multiple axes.
- * @param sep2 The separator between axis name and value.
- * @param encodeValue true to encode value {@link Util#rawEncode(String)}
- * @return string representation.
- */
- public String toString(char sep1, char sep2, boolean encodeValue) {
- StringBuilder builder = new StringBuilder();
- if (encodeValue) {
- for (Map.Entry<String, String> e : entrySet()) {
- if (builder.length() > 0) {
- builder.append(sep1);
- }
- builder.append(e.getKey()).append(sep2).append(Util.rawEncode(e.getValue()));
- }
- } else {
- for (Map.Entry<String, String> e : entrySet()) {
- if (builder.length() > 0) {
- builder.append(sep1);
- }
- builder.append(e.getKey()).append(sep2).append(e.getValue());
- }
- }
- if (builder.length() == 0) {
- builder.append("default"); // special case to avoid 0-length name.
- }
- return builder.toString();
- }
- @Override
- public String toString() {
- return toString(',','=');
- }
- /**
- * Gets the 8 character-wide hash code for this combination
- */
- public String digest() {
- return Util.getDigestOf(toString());
- }
- /**
- * Reverse operation of {@link #toString()}.
- */
- public static Combination fromString(String id) {
- if(id.equals("default"))
- return new Combination(Collections.<String,String>emptyMap());
- Map<String,String> m = new HashMap<String,String>();
- StringTokenizer tokens = new StringTokenizer(id, DELIM);
- while(tokens.hasMoreTokens()) {
- String token = tokens.nextToken();
- int idx = token.indexOf('=');
- if(idx<0)
- throw new IllegalArgumentException("Can't parse "+id);
- m.put(token.substring(0,idx),token.substring(idx+1));
- }
- return new Combination(m);
- }
- /**
- * Creates compact string representataion suitable for display purpose.
- *
- * <p>
- * The string is made compact by looking for {@link Axis} whose values
- * are unique, and omit the axis name.
- */
- public String toCompactString(AxisList axes) {
- Set<String> nonUniqueAxes = new HashSet<String>();
- Map<String,Axis> axisByValue = new HashMap<String,Axis>();
- for (Axis a : axes) {
- for (String v : a.getValues()) {
- Axis old = axisByValue.put(v,a);
- if(old!=null) {
- // these two axes have colliding values
- nonUniqueAxes.add(old.getName());
- nonUniqueAxes.add(a.getName());
- }
- }
- }
- StringBuilder buf = new StringBuilder();
- for (Map.Entry<String,String> e : entrySet()) {
- if(buf.length()>0) buf.append(',');
- if(nonUniqueAxes.contains(e.getKey()))
- buf.append(e.getKey()).append('=');
- buf.append(e.getValue());
- }
- if(buf.length()==0) buf.append("default"); // special case to avoid 0-length name.
- return buf.toString();
- }
- // read-only
- @Override
- public void clear() {
- throw new UnsupportedOperationException();
- }
- @Override
- public void putAll(Map<? extends String, ? extends String> map) {
- throw new UnsupportedOperationException();
- }
- @Override
- public String put(String key, String value) {
- throw new UnsupportedOperationException();
- }
- @Override
- public String remove(Object key) {
- throw new UnsupportedOperationException();
- }
- /**
- * Duck-typing for boolean expressions.
- *
- * @see Combination#evalGroovyExpression(AxisList,String)
- */
- public static final class BooleanCategory {
- /**
- * x -> y
- */
- public static Boolean implies(Boolean lhs, Boolean rhs) {
- return !lhs || rhs;
- }
- }
- }