blob: bd7afb13ef66c975ac3f65e524a83929f4ac601d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.beam.sdk.util;
import static org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.Preconditions.checkArgument;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.beam.sdk.transforms.Combine.CombineFn;
import org.apache.beam.sdk.transforms.DoFn;
import org.apache.beam.sdk.transforms.PTransform;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.annotations.VisibleForTesting;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.base.Joiner;
/** Helpers for extracting the name of objects and classes. */
public class NameUtils {
/** Classes may implement this interface to change how names are generated for their instances. */
public interface NameOverride {
/** Return the name to use for this instance. */
String getNameOverride();
}
private static final String[] STANDARD_NAME_SUFFIXES = new String[] {"DoFn", "CombineFn", "Fn"};
/**
* Pattern to match a non-anonymous inner class. Eg, matches "Foo$Bar", or even "Foo$1$Bar", but
* not "Foo$1" or "Foo$1$2".
*/
private static final Pattern NAMED_INNER_CLASS = Pattern.compile(".+\\$(?<INNER>[^0-9].*)");
private static final String ANONYMOUS_CLASS_REGEX = "\\$[0-9]+\\$";
private static String approximateSimpleName(Class<?> clazz, boolean dropOuterClassNames) {
checkArgument(!clazz.isAnonymousClass(), "Attempted to get simple name of anonymous class");
return approximateSimpleName(clazz.getName(), dropOuterClassNames);
}
@VisibleForTesting
static String approximateSimpleName(String fullName, boolean dropOuterClassNames) {
String shortName = fullName.substring(fullName.lastIndexOf('.') + 1);
// Drop common suffixes for each named component.
String[] names = shortName.split("\\$");
for (int i = 0; i < names.length; i++) {
names[i] = simplifyNameComponent(names[i]);
}
shortName = Joiner.on('$').join(names);
if (dropOuterClassNames) {
// Simplify inner class name by dropping outer class prefixes.
Matcher m = NAMED_INNER_CLASS.matcher(shortName);
if (m.matches()) {
shortName = m.group("INNER");
}
} else {
// Dropping anonymous outer classes
shortName = shortName.replaceAll(ANONYMOUS_CLASS_REGEX, ".");
shortName = shortName.replaceAll("\\$", ".");
}
return shortName;
}
private static String simplifyNameComponent(String name) {
for (String suffix : STANDARD_NAME_SUFFIXES) {
if (name.endsWith(suffix) && name.length() > suffix.length()) {
return name.substring(0, name.length() - suffix.length());
}
}
return name;
}
/**
* As {@link #approximateSimpleName(Object, String)} but returning {@code "Anonymous"} when {@code
* object} is an instance of anonymous class.
*/
public static String approximateSimpleName(Object object) {
return approximateSimpleName(object, "Anonymous");
}
/**
* Returns a simple name describing a class that is being used as a function (eg., a {@link DoFn}
* or {@link CombineFn}, etc.).
*
* <p>Note: this is non-invertible - the name may be simplified to an extent that it cannot be
* mapped back to the original class.
*
* <p>This can be used to generate human-readable names. It removes the package and outer classes
* from the name, and removes common suffixes.
*
* <p>If the object is an instanceof {@link NameOverride}, the result of {@link
* NameOverride#getNameOverride()} is returned. This allows classes that act as wrappers to
* override the handling of names by delegating to the objects they wrap.
*
* <p>If the class is anonymous, the string {@code anonymousValue} is returned.
*
* <p>Examples:
*
* <ul>
* <li>{@code some.package.Word.SummaryDoFn} becomes "Summary"
* <li>{@code another.package.PairingFn} becomes "Pairing"
* </ul>
*/
public static String approximateSimpleName(Object object, String anonymousValue) {
if (object instanceof NameOverride) {
return ((NameOverride) object).getNameOverride();
}
Class<?> clazz;
if (object instanceof Class) {
clazz = (Class<?>) object;
} else {
clazz = object.getClass();
}
if (clazz.isAnonymousClass()) {
return anonymousValue;
}
return approximateSimpleName(clazz, /* dropOuterClassNames */ true);
}
/**
* Returns a name for a PTransform class.
*
* <p>This can be used to generate human-readable transform names. It removes the package from the
* name, and removes common suffixes.
*
* <p>It is different than approximateSimpleName:
*
* <ul>
* <li>1. It keeps the outer classes names.
* <li>2. It removes the common transform inner class: "Bound".
* <li>3. For classes generated by AutoValue, whose names start with AutoValue_, it delegates to
* the (parent) class declared in the user's source code.
* </ul>
*
* <p>Examples:
*
* <ul>
* <li>{@code some.package.Word.Summary} becomes "Word.Summary"
* <li>{@code another.package.Pairing.Bound} becomes "Pairing"
* </ul>
*/
public static String approximatePTransformName(Class<?> clazz) {
checkArgument(PTransform.class.isAssignableFrom(clazz));
if (clazz.getSimpleName().startsWith("AutoValue_")) {
return approximatePTransformName(clazz.getSuperclass());
}
return approximateSimpleName(clazz, /* dropOuterClassNames */ false)
.replaceFirst("\\.Bound$", "");
}
}