blob: 303ee3807db021ff4c6a065c6d5484d4ef30aa8b [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2009, 2021 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.ecommons.variables.core;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.variables.IDynamicVariable;
import org.eclipse.core.variables.IStringVariable;
import org.eclipse.core.variables.IStringVariableManager;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.osgi.util.NLS;
/**
* Allows string substitution of special variables with manual replacement strings
*/
public class VariableText {
public static interface LocationProcessor {
String process(String path) throws CoreException;
}
private static boolean isEscaped(final String text, final int offset) {
int count = 1;
while (offset >= count) { // offset-count >= 0
final char c = text.charAt(offset-count);
if (c == '$') {
count++;
}
else {
break;
}
}
return (count % 2) == 0;
}
private static void searchSurrounding(final String text, final int[] region) {
while (true) {
if (region[0] == 0) {
return;
}
final char c1 = text.charAt(region[0]-1);
if (c1 != ':') {
return;
}
final int start = text.lastIndexOf("${", region[0]-1); //$NON-NLS-1$
if (start < 0 || text.lastIndexOf("}", region[0]-1) > start || isEscaped(text, start)) { //$NON-NLS-1$
return;
}
region[0] = start;
final int end = text.indexOf('}', region[1]);
if (end >= 0) {
region[1] = end + 1;
}
}
}
private static final String[] LOCATION_VARIABLES = new String[] {
"resource_loc", "selected_resource_loc", "container_loc", "project_loc", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
};
private String fText;
private int fState;
private final List<String> fSpecialVariablesNames;
private List<IDynamicVariable> fSpecialVariables;
private final Set<String> fUnresolvedSpecial= new HashSet<>();
private final Map<String, String> fLocationVariables= new HashMap<>();
public VariableText(final String text, final List<String> specialVariablesNames) {
fSpecialVariablesNames = specialVariablesNames;
fText = text;
fState = 1;
}
public VariableText(final String text, final List<IDynamicVariable> checkedVariables, final boolean useDirectly) {
final List<String> specialVariablesNames= new ArrayList<>(checkedVariables.size());
for (final IStringVariable variable : checkedVariables) {
specialVariablesNames.add(variable.getName());
}
fSpecialVariablesNames = specialVariablesNames;
if (useDirectly) {
fSpecialVariables = checkedVariables;
}
fText = text;
fState = 1;
}
public String getText() {
return fText;
}
public void performInitialStringSubstitution(final boolean reportUndefinedVariables) throws CoreException {
if (fState != 1) {
throw new IllegalStateException();
}
String text = fText;
final LinkedHashMap<String, String> specialVariables= new LinkedHashMap<>();
for (final String variableName : fSpecialVariablesNames) {
final String pattern = "${"+variableName; //$NON-NLS-1$
int offset = -1;
while ((offset = text.indexOf(pattern, offset + 1)) >= 0) {
if (!isEscaped(text, offset)) {
final int length;
switch (offset+pattern.length() < text.length() ?
text.charAt(offset + pattern.length()) : 0) {
case '}':
length = pattern.length() + 1;
break;
case ':':
length = text.indexOf('}', offset + pattern.length()) - offset + 1;
if (length > 0) {
break;
}
default:
throw new CoreException(new Status(IStatus.ERROR, ECommonsVariablesCore.BUNDLE_ID,
NLS.bind("Malformed variable expression: variable ''{0}'' not closed.",
variableName )));
}
fUnresolvedSpecial.add(variableName);
final int[] region = new int[] { offset, offset + length };
searchSurrounding(text, region);
final String key = "XX-SPECIALVAR-"+specialVariables.size()+"-XX"; //$NON-NLS-1$ //$NON-NLS-2$
specialVariables.put(key, new String(text.substring(region[0], region[1])));
text = text.substring(0, region[0]) + key + text.substring(region[1], text.length());
}
}
}
text = searchResourceVar(text, reportUndefinedVariables);
text = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(text, reportUndefinedVariables);
final Entry<String, String>[] entries = specialVariables.entrySet().toArray(new Entry[specialVariables.size()]);
for (int i = entries.length - 1; i >= 0; i--) {
text = text.replace(entries[i].getKey(), entries[i].getValue());
}
fText = text;
if (fSpecialVariables != null) {
for (final IDynamicVariable variable : fSpecialVariables) {
if (require(variable.getName())) {
set(variable);
}
}
}
fState = 2;
}
private String searchResourceVar(String text, final boolean reportUndefinedVariables) throws CoreException {
final IStringVariableManager variableManager = VariablesPlugin.getDefault().getStringVariableManager();
for (final String variableName : LOCATION_VARIABLES) {
final String pattern = "${"+variableName; //$NON-NLS-1$
int offset = -1;
while ((offset = text.indexOf(pattern, offset+1)) >= 0) {
if (!isEscaped(text, offset)) {
int end = text.indexOf('}', offset);
int next = offset+1;
while ((next = text.indexOf('{', next+1)) >= 0 && next < end) {
end = text.indexOf('}', end+1);
}
final String key = "XX-RESOURCEVAR-" + fLocationVariables.size() + "-XX"; //$NON-NLS-1$ //$NON-NLS-2$
fLocationVariables.put(key, new String(variableManager.performStringSubstitution(text.substring(offset, end+1), reportUndefinedVariables)));
text = text.substring(0, offset) + key + text.substring(end+1, text.length());
}
}
}
return text;
}
public boolean require(final String variableName) {
return fUnresolvedSpecial.contains(variableName);
}
public void set(final String variableName, final String value) {
if (fUnresolvedSpecial.remove(variableName)) {
final String pattern = "${"+variableName+"}"; //$NON-NLS-1$ //$NON-NLS-2$
String text = fText;
int offset = -1;
while ((offset = text.indexOf(pattern, offset+1)) >= 0) {
if (!isEscaped(text, offset)) {
text = text.substring(0, offset) + value + text.substring(offset+pattern.length(), text.length());
}
}
fText = text;
}
}
public void set(final IDynamicVariable variable) throws CoreException {
if (fUnresolvedSpecial.remove(variable.getName())) {
final String pattern = "${"+variable.getName(); //$NON-NLS-1$
String text = fText;
int offset = -1;
while ((offset = text.indexOf(pattern, offset+1)) >= 0) {
if (!isEscaped(text, offset)) {
final int length;
final String value;
switch (text.charAt(offset + pattern.length())) {
case '}':
length = pattern.length() + 1;
value = variable.getValue(null);
break;
case ':':
if (!variable.supportsArgument()) {
throw new CoreException(new Status(IStatus.ERROR, ECommonsVariablesCore.BUNDLE_ID,
NLS.bind("Malformed variable expression: variable ''{0}'' doesn't support arguments.",
variable.getName() )));
}
length = text.indexOf('}', offset + pattern.length()) - offset + 1;
value = variable.getValue(
text.substring(offset + pattern.length() + 1, offset + length - 1) );
break;
default:
throw new IllegalStateException();
}
text = text.substring(0, offset) + value + text.substring(offset+length, text.length());
}
}
fText = text;
}
}
public void performFinalStringSubstitution(final LocationProcessor locationProcessor) throws CoreException {
if (!fUnresolvedSpecial.isEmpty()) {
throw new CoreException(new Status(IStatus.ERROR, ECommonsVariablesCore.BUNDLE_ID,
"Unresolved variable(s): " + fUnresolvedSpecial.toString() + "." ));
}
if (fState == 1) {
performInitialStringSubstitution(true);
}
if (fState != 2) {
throw new IllegalStateException();
}
String text = fText;
text = searchResourceVar(text, true);
text = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(text, true);
for (final Entry<String, String> entry : fLocationVariables.entrySet()) {
final String value = (locationProcessor != null) ? locationProcessor.process(entry.getValue()) : entry.getValue();
text = text.replace(entry.getKey(), value);
}
fText = text;
fState = 3;
}
}