package jetbrains.buildServer.server.rest.data; import com.intellij.openapi.util.MultiValuesMap; import java.util.Collection; import jetbrains.buildServer.server.rest.errors.LocatorProcessException; import jetbrains.buildServer.util.StringUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Class that support parsing of "locators". * Locator is a string with single value or several named "dimensions". * Example: * 31 - locator wth single value "31" * name:Frodo - locator wth single dimension "name" which has value "Frodo" * name:Frodo,age:14 - locator with two dimensions "name" which has value "Frodo" and "age", which has value "14" * text:(Freaking symbols:,name) - locator with single dimension "text" which has value "Freaking symbols:,name" *

* Dimension name should be is alpha-numeric. Dimension value should not contain symbol "," if not enclosed in "(" and ")" or * should not contain symbol ")" (if enclosed in "(" and ")") * * @author Yegor.Yarko * Date: 13.08.2010 */ public class Locator { private static final String DIMENSION_NAME_VALUE_DELIMITER = ":"; private static final String DIMENSIONS_DELIMITER = ","; private static final String DIMENSION_COMPLEX_VALUE_START_DELIMITER = "("; private static final String DIMENSION_COMPLEX_VALUE_END_DELIMITER = ")"; private final MultiValuesMap myDimensions; private final String mySingleValue; public Locator(@NotNull final String locator) { if (StringUtil.isEmpty(locator)) { throw new LocatorProcessException("Invalid locator. Cannot be empty."); } final boolean hasDimentions = locator.indexOf(DIMENSION_NAME_VALUE_DELIMITER) != -1; if (!hasDimentions) { mySingleValue = locator; myDimensions = new MultiValuesMap(); } else { mySingleValue = null; myDimensions = parse(locator); } } private static MultiValuesMap parse(final String locator) { MultiValuesMap result = new MultiValuesMap(); String currentDimensionName; String currentDimensionValue; int parsedIndex = 0; while (parsedIndex < locator.length()) { int nameEnd = locator.indexOf(DIMENSION_NAME_VALUE_DELIMITER, parsedIndex); if (nameEnd == parsedIndex || nameEnd == -1) { throw new LocatorProcessException(locator, parsedIndex, "Could not find '" + DIMENSION_NAME_VALUE_DELIMITER + "'"); } currentDimensionName = locator.substring(parsedIndex, nameEnd); if (!isValidName(currentDimensionName)){ throw new LocatorProcessException(locator, parsedIndex, "Invalid dimension name :'" + currentDimensionName + "'. Should contain only alpha-numeric symbols"); } final String valueAndRest = locator.substring(nameEnd + DIMENSION_NAME_VALUE_DELIMITER.length()); if (valueAndRest.startsWith(DIMENSION_COMPLEX_VALUE_START_DELIMITER)) { //complex value detected final int complexValueEnd = valueAndRest.indexOf(DIMENSION_COMPLEX_VALUE_END_DELIMITER, DIMENSION_COMPLEX_VALUE_START_DELIMITER.length()); if (complexValueEnd == -1) { throw new LocatorProcessException(locator, nameEnd + DIMENSION_NAME_VALUE_DELIMITER.length() + DIMENSION_COMPLEX_VALUE_START_DELIMITER.length(), "Could not find '" + DIMENSION_COMPLEX_VALUE_END_DELIMITER + "'"); } currentDimensionValue = valueAndRest.substring(DIMENSION_COMPLEX_VALUE_START_DELIMITER.length(), complexValueEnd); parsedIndex = nameEnd + DIMENSION_NAME_VALUE_DELIMITER.length() + complexValueEnd + DIMENSION_COMPLEX_VALUE_END_DELIMITER.length(); if (parsedIndex != locator.length()) { if (!locator.startsWith(DIMENSIONS_DELIMITER, parsedIndex)) { throw new LocatorProcessException(locator, parsedIndex, "No dimensions delimiter " + DIMENSIONS_DELIMITER + " after complex value"); } else { parsedIndex += DIMENSIONS_DELIMITER.length(); } } } else { int valueEnd = valueAndRest.indexOf(DIMENSIONS_DELIMITER); if (valueEnd == -1) { currentDimensionValue = valueAndRest; parsedIndex = locator.length(); } else { currentDimensionValue = valueAndRest.substring(0, valueEnd); parsedIndex = nameEnd + DIMENSION_NAME_VALUE_DELIMITER.length() + valueEnd + DIMENSIONS_DELIMITER.length(); } } result.put(currentDimensionName, currentDimensionValue); } return result; } private static boolean isValidName(final String name) { for (int i = 0; i < name.length(); i++) { if (!Character.isLetter(name.charAt(i)) && !Character.isDigit(name.charAt(i))) return false; } return true; } public boolean isSingleValue() { return mySingleValue != null; } @Nullable public String getSingleValue() { return mySingleValue; } @Nullable public Long getSingleValueAsLong() { if (mySingleValue == null) { return null; } try { return Long.parseLong(mySingleValue); } catch (NumberFormatException e) { throw new LocatorProcessException("Invalid single value: " + mySingleValue + ". Should be a number."); } } @Nullable public Long getSingleDimensionValueAsLong(@NotNull final String dimensionName) { final String value = getSingleDimensionValue(dimensionName); if (value == null) { return null; } try { return Long.parseLong(value); } catch (NumberFormatException e) { throw new LocatorProcessException("Invalid value of dimension '" + dimensionName + "': " + value + ". Should be a number."); } } @Nullable public Boolean getSingleDimensionValueAsBoolean(@NotNull final String dimensionName) { final String value = getSingleDimensionValue(dimensionName); if (value == null || "all".equalsIgnoreCase(value) || "any".equalsIgnoreCase(value)){ return null; } if ("true".equalsIgnoreCase(value) || "on".equalsIgnoreCase(value) || "yes".equalsIgnoreCase(value) || "in".equalsIgnoreCase(value)){ return true; } if ("false".equalsIgnoreCase(value) || "off".equalsIgnoreCase(value) || "no".equalsIgnoreCase(value) || "out".equalsIgnoreCase(value)){ return false; } throw new LocatorProcessException("Invalid value of dimension '" + dimensionName + "': " + value + ". Should be 'true', 'false' or 'any'."); } /** * * @param dimensionName * @param defaultValue * @return value specified by the dimension with name "dimensionName" (one of the possible values can be "null") or * "defaultValue" if such dimension is present */ @Nullable public Boolean getSingleDimensionValueAsBoolean(@NotNull final String dimensionName, @Nullable Boolean defaultValue) { final String value = getSingleDimensionValue(dimensionName); if (value == null){ return defaultValue; } return getSingleDimensionValueAsBoolean(dimensionName); } /** * Extracts the single dimension value from dimensions. * * @param dimensions dimensions to extract value from. * @param dimensionName the name of the dimension to extract value. @return 'null' if no such dimension is found, value of the dimension otherwise. * @throws jetbrains.buildServer.server.rest.errors.LocatorProcessException * if there are more then a single dimension defiition for a 'dimensionName' name or the dimension has no value specified. */ @Nullable public String getSingleDimensionValue(@NotNull final String dimensionName) { Collection idDimension = myDimensions.get(dimensionName); if (idDimension == null || idDimension.size() == 0) { return null; } if (idDimension.size() > 1) { throw new LocatorProcessException("Only single '" + dimensionName + "' dimension is supported in locator. Found: " + idDimension); } return idDimension.iterator().next(); } @NotNull public int getDimensionsCount() { return myDimensions.keySet().size(); } /** * Should be used only for multi-dimention locators * @param name * @param value */ public void setDimension(@NotNull final String name, @NotNull final String value) { myDimensions.removeAll(name); myDimensions.put(name, value); } }