blob: c970cecd54e89eb01c0f0b3e5a0bfc266df6910b [file] [log] [blame]
// Copyright (C) 2022 The Android Open Source Project
//
// 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 com.googlesource.gerrit.plugins.task.properties;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Use to expand properties like ${property} in Strings into their values.
*
* <p>Given some property name/value associations like this:
*
* <p><code>
* "animal" -> "fox"
* "bar" -> "foo"
* "obstacle" -> "fence"
* </code>
*
* <p>a String like: <code>"The brown ${animal} jumped over the ${obstacle}."</code>
*
* <p>will expand to: <code>"The brown fox jumped over the fence."</code> This class is meant to be
* used as a building block for other full featured expanders and thus must be overriden to provide
* the name/value associations via the getValueForName() method.
*/
public abstract class AbstractExpander {
protected Consumer<Matcher.Statistics> statisticsConsumer;
public void setStatisticsConsumer(Consumer<Matcher.Statistics> statisticsConsumer) {
this.statisticsConsumer = statisticsConsumer;
}
/**
* Returns expanded object if property found in the Strings in the object's Fields (except the
* excluded ones). Returns same object if no expansions occurred.
*/
public <C extends Cloneable> C expand(C object, Set<String> excludedFieldNames) {
return expand(new CopyOnWrite.CloneOnWrite<>(object), excludedFieldNames);
}
/**
* Returns expanded object if property found in the Strings in the object's Fields (except the
* excluded ones). Returns same object if no expansions occurred.
*/
public <T> T expand(T object, Function<T, T> copier, Set<String> excludedFieldNames) {
return expand(new CopyOnWrite<>(object, copier), excludedFieldNames);
}
/**
* Returns expanded object if property found in the Strings in the object's Fields (except the
* excluded ones). Returns same object if no expansions occurred.
*/
public <T> T expand(CopyOnWrite<T> cow, Set<String> excludedFieldNames) {
for (Field field : cow.getOriginal().getClass().getFields()) {
if (!excludedFieldNames.contains(field.getName())) {
expand(cow, field);
}
}
return cow.getForRead();
}
/**
* Returns expanded object if property found in the fieldName Field if it is a String, or in the
* List's Strings if it is a List. Returns same object if no expansions occurred.
*/
public <T> T expand(CopyOnWrite<T> cow, String fieldName) {
try {
return expand(cow, cow.getOriginal().getClass().getField(fieldName));
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
/**
* Returns expanded object if property found in the Field if it is a String, or in the List's
* Strings if it is a List. Returns same object if no expansions occurred.
*/
public <T> T expand(CopyOnWrite<T> cow, Field field) {
try {
field.setAccessible(true);
Object o = field.get(cow.getOriginal());
if (o instanceof String) {
String expanded = expandText((String) o);
if (expanded != o) {
field.set(cow.getForWrite(), expanded);
}
} else if (o instanceof List) {
@SuppressWarnings("unchecked")
List<String> forceCheck = List.class.cast(o);
List<String> expanded = expand(forceCheck);
if (expanded != o) {
field.set(cow.getForWrite(), expanded);
}
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return cow.getForRead();
}
/**
* Returns expanded unmodifiable List if property found. Returns same object if no expansions
* occurred.
*/
public List<String> expand(List<String> list) {
if (list != null) {
boolean hasProperty = false;
List<String> expandedList = new ArrayList<>(list.size());
for (String value : list) {
String expanded = expandText(value);
hasProperty = hasProperty || value != expanded;
expandedList.add(expanded);
}
return hasProperty ? Collections.unmodifiableList(expandedList) : list;
}
return null;
}
/**
* Expand all properties (${property_name} -> property_value) in the given text. Returns same
* object if no expansions occurred.
*/
public String expandText(String text) {
if (text == null) {
return null;
}
Matcher m = new Matcher(text);
m.setStatisticsConsumer(statisticsConsumer);
if (!m.find()) {
return text;
}
StringBuffer out = new StringBuffer();
do {
m.appendValue(out, getValueForName(m.getName()));
} while (m.find());
m.appendTail(out);
return out.toString();
}
/**
* Get the replacement value for the property identified by name
*
* @param name of the property to get the replacement value for
* @return the replacement value. Since the expandText() method alwyas needs a String to replace
* '${property-name}' reference with, even when the property does not exist, this will never
* return null, instead it will returns the empty string if the property is not found.
*/
protected abstract String getValueForName(String name);
}