lombok.core.AST Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of datacap-shaded-pinot Show documentation
Show all versions of datacap-shaded-pinot Show documentation
DataCap - Shaded for Apache Pinot
/*
* Copyright (C) 2009-2021 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package lombok.core;
import static lombok.Lombok.sneakyThrow;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import lombok.core.configuration.ConfigurationKey;
import lombok.core.debug.HistogramTracker;
import lombok.permit.Permit;
/**
* Lombok wraps the AST produced by a target platform into its own AST system, mostly because both Eclipse and javac
* do not allow upward traversal (from a method to its owning type, for example).
*
* @param A Self-type.
* @param L type of all LombokNodes.
* @param N The common type of all AST nodes in the internal representation of the target platform.
* For example, JCTree for javac, and ASTNode for Eclipse.
*/
public abstract class AST, L extends LombokNode, N> {
/** The kind of node represented by a given AST.Node object. */
public enum Kind {
COMPILATION_UNIT, TYPE, FIELD, INITIALIZER, METHOD, ANNOTATION, ARGUMENT, LOCAL, STATEMENT, TYPE_USE;
}
private L top;
private final String fileName;
private final String packageDeclaration;
private final ImportList imports;
private TypeResolver importsAsResolver;
Map identityDetector = new IdentityHashMap();
private Map nodeMap = new IdentityHashMap();
private boolean changed = false;
// The supertypes which are considered AST Node children. Usually, the Statement, and the Expression,
// though some platforms (such as Eclipse) group these under one common supertype.
private final Collection> statementTypes;
private static final HistogramTracker configTracker = System.getProperty("lombok.timeConfig") == null ? null : new HistogramTracker("lombok.config");
protected AST(String fileName, String packageDeclaration, ImportList imports, Collection> statementTypes) {
this.fileName = fileName == null ? "(unknown).java" : fileName;
this.packageDeclaration = packageDeclaration;
this.imports = imports;
this.statementTypes = statementTypes;
}
/**
* Attempts to find the absolute path (in URI form) to the source file represented by this AST.
*
* May return {@code null} if this cannot be done. We don't yet know under which conditions this will happen.
*/
public abstract URI getAbsoluteFileLocation();
public void setChanged() {
this.changed = true;
}
protected void clearChanged() {
this.changed = false;
}
public boolean isChanged() {
return changed;
}
/** Set the node object that wraps the internal Compilation Unit node. */
protected void setTop(L top) {
this.top = top;
}
/**
* Return the content of the package declaration on this AST's top (Compilation Unit) node.
*
* Example: "java.util".
*/
public final String getPackageDeclaration() {
return packageDeclaration;
}
/**
* Return the contents of each non-static import statement on this AST's top (Compilation Unit) node.
*/
public final ImportList getImportList() {
return imports;
}
/**
* Return the contents of each non-static import statement on this AST's top (Compilation Unit) node, packed into a (cached) TypeResolver.
*/
public final TypeResolver getImportListAsTypeResolver() {
if (importsAsResolver != null) return importsAsResolver;
return importsAsResolver = new TypeResolver(getImportList());
}
/**
* Puts the given node in the map so that javac/Eclipse's own internal AST object can be translated to
* an AST.Node object. Also registers the object as visited to avoid endless loops.
*/
protected L putInMap(L node) {
nodeMap.put(node.get(), node);
identityDetector.put(node.get(), node.get());
return node;
}
/** Returns the node map, that can map javac/Eclipse internal AST objects to AST.Node objects. */
protected Map getNodeMap() {
return nodeMap;
}
/** Clears the registry that avoids endless loops, and empties the node map. The existing node map
* object is left untouched, and instead a new map is created. */
protected void clearState() {
identityDetector = new IdentityHashMap();
nodeMap = new IdentityHashMap();
}
/**
* Marks the stated node as handled (to avoid endless loops if 2 nodes refer to each other, or a node
* refers to itself). Will then return true if it was already set as handled before this call - in which
* case you should do nothing lest the AST build process loops endlessly.
*/
protected boolean setAndGetAsHandled(N node) {
return identityDetector.put(node, node) != null;
}
public String getFileName() {
return fileName;
}
/** The AST.Node object representing the Compilation Unit. */
public L top() {
return top;
}
/** Maps a javac/Eclipse internal AST Node to the appropriate AST.Node object. */
public L get(N node) {
return nodeMap.get(node);
}
/**
* Returns the JLS spec version that the compiler uses to parse and compile this AST.
* For example, if -source 1.6 is on the command line, this will return {@code 6}.
*/
public int getSourceVersion() {
return 6;
}
/**
* Returns the latest version of the java language specification supported by the host compiler.
* For example, if compiling with javac v1.7, this returns {@code 7}.
*
* NB: Even if -source (lower than maximum) is specified, this method still returns the maximum supported number.
*/
public int getLatestJavaSpecSupported() {
return 6;
}
@SuppressWarnings({"unchecked", "rawtypes"})
L replaceNewWithExistingOld(Map oldNodes, L newNode) {
L oldNode = oldNodes.get(newNode.get());
L targetNode = oldNode == null ? newNode : oldNode;
List children = new ArrayList();
for (L child : newNode.children) {
L oldChild = replaceNewWithExistingOld(oldNodes, child);
children.add(oldChild);
oldChild.parent = targetNode;
}
targetNode.children = LombokImmutableList.copyOf(children);
return targetNode;
}
/** Build an AST.Node object for the stated internal (javac/Eclipse) AST Node object. */
protected abstract L buildTree(N item, Kind kind);
/**
* Represents a field that contains AST children.
*/
protected static class FieldAccess {
/** The actual field. */
public final Field field;
/** Dimensions of the field. Works for arrays, or for java.util.collections. */
public final int dim;
FieldAccess(Field field, int dim) {
this.field = field;
this.dim = dim;
}
}
private static final ConcurrentMap, FieldAccess[]> fieldsOfASTClasses = new ConcurrentHashMap, FieldAccess[]>();
/** Returns FieldAccess objects for the stated class. Each field that contains objects of the kind returned by
* {@link #getStatementTypes()}, either directly or inside of an array or java.util.collection (or array-of-arrays,
* or collection-of-collections, et cetera), is returned.
*/
protected FieldAccess[] fieldsOf(Class c) {
FieldAccess[] fields = fieldsOfASTClasses.get(c);
if (fields != null) return fields;
List fieldList = new ArrayList();
getFields(c, fieldList);
fieldsOfASTClasses.putIfAbsent(c, fieldList.toArray(new FieldAccess[0]));
return fieldsOfASTClasses.get(c);
}
private void getFields(Class c, Collection fields) {
if (c == Object.class || c == null) return;
for (Field f : c.getDeclaredFields()) {
if (Modifier.isStatic(f.getModifiers())) continue;
Class t = f.getType();
int dim = 0;
if (t.isArray()) {
while (t.isArray()) {
dim++;
t = t.getComponentType();
}
} else {
while (Collection.class.isAssignableFrom(t)) {
dim++;
t = getComponentType(f.getGenericType());
}
}
if (shouldDrill(c, t, f.getName())) {
Permit.setAccessible(f);
fields.add(new FieldAccess(f, dim));
}
}
getFields(c.getSuperclass(), fields);
}
private Class getComponentType(Type type) {
if (type instanceof ParameterizedType) {
Type component = ((ParameterizedType) type).getActualTypeArguments()[0];
return component instanceof Class ? (Class) component : Object.class;
}
return Object.class;
}
private boolean shouldDrill(Class parentType, Class childType, String fieldName) {
for (Class statementType : statementTypes) {
if (statementType.isAssignableFrom(childType)) return true;
}
return false;
}
/**
* buildTree implementation that uses reflection to find all child nodes by way of inspecting
* the fields. */
protected Collection buildWithField(Class nodeType, N statement, FieldAccess fa) {
List list = new ArrayList();
buildWithField0(nodeType, statement, fa, list);
return list;
}
/**
* Uses reflection to find the given direct child on the given statement, and replace it with a new child.
*/
protected boolean replaceStatementInNode(N statement, N oldN, N newN) {
for (FieldAccess fa : fieldsOf(statement.getClass())) {
if (replaceStatementInField(fa, statement, oldN, newN)) return true;
}
return false;
}
private boolean replaceStatementInField(FieldAccess fa, N statement, N oldN, N newN) {
try {
Object o = fa.field.get(statement);
if (o == null) return false;
if (o == oldN) {
fa.field.set(statement, newN);
return true;
}
if (fa.dim > 0) {
if (o.getClass().isArray()) {
return replaceStatementInArray(o, oldN, newN);
} else if (Collection.class.isInstance(o)) {
return replaceStatementInCollection(fa.field, statement, new ArrayList>(), (Collection)o, oldN, newN);
}
}
return false;
} catch (IllegalAccessException e) {
throw sneakyThrow(e);
}
}
private boolean replaceStatementInCollection(Field field, Object fieldRef, List> chain, Collection collection, N oldN, N newN) throws IllegalAccessException {
if (collection == null) return false;
int idx = -1;
for (Object o : collection) {
idx++;
if (o == null) continue;
if (Collection.class.isInstance(o)) {
Collection newC = (Collection) o;
List> newChain = new ArrayList>(chain);
newChain.add(newC);
if (replaceStatementInCollection(field, fieldRef, newChain, newC, oldN, newN)) return true;
}
if (o == oldN) {
setElementInASTCollection(field, fieldRef, chain, collection, idx, newN);
return true;
}
}
return false;
}
/**
* Override if your AST collection does not support the set method. Javac's for example, does not.
*
* @param field The field that contains the array or list of AST nodes.
* @param fieldRef The object that you can supply to the field's {@code get} method.
* @param chain If the collection is immutable, you need to update the pointer to the collection in each element in the chain.
*
* @throws IllegalAccessException This exception won't happen, but we allow you to throw it so you can avoid having to catch it.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
protected void setElementInASTCollection(Field field, Object fieldRef, List> chain, Collection collection, int idx, N newN) throws IllegalAccessException {
if (collection instanceof List) {
((List) collection).set(idx, newN);
}
}
private boolean replaceStatementInArray(Object array, N oldN, N newN) {
if (array == null) return false;
int len = Array.getLength(array);
for (int i = 0; i < len; i++) {
Object o = Array.get(array, i);
if (o == null) continue;
if (o.getClass().isArray()) {
if (replaceStatementInArray(o, oldN, newN)) return true;
} else if (o == oldN) {
Array.set(array, i, newN);
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
private void buildWithField0(Class nodeType, N child, FieldAccess fa, Collection list) {
try {
Object o = fa.field.get(child);
if (o == null) return;
if (fa.dim == 0) {
L node = buildTree((N) o, Kind.STATEMENT);
if (node != null) list.add(nodeType.cast(node));
} else if (o.getClass().isArray()) {
buildWithArray(nodeType, o, list, fa.dim);
} else if (Collection.class.isInstance(o)) {
buildWithCollection(nodeType, o, list, fa.dim);
}
} catch (IllegalAccessException e) {
throw sneakyThrow(e);
}
}
@SuppressWarnings("unchecked")
private void buildWithArray(Class nodeType, Object array, Collection list, int dim) {
if (dim == 1) {
for (Object v : (Object[]) array) {
if (v == null) continue;
L node = buildTree((N)v, Kind.STATEMENT);
if (node != null) list.add(nodeType.cast(node));
}
} else for (Object v : (Object[]) array) {
if (v == null) return;
buildWithArray(nodeType, v, list, dim -1);
}
}
@SuppressWarnings("unchecked")
private void buildWithCollection(Class nodeType, Object collection, Collection list, int dim) {
if (dim == 1) {
for (Object v : (Collection) collection) {
if (v == null) continue;
L node = buildTree((N) v, Kind.STATEMENT);
if (node != null) list.add(nodeType.cast(node));
}
} else for (Object v : (Collection) collection) {
buildWithCollection(nodeType, v, list, dim - 1);
}
}
/**
* @return The {@code lombok.config} configuration value for the provided {@code key}, or {@code null} if that key is not in the config / there is no config.
*/
public final T readConfiguration(ConfigurationKey key) {
long start = configTracker == null ? 0L : configTracker.start();
try {
return LombokConfiguration.read(key, this);
} finally {
if (configTracker != null) configTracker.end(start);
}
}
/**
* @return The {@code lombok.config} configuration value for the provided {@code key}, or {@code defaultValue} if that key is not in the config / there is no config.
*/
public final T readConfigurationOr(ConfigurationKey key, T defaultValue) {
long start = configTracker == null ? 0L : configTracker.start();
try {
T value = LombokConfiguration.read(key, this);
return value != null ? value : defaultValue;
} finally {
if (configTracker != null) configTracker.end(start);
}
}
public boolean getBooleanAnnotationValue(AnnotationValues annotation, String annoMethod, ConfigurationKey confKey) {
Boolean conf = readConfiguration(confKey);
return annotation.isExplicit(annoMethod) || conf == null ? annotation.getAsBoolean(annoMethod) : conf;
}
}