/******************************************************************************* * Copyright (c) 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package com.ibm.team.build.internal.hjplugin.rtc; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import com.ibm.team.build.common.model.IBuildConfigurationElement; import com.ibm.team.build.common.model.IConfigurationProperty; /** * Helper for processing build properties and performing variable substitution. */ public class PropertyVariableHelper { private static final String VARIABLE_START = "${"; //$NON-NLS-1$ private static final String VARIABLE_END = "}"; //$NON-NLS-1$ /** * Substitute any property references within the build property values (one * property value references another property's name). Substitution * continues until there are no more references to substitute or no * substitutions are made. For example, before expansion: * <ul> * <li>a = hello</li> * <li>b = ${a} there</li> * </ul> * After expansion: * <ul> * <li>a = hello</li> * <li>b = hello there</li> * </ul> * * @param buildProperties * The build properties. * @return A list of substitution descriptions. Each entry looks like * <code>b = ${a} there --> b = hello there</code>. * @throws IllegalArgumentException * If there are any direct or indirect cycles in the property * references. */ public static List<String> substituteBuildPropertyVariables(Map<String, String> buildProperties) { boolean substitutionsMade = true; List<String> substitutions = new ArrayList<String>(); while (containsAnyVariables(buildProperties.values()) && substitutionsMade) { substitutionsMade = false; // For each value. for (Map.Entry<String, String> valueEntry : buildProperties.entrySet()) { String originalValue = valueEntry.getValue(); if (originalValue.contains(VARIABLE_START)) { String newValue = originalValue; // For each name. for (Map.Entry<String, String> nameEntry : buildProperties.entrySet()) { // Check for a cycle, such as a = ${a} if (newValue.contains(variableReference(valueEntry.getKey()))) { handleCycle(substitutions, valueEntry.getKey(), newValue); } // Substitute any variable occurrences of the name in the // original value. newValue = newValue.replace(variableReference(nameEntry.getKey()), nameEntry.getValue()); } if (!newValue.equals(originalValue)) { substitutions.add(Messages.getDefault().PropertyVariableHelper_substitution( valueEntry.getKey(), originalValue, valueEntry.getKey(), newValue )); substitutionsMade = true; valueEntry.setValue(newValue); } } } } return substitutions; } /** * Substitute any build property references within the configuration * property values (a configuration property value references a build * property name). Configuration properties cannot reference other * configuration properties. * * @param configElements * The request containing the configuration properties. * @param buildProperties * The build properties to use as a source. * @return A list of substitution descriptions. Each entry looks like * <code>destination = ${root}/fetched --> destination = /releng/fetched</code>. */ public static List<String> substituteConfigurationElementPropertyVariables( List<IBuildConfigurationElement> configElements, Map<String, String> buildProperties) { List<String> substitutions = new ArrayList<String>(); for (IBuildConfigurationElement element : configElements) { if (element.isVariableSubstitutionAllowed()) { // For each configuration property for (Object object : element.getConfigurationProperties()) { IConfigurationProperty configProperty = (IConfigurationProperty) object; String originalValue = configProperty.getValue(); if (originalValue.contains(VARIABLE_START)) { // For each build property. for (Map.Entry<String, String> buildProperty : buildProperties.entrySet()) { // Replace any variable occurences of the name in // the config element property value. String newValue = originalValue.replace(variableReference(buildProperty.getKey()), buildProperty.getValue()); if (!newValue.equals(originalValue)) { substitutions.add(Messages.getDefault().PropertyVariableHelper_substitution_for_element( element.getElementId(), configProperty.getName(), originalValue, configProperty.getName(), newValue )); originalValue = newValue; } configProperty.setValue(newValue); } } } } } return substitutions; } private static void handleCycle(List<String> substitutions, String propertyName, String propertyValue) { StringBuffer message = new StringBuffer(); message.append(Messages.getDefault().PropertyVariableHelper_cycle(propertyName, propertyValue)); if (!substitutions.isEmpty()) { String newline = System.getProperty("line.separator"); //$NON-NLS-1$ message.append(newline); message.append(Messages.getDefault().PropertyVariableHelper_cycle_description()); message.append(newline); for (String substitution : substitutions) { message.append(substitution); message.append(newline); } } throw new IllegalArgumentException(message.toString()); } private static String variableReference(String propertyName) { return VARIABLE_START + propertyName + VARIABLE_END; } private static boolean containsAnyVariables(Collection<String> propertyValues) { for (String propertyValue : propertyValues) { if (propertyValue.contains(VARIABLE_START)) { return true; } } return false; } }