| /* |
| * 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$", ""); |
| } |
| } |