/* * Copyright 2000-2010 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.buildServer.scriptedProperties; import com.intellij.openapi.diagnostic.Logger; import jetbrains.buildServer.parameters.ContextVariables; import jetbrains.buildServer.parameters.ParametersProvider; import jetbrains.buildServer.parameters.ProcessingResult; import jetbrains.buildServer.parameters.impl.ProcessingResultImpl; import org.jetbrains.annotations.NotNull; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Created by IntelliJ IDEA. * User: yaegor * Date: 18.09.2010 * Time: 11:51:59 * To change this template use File | Settings | File Templates. */ public class ScriptedPropertyRegistry { @NotNull private static final Logger LOG = Logger.getInstance(ScriptedPropertyProcessor.class.getName()); private Map myRegistry = new HashMap(); private static final Pattern OUR_NAME_REGEXP = Pattern.compile("[\\w.-/\\\\]*"); private static final Pattern OUR_LONG_VALUE_REGEXP = Pattern.compile("[^\\s]*"); public ScriptedPropertyRegistry() { } public ScriptedPropertyRegistry(Collection processors) { myRegistry.clear(); for (ScriptedPropertyProcessor processor : processors) { myRegistry.put(processor.getId(), processor); } } void registerScriptedPropertyType(@NotNull ScriptedPropertyProcessor processor) { myRegistry.put(processor.getId(), processor); } @NotNull public ProcessingResult resolve(@NotNull String key, @NotNull String value, @NotNull ParametersProvider parameters, @NotNull ContextVariables contextVariables, final boolean reportErrors) { ProcessingResultBuilder builder = new ProcessingResultBuilder(reportErrors); // ##id:endseq:...endseq // ##id{JSON} <- NOT YET IMPLEMENTED! // ##id // "##id::value_till.:+=whitespace " <- till a whitespace int nextStartIndex = 0; while (nextStartIndex < value.length()) { int startIndex = value.indexOf("##", nextStartIndex); if (startIndex == -1) { builder.appendUnmodified(value, nextStartIndex, value.length()); break; } builder.appendUnmodified(value, nextStartIndex, startIndex); final int idStartIndex = startIndex + "##".length(); String id = getNameAt(value, idStartIndex); if (id.length() == 0) { nextStartIndex = startIndex + 1; builder.appendUnmodified(value, startIndex, nextStartIndex); continue; } final int endIdIndex = startIndex + "##".length() + id.length(); ScriptedPropertyProcessor processor = getProcessorById(id); if (processor == null) { LOG.debug("Found usage of unknown scripted property: '" + id + "' while resolving property " + key + "=" + value + ". Ignoring."); nextStartIndex = endIdIndex; builder.appendError(value, startIndex, nextStartIndex, "unknown scripted property: '" + id + "'"); continue; } if (value.length() == endIdIndex || (':' != value.charAt(endIdIndex))) { //##id nextStartIndex = endIdIndex; try { builder.appendModified(value, startIndex, nextStartIndex, processor.process(parameters, contextVariables)); } catch (Exception e) { LOG.debug("Error while resolving property " + key + "=" + value + ":" + e.getMessage(), e); builder.appendError(value, startIndex, nextStartIndex, e.getMessage()); } continue; } int secondColonIndex = value.indexOf(":", endIdIndex + 1); if (secondColonIndex == -1) { LOG.debug( "Scripted property '" + id + "' does not have second delimiting colon. Encountered while resolving property " + key + "=" + value + "."); nextStartIndex = endIdIndex + 1; builder.appendError(value, startIndex, nextStartIndex, "empty parameter text"); continue; } String endSequence = value.substring(endIdIndex + 1, secondColonIndex); if (endSequence.length() == 0) { //##id::value_till.:+=whitespace final String parameterText = getValueTillWhitespace(value, secondColonIndex + 1); if (parameterText.length() == 0) { LOG.debug( "Scripted property '" + id + "' has empty parameter text. Encountered while resolving property " + key + "=" + value + "."); nextStartIndex = secondColonIndex + 1; builder.appendError(value, startIndex, nextStartIndex, "empty parameter text"); continue; } nextStartIndex = secondColonIndex + parameterText.length() + 1; try { builder.appendModified(value, startIndex, nextStartIndex, processor.process(parameterText, parameters, contextVariables)); } catch (Exception e) { LOG.debug("Error while resolving property " + key + "=" + value + ":" + e.getMessage(), e); builder.appendError(value, startIndex, nextStartIndex, e.getMessage()); } continue; } //##id:seq:XXXseq int startOfValueIndex = secondColonIndex + 1; int endOfValueIndex = value.indexOf(endSequence, startOfValueIndex); if (endOfValueIndex == -1) { nextStartIndex = startOfValueIndex; builder.appendError(value, startIndex, nextStartIndex, "no end sequence occurrence"); continue; } String parameterText = value.substring(startOfValueIndex, endOfValueIndex); nextStartIndex = endOfValueIndex + endSequence.length(); try { builder.appendModified(value, startIndex, nextStartIndex, processor.process(parameterText, parameters, contextVariables)); } catch (Exception e) { LOG.debug("Error while resolving property " + key + "=" + value + ":" + e.getMessage(), e); builder.appendError(value, startIndex, nextStartIndex, e.getMessage()); } } return builder.getResult(); } private String getValueTillWhitespace(final String text, final int fromPosition) { final Matcher matcher = OUR_LONG_VALUE_REGEXP.matcher(text); if (!matcher.find(fromPosition)) { return ""; } return matcher.group(); } private ScriptedPropertyProcessor getProcessorById(String id) { return myRegistry.get(id); } @NotNull private String getNameAt(String text, int startAt) { final Matcher matcher = OUR_NAME_REGEXP.matcher(text); if (!matcher.find(startAt)) { return ""; } return matcher.group(); } private class ProcessingResultBuilder { private StringBuffer myResultValue = new StringBuffer(); private boolean myModified = false; private boolean myFullyResolved = true; private boolean myReportErrors; public ProcessingResultBuilder(boolean reportErrors) { myReportErrors = reportErrors; } public void appendUnmodified(String value, int fromIndex, int toIndex) { myResultValue.append(value, fromIndex, toIndex); } public void appendError(String value, int fromIndex, int toIndex, String errorMessage) { if (myReportErrors) { myResultValue.append("##"); myModified |= true; myFullyResolved &= false; //todo: or true? } else { appendUnmodified(value, fromIndex, toIndex); } } public void appendModified(String value, int fromIndex, int toIndex, ProcessingResult processingResult) { if (processingResult.isModified()) { myResultValue.append(processingResult.getResult()); myModified = true; myFullyResolved &= processingResult.isFullyResolved(); } else { appendUnmodified(value, fromIndex, toIndex); } } public ProcessingResult getResult() { return new ProcessingResultImpl(myResultValue.toString(), myModified, myFullyResolved); } @Override public String toString() { return "ProcessingResultBuilder{value=" + myResultValue + ", modified=" + myModified + ", fullyResolved=" + myFullyResolved + "}"; } } }