All Downloads are FREE. Search and download functionalities are using the official Maven repository.

lombok.javac.JavacResolution Maven / Gradle / Ivy

Go to download

Spice up your java: Automatic Resource Management, automatic generation of getters, setters, equals, hashCode and toString, and more!

There is a newer version: 1.18.36
Show newest version
package lombok.javac;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

import javax.lang.model.type.TypeKind;
import javax.tools.DiagnosticListener;

import com.sun.tools.javac.code.BoundKind;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Type.ArrayType;
import com.sun.tools.javac.code.Type.CapturedType;
import com.sun.tools.javac.code.Type.ClassType;
import com.sun.tools.javac.code.Type.WildcardType;
import com.sun.tools.javac.code.TypeTags;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.comp.Attr;
import com.sun.tools.javac.comp.AttrContext;
import com.sun.tools.javac.comp.Enter;
import com.sun.tools.javac.comp.Env;
import com.sun.tools.javac.comp.MemberEnter;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Log;

public class JavacResolution {
	private final Attr attr;
	private final LogDisabler logDisabler;
	
	public JavacResolution(Context context) {
		attr = Attr.instance(context);
		logDisabler = new LogDisabler(context);
	}
	
	/**
	 * During resolution, the resolver will emit resolution errors, but without appropriate file names and line numbers. If these resolution errors stick around
	 * then they will be generated AGAIN, this time with proper names and line numbers, at the end. Therefore, we want to suppress the logger.
	 */
	private static final class LogDisabler {
		private final Log log;
		private static final Field errWriterField, warnWriterField, noticeWriterField, dumpOnErrorField, promptOnErrorField, diagnosticListenerField;
		private static final Field deferDiagnosticsField, deferredDiagnosticsField;
		private PrintWriter errWriter, warnWriter, noticeWriter;
		private Boolean dumpOnError, promptOnError;
		private DiagnosticListener contextDiagnosticListener, logDiagnosticListener;
		private final Context context;
		
		// If this is true, the fields changed. Better to print weird error messages than to fail outright.
		private static final boolean dontBother;
		
		private static final ThreadLocal> queueCache = new ThreadLocal>();
		
		static {
			boolean z;
			Field a = null, b = null, c = null, d = null, e = null, f = null, g = null, h = null;
			try {
				a = Log.class.getDeclaredField("errWriter");
				b = Log.class.getDeclaredField("warnWriter");
				c = Log.class.getDeclaredField("noticeWriter");
				d = Log.class.getDeclaredField("dumpOnError");
				e = Log.class.getDeclaredField("promptOnError");
				f = Log.class.getDeclaredField("diagListener");
				z = false;
				a.setAccessible(true);
				b.setAccessible(true);
				c.setAccessible(true);
				d.setAccessible(true);
				e.setAccessible(true);
				f.setAccessible(true);
			} catch (Exception x) {
				z = true;
			}
			
			try {
				g = Log.class.getDeclaredField("deferDiagnostics");
				h = Log.class.getDeclaredField("deferredDiagnostics");
				g.setAccessible(true);
				h.setAccessible(true);
			} catch (Exception x) {
			}
			
			errWriterField = a;
			warnWriterField = b;
			noticeWriterField = c;
			dumpOnErrorField = d;
			promptOnErrorField = e;
			diagnosticListenerField = f;
			deferDiagnosticsField = g;
			deferredDiagnosticsField = h;
			dontBother = z;
		}
		
		LogDisabler(Context context) {
			this.log = Log.instance(context);
			this.context = context;
		}
		
		boolean disableLoggers() {
			contextDiagnosticListener = context.get(DiagnosticListener.class);
			context.put(DiagnosticListener.class, (DiagnosticListener) null);
			if (dontBother) return false;
			boolean dontBotherInstance = false;
			
			PrintWriter dummyWriter = new PrintWriter(new OutputStream() {
				@Override public void write(int b) throws IOException {
					// Do nothing on purpose
				}
			});
			
			if (deferDiagnosticsField != null) try {
				if (Boolean.TRUE.equals(deferDiagnosticsField.get(log))) {
					queueCache.set((Queue) deferredDiagnosticsField.get(log));
					Queue empty = new LinkedList();
					deferredDiagnosticsField.set(log, empty);
				}
			} catch (Exception e) {}
			
			if (!dontBotherInstance) try {
				errWriter = (PrintWriter) errWriterField.get(log);
				errWriterField.set(log, dummyWriter);
			} catch (Exception e) {
				dontBotherInstance = true;
			}
			
			if (!dontBotherInstance) try {
				warnWriter = (PrintWriter) warnWriterField.get(log);
				warnWriterField.set(log, dummyWriter);
			} catch (Exception e) {
				dontBotherInstance = true;
			}
			
			if (!dontBotherInstance) try {
				noticeWriter = (PrintWriter) noticeWriterField.get(log);
				noticeWriterField.set(log, dummyWriter);
			} catch (Exception e) {
				dontBotherInstance = true;
			}
			
			if (!dontBotherInstance) try {
				dumpOnError = (Boolean) dumpOnErrorField.get(log);
				dumpOnErrorField.set(log, false);
			} catch (Exception e) {
				dontBotherInstance = true;
			}
			
			if (!dontBotherInstance) try {
				promptOnError = (Boolean) promptOnErrorField.get(log);
				promptOnErrorField.set(log, false);
			} catch (Exception e) {
				dontBotherInstance = true;
			}
			
			if (!dontBotherInstance) try {
				logDiagnosticListener = (DiagnosticListener) diagnosticListenerField.get(log);
				diagnosticListenerField.set(log, null);
			} catch (Exception e) {
				dontBotherInstance = true;
			}
			
			if (dontBotherInstance) enableLoggers();
			return !dontBotherInstance;
		}
		
		void enableLoggers() {
			if (contextDiagnosticListener != null) {
				context.put(DiagnosticListener.class, contextDiagnosticListener);
				contextDiagnosticListener = null;
			}
			
			if (errWriter != null) try {
				errWriterField.set(log, errWriter);
				errWriter = null;
			} catch (Exception e) {}
			
			if (warnWriter != null) try {
				warnWriterField.set(log, warnWriter);
				warnWriter = null;
			} catch (Exception e) {}
			
			if (noticeWriter != null) try {
				noticeWriterField.set(log, noticeWriter);
				noticeWriter = null;
			} catch (Exception e) {}
			
			if (dumpOnError != null) try {
				dumpOnErrorField.set(log, dumpOnError);
				dumpOnError = null;
			} catch (Exception e) {}
			
			if (promptOnError != null) try {
				promptOnErrorField.set(log, promptOnError);
				promptOnError = null;
			} catch (Exception e) {}
			
			if (logDiagnosticListener != null) try {
				diagnosticListenerField.set(log, logDiagnosticListener);
				logDiagnosticListener = null;
			} catch (Exception e) {}
			
			if (deferDiagnosticsField != null && queueCache.get() != null) try {
				deferredDiagnosticsField.set(log, queueCache.get());
				queueCache.set(null);
			} catch (Exception e) {}
		}
	}
	
	/*
	 * We need to dig down to the level of the method or field declaration or (static) initializer block, then attribute that entire method/field/block using
	 * the appropriate environment. So, we start from the top and walk down the node tree until we hit that method/field/block and stop there, recording both
	 * the environment object (`env`) and the exact tree node (`copyAt`) at which to begin the attr process.
	 */
	private static final class EnvFinder extends JCTree.Visitor {
		private Env env = null;
		private Enter enter;
		private MemberEnter memberEnter;
		private JCTree copyAt = null;
		
		EnvFinder(Context context) {
			this.enter = Enter.instance(context);
			this.memberEnter = MemberEnter.instance(context);
		}
		
		Env get() {
			return env;
		}
		
		JCTree copyAt() {
			return copyAt;
		}
		
		@Override public void visitTopLevel(JCCompilationUnit tree) {
			if (copyAt != null) return;
			env = enter.getTopLevelEnv(tree);
		}
		
		@Override public void visitClassDef(JCClassDecl tree) {
			if (copyAt != null) return;
			// The commented out stuff requires reflection tricks to avoid leaving lint unset which causes NPEs during attrib. So, we use the other one, much less code.
//			env = enter.classEnv((JCClassDecl) tree, env);
//			try {
//				Field f = env.info.getClass().getDeclaredField("lint");
//				f.setAccessible(true);
//				Constructor c = Lint.class.getDeclaredConstructor(Lint.class);
//				c.setAccessible(true);
//				f.set(env.info, c.newInstance(lint));
//			} catch (Exception e) {
//				throw Lombok.sneakyThrow(e);
//			}
			env = enter.getClassEnv(tree.sym);
		}
		
		@Override public void visitMethodDef(JCMethodDecl tree) {
			if (copyAt != null) return;
			env = memberEnter.getMethodEnv(tree, env);
			copyAt = tree;
		}
		
		public void visitVarDef(JCVariableDecl tree) {
			if (copyAt != null) return;
			env = memberEnter.getInitEnv(tree, env);
			copyAt = tree;
		}
		
		@Override public void visitBlock(JCBlock tree) {
			if (copyAt != null) return;
			copyAt = tree;
		}
		
		@Override public void visitTree(JCTree that) {
		}
	}
	
	public Map resolveMethodMember(JavacNode node) {
		ArrayDeque stack = new ArrayDeque();
		
		{
			JavacNode n = node;
			while (n != null) {
				stack.push(n.get());
				n = n.up();
			}
		}
		
		logDisabler.disableLoggers();
		try {
			EnvFinder finder = new EnvFinder(node.getContext());
			while (!stack.isEmpty()) stack.pop().accept(finder);
			
			TreeMirrorMaker mirrorMaker = new TreeMirrorMaker(node.getTreeMaker());
			JCTree copy = mirrorMaker.copy(finder.copyAt());
			
			attrib(copy, finder.get());
			return mirrorMaker.getOriginalToCopyMap();
		} finally {
			logDisabler.enableLoggers();
		}
	}
	
	public void resolveClassMember(JavacNode node) {
		ArrayDeque stack = new ArrayDeque();
		
		{
			JavacNode n = node;
			while (n != null) {
				stack.push(n.get());
				n = n.up();
			}
		}
		
		logDisabler.disableLoggers();
		try {
			EnvFinder finder = new EnvFinder(node.getContext());
			while (!stack.isEmpty()) stack.pop().accept(finder);
			
			attrib(node.get(), finder.get());
		} finally {
			logDisabler.enableLoggers();
		}
	}
	
	private void attrib(JCTree tree, Env env) {
		if (tree instanceof JCBlock) attr.attribStat(tree, env);
		else if (tree instanceof JCMethodDecl) attr.attribStat(((JCMethodDecl)tree).body, env);
		else if (tree instanceof JCVariableDecl) attr.attribStat(tree, env);
		else throw new IllegalStateException("Called with something that isn't a block, method decl, or variable decl");
	}
	
	public static class TypeNotConvertibleException extends Exception {
		public TypeNotConvertibleException(String msg) {
			super(msg);
		}
	}
	
	public static Type ifTypeIsIterableToComponent(Type type, JavacAST ast) {
		Types types = Types.instance(ast.getContext());
		Symtab syms = Symtab.instance(ast.getContext());
		Type boundType = types.upperBound(type);
		Type elemTypeIfArray = types.elemtype(boundType);
		if (elemTypeIfArray != null) return elemTypeIfArray;
		
		Type base = types.asSuper(boundType, syms.iterableType.tsym);
		if (base == null) return syms.objectType;
		
		List iterableParams = base.allparams();
		return iterableParams.isEmpty() ? syms.objectType : types.upperBound(iterableParams.head);
	}
	
	public static JCExpression typeToJCTree(Type type, TreeMaker maker, JavacAST ast, boolean allowVoid) throws TypeNotConvertibleException {
		return typeToJCTree(type, maker, ast, false, allowVoid);
	}
	
	public static JCExpression createJavaLangObject(TreeMaker maker, JavacAST ast) {
		JCExpression out = maker.Ident(ast.toName("java"));
		out = maker.Select(out, ast.toName("lang"));
		out = maker.Select(out, ast.toName("Object"));
		return out;
	}
	
	private static JCExpression typeToJCTree(Type type, TreeMaker maker, JavacAST ast, boolean allowCompound, boolean allowVoid) throws TypeNotConvertibleException {
		int dims = 0;
		Type type0 = type;
		while (type0 instanceof ArrayType) {
			dims++;
			type0 = ((ArrayType)type0).elemtype;
		}
		
		JCExpression result = typeToJCTree0(type0, maker, ast, allowCompound, allowVoid);
		while (dims > 0) {
			result = maker.TypeArray(result);
			dims--;
		}
		return result;
	}
	
	private static JCExpression typeToJCTree0(Type type, TreeMaker maker, JavacAST ast, boolean allowCompound, boolean allowVoid) throws TypeNotConvertibleException {
		// NB: There's such a thing as maker.Type(type), but this doesn't work very well; it screws up anonymous classes, captures, and adds an extra prefix dot for some reason too.
		//  -- so we write our own take on that here.
		
		if (type.tag == Javac.getCtcInt(TypeTags.class, "BOT")) return createJavaLangObject(maker, ast);
		if (type.tag == Javac.getCtcInt(TypeTags.class, "VOID")) return allowVoid ? primitiveToJCTree(type.getKind(), maker) : createJavaLangObject(maker, ast);
		if (type.isPrimitive()) return primitiveToJCTree(type.getKind(), maker);
		if (type.isErroneous()) throw new TypeNotConvertibleException("Type cannot be resolved");
		
		TypeSymbol symbol = type.asElement();
		List generics = type.getTypeArguments();
		
		JCExpression replacement = null;
		
		if (symbol == null) throw new TypeNotConvertibleException("Null or compound type");
		
		if (symbol.name.length() == 0) {
			// Anonymous inner class
			if (type instanceof ClassType) {
				List ifaces = ((ClassType)type).interfaces_field;
				Type supertype = ((ClassType)type).supertype_field;
				if (ifaces != null && ifaces.length() == 1) {
					return typeToJCTree(ifaces.get(0), maker, ast, allowCompound, allowVoid);
				}
				if (supertype != null) return typeToJCTree(supertype, maker, ast, allowCompound, allowVoid);
			}
			throw new TypeNotConvertibleException("Anonymous inner class");
		}
		
		if (type instanceof CapturedType || type instanceof WildcardType) {
			Type lower, upper;
			if (type instanceof WildcardType) {
				upper = ((WildcardType)type).getExtendsBound();
				lower = ((WildcardType)type).getSuperBound();
			} else {
				lower = type.getLowerBound();
				upper = type.getUpperBound();
			}
			if (allowCompound) {
				if (lower == null || lower.tag == Javac.getCtcInt(TypeTags.class, "BOT")) {
					if (upper == null || upper.toString().equals("java.lang.Object")) {
						return maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null);
					}
					return maker.Wildcard(maker.TypeBoundKind(BoundKind.EXTENDS), typeToJCTree(upper, maker, ast, false, false));
				} else {
					return maker.Wildcard(maker.TypeBoundKind(BoundKind.SUPER), typeToJCTree(lower, maker, ast, false, false));
				}
			}
			if (upper != null) {
				return typeToJCTree(upper, maker, ast, allowCompound, allowVoid);
			}
			
			return createJavaLangObject(maker, ast);
		}
		
		String qName = symbol.getQualifiedName().toString();
		if (qName.isEmpty()) throw new TypeNotConvertibleException("unknown type");
		if (qName.startsWith("<")) throw new TypeNotConvertibleException(qName);
		String[] baseNames = symbol.getQualifiedName().toString().split("\\.");
		replacement = maker.Ident(ast.toName(baseNames[0]));
		for (int i = 1; i < baseNames.length; i++) {
			replacement = maker.Select(replacement, ast.toName(baseNames[i]));
		}
		
		if (generics != null && !generics.isEmpty()) {
			ListBuffer args = ListBuffer.lb();
			for (Type t : generics) args.append(typeToJCTree(t, maker, ast, true, false));
			replacement = maker.TypeApply(replacement, args.toList());
		}
		
		return replacement;
	}
	
	private static JCExpression primitiveToJCTree(TypeKind kind, TreeMaker maker) throws TypeNotConvertibleException {
		switch (kind) {
		case BYTE:
			return maker.TypeIdent(Javac.getCtcInt(TypeTags.class, "BYTE"));
		case CHAR:
			return maker.TypeIdent(Javac.getCtcInt(TypeTags.class, "CHAR"));
		case SHORT:
			return maker.TypeIdent(Javac.getCtcInt(TypeTags.class, "SHORT"));
		case INT:
			return maker.TypeIdent(Javac.getCtcInt(TypeTags.class, "INT"));
		case LONG:
			return maker.TypeIdent(Javac.getCtcInt(TypeTags.class, "LONG"));
		case FLOAT:
			return maker.TypeIdent(Javac.getCtcInt(TypeTags.class, "FLOAT"));
		case DOUBLE:
			return maker.TypeIdent(Javac.getCtcInt(TypeTags.class, "DOUBLE"));
		case BOOLEAN:
			return maker.TypeIdent(Javac.getCtcInt(TypeTags.class, "BOOLEAN"));
		case VOID:
			return maker.TypeIdent(Javac.getCtcInt(TypeTags.class, "VOID"));
		case NULL:
		case NONE:
		case OTHER:
		default:
			throw new TypeNotConvertibleException("Nulltype");
		}
	}
}