diff --git a/.gitignore b/.gitignore index 0f182a0..30e3d38 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,8 @@ *.jar *.war *.ear + +*.classpath +*.project +*.settings +target diff --git a/README.md b/README.md index 947d189..c5134df 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,39 @@ Douyu ===== -一种新颖的将编译器、HTTP服务器、MVC框架、ORM框架有效结合的Java开发平台 \ No newline at end of file +一种新颖的将编译器、HTTP服务器、MVC框架、ORM框架有效结合的Java开发平台 + + +================================================================= +1. 要生成eclipse工程请运行: + +eclipse.bat + +在eclipse中所有文件的编码要用UTF-8. + + + +2.打包发布请运行: + +package.bat + +打包后的文件放在target目录中: +target\douyu-x.y.z.jar + +(注: x.y.z是实际的版本号) + + + + +3. 每次更新版本时要修改下面这些文件: + +package.bat (对应set version=那一行) +pom.xml (对应那一行) + + + +4. 要想得到所有的依赖jar包,运行: + +assembly.bat 或 +mvn dependency:copy-dependencies + diff --git a/apidocs/allclasses-frame.html b/apidocs/allclasses-frame.html new file mode 100644 index 0000000..c8d2110 --- /dev/null +++ b/apidocs/allclasses-frame.html @@ -0,0 +1,66 @@ + + + + + + + +所有类 (Douyu 0.7.1 API) + + + + + + + + + + + +所有类 +
+ + + + + +
Action +
+Async +
+Context +
+Controller +
+ControllerException +
+ControllerManager +
+HttpMethod +
+HttpRequest +
+HttpResponse +
+Model +
+ModelException +
+ModelManager +
+UploadedFile +
+ViewException +
+ViewManager +
+ViewManagerProvider +
+WebSocket +
+WebSocket.Outbound +
+
+ + + diff --git a/apidocs/allclasses-noframe.html b/apidocs/allclasses-noframe.html new file mode 100644 index 0000000..afa3012 --- /dev/null +++ b/apidocs/allclasses-noframe.html @@ -0,0 +1,66 @@ + + + + + + + +所有类 (Douyu 0.7.1 API) + + + + + + + + + + + +所有类 +
+ + + + + +
Action +
+Async +
+Context +
+Controller +
+ControllerException +
+ControllerManager +
+HttpMethod +
+HttpRequest +
+HttpResponse +
+Model +
+ModelException +
+ModelManager +
+UploadedFile +
+ViewException +
+ViewManager +
+ViewManagerProvider +
+WebSocket +
+WebSocket.Outbound +
+
+ + + diff --git a/apidocs/constant-values.html b/apidocs/constant-values.html new file mode 100644 index 0000000..29f0f77 --- /dev/null +++ b/apidocs/constant-values.html @@ -0,0 +1,147 @@ + + + + + + + +常量字段值 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+常量字段值

+
+
+目录 + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/deprecated-list.html b/apidocs/deprecated-list.html new file mode 100644 index 0000000..c56e5c2 --- /dev/null +++ b/apidocs/deprecated-list.html @@ -0,0 +1,147 @@ + + + + + + + +已过时项目列表 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+已过时的 API

+
+
+目录 + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/http/HttpMethod.html b/apidocs/douyu/http/HttpMethod.html new file mode 100644 index 0000000..42cef76 --- /dev/null +++ b/apidocs/douyu/http/HttpMethod.html @@ -0,0 +1,399 @@ + + + + + + + +HttpMethod (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +douyu.http +
+枚举 HttpMethod

+
+java.lang.Object
+  继承者 java.lang.Enum<HttpMethod>
+      继承者 douyu.http.HttpMethod
+
+
+
所有已实现的接口:
java.io.Serializable, java.lang.Comparable<HttpMethod>
+
+
+
+
public enum HttpMethod
extends java.lang.Enum<HttpMethod>
+ + +

+


+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+枚举常量摘要
DELETE + +
+           
GET + +
+           
HEAD + +
+           
OPTIONS + +
+           
POST + +
+           
PUT + +
+           
TRACE + +
+           
+  + + + + + + + + + + + + + + + +
+方法摘要
+static HttpMethodvalueOf(java.lang.String name) + +
+          返回带有指定名称的该类型的枚举常量。
+static HttpMethod[]values() + +
+          按照声明该枚举类型的常量的顺序,返回 +包含这些常量的数组。
+ + + + + + + +
从类 java.lang.Enum 继承的方法
clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
+ + + + + + + +
从类 java.lang.Object 继承的方法
getClass, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+枚举常量详细信息
+ +

+DELETE

+
+public static final HttpMethod DELETE
+
+
+
+
+
+ +

+HEAD

+
+public static final HttpMethod HEAD
+
+
+
+
+
+ +

+GET

+
+public static final HttpMethod GET
+
+
+
+
+
+ +

+OPTIONS

+
+public static final HttpMethod OPTIONS
+
+
+
+
+
+ +

+POST

+
+public static final HttpMethod POST
+
+
+
+
+
+ +

+PUT

+
+public static final HttpMethod PUT
+
+
+
+
+
+ +

+TRACE

+
+public static final HttpMethod TRACE
+
+
+
+
+ + + + + + + + +
+方法详细信息
+ +

+values

+
+public static HttpMethod[] values()
+
+
按照声明该枚举类型的常量的顺序,返回 +包含这些常量的数组。该方法可用于迭代 +常量,如下所示: +
+for (HttpMethod c : HttpMethod.values())
+    System.out.println(c);
+
+

+

+
+
+
+
+ +

+valueOf

+
+public static HttpMethod valueOf(java.lang.String name)
+
+
返回带有指定名称的该类型的枚举常量。 +字符串必须与用于声明该类型的枚举常量的 +标识符完全匹配。(不允许有多余 +的空格。) +

+

+
参数:
name - 要返回的枚举常量的名称。 +
返回:
返回带有指定名称的枚举常量 +
抛出: +
如果该枚举类型没有带有指定名称的常量, - 则抛出 IllegalArgumentException
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/http/HttpRequest.html b/apidocs/douyu/http/HttpRequest.html new file mode 100644 index 0000000..47f6077 --- /dev/null +++ b/apidocs/douyu/http/HttpRequest.html @@ -0,0 +1,321 @@ + + + + + + + +HttpRequest (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +douyu.http +
+接口 HttpRequest

+
+
+
public interface HttpRequest
+ + +

+


+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+方法摘要
+ java.lang.ObjectgetAttribute(java.lang.String name) + +
+           
+ java.lang.StringgetMethod() + +
+           
+ java.lang.StringgetParameter(java.lang.String name) + +
+           
+ java.lang.String[]getParameterValues(java.lang.String name) + +
+           
+ java.lang.StringgetRequestURI() + +
+           
+ UploadedFilegetUploadedFile(java.lang.String name) + +
+           
+ UploadedFile[]getUploadedFiles() + +
+           
+  +

+ + + + + + + + +
+方法详细信息
+ +

+getMethod

+
+java.lang.String getMethod()
+
+
+
+
+
+
+ +

+getParameter

+
+java.lang.String getParameter(java.lang.String name)
+
+
+
+
+
+
+ +

+getParameterValues

+
+java.lang.String[] getParameterValues(java.lang.String name)
+
+
+
+
+
+
+ +

+getUploadedFile

+
+UploadedFile getUploadedFile(java.lang.String name)
+
+
+
+
+
+
+ +

+getUploadedFiles

+
+UploadedFile[] getUploadedFiles()
+
+
+
+
+
+
+ +

+getAttribute

+
+java.lang.Object getAttribute(java.lang.String name)
+
+
+
+
+
+
+ +

+getRequestURI

+
+java.lang.String getRequestURI()
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/http/HttpResponse.html b/apidocs/douyu/http/HttpResponse.html new file mode 100644 index 0000000..7d042e0 --- /dev/null +++ b/apidocs/douyu/http/HttpResponse.html @@ -0,0 +1,288 @@ + + + + + + + +HttpResponse (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +douyu.http +
+接口 HttpResponse

+
+
+
public interface HttpResponse
+ + +

+


+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+方法摘要
+ java.lang.StringgetContentType() + +
+           
+ java.io.PrintWritergetWriter() + +
+           
+ voidsendError(int status, + java.lang.String message) + +
+           
+ voidsetCharacterEncoding(java.lang.String charset) + +
+           
+ voidsetContentType(java.lang.String contentType) + +
+           
+  +

+ + + + + + + + +
+方法详细信息
+ +

+getContentType

+
+java.lang.String getContentType()
+
+
+
+
+
+
+ +

+setContentType

+
+void setContentType(java.lang.String contentType)
+
+
+
+
+
+
+ +

+getWriter

+
+java.io.PrintWriter getWriter()
+                              throws java.lang.Exception
+
+
+ +
抛出: +
java.lang.Exception
+
+
+
+ +

+setCharacterEncoding

+
+void setCharacterEncoding(java.lang.String charset)
+
+
+
+
+
+
+ +

+sendError

+
+void sendError(int status,
+               java.lang.String message)
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/http/UploadedFile.html b/apidocs/douyu/http/UploadedFile.html new file mode 100644 index 0000000..e0c0c50 --- /dev/null +++ b/apidocs/douyu/http/UploadedFile.html @@ -0,0 +1,384 @@ + + + + + + + +UploadedFile (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +douyu.http +
+接口 UploadedFile

+
+
+
public interface UploadedFile
+ + +

+


+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+方法摘要
+ byte[]getBytes() + +
+           
+ java.lang.StringgetContent() + +
+           
+ java.lang.StringgetContent(java.lang.String encoding) + +
+           
+ java.lang.StringgetContentType() + +
+           
+ java.lang.StringgetFullName() + +
+           
+ java.lang.StringgetPathName() + +
+           
+ java.lang.StringgetSimpleName() + +
+           
+ longgetSize() + +
+           
+ voidsaveTo(java.io.File file) + +
+           
+ voidsaveTo(java.lang.String file) + +
+           
+  +

+ + + + + + + + +
+方法详细信息
+ +

+getSize

+
+long getSize()
+
+
+
+
+
+
+ +

+getContentType

+
+java.lang.String getContentType()
+
+
+
+
+
+
+ +

+getSimpleName

+
+java.lang.String getSimpleName()
+
+
+
+
+
+
+ +

+getFullName

+
+java.lang.String getFullName()
+
+
+
+
+
+
+ +

+getPathName

+
+java.lang.String getPathName()
+
+
+
+
+
+
+ +

+getBytes

+
+byte[] getBytes()
+
+
+
+
+
+
+ +

+getContent

+
+java.lang.String getContent()
+
+
+
+
+
+
+ +

+getContent

+
+java.lang.String getContent(java.lang.String encoding)
+
+
+
+
+
+
+ +

+saveTo

+
+void saveTo(java.lang.String file)
+            throws java.lang.Exception
+
+
+ +
抛出: +
java.lang.Exception
+
+
+
+ +

+saveTo

+
+void saveTo(java.io.File file)
+            throws java.lang.Exception
+
+
+ +
抛出: +
java.lang.Exception
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/http/WebSocket.Outbound.html b/apidocs/douyu/http/WebSocket.Outbound.html new file mode 100644 index 0000000..40f4ae0 --- /dev/null +++ b/apidocs/douyu/http/WebSocket.Outbound.html @@ -0,0 +1,327 @@ + + + + + + + +WebSocket.Outbound (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +douyu.http +
+接口 WebSocket.Outbound

+
+
正在封闭接口:
WebSocket
+
+
+
+
public static interface WebSocket.Outbound
+ + +

+


+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+方法摘要
+ voidclose() + +
+           
+ booleanisOpen() + +
+           
+ voidsend(int type, + byte[] data) + +
+           
+ voidsend(int type, + byte[] data, + int offset, + int length) + +
+           
+ voidsend(int type, + java.lang.String data) + +
+           
+ voidsend(java.lang.String data) + +
+           
+  +

+ + + + + + + + +
+方法详细信息
+ +

+send

+
+void send(java.lang.String data)
+          throws java.io.IOException
+
+
+ +
抛出: +
java.io.IOException
+
+
+
+ +

+send

+
+void send(int type,
+          java.lang.String data)
+          throws java.io.IOException
+
+
+ +
抛出: +
java.io.IOException
+
+
+
+ +

+send

+
+void send(int type,
+          byte[] data)
+          throws java.io.IOException
+
+
+ +
抛出: +
java.io.IOException
+
+
+
+ +

+send

+
+void send(int type,
+          byte[] data,
+          int offset,
+          int length)
+          throws java.io.IOException
+
+
+ +
抛出: +
java.io.IOException
+
+
+
+ +

+close

+
+void close()
+
+
+
+
+
+
+ +

+isOpen

+
+boolean isOpen()
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/http/WebSocket.html b/apidocs/douyu/http/WebSocket.html new file mode 100644 index 0000000..2cecfaf --- /dev/null +++ b/apidocs/douyu/http/WebSocket.html @@ -0,0 +1,285 @@ + + + + + + + +WebSocket (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +douyu.http +
+接口 WebSocket

+
+
+
public interface WebSocket
+ + +

+


+ +

+ + + + + + + + + + + +
+嵌套类摘要
+static interfaceWebSocket.Outbound + +
+           
+  + + + + + + + + + + + + + + + + + + + + + + + +
+方法摘要
+ voidonConnect(WebSocket.Outbound outbound) + +
+           
+ voidonDisconnect() + +
+           
+ voidonMessage(int type, + byte[] data) + +
+           
+ voidonMessage(int type, + java.lang.String data) + +
+           
+  +

+ + + + + + + + +
+方法详细信息
+ +

+onConnect

+
+void onConnect(WebSocket.Outbound outbound)
+
+
+
+
+
+
+ +

+onMessage

+
+void onMessage(int type,
+               java.lang.String data)
+
+
+
+
+
+
+ +

+onMessage

+
+void onMessage(int type,
+               byte[] data)
+
+
+
+
+
+
+ +

+onDisconnect

+
+void onDisconnect()
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/http/class-use/HttpMethod.html b/apidocs/douyu/http/class-use/HttpMethod.html new file mode 100644 index 0000000..262b896 --- /dev/null +++ b/apidocs/douyu/http/class-use/HttpMethod.html @@ -0,0 +1,190 @@ + + + + + + + +类 douyu.http.HttpMethod 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+类 douyu.http.HttpMethod
的使用

+
+ + + + + + + + + +
+使用 HttpMethod 的软件包
douyu.http提供与HTTP相关的接口 
+  +

+ + + + + +
+douyu.httpHttpMethod 的使用
+  +

+ + + + + + + + + + + + + +
返回 HttpMethoddouyu.http 中的方法
+static HttpMethodHttpMethod.valueOf(java.lang.String name) + +
+          返回带有指定名称的该类型的枚举常量。
+static HttpMethod[]HttpMethod.values() + +
+          按照声明该枚举类型的常量的顺序,返回 +包含这些常量的数组。
+  +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/http/class-use/HttpRequest.html b/apidocs/douyu/http/class-use/HttpRequest.html new file mode 100644 index 0000000..eeba030 --- /dev/null +++ b/apidocs/douyu/http/class-use/HttpRequest.html @@ -0,0 +1,181 @@ + + + + + + + +接口 douyu.http.HttpRequest 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+接口 douyu.http.HttpRequest
的使用

+
+ + + + + + + + + +
+使用 HttpRequest 的软件包
douyu.mvc提供与MVC相关的接口 
+  +

+ + + + + +
+douyu.mvcHttpRequest 的使用
+  +

+ + + + + + + + + +
返回 HttpRequestdouyu.mvc 中的方法
+ HttpRequestControllerManager.getHttpRequest() + +
+           
+  +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/http/class-use/HttpResponse.html b/apidocs/douyu/http/class-use/HttpResponse.html new file mode 100644 index 0000000..7435e0f --- /dev/null +++ b/apidocs/douyu/http/class-use/HttpResponse.html @@ -0,0 +1,181 @@ + + + + + + + +接口 douyu.http.HttpResponse 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+接口 douyu.http.HttpResponse
的使用

+
+ + + + + + + + + +
+使用 HttpResponse 的软件包
douyu.mvc提供与MVC相关的接口 
+  +

+ + + + + +
+douyu.mvcHttpResponse 的使用
+  +

+ + + + + + + + + +
返回 HttpResponsedouyu.mvc 中的方法
+ HttpResponseControllerManager.getHttpResponse() + +
+           
+  +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/http/class-use/UploadedFile.html b/apidocs/douyu/http/class-use/UploadedFile.html new file mode 100644 index 0000000..6d63f98 --- /dev/null +++ b/apidocs/douyu/http/class-use/UploadedFile.html @@ -0,0 +1,189 @@ + + + + + + + +接口 douyu.http.UploadedFile 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+接口 douyu.http.UploadedFile
的使用

+
+ + + + + + + + + +
+使用 UploadedFile 的软件包
douyu.http提供与HTTP相关的接口 
+  +

+ + + + + +
+douyu.httpUploadedFile 的使用
+  +

+ + + + + + + + + + + + + +
返回 UploadedFiledouyu.http 中的方法
+ UploadedFileHttpRequest.getUploadedFile(java.lang.String name) + +
+           
+ UploadedFile[]HttpRequest.getUploadedFiles() + +
+           
+  +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/http/class-use/WebSocket.Outbound.html b/apidocs/douyu/http/class-use/WebSocket.Outbound.html new file mode 100644 index 0000000..1d53595 --- /dev/null +++ b/apidocs/douyu/http/class-use/WebSocket.Outbound.html @@ -0,0 +1,181 @@ + + + + + + + +接口 douyu.http.WebSocket.Outbound 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+接口 douyu.http.WebSocket.Outbound
的使用

+
+ + + + + + + + + +
+使用 WebSocket.Outbound 的软件包
douyu.http提供与HTTP相关的接口 
+  +

+ + + + + +
+douyu.httpWebSocket.Outbound 的使用
+  +

+ + + + + + + + + +
参数类型为 WebSocket.Outbounddouyu.http 中的方法
+ voidWebSocket.onConnect(WebSocket.Outbound outbound) + +
+           
+  +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/http/class-use/WebSocket.html b/apidocs/douyu/http/class-use/WebSocket.html new file mode 100644 index 0000000..8462607 --- /dev/null +++ b/apidocs/douyu/http/class-use/WebSocket.html @@ -0,0 +1,197 @@ + + + + + + + +接口 douyu.http.WebSocket 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+接口 douyu.http.WebSocket
的使用

+
+ + + + + + + + + +
+使用 WebSocket 的软件包
douyu.mvc提供与MVC相关的接口 
+  +

+ + + + + +
+douyu.mvcWebSocket 的使用
+  +

+ + + + + + + + + +
返回 WebSocketdouyu.mvc 中的方法
+ WebSocketControllerManager.getWebSocket() + +
+           
+  +

+ + + + + + + + + +
参数类型为 WebSocketdouyu.mvc 中的方法
+ voidControllerManager.setWebSocket(WebSocket ws) + +
+           
+  +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/http/package-frame.html b/apidocs/douyu/http/package-frame.html new file mode 100644 index 0000000..9b74708 --- /dev/null +++ b/apidocs/douyu/http/package-frame.html @@ -0,0 +1,52 @@ + + + + + + + +douyu.http (Douyu 0.7.1 API) + + + + + + + + + + + +douyu.http + + + + +
+接口  + +
+HttpRequest +
+HttpResponse +
+UploadedFile +
+WebSocket +
+WebSocket.Outbound
+ + + + + + +
+枚举  + +
+HttpMethod
+ + + + diff --git a/apidocs/douyu/http/package-summary.html b/apidocs/douyu/http/package-summary.html new file mode 100644 index 0000000..1f48aba --- /dev/null +++ b/apidocs/douyu/http/package-summary.html @@ -0,0 +1,207 @@ + + + + + + + +douyu.http (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+

+软件包 douyu.http +

+提供与HTTP相关的接口 +

+请参见: +
+          描述 +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+接口摘要
HttpRequest 
HttpResponse 
UploadedFile 
WebSocket 
WebSocket.Outbound 
+  + +

+ + + + + + + + + +
+枚举摘要
HttpMethod 
+  + +

+

+软件包 douyu.http 的描述 +

+ +

+提供与HTTP相关的接口 +

+ +

+

+
从以下版本开始:
+
0.6.1
+
作者:
+
ZHH
+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/http/package-tree.html b/apidocs/douyu/http/package-tree.html new file mode 100644 index 0000000..55064a9 --- /dev/null +++ b/apidocs/douyu/http/package-tree.html @@ -0,0 +1,162 @@ + + + + + + + +douyu.http 类分层结构 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+软件包 douyu.http 的分层结构 +

+
+
+
软件包分层结构:
所有软件包
+
+

+接口分层结构 +

+ +

+枚举分层结构 +

+ +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/http/package-use.html b/apidocs/douyu/http/package-use.html new file mode 100644 index 0000000..2222630 --- /dev/null +++ b/apidocs/douyu/http/package-use.html @@ -0,0 +1,214 @@ + + + + + + + +软件包 douyu.http 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+软件包 douyu.http
的使用

+
+ + + + + + + + + + + + + +
+使用 douyu.http 的软件包
douyu.http提供与HTTP相关的接口 
douyu.mvc提供与MVC相关的接口 
+  +

+ + + + + + + + + + + + + + +
+douyu.http 使用的 douyu.http 中的类
HttpMethod + +
+           
UploadedFile + +
+           
WebSocket.Outbound + +
+           
+  +

+ + + + + + + + + + + + + + +
+douyu.mvc 使用的 douyu.http 中的类
HttpRequest + +
+           
HttpResponse + +
+           
WebSocket + +
+           
+  +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/Action.html b/apidocs/douyu/mvc/Action.html new file mode 100644 index 0000000..d6ec940 --- /dev/null +++ b/apidocs/douyu/mvc/Action.html @@ -0,0 +1,228 @@ + + + + + + + +Action (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +douyu.mvc +
+注释类型 Action

+
+
+
@Retention(value=RUNTIME)
+@Target(value=METHOD)
+public @interface Action
+ + +

+


+ +

+ + + + + + + + + + + + + + + +
+可选元素摘要
+ HttpMethod[]httpMethods + +
+          Action所能接受的HTTT请求方法类型
+ intmaxConcurrentRequests + +
+           
+  +

+

+maxConcurrentRequests

+
+public abstract int maxConcurrentRequests
+
+
+
+
+
+
+
+
默认值:
-1
+
+
+
+ +

+httpMethods

+
+public abstract HttpMethod[] httpMethods
+
+
Action所能接受的HTTT请求方法类型 +

+

+
+
+
+
+
+
默认值:
{douyu.http.HttpMethod.GET, douyu.http.HttpMethod.POST}
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/Async.html b/apidocs/douyu/mvc/Async.html new file mode 100644 index 0000000..19250af --- /dev/null +++ b/apidocs/douyu/mvc/Async.html @@ -0,0 +1,202 @@ + + + + + + + +Async (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +douyu.mvc +
+注释类型 Async

+
+
+
@Retention(value=RUNTIME)
+@Target(value={METHOD,TYPE})
+public @interface Async
+ + +

+


+ +

+ + + + + + + + + + + +
+可选元素摘要
+ intmaxConcurrentRequests + +
+           
+  +

+

+maxConcurrentRequests

+
+public abstract int maxConcurrentRequests
+
+
+
+
+
+
+
+
默认值:
-1
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/Context.html b/apidocs/douyu/mvc/Context.html new file mode 100644 index 0000000..910b1c6 --- /dev/null +++ b/apidocs/douyu/mvc/Context.html @@ -0,0 +1,212 @@ + + + + + + + +Context (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +douyu.mvc +
+接口 Context

+
+
所有超级接口:
ControllerManager, ModelManager, ViewManager
+
+
+
+
public interface Context
extends ModelManager, ViewManager, ControllerManager
+ + +

+执行控制器的Action时,会为这个控制器生成一个Context,这个Context用来管理一次请求过程中用到的相关上下文信息。 + + 此类不是线程安全的,存活期与HttpRequest相同。 +

+ +

+

+
从以下版本开始:
+
0.6.1
+
作者:
+
ZHH
+
+
+ +

+ + + + + + + + +
+方法摘要
+ + + + + + + +
从接口 douyu.mvc.ViewManager 继承的方法
out, out, put
+ + + + + + + +
从接口 douyu.mvc.ControllerManager 继承的方法
executeAction, getActionName, getApplicationBase, getControllerClassName, getHttpRequest, getHttpResponse, getWebSocket, setWebSocket
+  +

+ +


+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/Controller.html b/apidocs/douyu/mvc/Controller.html new file mode 100644 index 0000000..d0e9bcb --- /dev/null +++ b/apidocs/douyu/mvc/Controller.html @@ -0,0 +1,228 @@ + + + + + + + +Controller (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +douyu.mvc +
+注释类型 Controller

+
+
+
@Retention(value=RUNTIME)
+@Target(value=TYPE)
+public @interface Controller
+ + +

+


+ +

+ + + + + + + + + + + + + + + +
+可选元素摘要
+ java.lang.StringdefaultAction + +
+           
+ HttpMethod[]httpMethods + +
+          Controller所能接受的HTTT请求方法类型,会被Action中的值覆盖
+  +

+

+defaultAction

+
+public abstract java.lang.String defaultAction
+
+
+
+
+
+
+
+
默认值:
"index"
+
+
+
+ +

+httpMethods

+
+public abstract HttpMethod[] httpMethods
+
+
Controller所能接受的HTTT请求方法类型,会被Action中的值覆盖 +

+

+
+
+
+
+
+
默认值:
{douyu.http.HttpMethod.GET, douyu.http.HttpMethod.POST}
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/ControllerException.html b/apidocs/douyu/mvc/ControllerException.html new file mode 100644 index 0000000..4e91384 --- /dev/null +++ b/apidocs/douyu/mvc/ControllerException.html @@ -0,0 +1,286 @@ + + + + + + + +ControllerException (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +douyu.mvc +
+类 ControllerException

+
+java.lang.Object
+  继承者 java.lang.Throwable
+      继承者 java.lang.Exception
+          继承者 java.lang.RuntimeException
+              继承者 douyu.mvc.ControllerException
+
+
+
所有已实现的接口:
java.io.Serializable
+
+
+
+
public class ControllerException
extends java.lang.RuntimeException
+ + +

+

+
另请参见:
序列化表格
+
+ +

+ + + + + + + + + + + + + + + + + + + + +
+构造方法摘要
ControllerException() + +
+           
ControllerException(java.lang.String message) + +
+           
ControllerException(java.lang.String message, + java.lang.Throwable cause) + +
+           
ControllerException(java.lang.Throwable cause) + +
+           
+  + + + + + + + +
+方法摘要
+ + + + + + + +
从类 java.lang.Throwable 继承的方法
fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
+ + + + + + + +
从类 java.lang.Object 继承的方法
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+构造方法详细信息
+ +

+ControllerException

+
+public ControllerException()
+
+
+
+ +

+ControllerException

+
+public ControllerException(java.lang.String message)
+
+
+
+ +

+ControllerException

+
+public ControllerException(java.lang.Throwable cause)
+
+
+
+ +

+ControllerException

+
+public ControllerException(java.lang.String message,
+                           java.lang.Throwable cause)
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/ControllerManager.html b/apidocs/douyu/mvc/ControllerManager.html new file mode 100644 index 0000000..6717865 --- /dev/null +++ b/apidocs/douyu/mvc/ControllerManager.html @@ -0,0 +1,359 @@ + + + + + + + +ControllerManager (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +douyu.mvc +
+接口 ControllerManager

+
+
所有已知子接口:
Context
+
+
+
+
public interface ControllerManager
+ + +

+执行控制器的Action时,会为这个控制器生成一个ControllerManager, + 这个ControllerManager用来管理一次请求过程中用到的相关上下文信息。 + + 此类不是线程安全的,存活期与HttpRequest相同。 +

+ +

+

+
从以下版本开始:
+
0.6.1
+
作者:
+
ZHH
+
+
+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+方法摘要
+ voidexecuteAction(java.lang.String actionName) + +
+           
+ java.lang.StringgetActionName() + +
+           
+ java.lang.StringgetApplicationBase() + +
+           
+ java.lang.StringgetControllerClassName() + +
+           
+ HttpRequestgetHttpRequest() + +
+           
+ HttpResponsegetHttpResponse() + +
+           
+ WebSocketgetWebSocket() + +
+           
+ voidsetWebSocket(WebSocket ws) + +
+           
+  +

+ + + + + + + + +
+方法详细信息
+ +

+getHttpRequest

+
+HttpRequest getHttpRequest()
+
+
+
+
+
+
+ +

+getHttpResponse

+
+HttpResponse getHttpResponse()
+
+
+
+
+
+
+ +

+getControllerClassName

+
+java.lang.String getControllerClassName()
+
+
+
+
+
+
+ +

+getActionName

+
+java.lang.String getActionName()
+
+
+
+
+
+
+ +

+getApplicationBase

+
+java.lang.String getApplicationBase()
+
+
+
+
+
+
+ +

+executeAction

+
+void executeAction(java.lang.String actionName)
+                   throws ControllerException
+
+
+ +
抛出: +
ControllerException
+
+
+
+ +

+setWebSocket

+
+void setWebSocket(WebSocket ws)
+
+
+
+
+
+
+ +

+getWebSocket

+
+WebSocket getWebSocket()
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/Model.html b/apidocs/douyu/mvc/Model.html new file mode 100644 index 0000000..213ee24 --- /dev/null +++ b/apidocs/douyu/mvc/Model.html @@ -0,0 +1,170 @@ + + + + + + + +Model (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +douyu.mvc +
+注释类型 Model

+
+
+
@Retention(value=RUNTIME)
+@Target(value=TYPE)
+public @interface Model
+ + +

+ +

+ +

+ +


+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/ModelException.html b/apidocs/douyu/mvc/ModelException.html new file mode 100644 index 0000000..264609e --- /dev/null +++ b/apidocs/douyu/mvc/ModelException.html @@ -0,0 +1,286 @@ + + + + + + + +ModelException (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +douyu.mvc +
+类 ModelException

+
+java.lang.Object
+  继承者 java.lang.Throwable
+      继承者 java.lang.Exception
+          继承者 java.lang.RuntimeException
+              继承者 douyu.mvc.ModelException
+
+
+
所有已实现的接口:
java.io.Serializable
+
+
+
+
public class ModelException
extends java.lang.RuntimeException
+ + +

+

+
另请参见:
序列化表格
+
+ +

+ + + + + + + + + + + + + + + + + + + + +
+构造方法摘要
ModelException() + +
+           
ModelException(java.lang.String message) + +
+           
ModelException(java.lang.String message, + java.lang.Throwable cause) + +
+           
ModelException(java.lang.Throwable cause) + +
+           
+  + + + + + + + +
+方法摘要
+ + + + + + + +
从类 java.lang.Throwable 继承的方法
fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
+ + + + + + + +
从类 java.lang.Object 继承的方法
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+构造方法详细信息
+ +

+ModelException

+
+public ModelException()
+
+
+
+ +

+ModelException

+
+public ModelException(java.lang.String message)
+
+
+
+ +

+ModelException

+
+public ModelException(java.lang.Throwable cause)
+
+
+
+ +

+ModelException

+
+public ModelException(java.lang.String message,
+                      java.lang.Throwable cause)
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/ModelManager.html b/apidocs/douyu/mvc/ModelManager.html new file mode 100644 index 0000000..6e9fe61 --- /dev/null +++ b/apidocs/douyu/mvc/ModelManager.html @@ -0,0 +1,180 @@ + + + + + + + +ModelManager (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +douyu.mvc +
+接口 ModelManager

+
+
所有已知子接口:
Context
+
+
+
+
public interface ModelManager
+ + +

+未实现 +

+ +

+

+
作者:
+
ZHH
+
+
+ +

+ +

+ +


+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/ViewException.html b/apidocs/douyu/mvc/ViewException.html new file mode 100644 index 0000000..55fb774 --- /dev/null +++ b/apidocs/douyu/mvc/ViewException.html @@ -0,0 +1,286 @@ + + + + + + + +ViewException (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +douyu.mvc +
+类 ViewException

+
+java.lang.Object
+  继承者 java.lang.Throwable
+      继承者 java.lang.Exception
+          继承者 java.lang.RuntimeException
+              继承者 douyu.mvc.ViewException
+
+
+
所有已实现的接口:
java.io.Serializable
+
+
+
+
public class ViewException
extends java.lang.RuntimeException
+ + +

+

+
另请参见:
序列化表格
+
+ +

+ + + + + + + + + + + + + + + + + + + + +
+构造方法摘要
ViewException() + +
+           
ViewException(java.lang.String message) + +
+           
ViewException(java.lang.String message, + java.lang.Throwable cause) + +
+           
ViewException(java.lang.Throwable cause) + +
+           
+  + + + + + + + +
+方法摘要
+ + + + + + + +
从类 java.lang.Throwable 继承的方法
fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
+ + + + + + + +
从类 java.lang.Object 继承的方法
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
+  +

+ + + + + + + + +
+构造方法详细信息
+ +

+ViewException

+
+public ViewException()
+
+
+
+ +

+ViewException

+
+public ViewException(java.lang.String message)
+
+
+
+ +

+ViewException

+
+public ViewException(java.lang.Throwable cause)
+
+
+
+ +

+ViewException

+
+public ViewException(java.lang.String message,
+                     java.lang.Throwable cause)
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/ViewManager.html b/apidocs/douyu/mvc/ViewManager.html new file mode 100644 index 0000000..148237e --- /dev/null +++ b/apidocs/douyu/mvc/ViewManager.html @@ -0,0 +1,262 @@ + + + + + + + +ViewManager (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +douyu.mvc +
+接口 ViewManager

+
+
所有已知子接口:
Context
+
+
+
+
public interface ViewManager
+ + +

+输出视图模板文件,存放模板文件需要用到的参数。 +

+ +

+

+
从以下版本开始:
+
0.6.1
+
作者:
+
ZHH
+
+
+ +

+ + + + + + + + + + + + + + + + + + + + +
+方法摘要
+ voidout() + +
+          输出默认视图,默认视图的位置取决于ViewManagerProvider,通常会根据控制器类名和Action名来决定
+ voidout(java.lang.String viewFileName) + +
+           
+ voidput(java.lang.String key, + java.lang.Object value) + +
+           
+  +

+ + + + + + + + +
+方法详细信息
+ +

+out

+
+void out()
+
+
输出默认视图,默认视图的位置取决于ViewManagerProvider,通常会根据控制器类名和Action名来决定 +

+

+
+
+
+
+ +

+out

+
+void out(java.lang.String viewFileName)
+
+
+
+
+
+
+ +

+put

+
+void put(java.lang.String key,
+         java.lang.Object value)
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/ViewManagerProvider.html b/apidocs/douyu/mvc/ViewManagerProvider.html new file mode 100644 index 0000000..7bcb5f0 --- /dev/null +++ b/apidocs/douyu/mvc/ViewManagerProvider.html @@ -0,0 +1,207 @@ + + + + + + + +ViewManagerProvider (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ +

+ +douyu.mvc +
+接口 ViewManagerProvider

+
+
+
public interface ViewManagerProvider
+ + +

+


+ +

+ + + + + + + + + + + + +
+方法摘要
+ ViewManagergetViewManager(Context context) + +
+           
+  +

+ + + + + + + + +
+方法详细信息
+ +

+getViewManager

+
+ViewManager getViewManager(Context context)
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/class-use/Action.html b/apidocs/douyu/mvc/class-use/Action.html new file mode 100644 index 0000000..2db0917 --- /dev/null +++ b/apidocs/douyu/mvc/class-use/Action.html @@ -0,0 +1,145 @@ + + + + + + + +类 douyu.mvc.Action 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+类 douyu.mvc.Action
的使用

+
+没有 douyu.mvc.Action 的用法 +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/class-use/Async.html b/apidocs/douyu/mvc/class-use/Async.html new file mode 100644 index 0000000..986a997 --- /dev/null +++ b/apidocs/douyu/mvc/class-use/Async.html @@ -0,0 +1,145 @@ + + + + + + + +类 douyu.mvc.Async 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+类 douyu.mvc.Async
的使用

+
+没有 douyu.mvc.Async 的用法 +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/class-use/Context.html b/apidocs/douyu/mvc/class-use/Context.html new file mode 100644 index 0000000..a7d09fc --- /dev/null +++ b/apidocs/douyu/mvc/class-use/Context.html @@ -0,0 +1,181 @@ + + + + + + + +接口 douyu.mvc.Context 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+接口 douyu.mvc.Context
的使用

+
+ + + + + + + + + +
+使用 Context 的软件包
douyu.mvc提供与MVC相关的接口 
+  +

+ + + + + +
+douyu.mvcContext 的使用
+  +

+ + + + + + + + + +
参数类型为 Contextdouyu.mvc 中的方法
+ ViewManagerViewManagerProvider.getViewManager(Context context) + +
+           
+  +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/class-use/Controller.html b/apidocs/douyu/mvc/class-use/Controller.html new file mode 100644 index 0000000..4c0974d --- /dev/null +++ b/apidocs/douyu/mvc/class-use/Controller.html @@ -0,0 +1,145 @@ + + + + + + + +类 douyu.mvc.Controller 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+类 douyu.mvc.Controller
的使用

+
+没有 douyu.mvc.Controller 的用法 +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/class-use/ControllerException.html b/apidocs/douyu/mvc/class-use/ControllerException.html new file mode 100644 index 0000000..32bdb21 --- /dev/null +++ b/apidocs/douyu/mvc/class-use/ControllerException.html @@ -0,0 +1,181 @@ + + + + + + + +类 douyu.mvc.ControllerException 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+类 douyu.mvc.ControllerException
的使用

+
+ + + + + + + + + +
+使用 ControllerException 的软件包
douyu.mvc提供与MVC相关的接口 
+  +

+ + + + + +
+douyu.mvcControllerException 的使用
+  +

+ + + + + + + + + +
抛出 ControllerExceptiondouyu.mvc 中的方法
+ voidControllerManager.executeAction(java.lang.String actionName) + +
+           
+  +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/class-use/ControllerManager.html b/apidocs/douyu/mvc/class-use/ControllerManager.html new file mode 100644 index 0000000..451b9df --- /dev/null +++ b/apidocs/douyu/mvc/class-use/ControllerManager.html @@ -0,0 +1,181 @@ + + + + + + + +接口 douyu.mvc.ControllerManager 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+接口 douyu.mvc.ControllerManager
的使用

+
+ + + + + + + + + +
+使用 ControllerManager 的软件包
douyu.mvc提供与MVC相关的接口 
+  +

+ + + + + +
+douyu.mvcControllerManager 的使用
+  +

+ + + + + + + + + +
douyu.mvcControllerManager 的子接口
+ interfaceContext + +
+          执行控制器的Action时,会为这个控制器生成一个Context,这个Context用来管理一次请求过程中用到的相关上下文信息。
+  +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/class-use/Model.html b/apidocs/douyu/mvc/class-use/Model.html new file mode 100644 index 0000000..e07d4d0 --- /dev/null +++ b/apidocs/douyu/mvc/class-use/Model.html @@ -0,0 +1,145 @@ + + + + + + + +类 douyu.mvc.Model 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+类 douyu.mvc.Model
的使用

+
+没有 douyu.mvc.Model 的用法 +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/class-use/ModelException.html b/apidocs/douyu/mvc/class-use/ModelException.html new file mode 100644 index 0000000..cfcb6bb --- /dev/null +++ b/apidocs/douyu/mvc/class-use/ModelException.html @@ -0,0 +1,145 @@ + + + + + + + +类 douyu.mvc.ModelException 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+类 douyu.mvc.ModelException
的使用

+
+没有 douyu.mvc.ModelException 的用法 +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/class-use/ModelManager.html b/apidocs/douyu/mvc/class-use/ModelManager.html new file mode 100644 index 0000000..4f09d1e --- /dev/null +++ b/apidocs/douyu/mvc/class-use/ModelManager.html @@ -0,0 +1,181 @@ + + + + + + + +接口 douyu.mvc.ModelManager 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+接口 douyu.mvc.ModelManager
的使用

+
+ + + + + + + + + +
+使用 ModelManager 的软件包
douyu.mvc提供与MVC相关的接口 
+  +

+ + + + + +
+douyu.mvcModelManager 的使用
+  +

+ + + + + + + + + +
douyu.mvcModelManager 的子接口
+ interfaceContext + +
+          执行控制器的Action时,会为这个控制器生成一个Context,这个Context用来管理一次请求过程中用到的相关上下文信息。
+  +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/class-use/ViewException.html b/apidocs/douyu/mvc/class-use/ViewException.html new file mode 100644 index 0000000..cb8f531 --- /dev/null +++ b/apidocs/douyu/mvc/class-use/ViewException.html @@ -0,0 +1,145 @@ + + + + + + + +类 douyu.mvc.ViewException 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+类 douyu.mvc.ViewException
的使用

+
+没有 douyu.mvc.ViewException 的用法 +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/class-use/ViewManager.html b/apidocs/douyu/mvc/class-use/ViewManager.html new file mode 100644 index 0000000..48e8c62 --- /dev/null +++ b/apidocs/douyu/mvc/class-use/ViewManager.html @@ -0,0 +1,197 @@ + + + + + + + +接口 douyu.mvc.ViewManager 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+接口 douyu.mvc.ViewManager
的使用

+
+ + + + + + + + + +
+使用 ViewManager 的软件包
douyu.mvc提供与MVC相关的接口 
+  +

+ + + + + +
+douyu.mvcViewManager 的使用
+  +

+ + + + + + + + + +
douyu.mvcViewManager 的子接口
+ interfaceContext + +
+          执行控制器的Action时,会为这个控制器生成一个Context,这个Context用来管理一次请求过程中用到的相关上下文信息。
+  +

+ + + + + + + + + +
返回 ViewManagerdouyu.mvc 中的方法
+ ViewManagerViewManagerProvider.getViewManager(Context context) + +
+           
+  +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/class-use/ViewManagerProvider.html b/apidocs/douyu/mvc/class-use/ViewManagerProvider.html new file mode 100644 index 0000000..7a48278 --- /dev/null +++ b/apidocs/douyu/mvc/class-use/ViewManagerProvider.html @@ -0,0 +1,145 @@ + + + + + + + +接口 douyu.mvc.ViewManagerProvider 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+接口 douyu.mvc.ViewManagerProvider
的使用

+
+没有 douyu.mvc.ViewManagerProvider 的用法 +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/package-frame.html b/apidocs/douyu/mvc/package-frame.html new file mode 100644 index 0000000..92765f6 --- /dev/null +++ b/apidocs/douyu/mvc/package-frame.html @@ -0,0 +1,73 @@ + + + + + + + +douyu.mvc (Douyu 0.7.1 API) + + + + + + + + + + + +douyu.mvc + + + + +
+接口  + +
+Context +
+ControllerManager +
+ModelManager +
+ViewManager +
+ViewManagerProvider
+ + + + + + +
+异常  + +
+ControllerException +
+ModelException +
+ViewException
+ + + + + + +
+注释类型  + +
+Action +
+Async +
+Controller +
+Model
+ + + + diff --git a/apidocs/douyu/mvc/package-summary.html b/apidocs/douyu/mvc/package-summary.html new file mode 100644 index 0000000..cc345db --- /dev/null +++ b/apidocs/douyu/mvc/package-summary.html @@ -0,0 +1,242 @@ + + + + + + + +douyu.mvc (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+

+软件包 douyu.mvc +

+提供与MVC相关的接口 +

+请参见: +
+          描述 +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+接口摘要
Context执行控制器的Action时,会为这个控制器生成一个Context,这个Context用来管理一次请求过程中用到的相关上下文信息。
ControllerManager执行控制器的Action时,会为这个控制器生成一个ControllerManager, + 这个ControllerManager用来管理一次请求过程中用到的相关上下文信息。
ModelManager未实现
ViewManager输出视图模板文件,存放模板文件需要用到的参数。
ViewManagerProvider 
+  + +

+ + + + + + + + + + + + + + + + + +
+异常摘要
ControllerException 
ModelException 
ViewException 
+  + +

+ + + + + + + + + + + + + + + + + + + + + +
+注释类型摘要
Action 
Async 
Controller 
Model 
+  + +

+

+软件包 douyu.mvc 的描述 +

+ +

+提供与MVC相关的接口 +

+ +

+

+
从以下版本开始:
+
0.6.1
+
作者:
+
ZHH
+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/package-tree.html b/apidocs/douyu/mvc/package-tree.html new file mode 100644 index 0000000..88d9720 --- /dev/null +++ b/apidocs/douyu/mvc/package-tree.html @@ -0,0 +1,184 @@ + + + + + + + +douyu.mvc 类分层结构 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+软件包 douyu.mvc 的分层结构 +

+
+
+
软件包分层结构:
所有软件包
+
+

+类分层结构 +

+ +

+接口分层结构 +

+ +

+注释类型分层结构 +

+ +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/douyu/mvc/package-use.html b/apidocs/douyu/mvc/package-use.html new file mode 100644 index 0000000..cdd14e7 --- /dev/null +++ b/apidocs/douyu/mvc/package-use.html @@ -0,0 +1,196 @@ + + + + + + + +软件包 douyu.mvc 的使用 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+软件包 douyu.mvc
的使用

+
+ + + + + + + + + +
+使用 douyu.mvc 的软件包
douyu.mvc提供与MVC相关的接口 
+  +

+ + + + + + + + + + + + + + + + + + + + +
+douyu.mvc 使用的 douyu.mvc 中的类
Context + +
+          执行控制器的Action时,会为这个控制器生成一个Context,这个Context用来管理一次请求过程中用到的相关上下文信息。
ControllerException + +
+           
ControllerManager + +
+          执行控制器的Action时,会为这个控制器生成一个ControllerManager, + 这个ControllerManager用来管理一次请求过程中用到的相关上下文信息。
ModelManager + +
+          未实现
ViewManager + +
+          输出视图模板文件,存放模板文件需要用到的参数。
+  +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/help-doc.html b/apidocs/help-doc.html new file mode 100644 index 0000000..f683138 --- /dev/null +++ b/apidocs/help-doc.html @@ -0,0 +1,224 @@ + + + + + + + +API 帮助 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+此 API 文档的组织方式

+
+此 API(应用程序编程接口)文档包含对应于导航栏中的项目的页面,如下所述。

+概述

+
+ +

+概述 页面是此 API 文档的首页,提供了所有软件包的列表及其摘要。此页面也可能包含这些软件包的总体描述。

+

+软件包

+
+ +

+每个软件包都有一个页面,其中包含它的类和接口的列表及其摘要。此页面可以包含四个类别:

+
+

+类/接口

+
+ +

+每个类、接口、嵌套类和嵌套接口都有各自的页面。其中每个页面都由三部分(类/接口描述、摘要表,以及详细的成员描述)组成:

+每个摘要条目都包含该项目的详细描述的第一句。摘要条目按字母顺序排列,而详细描述则按其在源代码中出现的顺序排列。这样保持了程序员所建立的逻辑分组。
+ +

+注释类型

+
+ +

+每个注释类型都有各自的页面,其中包含以下部分:

+
+ +

+枚举

+
+ +

+每个枚举都有各自的页面,其中包含以下部分:

+
+

+使用

+
+每个已文档化的软件包、类和接口都有各自的“使用”页面。此页面介绍了使用给定类或软件包的任何部分的软件包、类、方法、构造方法和字段。对于给定的类或接口 A,其“使用”页面包含 A 的子类、声明为 A 的字段、返回 A 的方法,以及带有类型为 A 的参数的方法和构造方法。访问此页面的方法是:首先转至软件包、类或接口,然后单击导航栏中的“使用”链接。
+

+树(类分层结构)

+
+对于所有软件包,有一个 类分层结构 页面,以及每个软件包的分层结构。每个分层结构页面都包含类的列表和接口的列表。从 java.lang.Object 开始,按继承结构对类进行排列。接口不从 java.lang.Object 继承。 +
+

+已过时的 API

+
+已过时的 API 页面列出了所有已过时的 API。一般由于进行了改进并且通常提供了替代的 API,所以建议不要使用已过时的 API。在将来的实施过程中,可能会删除已过时的 API。
+

+索引

+
+索引 包含按字母顺序排列的所有类、接口、构造方法、方法和字段的列表。
+

+上一个/下一个

+这些链接使您可以转至下一个或上一个类、接口、软件包或相关页面。

+框架/无框架

+这些链接用于显示和隐藏 HTML 框架。所有页面均具有有框架和无框架两种显示方式。 +

+

+序列化表格

+每个可序列化或可外部化的类都有其序列化字段和方法的描述。此信息对重新实现者有用,而对使用 API 的开发者则没有什么用处。尽管导航栏中没有链接,但您可以通过下列方式获取此信息:转至任何序列化类,然后单击类描述的“另请参见”部分中的“序列化表格”。 +

+

+常量字段值

+常量字段值页面列出了静态最终字段及其值。 +

+ + +此帮助文件适用于使用标准 doclet 生成的 API 文档。 + +
+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/index-all.html b/apidocs/index-all.html new file mode 100644 index 0000000..a072be9 --- /dev/null +++ b/apidocs/index-all.html @@ -0,0 +1,384 @@ + + + + + + + +索引 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +A C D E G H I M O P S U V W
+

+A

+
+
Action - douyu.mvc 中的 注释类型
 
Async - douyu.mvc 中的 注释类型
 
+
+

+C

+
+
close() - +接口 douyu.http.WebSocket.Outbound 中的方法 +
  +
Context - douyu.mvc 中的 接口
执行控制器的Action时,会为这个控制器生成一个Context,这个Context用来管理一次请求过程中用到的相关上下文信息。
Controller - douyu.mvc 中的 注释类型
 
ControllerException - douyu.mvc 中的 异常
 
ControllerException() - +异常 douyu.mvc.ControllerException 的构造方法 +
  +
ControllerException(String) - +异常 douyu.mvc.ControllerException 的构造方法 +
  +
ControllerException(Throwable) - +异常 douyu.mvc.ControllerException 的构造方法 +
  +
ControllerException(String, Throwable) - +异常 douyu.mvc.ControllerException 的构造方法 +
  +
ControllerManager - douyu.mvc 中的 接口
执行控制器的Action时,会为这个控制器生成一个ControllerManager, + 这个ControllerManager用来管理一次请求过程中用到的相关上下文信息。
+
+

+D

+
+
douyu.http - 软件包 douyu.http
提供与HTTP相关的接口
douyu.mvc - 软件包 douyu.mvc
提供与MVC相关的接口
+
+

+E

+
+
executeAction(String) - +接口 douyu.mvc.ControllerManager 中的方法 +
  +
+
+

+G

+
+
getActionName() - +接口 douyu.mvc.ControllerManager 中的方法 +
  +
getApplicationBase() - +接口 douyu.mvc.ControllerManager 中的方法 +
  +
getAttribute(String) - +接口 douyu.http.HttpRequest 中的方法 +
  +
getBytes() - +接口 douyu.http.UploadedFile 中的方法 +
  +
getContent() - +接口 douyu.http.UploadedFile 中的方法 +
  +
getContent(String) - +接口 douyu.http.UploadedFile 中的方法 +
  +
getContentType() - +接口 douyu.http.HttpResponse 中的方法 +
  +
getContentType() - +接口 douyu.http.UploadedFile 中的方法 +
  +
getControllerClassName() - +接口 douyu.mvc.ControllerManager 中的方法 +
  +
getFullName() - +接口 douyu.http.UploadedFile 中的方法 +
  +
getHttpRequest() - +接口 douyu.mvc.ControllerManager 中的方法 +
  +
getHttpResponse() - +接口 douyu.mvc.ControllerManager 中的方法 +
  +
getMethod() - +接口 douyu.http.HttpRequest 中的方法 +
  +
getParameter(String) - +接口 douyu.http.HttpRequest 中的方法 +
  +
getParameterValues(String) - +接口 douyu.http.HttpRequest 中的方法 +
  +
getPathName() - +接口 douyu.http.UploadedFile 中的方法 +
  +
getRequestURI() - +接口 douyu.http.HttpRequest 中的方法 +
  +
getSimpleName() - +接口 douyu.http.UploadedFile 中的方法 +
  +
getSize() - +接口 douyu.http.UploadedFile 中的方法 +
  +
getUploadedFile(String) - +接口 douyu.http.HttpRequest 中的方法 +
  +
getUploadedFiles() - +接口 douyu.http.HttpRequest 中的方法 +
  +
getViewManager(Context) - +接口 douyu.mvc.ViewManagerProvider 中的方法 +
  +
getWebSocket() - +接口 douyu.mvc.ControllerManager 中的方法 +
  +
getWriter() - +接口 douyu.http.HttpResponse 中的方法 +
  +
+
+

+H

+
+
HttpMethod - douyu.http 中的 枚举
 
HttpRequest - douyu.http 中的 接口
 
HttpResponse - douyu.http 中的 接口
 
+
+

+I

+
+
isOpen() - +接口 douyu.http.WebSocket.Outbound 中的方法 +
  +
+
+

+M

+
+
Model - douyu.mvc 中的 注释类型
 
ModelException - douyu.mvc 中的 异常
 
ModelException() - +异常 douyu.mvc.ModelException 的构造方法 +
  +
ModelException(String) - +异常 douyu.mvc.ModelException 的构造方法 +
  +
ModelException(Throwable) - +异常 douyu.mvc.ModelException 的构造方法 +
  +
ModelException(String, Throwable) - +异常 douyu.mvc.ModelException 的构造方法 +
  +
ModelManager - douyu.mvc 中的 接口
未实现
+
+

+O

+
+
onConnect(WebSocket.Outbound) - +接口 douyu.http.WebSocket 中的方法 +
  +
onDisconnect() - +接口 douyu.http.WebSocket 中的方法 +
  +
onMessage(int, String) - +接口 douyu.http.WebSocket 中的方法 +
  +
onMessage(int, byte[]) - +接口 douyu.http.WebSocket 中的方法 +
  +
out() - +接口 douyu.mvc.ViewManager 中的方法 +
输出默认视图,默认视图的位置取决于ViewManagerProvider,通常会根据控制器类名和Action名来决定 +
out(String) - +接口 douyu.mvc.ViewManager 中的方法 +
  +
+
+

+P

+
+
put(String, Object) - +接口 douyu.mvc.ViewManager 中的方法 +
  +
+
+

+S

+
+
saveTo(String) - +接口 douyu.http.UploadedFile 中的方法 +
  +
saveTo(File) - +接口 douyu.http.UploadedFile 中的方法 +
  +
send(String) - +接口 douyu.http.WebSocket.Outbound 中的方法 +
  +
send(int, String) - +接口 douyu.http.WebSocket.Outbound 中的方法 +
  +
send(int, byte[]) - +接口 douyu.http.WebSocket.Outbound 中的方法 +
  +
send(int, byte[], int, int) - +接口 douyu.http.WebSocket.Outbound 中的方法 +
  +
sendError(int, String) - +接口 douyu.http.HttpResponse 中的方法 +
  +
setCharacterEncoding(String) - +接口 douyu.http.HttpResponse 中的方法 +
  +
setContentType(String) - +接口 douyu.http.HttpResponse 中的方法 +
  +
setWebSocket(WebSocket) - +接口 douyu.mvc.ControllerManager 中的方法 +
  +
+
+

+U

+
+
UploadedFile - douyu.http 中的 接口
 
+
+

+V

+
+
valueOf(String) - +枚举 douyu.http.HttpMethod 中的静态方法 +
返回带有指定名称的该类型的枚举常量。 +
values() - +枚举 douyu.http.HttpMethod 中的静态方法 +
按照声明该枚举类型的常量的顺序,返回 +包含这些常量的数组。 +
ViewException - douyu.mvc 中的 异常
 
ViewException() - +异常 douyu.mvc.ViewException 的构造方法 +
  +
ViewException(String) - +异常 douyu.mvc.ViewException 的构造方法 +
  +
ViewException(Throwable) - +异常 douyu.mvc.ViewException 的构造方法 +
  +
ViewException(String, Throwable) - +异常 douyu.mvc.ViewException 的构造方法 +
  +
ViewManager - douyu.mvc 中的 接口
输出视图模板文件,存放模板文件需要用到的参数。
ViewManagerProvider - douyu.mvc 中的 接口
 
+
+

+W

+
+
WebSocket - douyu.http 中的 接口
 
WebSocket.Outbound - douyu.http 中的 接口
 
+
+A C D E G H I M O P S U V W + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/index.html b/apidocs/index.html new file mode 100644 index 0000000..97698a7 --- /dev/null +++ b/apidocs/index.html @@ -0,0 +1,40 @@ + + + + + + + +Douyu 0.7.1 API + + + + + + + + + + + +<H2> +框架警报</H2> + +<P> +请使用框架功能查看此文档。如果看到此消息,则表明您使用的是不支持框架的 Web 客户机。 +<BR> +链接到<A HREF="overview-summary.html">非框架版本。</A> + + + diff --git a/apidocs/overview-frame.html b/apidocs/overview-frame.html new file mode 100644 index 0000000..d603e07 --- /dev/null +++ b/apidocs/overview-frame.html @@ -0,0 +1,45 @@ + + + + + + + +概述列表 (Douyu 0.7.1 API) + + + + + + + + + + + + + + + +
+
+ + + + + +
所有类 +

+ +软件包 +
+douyu.http +
+douyu.mvc +
+

+ +

+  + + diff --git a/apidocs/overview-summary.html b/apidocs/overview-summary.html new file mode 100644 index 0000000..d2259d8 --- /dev/null +++ b/apidocs/overview-summary.html @@ -0,0 +1,161 @@ + + + + + + + +概述 (Douyu 0.7.1 API) + + + + + + + + + + + + +


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+Douyu 0.7.1 API Specification +

+
+ + + + + + + + + + + + + +
+软件包
douyu.http提供与HTTP相关的接口
douyu.mvc提供与MVC相关的接口
+ +


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/overview-tree.html b/apidocs/overview-tree.html new file mode 100644 index 0000000..61e2f75 --- /dev/null +++ b/apidocs/overview-tree.html @@ -0,0 +1,193 @@ + + + + + + + +类分层结构 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+所有软件包的分层结构

+
+
+
软件包分层结构:
douyu.http, douyu.mvc
+
+

+类分层结构 +

+ +

+接口分层结构 +

+ +

+注释类型分层结构 +

+ +

+枚举分层结构 +

+ +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/package-list b/apidocs/package-list new file mode 100644 index 0000000..2ac86c4 --- /dev/null +++ b/apidocs/package-list @@ -0,0 +1,2 @@ +douyu.http +douyu.mvc diff --git a/apidocs/resources/inherit.gif b/apidocs/resources/inherit.gif new file mode 100644 index 0000000..c814867 Binary files /dev/null and b/apidocs/resources/inherit.gif differ diff --git a/apidocs/serialized-form.html b/apidocs/serialized-form.html new file mode 100644 index 0000000..ccf20ac --- /dev/null +++ b/apidocs/serialized-form.html @@ -0,0 +1,195 @@ + + + + + + + +序列化表格 (Douyu 0.7.1 API) + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
+ + + +
+
+

+序列化表格

+
+
+ + + + + +
+软件包 douyu.mvc
+ +

+ + + + + +
+douyu.mvc.ControllerException 继承 java.lang.RuntimeException 实现 Serializable
+ +

+serialVersionUID: 1L + +

+ +

+ + + + + +
+douyu.mvc.ModelException 继承 java.lang.RuntimeException 实现 Serializable
+ +

+serialVersionUID: 1L + +

+ +

+ + + + + +
+douyu.mvc.ViewException 继承 java.lang.RuntimeException 实现 Serializable
+ +

+serialVersionUID: 1L + +

+ +

+


+ + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + diff --git a/apidocs/stylesheet.css b/apidocs/stylesheet.css new file mode 100644 index 0000000..a7fc7f1 --- /dev/null +++ b/apidocs/stylesheet.css @@ -0,0 +1,29 @@ +/* Javadoc 样式表 */ + +/* 在此处定义颜色、字体和其他样式属性以覆盖默认值 */ + +/* 页面背景颜色 */ +body { background-color: #FFFFFF; color:#000000 } + +/* 标题 */ +h1 { font-size: 145% } + +/* 表格颜色 */ +.TableHeadingColor { background: #CCCCFF; color:#000000 } /* 深紫色 */ +.TableSubHeadingColor { background: #EEEEFF; color:#000000 } /* 淡紫色 */ +.TableRowColor { background: #FFFFFF; color:#000000 } /* 白色 */ + +/* 左侧的框架列表中使用的字体 */ +.FrameTitleFont { font-size: 100%; font-family: Helvetica, Arial, sans-serif; color:#000000 } +.FrameHeadingFont { font-size: 90%; font-family: Helvetica, Arial, sans-serif; color:#000000 } +.FrameItemFont { font-size: 90%; font-family: Helvetica, Arial, sans-serif; color:#000000 } + +/* 导航栏字体和颜色 */ +.NavBarCell1 { background-color:#EEEEFF; color:#000000} /* 淡紫色 */ +.NavBarCell1Rev { background-color:#00008B; color:#FFFFFF} /* 深蓝色 */ +.NavBarFont1 { font-family: Arial, Helvetica, sans-serif; color:#000000;color:#000000;} +.NavBarFont1Rev { font-family: Arial, Helvetica, sans-serif; color:#FFFFFF;color:#FFFFFF;} + +.NavBarCell2 { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF; color:#000000} +.NavBarCell3 { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF; color:#000000} + diff --git a/assembly.bat b/assembly.bat new file mode 100644 index 0000000..d7cdabf --- /dev/null +++ b/assembly.bat @@ -0,0 +1,2 @@ +@echo off +call mvn clean package assembly:assembly -Dmaven.test.skip=true diff --git a/assembly.xml b/assembly.xml new file mode 100644 index 0000000..6ab688a --- /dev/null +++ b/assembly.xml @@ -0,0 +1,104 @@ + + + + + dir + + + disc + + + + + org.douyu:douyu-api + + + false + douyu-api + + + true + true + + + + + + + + org.douyu:douyu-javac + + + false + douyu-javac + + + true + true + + org.douyu:douyu-api + + + + + + + + + org.douyu:douyu-core + + + false + douyu-core + + + true + true + + org.douyu:douyu-javac + + + + + + + + + org.douyu:douyu-mvc + + + false + douyu-mvc + + + true + true + + org.douyu:douyu-core + + + + + + + + + org.douyu:douyu-plugins + + + false + douyu-plugins + + + true + true + + org.douyu:douyu-api + + + + + + + + diff --git a/douyu-ajp/pom.xml b/douyu-ajp/pom.xml new file mode 100644 index 0000000..3761d97 --- /dev/null +++ b/douyu-ajp/pom.xml @@ -0,0 +1,27 @@ + + 4.0.0 + + com.codefollower.douyu + douyu + 0.1.0 + + + douyu-ajp + jar + ${douyu.version} + douyu ajp + + + + com.codefollower.douyu + douyu-core + ${douyu.version} + + + com.codefollower.douyu + douyu-http + ${douyu.version} + + + diff --git a/douyu-ajp/src/main/java/com/codefollower/douyu/ajp/AjpMessage.java b/douyu-ajp/src/main/java/com/codefollower/douyu/ajp/AjpMessage.java new file mode 100644 index 0000000..81b59eb --- /dev/null +++ b/douyu-ajp/src/main/java/com/codefollower/douyu/ajp/AjpMessage.java @@ -0,0 +1,249 @@ +/* + * 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 com.codefollower.douyu.ajp; + + +import com.codefollower.douyu.core.StringManager; +import com.codefollower.douyu.logging.InternalLogger; +import com.codefollower.douyu.logging.InternalLoggerFactory; + +/** + * A single packet for communication between the web server and the + * container. Designed to be reused many times with no creation of + * garbage. Understands the format of data types for these packets. + * Can be used (somewhat confusingly) for both incoming and outgoing + * packets. + * + * @author Henri Gomez + * @author Dan Milstein + * @author Keith Wannamaker + * @author Kevin Seguin + * @author Costin Manolache + */ +public class AjpMessage { + + private static final InternalLogger log = InternalLoggerFactory.getInstance(AjpMessage.class); + + /** + * The string manager for this package. + */ + protected static final StringManager sm = StringManager.getManager(Constants.Package); + + // ------------------------------------------------------------ Constructor + + public AjpMessage(int packetSize) { + buf = new byte[packetSize]; + } + + // ----------------------------------------------------- Instance Variables + + /** + * Fixed size buffer. + */ + protected byte buf[] = null; + + /** + * The current read or write position in the buffer. + */ + protected int pos; + + /** + * This actually means different things depending on whether the + * packet is read or write. For read, it's the length of the + * payload (excluding the header). For write, it's the length of + * the packet as a whole (counting the header). Oh, well. + */ + protected int len; + + // --------------------------------------------------------- Public Methods + + /** + * Prepare this packet for accumulating a message from the container to + * the web server. Set the write position to just after the header + * (but leave the length unwritten, because it is as yet unknown). + */ + public void reset() { + len = 4; + pos = 4; + } + + /** + * For a packet to be sent to the web server, finish the process of + * accumulating data and write the length of the data payload into + * the header. + */ + public void end() { + len = pos; + int dLen = len - 4; + + buf[0] = (byte) 0x41; + buf[1] = (byte) 0x42; + buf[2] = (byte) ((dLen >>> 8) & 0xFF); + buf[3] = (byte) (dLen & 0xFF); + } + + /** + * Return the underlying byte buffer. + */ + public byte[] getBuffer() { + return buf; + } + + /** + * Return the current message length. For read, it's the length of the + * payload (excluding the header). For write, it's the length of + * the packet as a whole (counting the header). + */ + public int getLen() { + return len; + } + + /** + * Add a short integer (2 bytes) to the message. + */ + public void appendInt(int val) { + buf[pos++] = (byte) ((val >>> 8) & 0xFF); + buf[pos++] = (byte) (val & 0xFF); + } + + /** + * Append a byte (1 byte) to the message. + */ + public void appendByte(int val) { + buf[pos++] = (byte) val; + } + + + /** + * Write a String out at the current write position. Strings are + * encoded with the length in two bytes first, then the string, and + * then a terminating \0 (which is not included in the + * encoded length). The terminator is for the convenience of the C + * code, where it saves a round of copying. A null string is + * encoded as a string with length 0. + */ + public void appendString(String str) { + if (str == null) { + log.error(sm.getString("ajpmessage.null"), new NullPointerException()); + appendInt(0); + appendByte(0); + return; + } + int len = str.length(); + appendInt(len); + for (int i = 0; i < len; i++) { + char c = str.charAt(i); + // Note: This is clearly incorrect for many strings, + // but is the only consistent approach within the current + // servlet framework. It must suffice until servlet output + // streams properly encode their output. + if ((c <= 31) && (c != 9)) { + c = ' '; + } else if (c == 127) { + c = ' '; + } + appendByte(c); + } + appendByte(0); + } + + /** + * Copy a chunk of bytes into the packet, starting at the current + * write position. The chunk of bytes is encoded with the length + * in two bytes first, then the data itself, and finally a + * terminating \0 (which is not included in the encoded + * length). + * + * @param b The array from which to copy bytes. + * @param off The offset into the array at which to start copying + * @param numBytes The number of bytes to copy. + */ + public void appendBytes(byte[] b, int off, int numBytes) { + if (pos + numBytes + 3 > buf.length) { + log.error(sm.getString("ajpmessage.overflow", "" + numBytes, "" + pos), new ArrayIndexOutOfBoundsException()); + return; + } + appendInt(numBytes); + System.arraycopy(b, off, buf, pos, numBytes); + pos += numBytes; + appendByte(0); + } + + /** + * Read an integer from packet, and advance the read position past + * it. Integers are encoded as two unsigned bytes with the + * high-order byte first, and, as far as I can tell, in + * little-endian order within each byte. + */ + public int getInt() { + int b1 = buf[pos++] & 0xFF; + int b2 = buf[pos++] & 0xFF; + return (b1 << 8) + b2; + } + + public int peekInt() { + int b1 = buf[pos] & 0xFF; + int b2 = buf[pos + 1] & 0xFF; + return (b1 << 8) + b2; + } + + public byte getByte() { + byte res = buf[pos++]; + return res; + } + + /** + * Read a 32 bits integer from packet, and advance the read position past + * it. Integers are encoded as four unsigned bytes with the + * high-order byte first, and, as far as I can tell, in + * little-endian order within each byte. + */ + public int getLongInt() { + int b1 = buf[pos++] & 0xFF; // No swap, Java order + b1 <<= 8; + b1 |= (buf[pos++] & 0xFF); + b1 <<= 8; + b1 |= (buf[pos++] & 0xFF); + b1 <<= 8; + b1 |= (buf[pos++] & 0xFF); + return b1; + } + + public int getHeaderLength() { + return 4; + } + + public int getPacketSize() { + return buf.length; + } + + public int processHeader() { + pos = 0; + int mark = getInt(); + len = getInt(); + // Verify message signature + if ((mark != 0x1234) && (mark != 0x4142)) { + log.error(sm.getString("ajpmessage.invalid", "" + mark)); + return -1; + } + if (log.isDebugEnabled()) { + log.debug("Received " + len + " " + buf[0]); + } + return len; + } +} diff --git a/douyu-ajp/src/main/java/com/codefollower/douyu/ajp/AjpRequestDecoder.java b/douyu-ajp/src/main/java/com/codefollower/douyu/ajp/AjpRequestDecoder.java new file mode 100644 index 0000000..7e8a9f2 --- /dev/null +++ b/douyu-ajp/src/main/java/com/codefollower/douyu/ajp/AjpRequestDecoder.java @@ -0,0 +1,473 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 com.codefollower.douyu.ajp; + +import java.nio.charset.Charset; + + +import com.codefollower.douyu.http.DouyuHttpRequest; +import com.codefollower.douyu.http.HttpHeaders; +import com.codefollower.douyu.http.HttpMethod; +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; +import com.codefollower.douyu.netty.channel.Channel; +import com.codefollower.douyu.netty.channel.ChannelHandlerContext; +import com.codefollower.douyu.netty.handler.codec.frame.FrameDecoder; + +public class AjpRequestDecoder extends FrameDecoder { + + /** + * The Request attribute key for the cipher suite. + */ + public static final String CIPHER_SUITE_KEY = "javax.servlet.request.cipher_suite"; + + /** + * The Request attribute key for the key size. + */ + public static final String KEY_SIZE_KEY = "javax.servlet.request.key_size"; + + /** + * The Request attribute key for the client certificate chain. + */ + public static final String CERTIFICATE_KEY = "javax.servlet.request.X509Certificate"; + + /** + * The Request attribute key for the session id. This one is a Tomcat + * extension to the Servlet spec. + */ + public static final String SESSION_ID_KEY = "javax.servlet.request.ssl_session"; + + /** + * The request attribute key for the session manager. This one is a Tomcat + * extension to the Servlet spec. + */ + public static final String SESSION_MGR = "javax.servlet.request.ssl_session_mgr"; + + /** + * End message array. + */ + protected static final byte[] endMessageArray; + protected static final byte[] endAndCloseMessageArray; + + /** + * Flush message array. + */ + protected static final byte[] flushMessageArray; + + /** + * Pong message array. + */ + protected static final byte[] pongMessageArray; + + /** + * GetBody message array. Not static like the other message arrays since the + * message varies with packetSize and that can vary per connector. + */ + protected static final byte[] getBodyMessageArray; + /** + * AJP packet size. + */ + public static int packetSize = 8192; // 8k + public static Charset UTF8 = Charset.forName("UTF-8"); + static { + // Allocate the end message array + AjpMessage endMessage = new AjpMessage(16); + endMessage.reset(); + endMessage.appendByte(Constants.JK_AJP13_END_RESPONSE); + endMessage.appendByte(1); + endMessage.end(); + endMessageArray = new byte[endMessage.getLen()]; + System.arraycopy(endMessage.getBuffer(), 0, endMessageArray, 0, endMessage.getLen()); + + // Allocate the end and close message array + AjpMessage endAndCloseMessage = new AjpMessage(16); + endAndCloseMessage.reset(); + endAndCloseMessage.appendByte(Constants.JK_AJP13_END_RESPONSE); + endAndCloseMessage.appendByte(0); + endAndCloseMessage.end(); + endAndCloseMessageArray = new byte[endAndCloseMessage.getLen()]; + System.arraycopy(endAndCloseMessage.getBuffer(), 0, endAndCloseMessageArray, 0, endAndCloseMessage.getLen()); + + // Allocate the flush message array + AjpMessage flushMessage = new AjpMessage(16); + flushMessage.reset(); + flushMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK); + flushMessage.appendInt(0); + flushMessage.appendByte(0); + flushMessage.end(); + flushMessageArray = new byte[flushMessage.getLen()]; + System.arraycopy(flushMessage.getBuffer(), 0, flushMessageArray, 0, flushMessage.getLen()); + + // Allocate the pong message array + AjpMessage pongMessage = new AjpMessage(16); + pongMessage.reset(); + pongMessage.appendByte(Constants.JK_AJP13_CPONG_REPLY); + pongMessage.end(); + pongMessageArray = new byte[pongMessage.getLen()]; + System.arraycopy(pongMessage.getBuffer(), 0, pongMessageArray, 0, pongMessage.getLen()); + + // Set the getBody message buffer + AjpMessage getBodyMessage = new AjpMessage(16); + getBodyMessage.reset(); + getBodyMessage.appendByte(Constants.JK_AJP13_GET_BODY_CHUNK); + // Adjust read size if packetSize != default (Constants.MAX_PACKET_SIZE) + getBodyMessage.appendInt(Constants.MAX_READ_SIZE + packetSize - Constants.MAX_PACKET_SIZE); + getBodyMessage.end(); + getBodyMessageArray = new byte[getBodyMessage.getLen()]; + System.arraycopy(getBodyMessage.getBuffer(), 0, getBodyMessageArray, 0, getBodyMessage.getLen()); + } + + protected static enum State { + READ_REQUEST_HEADER_MESSAGE, // + READ_FIRST_BODY_MESSAGE, // + READ_MORE_BODY_MESSAGE, // + READ_END, // + READ_END_AND_CLOSE, + } + + private boolean useFrontEndServerAuthentication = false; + private String requiredSecret; + protected ChannelBuffer certificates; + + private int contentLength = -1; + private DouyuHttpRequest request; + private State state = State.READ_REQUEST_HEADER_MESSAGE; + + public AjpRequestDecoder() { + } + + public String readCString(ChannelBuffer buffer) { + int length = readShort(buffer); + if ((length == 0xFFFF) || (length == -1)) { + return ""; + } + + String str = buffer.readBytes(length).toString(UTF8); + buffer.readByte(); // Skip the terminating \0 + return str; + } + + public int readShort(ChannelBuffer buffer) { + int b1 = buffer.readByte() & 0xFF; + int b2 = buffer.readByte() & 0xFF; + return (b1 << 8) + b2; + + // return buffer.readShort(); + } + + private Object reset() { + DouyuHttpRequest request = this.request; + this.request = null; + this.state = State.READ_REQUEST_HEADER_MESSAGE; + certificates = null; + contentLength = -1; + return request; + } + + protected void prepareRequest(ChannelBuffer buffer) { + request = new DouyuHttpRequest(); + // Translate the HTTP method code to a String. + byte methodCode = buffer.readByte(); + if (methodCode != Constants.SC_M_JK_STORED) { + String methodName = Constants.getMethodForCode(methodCode - 1); + + request.setMethod(HttpMethod.valueOf(methodName)); + } + + request.setProtocol(readCString(buffer)); + request.setRequestURI(readCString(buffer)); + request.setRemoteAddr(readCString(buffer)); + request.setRemoteHost(readCString(buffer)); + request.setLocalName(readCString(buffer)); + request.setLocalPort(readShort(buffer)); + request.setSSL(buffer.readByte() != 0); + + int hCount = readShort(buffer); + for (int i = 0; i < hCount; i++) { + String hName = null; + + // Header names are encoded as either an integer code starting + // with 0xA0, or as a normal string (in which case the first + // two bytes are the length). + int isc = buffer.getShort(buffer.readerIndex()); + int hId = isc & 0xFF; + + String hValue = null; + isc &= 0xFF00; + if (0xA000 == isc) { + readShort(buffer); // To advance the read position + hName = Constants.getHeaderForCode(hId - 1); + } else { + // reset hId -- if the header currently being read + // happens to be 7 or 8 bytes long, the code below + // will think it's the content-type header or the + // content-length header - SC_REQ_CONTENT_TYPE=7, + // SC_REQ_CONTENT_LENGTH=8 - leading to unexpected + // behaviour. see bug 5861 for more information. + hId = -1; + hName = readCString(buffer); + } + + hValue = readCString(buffer); + + if (hId == Constants.SC_REQ_CONTENT_LENGTH || (hId == -1 && hName.equalsIgnoreCase("Content-Length"))) { + // just read the content-length header, so set it + try { + long cl = Long.parseLong(hValue); + if (cl < Integer.MAX_VALUE) { + contentLength = (int) cl; + request.setContentLength(contentLength); + } + } catch (NumberFormatException e) { + // TODO + } + } else if (hId == Constants.SC_REQ_CONTENT_TYPE || (hId == -1 && hName.equalsIgnoreCase("Content-Type"))) { + // just read the content-type header, so set it + request.setContentType(hValue); + } + request.addHeader(hName, hValue); + System.out.println(hName + " : " + hValue); + } + + parseAttributes(buffer); + + } + + protected void parseCertificates(ChannelBuffer buffer) { + int length = readShort(buffer); + if ((length == 0xFFFF) || (length == -1)) { + return; + } + + certificates = buffer.copy(buffer.readerIndex(), length); + buffer.readByte(); // Skip the terminating \0 + } + + protected void parseAttributes(ChannelBuffer buffer) { + // Decode extra attributes + boolean secret = false; + byte attributeCode; + while ((attributeCode = buffer.readByte()) != Constants.SC_A_ARE_DONE) { + + switch (attributeCode) { + + case Constants.SC_A_REQ_ATTRIBUTE: + String n = readCString(buffer); + String v = readCString(buffer); + /* + * AJP13 misses to forward the remotePort. Allow the AJP + * connector to add this info via a private request attribute. + * We will accept the forwarded data as the remote port, and + * remove it from the public list of request attributes. + */ + if (n.equals(Constants.SC_A_REQ_REMOTE_PORT)) { + try { + request.setRemotePort(Integer.parseInt(v)); + } catch (NumberFormatException nfe) { + // Ignore invalid value + } + } else { + request.setAttribute(n, v); + } + break; + + case Constants.SC_A_CONTEXT: + readCString(buffer); + // nothing + break; + + case Constants.SC_A_SERVLET_PATH: + readCString(buffer); + // nothing + break; + + case Constants.SC_A_REMOTE_USER: + if (useFrontEndServerAuthentication) { + request.setRemoteUser(readCString(buffer)); + } else { + // ignore server + readCString(buffer); + } + break; + + case Constants.SC_A_AUTH_TYPE: + if (useFrontEndServerAuthentication) { + request.setAuthType(readCString(buffer)); + } else { + // ignore server + readCString(buffer); + } + break; + + case Constants.SC_A_QUERY_STRING: + request.setQueryString(readCString(buffer)); + break; + + case Constants.SC_A_JVM_ROUTE: + request.setInstanceId(readCString(buffer)); + break; + + case Constants.SC_A_SSL_CERT: + request.setScheme("https"); + // SSL certificate extraction is lazy + parseCertificates(buffer); + break; + + case Constants.SC_A_SSL_CIPHER: + request.setScheme("https"); + request.setAttribute(CIPHER_SUITE_KEY, readCString(buffer)); + break; + + case Constants.SC_A_SSL_SESSION: + request.setScheme("https"); + request.setAttribute(SESSION_ID_KEY, readCString(buffer)); + break; + + case Constants.SC_A_SSL_KEY_SIZE: + request.setAttribute(KEY_SIZE_KEY, readShort(buffer)); + break; + + case Constants.SC_A_SECRET: + String secretStr = readCString(buffer); + if (requiredSecret != null) { + secret = true; + if (!secretStr.equals(requiredSecret)) { + //TODO 响应403并记日志 + } + } + break; + + case Constants.SC_A_STORED_METHOD: + request.setMethod(HttpMethod.valueOf(readCString(buffer))); + break; + + default: + // Ignore unknown attribute for backward compatibility + break; + + } + } + + // Check if secret was submitted if required + if ((requiredSecret != null) && !secret) { + //TODO 响应403并记日 + } + } + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception { + if (buffer.readableBytes() < 4) { + return null; + } + + buffer.markReaderIndex(); + int mark = readShort(buffer); + //if ((mark != 0x1234) && (mark != 0x4142)) { + if (mark != 0x1234) { + throw new IllegalStateException("Unexpected mark = " + mark); + } + + int length = readShort(buffer); + if (buffer.readableBytes() < length) { + buffer.resetReaderIndex(); + return null; + } + + switch (state) { + case READ_REQUEST_HEADER_MESSAGE: { + int type = buffer.readByte(); + if (type == Constants.JK_AJP13_CPING_REQUEST) { + channel.write(pongMessageArray); + return null; + } else if (type != Constants.JK_AJP13_FORWARD_REQUEST) { + return null; + } + + prepareRequest(buffer); + + if (contentLength == 0) { + return reset(); + } else if (contentLength > 0) { + state = State.READ_FIRST_BODY_MESSAGE; + } else { + channel.write(ChannelBuffers.wrappedBuffer(getBodyMessageArray)); + state = State.READ_MORE_BODY_MESSAGE; + } + + request.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING); + + return null; + } + + case READ_FIRST_BODY_MESSAGE: + length = readShort(buffer); + request.setContent(buffer.readBytes(length)); + contentLength -= length; + + //buffer.readByte(); //请求体不会在末尾加\0 + + if (contentLength == 0) { + return reset(); + } else if (contentLength > 0) { + channel.write(ChannelBuffers.wrappedBuffer(getBodyMessageArray)); + state = State.READ_MORE_BODY_MESSAGE; + return null; + } else { + throw new IllegalArgumentException("body message length greater than max context length" + "(" + (-contentLength) + + ")"); + } + case READ_MORE_BODY_MESSAGE: + if (length == 0) { + return reset(); + } + + length = readShort(buffer); + + if (request.getContent() == null) + request.setContent(buffer.readBytes(length)); + else + request.setContent(ChannelBuffers.wrappedBuffer(request.getContent(), buffer.readBytes(length))); + + //buffer.readByte(); //请求体不会在末尾加\0 + + //chunked一般没有Content-Length请求头 + if (contentLength < 0) { + channel.write(ChannelBuffers.wrappedBuffer(getBodyMessageArray)); + state = State.READ_MORE_BODY_MESSAGE; + return null; + } + + contentLength -= length; + + if (contentLength == 0) { + return reset(); + } else if (contentLength > 0) { + channel.write(ChannelBuffers.wrappedBuffer(getBodyMessageArray)); + state = State.READ_MORE_BODY_MESSAGE; + return null; + } else { + throw new IllegalArgumentException("body message length greater than max context length" + "(" + (-contentLength) + + ")"); + } + } + + return null; + } +} diff --git a/douyu-ajp/src/main/java/com/codefollower/douyu/ajp/AjpResponseEncoder.java b/douyu-ajp/src/main/java/com/codefollower/douyu/ajp/AjpResponseEncoder.java new file mode 100644 index 0000000..c5b1a0f --- /dev/null +++ b/douyu-ajp/src/main/java/com/codefollower/douyu/ajp/AjpResponseEncoder.java @@ -0,0 +1,148 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 com.codefollower.douyu.ajp; + +import java.util.Map; + + +import com.codefollower.douyu.http.HttpChunk; +import com.codefollower.douyu.http.HttpHeaders; +import com.codefollower.douyu.http.HttpResponse; +import com.codefollower.douyu.http.util.HttpMessages; +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; +import com.codefollower.douyu.netty.channel.ChannelDownstreamHandler; +import com.codefollower.douyu.netty.channel.ChannelEvent; +import com.codefollower.douyu.netty.channel.ChannelHandlerContext; +import com.codefollower.douyu.netty.channel.Channels; +import com.codefollower.douyu.netty.channel.MessageEvent; + +public class AjpResponseEncoder implements ChannelDownstreamHandler { + /** + * If true, custom HTTP status messages will be used in headers. + */ + public static final boolean USE_CUSTOM_STATUS_MSG_IN_HEADER = Boolean.valueOf( + System.getProperty("org.douyu.ajp.USE_CUSTOM_STATUS_MSG_IN_HEADER", "false")).booleanValue(); + + private AjpMessage responsMessage = null; + private int packetSize = 8192; + + public AjpResponseEncoder() { + responsMessage = new AjpMessage(packetSize); + } + + @Override + public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent evt) throws Exception { + if (!(evt instanceof MessageEvent)) { + ctx.sendDownstream(evt); + return; + } + + MessageEvent e = (MessageEvent) evt; + Object msg = e.getMessage(); + + if (msg instanceof HttpResponse) { + HttpResponse response = (HttpResponse) msg; + writeHeaders(ctx, e, response); + writeContent(ctx, e, response.getContent(), true); + } else if (msg instanceof HttpChunk) { + HttpChunk chunk = (HttpChunk) msg; + writeContent(ctx, e, chunk.getContent(), chunk.isLast()); + } else { + ctx.sendDownstream(evt); + } + } + + private void writeHeaders(ChannelHandlerContext ctx, MessageEvent e, HttpResponse response) { + responsMessage.reset(); + responsMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS); + + // HTTP header contents + responsMessage.appendInt(response.getStatus().getCode()); + String message = null; + if (USE_CUSTOM_STATUS_MSG_IN_HEADER && HttpMessages.isSafeInHttpHeader(response.getStatus().getReasonPhrase())) { + message = response.getStatus().getReasonPhrase(); + } + if (message == null) { + message = HttpMessages.getMessage(response.getStatus().getCode()); + } + if (message == null) { + // mod_jk + httpd 2.x fails with a null status message - bug 45026 + message = Integer.toString(response.getStatus().getCode()); + } + responsMessage.appendString(message); + + // Special headers + if(!response.isChunked() && !response.containsHeader(HttpHeaders.Names.CONTENT_LENGTH)) { + response.addHeader(HttpHeaders.Names.CONTENT_LENGTH, response.getContent().readableBytes()); + } + + // Other headers + int numHeaders = response.getHeaders().size(); + responsMessage.appendInt(numHeaders); + + for (Map.Entry entry : response.getHeaders()) { + String hN = entry.getKey(); + int hC = Constants.getResponseAjpIndex(hN.toString()); + if (hC > 0) { + responsMessage.appendInt(hC); + } else { + responsMessage.appendString(hN); + } + responsMessage.appendString(entry.getValue()); + } + responsMessage.end(); + write(ctx, e, ChannelBuffers.wrappedBuffer(responsMessage.getBuffer(), 0, responsMessage.getLen())); + } + + private void writeContent(ChannelHandlerContext ctx, MessageEvent e, ChannelBuffer content, boolean endMessage) { + if (content != null) { + int len = content.readableBytes(); + // 4 - hardcoded, byte[] marshaling overhead + // Adjust allowed size if packetSize != default (Constants.MAX_PACKET_SIZE) + int chunkSize = Constants.MAX_SEND_SIZE + packetSize - Constants.MAX_PACKET_SIZE; + int off = 0; + while (len > 0) { + int thisTime = len; + if (thisTime > chunkSize) { + thisTime = chunkSize; + } + len -= thisTime; + responsMessage.reset(); + responsMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK); + byte[] bytes = new byte[thisTime]; + content.getBytes(content.readerIndex(), bytes); + responsMessage.appendBytes(bytes, 0, thisTime); + responsMessage.end(); + write(ctx, e, ChannelBuffers.wrappedBuffer(responsMessage.getBuffer(), 0, responsMessage.getLen())); + + off += thisTime; + } + + if (endMessage) + write(ctx, e, ChannelBuffers.wrappedBuffer(AjpRequestDecoder.endMessageArray, 0, + AjpRequestDecoder.endMessageArray.length)); + } + } + + private void write(ChannelHandlerContext ctx, MessageEvent e, Object msg) { + Channels.write(ctx, e.getFuture(), msg, e.getRemoteAddress()); + } +} diff --git a/douyu-ajp/src/main/java/com/codefollower/douyu/ajp/Constants.java b/douyu-ajp/src/main/java/com/codefollower/douyu/ajp/Constants.java new file mode 100644 index 0000000..83af8c4 --- /dev/null +++ b/douyu-ajp/src/main/java/com/codefollower/douyu/ajp/Constants.java @@ -0,0 +1,260 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 com.codefollower.douyu.ajp; + +import java.util.Hashtable; + +//import org.apache.tomcat.util.buf.ByteChunk; + + +/** + * Constants. + * + * @author Remy Maucherat + */ +public final class Constants { + + + // -------------------------------------------------------------- Constants + + + /** + * Package name. + */ + public static final String Package = "org.douyu.ajp"; + + public static final int DEFAULT_CONNECTION_LINGER = -1; + public static final int DEFAULT_CONNECTION_TIMEOUT = -1; + public static final int DEFAULT_CONNECTION_UPLOAD_TIMEOUT = 300000; + public static final boolean DEFAULT_TCP_NO_DELAY = true; + public static final boolean DEFAULT_USE_SENDFILE = false; + + // Prefix codes for message types from server to container + public static final byte JK_AJP13_FORWARD_REQUEST = 2; + public static final byte JK_AJP13_SHUTDOWN = 7; + public static final byte JK_AJP13_PING_REQUEST = 8; + public static final byte JK_AJP13_CPING_REQUEST = 10; + + // Prefix codes for message types from container to server + public static final byte JK_AJP13_SEND_BODY_CHUNK = 3; + public static final byte JK_AJP13_SEND_HEADERS = 4; + public static final byte JK_AJP13_END_RESPONSE = 5; + public static final byte JK_AJP13_GET_BODY_CHUNK = 6; + public static final byte JK_AJP13_CPONG_REPLY = 9; + + // Integer codes for common response header strings + public static final int SC_RESP_CONTENT_TYPE = 0xA001; + public static final int SC_RESP_CONTENT_LANGUAGE = 0xA002; + public static final int SC_RESP_CONTENT_LENGTH = 0xA003; + public static final int SC_RESP_DATE = 0xA004; + public static final int SC_RESP_LAST_MODIFIED = 0xA005; + public static final int SC_RESP_LOCATION = 0xA006; + public static final int SC_RESP_SET_COOKIE = 0xA007; + public static final int SC_RESP_SET_COOKIE2 = 0xA008; + public static final int SC_RESP_SERVLET_ENGINE = 0xA009; + public static final int SC_RESP_STATUS = 0xA00A; + public static final int SC_RESP_WWW_AUTHENTICATE = 0xA00B; + public static final int SC_RESP_AJP13_MAX = 11; + + // Integer codes for common (optional) request attribute names + public static final byte SC_A_CONTEXT = 1; // XXX Unused + public static final byte SC_A_SERVLET_PATH = 2; // XXX Unused + public static final byte SC_A_REMOTE_USER = 3; + public static final byte SC_A_AUTH_TYPE = 4; + public static final byte SC_A_QUERY_STRING = 5; + public static final byte SC_A_JVM_ROUTE = 6; + public static final byte SC_A_SSL_CERT = 7; + public static final byte SC_A_SSL_CIPHER = 8; + public static final byte SC_A_SSL_SESSION = 9; + public static final byte SC_A_SSL_KEYSIZE = 11; + public static final byte SC_A_SECRET = 12; + public static final byte SC_A_STORED_METHOD = 13; + + // Used for attributes which are not in the list above + public static final byte SC_A_REQ_ATTRIBUTE = 10; + + /** + * AJP private request attributes + */ + public static final String SC_A_REQ_REMOTE_PORT = "AJP_REMOTE_PORT"; + + // Terminates list of attributes + public static final byte SC_A_ARE_DONE = (byte)0xFF; + + // Ajp13 specific - needs refactoring for the new model + + /** + * Default maximum total byte size for a AJP packet + */ + public static final int MAX_PACKET_SIZE = 8192; + /** + * Size of basic packet header + */ + public static final int H_SIZE = 4; + + /** + * Size of the header metadata + */ + public static final int READ_HEAD_LEN = 6; + public static final int SEND_HEAD_LEN = 8; + + /** + * Default maximum size of data that can be sent in one packet + */ + public static final int MAX_READ_SIZE = MAX_PACKET_SIZE - READ_HEAD_LEN; + public static final int MAX_SEND_SIZE = MAX_PACKET_SIZE - SEND_HEAD_LEN; + + // Translates integer codes to names of HTTP methods + private static final String [] methodTransArray = { + "OPTIONS", + "GET", + "HEAD", + "POST", + "PUT", + "DELETE", + "TRACE", + "PROPFIND", + "PROPPATCH", + "MKCOL", + "COPY", + "MOVE", + "LOCK", + "UNLOCK", + "ACL", + "REPORT", + "VERSION-CONTROL", + "CHECKIN", + "CHECKOUT", + "UNCHECKOUT", + "SEARCH", + "MKWORKSPACE", + "UPDATE", + "LABEL", + "MERGE", + "BASELINE-CONTROL", + "MKACTIVITY" + }; + + /** + * Converts an AJP coded HTTP method to the method name. + * @param code the coded value + * @return the string value of the method + */ + public static final String getMethodForCode(final int code) { + return methodTransArray[code]; + } + + public static final int SC_M_JK_STORED = (byte) 0xFF; + + // id's for common request headers + public static final int SC_REQ_ACCEPT = 1; + public static final int SC_REQ_ACCEPT_CHARSET = 2; + public static final int SC_REQ_ACCEPT_ENCODING = 3; + public static final int SC_REQ_ACCEPT_LANGUAGE = 4; + public static final int SC_REQ_AUTHORIZATION = 5; + public static final int SC_REQ_CONNECTION = 6; + public static final int SC_REQ_CONTENT_TYPE = 7; + public static final int SC_REQ_CONTENT_LENGTH = 8; + public static final int SC_REQ_COOKIE = 9; + public static final int SC_REQ_COOKIE2 = 10; + public static final int SC_REQ_HOST = 11; + public static final int SC_REQ_PRAGMA = 12; + public static final int SC_REQ_REFERER = 13; + public static final int SC_REQ_USER_AGENT = 14; + // AJP14 new header + public static final byte SC_A_SSL_KEY_SIZE = 11; // XXX ??? + + // Translates integer codes to request header names + private static final String [] headerTransArray = { + "accept", + "accept-charset", + "accept-encoding", + "accept-language", + "authorization", + "connection", + "content-type", + "content-length", + "cookie", + "cookie2", + "host", + "pragma", + "referer", + "user-agent" + }; + + /** + * Converts an AJP coded HTTP request header to the header name. + * @param code the coded value + * @return the string value of the header name + */ + public static final String getHeaderForCode(final int code) { + return headerTransArray[code]; + } + + // Translates integer codes to response header names + private static final String [] responseTransArray = { + "Content-Type", + "Content-Language", + "Content-Length", + "Date", + "Last-Modified", + "Location", + "Set-Cookie", + "Set-Cookie2", + "Servlet-Engine", + "Status", + "WWW-Authenticate" + }; + + /** + * Converts an AJP coded response header name to the HTTP response header name. + * @param code the coded value + * @return the string value of the header + */ + public static final String getResponseHeaderForCode(final int code) { + return responseTransArray[code]; + } + + private static final Hashtable responseTransHash = + new Hashtable(20); + + static { + try { + int i; + for (i = 0; i < SC_RESP_AJP13_MAX; i++) { + responseTransHash.put(getResponseHeaderForCode(i), + Integer.valueOf(0xA001 + i)); + } + } + catch (Exception e) { + // Do nothing + } + } + + public static final int getResponseAjpIndex(String header) + { + Integer i = responseTransHash.get(header); + if (i == null) + return 0; + else + return i.intValue(); + } + +} diff --git a/douyu-api/pom.xml b/douyu-api/pom.xml new file mode 100644 index 0000000..e8b9c32 --- /dev/null +++ b/douyu-api/pom.xml @@ -0,0 +1,14 @@ + + 4.0.0 + + com.codefollower.douyu + douyu + 0.1.0 + + + douyu-api + jar + ${douyu.version} + douyu api + diff --git a/douyu-api/src/main/java/douyu/http/Comet.java b/douyu-api/src/main/java/douyu/http/Comet.java new file mode 100644 index 0000000..cb33e33 --- /dev/null +++ b/douyu-api/src/main/java/douyu/http/Comet.java @@ -0,0 +1,51 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 douyu.http; + +import java.io.IOException; + +import douyu.mvc.Context; + +public interface Comet { + void onConnect(Context context); + + //void onRead(Context context, String data); + + void onMessage(String data); + + //void onMessage(Context context, byte[] data); + + void onError(); + + void onDisconnect(); + + + public interface Outbound { + void send(String data) throws IOException; + + void send(byte[] data) throws IOException; + + void send(byte[] data, int offset, int length) throws IOException; + + void close(); + + boolean isOpen(); + } +} diff --git a/douyu-api/src/main/java/douyu/http/HttpMethod.java b/douyu-api/src/main/java/douyu/http/HttpMethod.java new file mode 100644 index 0000000..daa29f5 --- /dev/null +++ b/douyu-api/src/main/java/douyu/http/HttpMethod.java @@ -0,0 +1,24 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 douyu.http; + +public enum HttpMethod { + GET, POST, HEAD, OPTIONS, PUT, TRACE, DELETE; +} diff --git a/douyu-api/src/main/java/douyu/http/HttpRequest.java b/douyu-api/src/main/java/douyu/http/HttpRequest.java new file mode 100644 index 0000000..298f316 --- /dev/null +++ b/douyu-api/src/main/java/douyu/http/HttpRequest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 douyu.http; + +public interface HttpRequest { + + public HttpMethod getHttpMethod(); + + public String getProtocol(); + + public String getParameter(String name); + + public String[] getParameterValues(String name); + + public UploadedFile getUploadedFile(String name); + + public UploadedFile[] getUploadedFiles(); + + public Object getAttribute(String name); + public void setAttribute(String name, Object value); + + public String getRequestURI(); + +} diff --git a/douyu-api/src/main/java/douyu/http/HttpResponse.java b/douyu-api/src/main/java/douyu/http/HttpResponse.java new file mode 100644 index 0000000..3152d12 --- /dev/null +++ b/douyu-api/src/main/java/douyu/http/HttpResponse.java @@ -0,0 +1,35 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 douyu.http; + +import java.io.PrintWriter; + +public interface HttpResponse { + + public String getContentType(); + + public void setContentType(String contentType); + + public PrintWriter getWriter() throws Exception; + + public void setCharacterEncoding(String charset); + + public void sendError(int status, String message); +} diff --git a/douyu-api/src/main/java/douyu/http/UploadedFile.java b/douyu-api/src/main/java/douyu/http/UploadedFile.java new file mode 100644 index 0000000..b04d47d --- /dev/null +++ b/douyu-api/src/main/java/douyu/http/UploadedFile.java @@ -0,0 +1,44 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 douyu.http; + +import java.io.File; + +public interface UploadedFile { + public long getSize(); + + public String getContentType(); + + public String getSimpleName(); + + public String getFullName(); + + public String getPathName(); + + public byte[] getBytes(); + + public String getContent(); + + public String getContent(String encoding); + + public void saveTo(String file) throws Exception; + + public void saveTo(File file) throws Exception; +} \ No newline at end of file diff --git a/douyu-api/src/main/java/douyu/http/WebSocket.java b/douyu-api/src/main/java/douyu/http/WebSocket.java new file mode 100644 index 0000000..4abb11b --- /dev/null +++ b/douyu-api/src/main/java/douyu/http/WebSocket.java @@ -0,0 +1,46 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 douyu.http; + +import java.io.IOException; + +public interface WebSocket { + void onConnect(Outbound outbound); + + void onMessage(int type, String data); + + void onMessage(int type, byte[] data); + + void onDisconnect(); + + public interface Outbound { + void send(String data) throws IOException; + + void send(int type, String data) throws IOException; + + void send(int type, byte[] data) throws IOException; + + void send(int type, byte[] data, int offset, int length) throws IOException; + + void close(); + + boolean isOpen(); + } +} diff --git a/douyu-api/src/main/java/douyu/http/package-info.java b/douyu-api/src/main/java/douyu/http/package-info.java new file mode 100644 index 0000000..f7731a0 --- /dev/null +++ b/douyu-api/src/main/java/douyu/http/package-info.java @@ -0,0 +1,8 @@ +/** + * 提供与HTTP相关的接口 + * + * + * @author ZHH + * @since 0.6.1 + */ +package douyu.http; diff --git a/douyu-api/src/main/java/douyu/mvc/Action.java b/douyu-api/src/main/java/douyu/mvc/Action.java new file mode 100644 index 0000000..d05bd5d --- /dev/null +++ b/douyu-api/src/main/java/douyu/mvc/Action.java @@ -0,0 +1,39 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 douyu.mvc; + +import java.lang.annotation.*; + +import douyu.http.HttpMethod; +import static java.lang.annotation.RetentionPolicy.*; +import static java.lang.annotation.ElementType.*; + +@Retention(RUNTIME) +@Target(METHOD) +public @interface Action { + //int value() default -1; //同maxConcurrentRequests + int maxConcurrentRequests() default -1; + //boolean isAsync() default false; + + /** + * Action所能接受的HTTT请求方法类型 + */ + HttpMethod[] httpMethods() default { HttpMethod.GET, HttpMethod.POST }; +} diff --git a/douyu-api/src/main/java/douyu/mvc/Async.java b/douyu-api/src/main/java/douyu/mvc/Async.java new file mode 100644 index 0000000..e44c65e --- /dev/null +++ b/douyu-api/src/main/java/douyu/mvc/Async.java @@ -0,0 +1,32 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 douyu.mvc; + +import static java.lang.annotation.RetentionPolicy.*; +import static java.lang.annotation.ElementType.*; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target( { METHOD, TYPE }) +public @interface Async { + int maxConcurrentRequests() default -1; +} diff --git a/douyu-api/src/main/java/douyu/mvc/Context.java b/douyu-api/src/main/java/douyu/mvc/Context.java new file mode 100644 index 0000000..b16862b --- /dev/null +++ b/douyu-api/src/main/java/douyu/mvc/Context.java @@ -0,0 +1,33 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 douyu.mvc; + +/** + * + * 执行控制器的Action时,会为这个控制器生成一个Context,这个Context用来管理一次请求过程中用到的相关上下文信息。 + * + * 此类不是线程安全的,存活期与HttpRequest相同。 + * + * @author ZHH + * @since 0.6.1 + * + */ +public interface Context extends ModelManager, ViewManager, ControllerManager { +} \ No newline at end of file diff --git a/douyu-api/src/main/java/douyu/mvc/Controller.java b/douyu-api/src/main/java/douyu/mvc/Controller.java new file mode 100644 index 0000000..fb2a00e --- /dev/null +++ b/douyu-api/src/main/java/douyu/mvc/Controller.java @@ -0,0 +1,37 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 douyu.mvc; + +import java.lang.annotation.*; + +import douyu.http.HttpMethod; +import static java.lang.annotation.RetentionPolicy.*; +import static java.lang.annotation.ElementType.*; + +@Retention(RUNTIME) +@Target(TYPE) +public @interface Controller { + String defaultAction() default "index"; + + /** + * Controller所能接受的HTTT请求方法类型,会被Action中的值覆盖 + */ + HttpMethod[] httpMethods() default { HttpMethod.GET, HttpMethod.POST }; +} diff --git a/douyu-api/src/main/java/douyu/mvc/ControllerException.java b/douyu-api/src/main/java/douyu/mvc/ControllerException.java new file mode 100644 index 0000000..8cae40d --- /dev/null +++ b/douyu-api/src/main/java/douyu/mvc/ControllerException.java @@ -0,0 +1,41 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 douyu.mvc; + +public class ControllerException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public ControllerException() { + super(); + } + + public ControllerException(String message) { + super(message); + } + + public ControllerException(Throwable cause) { + super(cause); + } + + public ControllerException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/douyu-api/src/main/java/douyu/mvc/ControllerManager.java b/douyu-api/src/main/java/douyu/mvc/ControllerManager.java new file mode 100644 index 0000000..3395d12 --- /dev/null +++ b/douyu-api/src/main/java/douyu/mvc/ControllerManager.java @@ -0,0 +1,59 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 douyu.mvc; + +import douyu.http.Comet; +import douyu.http.HttpRequest; +import douyu.http.HttpResponse; +import douyu.http.WebSocket; + +/** + * + * 执行控制器的Action时,会为这个控制器生成一个ControllerManager, + * 这个ControllerManager用来管理一次请求过程中用到的相关上下文信息。 + * + * 此类不是线程安全的,存活期与HttpRequest相同。 + * + * @author ZHH + * @since 0.6.1 + * + */ +public interface ControllerManager { + + public HttpRequest getHttpRequest(); + + public HttpResponse getHttpResponse(); + + public String getControllerClassName(); + + public String getActionName(); + + public String getApplicationBase(); + + public void executeAction(String actionName) throws ControllerException; + + public void setWebSocket(WebSocket ws); + + public WebSocket getWebSocket(); + + public void setComet(Comet comet); + + public Comet getComet(); +} \ No newline at end of file diff --git a/douyu-api/src/main/java/douyu/mvc/Model.java b/douyu-api/src/main/java/douyu/mvc/Model.java new file mode 100644 index 0000000..855a13e --- /dev/null +++ b/douyu-api/src/main/java/douyu/mvc/Model.java @@ -0,0 +1,29 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 douyu.mvc; + +import java.lang.annotation.*; +import static java.lang.annotation.RetentionPolicy.*; +import static java.lang.annotation.ElementType.*; + +@Retention(RUNTIME) +@Target(TYPE) +public @interface Model { +} diff --git a/douyu-api/src/main/java/douyu/mvc/ModelException.java b/douyu-api/src/main/java/douyu/mvc/ModelException.java new file mode 100644 index 0000000..8320c20 --- /dev/null +++ b/douyu-api/src/main/java/douyu/mvc/ModelException.java @@ -0,0 +1,41 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 douyu.mvc; + +public class ModelException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public ModelException() { + super(); + } + + public ModelException(String message) { + super(message); + } + + public ModelException(Throwable cause) { + super(cause); + } + + public ModelException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/douyu-api/src/main/java/douyu/mvc/ModelManager.java b/douyu-api/src/main/java/douyu/mvc/ModelManager.java new file mode 100644 index 0000000..6473d68 --- /dev/null +++ b/douyu-api/src/main/java/douyu/mvc/ModelManager.java @@ -0,0 +1,37 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 douyu.mvc; + +/** + * + * 未实现 + * + * @author ZHH + * + */ +public interface ModelManager { + // public boolean insert(Object model); + // + // public boolean update(Object model); + // + // public boolean delete(Object model); + // + // public boolean select(Object model); +} \ No newline at end of file diff --git a/douyu-api/src/main/java/douyu/mvc/ViewException.java b/douyu-api/src/main/java/douyu/mvc/ViewException.java new file mode 100644 index 0000000..e0906c3 --- /dev/null +++ b/douyu-api/src/main/java/douyu/mvc/ViewException.java @@ -0,0 +1,41 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 douyu.mvc; + +public class ViewException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public ViewException() { + super(); + } + + public ViewException(String message) { + super(message); + } + + public ViewException(Throwable cause) { + super(cause); + } + + public ViewException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/douyu-api/src/main/java/douyu/mvc/ViewManager.java b/douyu-api/src/main/java/douyu/mvc/ViewManager.java new file mode 100644 index 0000000..698524c --- /dev/null +++ b/douyu-api/src/main/java/douyu/mvc/ViewManager.java @@ -0,0 +1,39 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 douyu.mvc; + +/** + * + * 输出视图模板文件,存放模板文件需要用到的参数。 + * + * @author ZHH + * @since 0.6.1 + * + */ +public interface ViewManager { + /** + * 输出默认视图,默认视图的位置取决于ViewManagerProvider,通常会根据控制器类名和Action名来决定 + */ + public void out(); + + public void out(String viewFileName); + + public void put(String key, Object value); +} \ No newline at end of file diff --git a/douyu-api/src/main/java/douyu/mvc/ViewManagerProvider.java b/douyu-api/src/main/java/douyu/mvc/ViewManagerProvider.java new file mode 100644 index 0000000..020f65b --- /dev/null +++ b/douyu-api/src/main/java/douyu/mvc/ViewManagerProvider.java @@ -0,0 +1,24 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 douyu.mvc; + +public interface ViewManagerProvider { + public ViewManager getViewManager(Context context); +} \ No newline at end of file diff --git a/douyu-api/src/main/java/douyu/mvc/package-info.java b/douyu-api/src/main/java/douyu/mvc/package-info.java new file mode 100644 index 0000000..195d091 --- /dev/null +++ b/douyu-api/src/main/java/douyu/mvc/package-info.java @@ -0,0 +1,8 @@ +/** + * 提供与MVC相关的接口 + * + * + * @author ZHH + * @since 0.6.1 + */ +package douyu.mvc; diff --git a/douyu-core/pom.xml b/douyu-core/pom.xml new file mode 100644 index 0000000..44ca228 --- /dev/null +++ b/douyu-core/pom.xml @@ -0,0 +1,22 @@ + + 4.0.0 + + com.codefollower.douyu + douyu + 0.1.0 + + + douyu-core + jar + ${douyu.version} + douyu core + + + + com.codefollower.douyu + douyu-javac + ${douyu.version} + + + diff --git a/douyu-core/src/main/java/com/codefollower/douyu/core/ClassResource.java b/douyu-core/src/main/java/com/codefollower/douyu/core/ClassResource.java new file mode 100644 index 0000000..3afacad --- /dev/null +++ b/douyu-core/src/main/java/com/codefollower/douyu/core/ClassResource.java @@ -0,0 +1,51 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 com.codefollower.douyu.core; + +import java.io.File; + +/** + * + * @author ZHH + * + */ +public class ClassResource { + File sourceFile; + long sourceFileLastModified = -1; + + File classFile; + long classFileLastModified = -1; + + public Class loadedClass; + + ClassResource() { + } + + void free() { + sourceFile = null; + classFile = null; + loadedClass = null; + } + + public String toString() { + return "ClassResource(classFile=" + classFile + ", sourceFile=" + sourceFile + ")"; + } + +} diff --git a/douyu-core/src/main/java/com/codefollower/douyu/core/Config.java b/douyu-core/src/main/java/com/codefollower/douyu/core/Config.java new file mode 100644 index 0000000..0df21d0 --- /dev/null +++ b/douyu-core/src/main/java/com/codefollower/douyu/core/Config.java @@ -0,0 +1,176 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 com.codefollower.douyu.core; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import douyu.mvc.ViewException; +import douyu.mvc.ViewManagerProvider; + +/** + * + * @author ZHH + * + */ +public class Config { + /** + * 应用名 + */ + public String appName; + + /** + * 对应javac的-encoding参数 + */ + public String javacEncoding; + + // 警告: srcDir目录和classesDir目录不能都放class文件,如果是由两个编译器编译的,时间不同,会导致一直重复编译 + + /** + * java源文件目录 + */ + public String srcDir; + + /** + * 存放编译后的类文件或自动生成的java源文件 + */ + public String classesDir; + + /** + * vm、ftl文件目录,如果没有设置,则默认是srcDir + */ + // public String viewsDir; + /** + * 在开发模式下, java源文件修改时会重新编译并重新加载(通常是替换ClassLoader) + */ + public boolean isDevMode = true; + + // public ResourceLoader loader; + + public ResourceLoader.Holder resourceLoaderHolder; + + public ResourceLoader getResourceLoader() { + return resourceLoaderHolder.get(); + } + + public void free() { + resourceLoaderHolder.free(); + resourceLoaderHolder = null; + } + + /** + * 应用类路径 + */ + public URL[] appClassPath = new URL[0]; + + public void addClassPath(String path) throws MalformedURLException { + if (path != null) { + URL url = new File(path).toURI().toURL(); + URL[] urls = new URL[appClassPath.length + 1]; + System.arraycopy(appClassPath, 0, urls, 0, appClassPath.length); + urls[appClassPath.length] = url; + appClassPath = urls; + } + } + + private ViewManagerProvider defaultViewManagerProvider; + private String defaultViewManagerProviderClassName; + // key: ViewManagerProvider的类名 + private Map viewManagerProviders = new HashMap(); + + // key: 视图文件扩展名 + // value: ViewManagerProvider的类名 + private Map extensionMap = new LinkedHashMap(); + + public void setDefaultViewManagerProvider(ViewManagerProvider defaultViewManagerProvider) { + this.defaultViewManagerProvider = defaultViewManagerProvider; + } + + public ViewManagerProvider getDefaultViewManagerProvider() { + if (defaultViewManagerProvider == null && defaultViewManagerProviderClassName != null) { + try { + this.defaultViewManagerProvider = (ViewManagerProvider) getResourceLoader().loadClass( + defaultViewManagerProviderClassName).newInstance(); + } catch (Exception e) { + throw new ViewException("failed to getDefaultViewManagerProvider: " + e); + } + } + return defaultViewManagerProvider; + } + + public List getViewManagerProviders() { + List list = new ArrayList(extensionMap.size()); + for (String extension : extensionMap.keySet()) { + list.add(getViewManagerProvider(extension)); + } + return list; + } + + /** + * + * @param extension + * @return + * 返回与extension相关的ViewManagerProvider,如果没有找到,返回默认的ViewManagerProvider + */ + public ViewManagerProvider getViewManagerProvider(String extension) { + String className = extensionMap.get(extension); + if (className == null) + return getDefaultViewManagerProvider(); + + ViewManagerProvider vmp = viewManagerProviders.get(className); + if (vmp == null) { + try { + vmp = (ViewManagerProvider) getResourceLoader().loadClass(className).newInstance(); + viewManagerProviders.put(className, vmp); + } catch (Exception e) { + throw new ViewException("failed to getViewManagerProvider for extension: " + extension, e); + } + } + return vmp; + } + + // 如:"org.douyu.plugins.velocity.VelocityViewManagerProvider=vm; org.douyu.plugins.freemarker.FreeMarkerViewManagerProvider=ftl"; + public void setViewManagerProviderConfig(String viewManagerProviderConfig) { + if (viewManagerProviderConfig != null) { + for (String m : viewManagerProviderConfig.split(";")) { + String[] n = m.trim().split("="); + if (n.length != 2) { + throw new IllegalArgumentException(viewManagerProviderConfig + + "(usage: classNameA=extensionA|extensionB;classNameB=extensionC|extensionD)"); + } + String className = n[0].trim(); + + // 默认是第一个 + if (defaultViewManagerProviderClassName == null) + defaultViewManagerProviderClassName = className; + + for (String extension : n[1].split("\\|")) { + extensionMap.put(extension.trim(), className); + } + } + } + } +} diff --git a/douyu-core/src/main/java/com/codefollower/douyu/core/Javac.java b/douyu-core/src/main/java/com/codefollower/douyu/core/Javac.java new file mode 100644 index 0000000..07d8055 --- /dev/null +++ b/douyu-core/src/main/java/com/codefollower/douyu/core/Javac.java @@ -0,0 +1,132 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 com.codefollower.douyu.core; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.lang.model.SourceVersion; + +import com.sun.tools.javac.Main; +import com.sun.tools.javac.code.Source; +import com.sun.tools.javac.processing.ControllerProcessor; + +/** + * + * @author ZHH + * + */ +public class Javac { + private StringBuilder classPath = new StringBuilder(); + private String classesDir; //存放编译后的类文件或自动生成的java源文件 + private String encoding; + + public Javac() { + } + + public void addClassPath(String cp) { + classPath.append(cp).append(File.pathSeparatorChar); + } + + public void setSrcDir(String srcDir) { + addClassPath(srcDir); + } + + public void setClassesDir(String classesDir) { + this.classesDir = classesDir; + addClassPath(classesDir); + } + + public void setEncoding(String encoding) { + this.encoding = encoding; + } + + public synchronized void compile(PrintWriter out, File... files) throws JavacException { + compile(out, Arrays.asList(files)); + } + + public synchronized void compile(PrintWriter out, List files) throws JavacException { + ArrayList args = new ArrayList(); + //args.add("-Xlint:unchecked"); + + //否则取不到方法参数名(因为有class文件时会优先加载class文件) + //ClassReader类并不解析方法参数,导致MethodSymbol.params为null + args.add("-Xprefer:source"); + + //为了清除javac出现的这个warning + //warning: Implicitly compiled files were not subject to annotation processing. + //args.add("-implicit:none"); + args.add("-implicit:class"); + + if (encoding != null) { + args.add("-encoding"); + args.add(encoding); + } + + if (classesDir != null) { + args.add("-s"); + args.add(classesDir); + args.add("-d"); + args.add(classesDir); + } + + args.add("-processor"); + args.add(ControllerProcessor.class.getName()); + + if (SourceVersion.latest() == SourceVersion.RELEASE_6) { + args.add("-target"); + args.add("1.6"); + args.add("-source"); + args.add("1.6"); + } + + //没有"-Xlint:-options", + //在com.sun.tools.javac.main.JavaCompiler.JavaCompiler(Context context)会出warning + if (Source.DEFAULT.ordinal() > Source.JDK1_6.ordinal()) + args.add("-Xlint:-options"); + + if (classPath.length() > 0) { + args.add("-cp"); + args.add(classPath.toString()); + } + + for (File f : files) { + try { + args.add(f.getCanonicalPath()); + } catch (IOException e) { + throw new JavacException("failed to compile file: " + f); + } + } + + try { + //0以外的值都表示编译失败 + if (Main.compile(args.toArray(new String[0]), out) != 0) { + throw new JavacException("failed to compile files: " + files); + } + } catch (Throwable e) { + throw new JavacException("failed to compile files: " + files, e); + } + } + +} \ No newline at end of file diff --git a/douyu-core/src/main/java/com/codefollower/douyu/core/JavacException.java b/douyu-core/src/main/java/com/codefollower/douyu/core/JavacException.java new file mode 100644 index 0000000..ca13323 --- /dev/null +++ b/douyu-core/src/main/java/com/codefollower/douyu/core/JavacException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 com.codefollower.douyu.core; + +/** + * + * @author ZHH + * + */ +public class JavacException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public JavacException() { + super(); + } + + public JavacException(String message) { + super(message); + } + + public JavacException(Throwable cause) { + super(cause); + } + + public JavacException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/douyu-core/src/main/java/com/codefollower/douyu/core/ResourceLoader.java b/douyu-core/src/main/java/com/codefollower/douyu/core/ResourceLoader.java new file mode 100644 index 0000000..dfb38f3 --- /dev/null +++ b/douyu-core/src/main/java/com/codefollower/douyu/core/ResourceLoader.java @@ -0,0 +1,517 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 com.codefollower.douyu.core; + +import java.io.File; +import java.io.InputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.PrintWriter; + +import java.net.URL; +import java.net.URLClassLoader; + +import java.util.ArrayList; +import java.util.Map; +import java.util.zip.ZipFile; +import java.util.zip.ZipEntry; +import java.util.concurrent.ConcurrentHashMap; + +/** + * + * @author ZHH + * + */ +public class ResourceLoader extends URLClassLoader { + + private static Map holders = new ConcurrentHashMap(); + + public static class Holder { + private ResourceLoader loader; + + public void set(ResourceLoader loader) { + this.loader = loader; + } + + public ResourceLoader get() { + return loader; + } + + public void free() { + loader.free(); + loader = null; + } + } + + public static Holder newHolder(Config config, ClassLoader parent) { + ResourceLoader rl = new ResourceLoader(config, parent); + Holder h = new Holder(); + h.set(rl); + + holders.put(config.appName, h); + return h; + } + + public static byte[] loadBytesFromStream(InputStream in, long len) throws IOException { + int length = (int) len; + byte[] buf = new byte[length]; + int nRead, count = 0; + + while ((length > 0) && ((nRead = in.read(buf, count, length)) != -1)) { + count += nRead; + length -= nRead; + } + return buf; + } + + private Map classResourceCache = new ConcurrentHashMap(); + + //查找java源文件和class文件的路径,要么是目录,要么是jar文件 + private ArrayList findPath = new ArrayList(); + private Javac javac = new Javac(); + private Config config; + private ClassLoader parent; + + private ResourceLoader(Config config, ClassLoader parent) { + super(config.appClassPath, parent); + + this.config = config; + this.parent = parent; + + javac.setSrcDir(config.srcDir); + javac.setClassesDir(config.classesDir); + javac.setEncoding(config.javacEncoding); + + if (config.srcDir != null) + findPath.add(new File(config.srcDir)); + if (config.classesDir != null) + findPath.add(new File(config.classesDir)); + + URL[] urls = null; + ClassLoader cl = parent; + while (cl != null) { + if (cl instanceof URLClassLoader) { + urls = ((URLClassLoader) cl).getURLs(); + for (URL url : urls) { + //javac.addClassPath(url.toExternalForm()); //javac不能识别这种格式 + try { + javac.addClassPath(new File(url.toURI()).getCanonicalPath()); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + cl = cl.getParent(); + } + } + + private ResourceLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + this.parent = parent; + } + + private ResourceLoader copy() { + ResourceLoader rl = new ResourceLoader(config.appClassPath, parent); + rl.findPath = findPath; + rl.javac = javac; + rl.config = config; + Holder h = holders.get(config.appName); + h.set(rl); + + this.free(); + return rl; + } + + public void free() { + for (ClassResource cr : classResourceCache.values()) + cr.free(); + classResourceCache.clear(); + classResourceCache = null; + + findPath = null; + config = null; + javac = null; + parent = null; + } + + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + boolean findJavaSourceFile = false; + if (config.isDevMode) + findJavaSourceFile = true; + + Object c = findClassOrClassResource(name, resolve, findJavaSourceFile); + + //if(null instanceof Type)总是返回false,所以这个if语句是没必要的 + //if (c == null) + // throw new ClassNotFoundException(name); + + if (c instanceof Class) + return (Class) c; + else if (c instanceof ClassResource) + return ((ClassResource) c).loadedClass; + else + throw new ClassNotFoundException(name); + } + + //因为此方法是最核心的也是频繁使用的, + //不但要加载ArrayList findPath中的本地类,还要加载服务器本身及java平台的相关类, + //如果每找到一个Class都生成一个ClassResource实例把它包装后再返回,那么开销太大。 + //所以只对findPath中的本地类才生成一个ClassResource实例 + + //另外还有个更重要的功能:如果uri是服务器本身或java平台的相关类 + //如在地址栏中输入/java.io.File,那么即使从上一级classLoader中找得到, + //但是返回的是一个Class而不是ClassResource,这样就可以直接响应404错误 + + //另外,如果是一个ClassResource,那么还会查找对应的Java源文件 + private Object findClassOrClassResource(String name, boolean resolve, boolean findJavaSourceFile) { + if (name == null) + return null; + + name = name.trim(); + if (name.length() < 1) + return null; + + Class c = null; + ClassResource classResource = classResourceCache.get(name); + //(1)先查看缓存(只缓存用户应用程序类),如果上次已加载过则直接返回 + if (classResource != null) { + c = classResource.loadedClass; + if (resolve) + resolveClass(c); + + return classResource; + } + + //******************************************************************************* + //用eclipse直接运行时,应用的类、容器类都在同一个ClassLoader,所以都能从parent查到, + //这样会导致错误的。 + //******************************************************************************* + + // (2)委托上一级装载器加载 + // if (parent != null) { + // //if (debug) log("[from parent] " + name); + // + // try { + // c = parent.loadClass(name); + // if (c != null) { + // if (resolve) + // resolveClass(c); + // return c; + // } + // } catch (Throwable t) { + // //忽略 + // } + // } + // + // //(3)尝试从系统装载器加载 + // //if (debug) log("[from system] " + name); + // try { + // c = findSystemClass(name); + // if (c != null) { + // if (resolve) + // resolveClass(c); + // return c; + // } + // } catch (Throwable t) { + // //忽略 + // } + + //(4)如果仍未找到, + //尝试从findPath加载 + //在找到指定的类后,同时要把它缓存起来 + //if (debug) log("[from local classPath] " + name); + + File classFile = null; + byte[] classData = null; + + String fileName = name.replace('.', File.separatorChar); + String classFileName = fileName + ".class"; + + String zipEntryName = classFileName; + if (File.separatorChar != '/') { + zipEntryName = name.replace('.', '/') + ".class"; + } + + for (File base : findPath) { + //从目录中查找class文件并读取字节数据 + if (base.isDirectory()) { + classFile = new File(base, classFileName); + + if (classFile.exists()) { + InputStream in = null; + try { + in = new FileInputStream(classFile); + classData = loadBytesFromStream(in, classFile.length()); + } catch (Throwable t) { + throw new ResourceLoaderException("failed to load file: " + classFile, t); + } finally { + if (in != null) { + try { + in.close(); + } catch (Throwable t) { + } + } + } + } + } else { //从zip(jar)文件中查找class文件并读取字节数据 + classFile = base; //注意: class文件是从zip(jar)文件中读取的 + + ZipFile zipfile = null; + try { + zipfile = new ZipFile(base); + ZipEntry entry = zipfile.getEntry(zipEntryName); + if (entry != null) + classData = loadBytesFromStream(zipfile.getInputStream(entry), entry.getSize()); + } catch (Throwable t) { + throw new ResourceLoaderException("failed to load zip entry: " + zipEntryName, t); + } finally { + if (zipfile != null) { + try { + zipfile.close(); + } catch (Throwable t) { + } + } + } + } + + if (classData != null) { + try { + c = defineClass(name, classData, 0, classData.length); + if (resolve) + resolveClass(c); + } catch (Throwable t) { + //defineClass可能发生异常(如ClassFormatError)? + throw new ResourceLoaderException("failed to define class: " + name, t); + } + + classResource = new ClassResource(); + classResource.loadedClass = c; + classResource.classFile = classFile; + classResource.classFileLastModified = classFile.lastModified(); + classResourceCache.put(name, classResource); + break; //classFile已经找到了,退出for循环 + } + } + //继续寻找对应的java源文件 + //(注意:退出for循环后,如果classResource还为null说明classFile没找到) + if (findJavaSourceFile) { + String javaSourceFileName = fileName + ".java"; + File javaSourceFile = null; + + //尝试寻找Java源文件 + for (File base : findPath) { + //TODO 只从目录中查找java文件,未来是否考虑从zip(jar)中读取? + if (base.isDirectory()) { + File f = new File(base, javaSourceFileName); + + if (f.exists()) { + javaSourceFile = f; + break; + } + } + } + + if (javaSourceFile != null) { + if (classResource == null) { + classResource = new ClassResource(); + } + + classResource.sourceFile = javaSourceFile; + classResource.sourceFileLastModified = javaSourceFile.lastModified(); + } + //如果classFile没找到,即使sourceFile找到了也不用缓存 + //因为只缓存class文件的Class实例 + //classResourceCache.put(name, classResource); + } + + if (classResource != null) { + return classResource; + } + + //(2)委托上一级装载器加载 + if (parent != null) { + try { + c = parent.loadClass(name); + if (c != null) { + if (resolve) + resolveClass(c); + return c; + } + } catch (Throwable t) { + //忽略 + } + } + + //(3)尝试从系统装载器加载 + try { + c = findSystemClass(name); + if (c != null) { + if (resolve) + resolveClass(c); + return c; + } + } catch (Throwable t) { + //忽略 + } + + return null; + } + + private static String SUFFIX = "$DOUYU"; + + public ClassResource loadContextClassResource(String controllerClassName, PrintWriter out) throws JavacException { + String contextClassName = controllerClassName + SUFFIX; + ClassResource resource = classResourceCache.get(contextClassName); + if (resource == null) { + resource = loadContextClassResource(controllerClassName, contextClassName, out); + + if (resource != null) { + classResourceCache.put(contextClassName, resource); + } + } + + if (resource != null && config.isDevMode) { + if (classResourceModified(out)) { + return copy().loadContextClassResource(controllerClassName, out); + } + } + + return resource; + } + + private ClassResource loadContextClassResource(String controllerClassName, String contextClassName, PrintWriter out) { + + //带有SUFFIX后缀的类(以下简称:context类),无需加载java源文件 + ClassResource context = loadClassResource(contextClassName, false); + + if (!config.isDevMode) + return context; + + //带有@Controller标注的类(以下简称:controller类) + //注意:事先并不知道controllerClassName是否是一个controller类, + //所以先假定它是controller类, + //当编译这个假想的controller类后,如果得不到对应的context类, + //那么就返回错误(比如返回404 或 返回400(Bad request) + ClassResource controller = loadClassResource(controllerClassName, true); + + //都未找到 + if (context == null && controller == null) { + return null; + } else { //找到controller类或context类其中之一,或两者都找到了 + + //controller类找不到(对应的java源文件和class文件都找不到) + //这可能是由于误删除引起的,所以不管context类是否存在都无意义了, + //因为context类总是要引用controller类的. + if (controller == null) { + return null; + } + //通常是第一次请求controller类,此时服务器需要尝试编译它,并生成context类 + else if (controller != null && context == null) { + //未找到controller类的java源文件 + if (controller.sourceFile == null) { + return null; + } else { + javac.compile(out, controller.sourceFile); + + //如果不是有效的controller类时,可能为null + return loadClassResource(contextClassName, false); + } + } else { //两者都找到了,直接返回context + return context; + } + } + } + + private ClassResource loadClassResource(String name, boolean findJavaSourceFile) { + ClassResource cr = null; + Object c = findClassOrClassResource(name, true, findJavaSourceFile); + if (c instanceof ClassResource) + cr = (ClassResource) c; + + return cr; + } + + private boolean classResourceModified(PrintWriter out) { + boolean modified = false; + ArrayList files = new ArrayList(); + + for (ClassResource cr : classResourceCache.values()) { + int command = check(cr); + if (command == LOAD || command == COMPLIE_AND_LOAD) { + modified = true; + + if (command == COMPLIE_AND_LOAD) { + files.add(cr.sourceFile); + } + } + } + + if (files.size() > 0) { + //TODO + //正确的做法应该是把classResourceCache中的所有源文件都重新编译一次,因为有可能对A修改了一个方法,A被重新编译了, + //但是因为B调用了A的这个方法,而B的源文件没有修改,所以B还是调用A的是旧版本. + //另外,如果所有源文件都要重新编译,性能问题是不是可以接受? + files.clear(); + for (ClassResource cr : classResourceCache.values()) { + if (cr.sourceFile != null) + files.add(cr.sourceFile); + } + + javac.compile(out, files); + } + return modified; + } + + private static final int NONE = 0; + private static final int LOAD = 1; + private static final int COMPLIE_AND_LOAD = 2; + + //ClassResource的sourceFile和classFile保证不同时为null + private int check(ClassResource cr) { + //(1)只有java源文件时,不管isDevMode的值是什么, + //先编译源文件,然后加载class文件 + if (cr.sourceFile != null && cr.classFile == null) + return COMPLIE_AND_LOAD; + + //(2)同时有java源文件和class文件, + //或者只有class文件的情况 + //(出现这种情况的原因是:在第一次由服务器自动编译(或用户手工编译)完java源文件后, + //用户可以把java源文件删除,或者class文件是从其他地方编译而来的) + //这两种情况都要根据isDevMode的值来决定, + //如果isDevMode是false,就表示仍然用缓存中的类 + //如果isDevMode是true, 就必须按下面的规则重新加载 + + if (config.isDevMode) { + //(2.1)同时有java源文件和class文件且java源文件比class文件新, + //那么先编译java源文件,然后加载class文件 + if (cr.sourceFile != null && cr.classFile != null && cr.sourceFile.lastModified() > cr.classFile.lastModified()) { + return COMPLIE_AND_LOAD; + } + + //(2.2)只有class文件的情况,如果class文件比缓存中的类新, + //那么重新加载class文件(缓存中的类的时间用cr.classFileLastModified表示) + if (cr.sourceFile == null && cr.classFile != null && cr.classFile.lastModified() > cr.classFileLastModified) { + return LOAD; + } + } + return NONE; + } + +} \ No newline at end of file diff --git a/douyu-core/src/main/java/com/codefollower/douyu/core/ResourceLoaderException.java b/douyu-core/src/main/java/com/codefollower/douyu/core/ResourceLoaderException.java new file mode 100644 index 0000000..7538264 --- /dev/null +++ b/douyu-core/src/main/java/com/codefollower/douyu/core/ResourceLoaderException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2011 The Apache Software Foundation + * + * 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 com.codefollower.douyu.core; + +/** + * + * @author ZHH + * + */ +public class ResourceLoaderException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public ResourceLoaderException() { + super(); + } + + public ResourceLoaderException(String message) { + super(message); + } + + public ResourceLoaderException(Throwable cause) { + super(cause); + } + + public ResourceLoaderException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/douyu-core/src/main/java/com/codefollower/douyu/core/StringManager.java b/douyu-core/src/main/java/com/codefollower/douyu/core/StringManager.java new file mode 100644 index 0000000..88b231e --- /dev/null +++ b/douyu-core/src/main/java/com/codefollower/douyu/core/StringManager.java @@ -0,0 +1,206 @@ +/* + * 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 com.codefollower.douyu.core; + +import java.text.MessageFormat; +import java.util.Hashtable; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * An internationalization / localization helper class which reduces + * the bother of handling ResourceBundles and takes care of the + * common cases of message formating which otherwise require the + * creation of Object arrays and such. + * + *

The StringManager operates on a package basis. One StringManager + * per package can be created and accessed via the getManager method + * call. + * + *

The StringManager will look for a ResourceBundle named by + * the package name given plus the suffix of "LocalStrings". In + * practice, this means that the localized information will be contained + * in a LocalStrings.properties file located in the package + * directory of the classpath. + * + *

Please see the documentation for java.util.ResourceBundle for + * more information. + * + * @version $Id: StringManager.java 1043103 2010-12-07 15:40:06Z markt $ + * + * @author James Duncan Davidson [duncan@eng.sun.com] + * @author James Todd [gonzo@eng.sun.com] + * @author Mel Martinez [mmartinez@g1440.com] + * @see java.util.ResourceBundle + */ + +public class StringManager { + + /** + * The ResourceBundle for this StringManager. + */ + private final ResourceBundle bundle; + private final Locale locale; + + /** + * Creates a new StringManager for a given package. This is a + * private method and all access to it is arbitrated by the + * static getManager method call so that only one StringManager + * per package will be created. + * + * @param packageName Name of package to create StringManager for. + */ + private StringManager(String packageName, Locale locale) { + String bundleName = packageName + ".LocalStrings"; + ResourceBundle bnd = null; + try { + bnd = ResourceBundle.getBundle(bundleName, locale); + } catch( MissingResourceException ex ) { + // Try from the current loader (that's the case for trusted apps) + // Should only be required if using a TC5 style classloader structure + // where common != shared != server + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + if( cl != null ) { + try { + bnd = ResourceBundle.getBundle(bundleName, locale, cl); + } catch(MissingResourceException ex2) { + // Ignore + } + } + } + bundle = bnd; + // Get the actual locale, which may be different from the requested one + if (bundle != null) { + this.locale = bundle.getLocale(); + } else { + this.locale = null; + } + } + + /** + Get a string from the underlying resource bundle or return + null if the String is not found. + + @param key to desired resource String + @return resource String matching key from underlying + bundle or null if not found. + @throws IllegalArgumentException if key is null. + */ + public String getString(String key) { + if(key == null){ + String msg = "key may not have a null value"; + + throw new IllegalArgumentException(msg); + } + + String str = null; + + try { + // Avoid NPE if bundle is null and treat it like an MRE + if (bundle != null) { + str = bundle.getString(key); + } + } catch(MissingResourceException mre) { + //bad: shouldn't mask an exception the following way: + // str = "[cannot find message associated with key '" + key + + // "' due to " + mre + "]"; + // because it hides the fact that the String was missing + // from the calling code. + //good: could just throw the exception (or wrap it in another) + // but that would probably cause much havoc on existing + // code. + //better: consistent with container pattern to + // simply return null. Calling code can then do + // a null check. + str = null; + } + + return str; + } + + /** + * Get a string from the underlying resource bundle and format + * it with the given set of arguments. + * + * @param key + * @param args + */ + public String getString(final String key, final Object... args) { + String value = getString(key); + if (value == null) { + value = key; + } + + MessageFormat mf = new MessageFormat(value); + mf.setLocale(locale); + return mf.format(args, new StringBuffer(), null).toString(); + } + + /** + * Identify the Locale this StringManager is associated with + */ + public Locale getLocale() { + return locale; + } + + // -------------------------------------------------------------- + // STATIC SUPPORT METHODS + // -------------------------------------------------------------- + + private static final Map> managers = + new Hashtable>(); + + /** + * Get the StringManager for a particular package. If a manager for + * a package already exists, it will be reused, else a new + * StringManager will be created and returned. + * + * @param packageName The package name + */ + public static final synchronized StringManager getManager( + String packageName) { + return getManager(packageName, Locale.getDefault()); + } + + /** + * Get the StringManager for a particular package and Locale. If a manager + * for a package/Locale combination already exists, it will be reused, else + * a new StringManager will be created and returned. + * + * @param packageName The package name + * @param locale The Locale + */ + public static final synchronized StringManager getManager( + String packageName, Locale locale) { + + Map map = managers.get(packageName); + if (map == null) { + map = new Hashtable(); + managers.put(packageName, map); + } + + StringManager mgr = map.get(locale); + if (mgr == null) { + mgr = new StringManager(packageName, locale); + map.put(locale, mgr); + } + return mgr; + } +} diff --git a/douyu-examples/DevTest.haha.jsp b/douyu-examples/DevTest.haha.jsp new file mode 100644 index 0000000..bf475c3 --- /dev/null +++ b/douyu-examples/DevTest.haha.jsp @@ -0,0 +1,14 @@ + + +ViewManager Test + + + +

+JSP: invoke douyu.mvc.ViewManager.out(): +


+name = ${name}
+age = ${age}
+ + + \ No newline at end of file diff --git a/douyu-examples/WEB-INF/src/AsyncExample.java b/douyu-examples/WEB-INF/src/AsyncExample.java new file mode 100644 index 0000000..92eabfe --- /dev/null +++ b/douyu-examples/WEB-INF/src/AsyncExample.java @@ -0,0 +1,39 @@ +import java.io.PrintWriter; +import java.util.Date; + +import douyu.mvc.Async; +import douyu.mvc.Context; +import douyu.mvc.Controller; + +@Controller +public class AsyncExample { + @Async + public void asyncAction(Context c, PrintWriter out) { + out.println("before invokeLongtimeService..."+Thread.currentThread()); + invokeLongtimeService(c, out); + out.println("after invokeLongtimeService..."); + } + + private void invokeLongtimeService(Context c, PrintWriter out) { + out.println("invokeLongtimeService..."); + out.println("at "+ new Date()); + + int seconds = 2; + out.println("sleep "+seconds+" seconds..."); + try { + Thread.sleep(seconds*1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + out.println("at "+ new Date()); + } + + @Async + public void asyncAction2(PrintWriter out) { + out.println("invoke asyncAction2..."+Thread.currentThread()); + } + + public void action3(PrintWriter out) { + out.println("invoke action3..."+Thread.currentThread()); + } +} diff --git a/douyu-examples/WEB-INF/src/BigPipeTest.java b/douyu-examples/WEB-INF/src/BigPipeTest.java new file mode 100644 index 0000000..7e58b76 --- /dev/null +++ b/douyu-examples/WEB-INF/src/BigPipeTest.java @@ -0,0 +1,156 @@ +//import java.io.IOException; +//import java.io.PrintWriter; +//import java.util.concurrent.Executor; +//import java.util.concurrent.Executors; +//import java.util.concurrent.ThreadFactory; +// +//import javax.servlet.AsyncContext; +//import javax.servlet.ServletException; +//import javax.servlet.http.HttpServletRequest; +//import javax.servlet.http.HttpServletResponse; +// +//import org.apache.juli.logging.Log; +//import org.apache.juli.logging.LogFactory; +// +///** +// * 模拟Facebook BigPipe异步展现页面 +// * @author yongboy +// * @date 2011-2-21 +// * @version 1.0 +// */ +//@douyu.mvc.Controller +//public class BigPipeTest { +// private static final long serialVersionUID = 14526556595656565L; +// private static final Log log = LogFactory.getLog(BigPipeTest.class); +// private static final Executor executor; +// +// static { +// executor = Executors.newFixedThreadPool(500, new ThreadFactory() { +// public Thread newThread(Runnable r) { +// Thread thread = new Thread(r); +// thread.setName("BigPipe Thread " + thread.getId()); +// thread.setPriority(Thread.MAX_PRIORITY); +// +// return thread; +// } +// }); +// } +// +// public void index(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { +// response.setHeader("Connection", "Keep-Alive"); +// response.setContentType("text/html;charset=UTF-8"); +// response.setCharacterEncoding("UTF-8"); +// +// PrintWriter out = response.getWriter(); +// +// out.println(docType); +// out.println(headPart); +// out.println(bodyPart); +// +// out.flush(); +// +// // 这里模拟把页面组件部分计算工作交由异步线程完成 +// executor.execute(new AsyncRunnable(request.startAsync(request, response))); +// } +// +// /** +// * 定义工作线程用于处理异步的内容输出 +// * @author yongboy +// * @date 2011-2-21 +// * @version 1.0 +// */ +// private static class AsyncRunnable implements Runnable { +// private final AsyncContext asyncContext; +// private int times = 1; +// +// public AsyncRunnable(final AsyncContext asyncContext) { +// this.asyncContext = asyncContext; +// } +// +// public void run() { +// try { +// // 设置超时为20s,系统默认超时时间为10s +// asyncContext.setTimeout(20L * 1000L); +// PrintWriter out = asyncContext.getResponse().getWriter(); +// +// // 模拟按照页面组件重要程度按照顺序计算 +// bussizeMethod(out, "header", "这里是主页LOGO区域"); +// bussizeMethod(out, "left", genStrings("这里是内容文字", 160, false)); +// bussizeMethod(out, "right", genStrings("肯德基在百事可乐杯子添加其他品牌可乐", 41, true)); +// bussizeMethod(out, "leftLeft", genStrings("肯德基在百事可乐杯子添加其他品牌可乐", 10, true)); +// bussizeMethod(out, "leftRight", genStrings("肯德基在百事可乐杯子添加其他品牌可乐", 10, true)); +// +// // 补齐页面标签 +// outFinish(out); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// +// // 结束当前异步请求 +// try { +// asyncContext.complete(); +// } catch (Exception e) { +// // 可能因为出现超时(大于默认的超时时间10s)异常 +// e.printStackTrace(); +// } +// } +// +// private void bussizeMethod(PrintWriter writer, String id, String content) { +// //模拟耗时 +// try { +// Thread.sleep(1000 * (times++)); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// +// pagelet(writer, id, content); +// } +// +// private void pagelet(PrintWriter writer, String id, String content) { +// if (writer.checkError()) +// return; +// writer.write("\n"); +// writer.flush(); +// } +// +// private void outFinish(PrintWriter writer) { +// if (writer.checkError()) +// return; +// writer.println(""); +// writer.println(""); +// writer.flush(); +// writer.close(); +// } +// +// private String genStrings(String ori, int num, boolean line) { +// if (num < 1) +// return ori; +// StringBuilder sb = new StringBuilder(); +// +// for (int i = 0; i < num; i++) { +// sb.append(ori); +// if (line) { +// sb.append("
"); +// } +// } +// +// return sb.toString(); +// } +// } +// +// private static final String docType = ""; +// private static final String headPart = "\n\n" +// + "\n" +// + "Bigpipe Demo\n" +// + "\n" +// + "\n" +// + ""; +// private static final String bodyPart = "\n" + "
\n" +// + "
\n" + "
\n" +// +// + "
\n" + "
loading ...
\n" +// + "
\n" +// + "
loading ...
\n" +// + "
loading ...
\n" + "
\n" +// + "
\n" + "
\n" + "
"; +//} diff --git a/douyu-examples/WEB-INF/src/CometExample.java b/douyu-examples/WEB-INF/src/CometExample.java new file mode 100644 index 0000000..f07c0fb --- /dev/null +++ b/douyu-examples/WEB-INF/src/CometExample.java @@ -0,0 +1,56 @@ +import java.io.IOException; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import douyu.http.Comet; +import douyu.mvc.Context; +import douyu.mvc.Controller; + +@Controller +public class CometExample { + public void index(Context c) { + c.out("CometExample.html"); + } + + public void join(Context c) { + c.setComet(new MyComet()); + } + + public static class MyComet implements Comet { + private final static Set members = new CopyOnWriteArraySet(); + Context context; + @Override + public void onConnect(Context context) { + members.add(this); + this.context = context; + } + + @Override + public void onDisconnect() { + members.remove(this); + } + + @Override + public void onError() { + } + + @Override + public void onMessage(String data) { + if (data.indexOf("disconnect") >= 0) + context.setComet(null); + else { + for (MyComet member : members) { + try { + member.context.getHttpResponse().getWriter().print(data); + } catch (IOException e) { + e.printStackTrace(); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + } + } + +} diff --git a/douyu-examples/WEB-INF/src/CookieExample.java b/douyu-examples/WEB-INF/src/CookieExample.java new file mode 100644 index 0000000..da76d9d --- /dev/null +++ b/douyu-examples/WEB-INF/src/CookieExample.java @@ -0,0 +1,104 @@ +///* +//* 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. +//*/ +///* $Id: CookieExample.java 1056820 2011-01-08 22:27:26Z markt $ +// * +// */ +// +//import java.io.PrintWriter; +//import java.util.ResourceBundle; +// +//import javax.servlet.http.Cookie; +//import javax.servlet.http.HttpServletRequest; +//import javax.servlet.http.HttpServletResponse; +// +//import util.HTMLFilter; +// +///** +// * Example servlet showing request headers +// * +// * @author James Duncan Davidson +// */ +//@douyu.mvc.Controller +//public class CookieExample { +// private static final ResourceBundle RB = ResourceBundle.getBundle("LocalStrings"); +// +// public void index(HttpServletRequest request, HttpServletResponse response, PrintWriter out) { +// +// String cookieName = request.getParameter("cookiename"); +// String cookieValue = request.getParameter("cookievalue"); +// Cookie aCookie = null; +// if (cookieName != null && cookieName.length() > 0 && cookieValue != null) { +// aCookie = new Cookie(cookieName, cookieValue); +// response.addCookie(aCookie); +// } +// +// response.setContentType("text/html"); +// out.println(""); +// out.println(""); +// out.println(""); +// +// String title = RB.getString("cookies.title"); +// out.println("" + title + ""); +// out.println(""); +// out.println(""); +// +// // relative links +// +// // XXX +// // making these absolute till we work out the +// // addition of a PathInfo issue +// +// out.println(""); +// out.println("\"view"); +// out.println(""); +// out.println("\"return\""); +// +// out.println("

" + title + "

"); +// +// Cookie[] cookies = request.getCookies(); +// if ((cookies != null) && (cookies.length > 0)) { +// out.println(RB.getString("cookies.cookies") + "
"); +// for (int i = 0; i < cookies.length; i++) { +// Cookie cookie = cookies[i]; +// out.print("Cookie Name: " + HTMLFilter.filter(cookie.getName()) + "
"); +// out.println(" Cookie Value: " + HTMLFilter.filter(cookie.getValue()) + "

"); +// } +// } else { +// out.println(RB.getString("cookies.no-cookies")); +// } +// +// if (aCookie != null) { +// out.println("

"); +// out.println(RB.getString("cookies.set") + "
"); +// out.print(RB.getString("cookies.name") + " " + HTMLFilter.filter(cookieName) + "
"); +// out.print(RB.getString("cookies.value") + " " + HTMLFilter.filter(cookieValue)); +// } +// +// out.println("

"); +// out.println(RB.getString("cookies.make-cookie") + "
"); +// out.print("

"); +// out.print(RB.getString("cookies.name") + " "); +// out.println("
"); +// out.print(RB.getString("cookies.value") + " "); +// out.println("
"); +// out.println("
"); +// +// out.println(""); +// out.println(""); +// } +//} diff --git a/douyu-examples/WEB-INF/src/DevTest.haha.vm b/douyu-examples/WEB-INF/src/DevTest.haha.vm new file mode 100644 index 0000000..d582d23 --- /dev/null +++ b/douyu-examples/WEB-INF/src/DevTest.haha.vm @@ -0,0 +1,14 @@ + + +ViewManager Test + + + +

+Veloctiy: invoke douyu.mvc.ViewManager.out(): +
+name = ${name}
+age = ${age}
+ + + diff --git a/douyu-examples/WEB-INF/src/DevTest.java b/douyu-examples/WEB-INF/src/DevTest.java new file mode 100644 index 0000000..2c99b20 --- /dev/null +++ b/douyu-examples/WEB-INF/src/DevTest.java @@ -0,0 +1,99 @@ +import java.io.PrintWriter; +import java.io.Writer; + +import douyu.http.HttpRequest; +import douyu.http.HttpResponse; +import douyu.mvc.Context; +import douyu.mvc.Controller; +import douyu.mvc.ControllerManager; +import douyu.mvc.ModelManager; +import douyu.mvc.ViewManager; +import models.MyModel; + +@Controller +public class DevTest { + //static {} + //{ } + public DevTest() { + //throw new Error(); + } + + //http://127.0.0.1:8080/douyu/DevTest + public void index(PrintWriter out) { + out.println("invoke defaultAction: 'index' at " + new java.util.Date()); + } + + //http://127.0.0.1:8080/douyu/DevTest.haha?name=haha&age=1000 + public void haha(Context c, String name, int age) { + + //c.out("/jsp/ViewTest.jsp"); + c.out(); //DevTest.haha.jsp + + c.out("/ViewTest.vm"); + c.out("/ViewTest.ftl"); + } + + //////////////////以下是所有可注入的方法参数类型///////////////// + public void method0() { + } + + public void method1(Context context, ModelManager m, ViewManager v, ControllerManager c) { + } + + public void method2(HttpRequest p1, HttpResponse p2) { + } + + public void method3(PrintWriter p1, Writer p2) { + } + + public void method4(int i, long l, float f, double d, boolean bool, byte b, short s, char c) { + + } + + public void method5(Integer i, Long l, Float f, Double d, Boolean bool, Byte b, Short s, Character c) { + + } + + public void method6(String[] strs) { + + } + + //servlet3.0才支持Part + //如果编译出错,注销掉这个方法 + /* + public void method7(javax.servlet.http.Part part, javax.servlet.http.Part[] parts, PrintWriter out) { + out.println("part=" + part); + + if (parts != null) { + out.println("parts.length=" + parts.length); + } else { + out.println("parts=null"); + } + } + */ + + //http://127.0.0.1:8080/douyu/DevTest.method8?model.f1=1&model.f2=2&model.subModel.f1=3&model.subModel.f2=4 + public void method8(Object obj, MyModel model, PrintWriter out) { + out.println("invoke 'method8' at " + new java.util.Date()); + + out.println(); + out.println("obj=" + obj); //总是为null,java.lang.Object不是可注入的类型 + + out.println(); + out.println("model=" + model); + } + + //////////////////package、private、protected以及所有static方法都是不能通过URI直接访问的///////////////// + void package_method() { + } + + @SuppressWarnings("unused") + private void private_method() { + } + + protected void protected_method() { + } + + public static void static_method() { + } +} diff --git a/douyu-examples/WEB-INF/src/FileUpload.html b/douyu-examples/WEB-INF/src/FileUpload.html new file mode 100644 index 0000000..e8a02dc --- /dev/null +++ b/douyu-examples/WEB-INF/src/FileUpload.html @@ -0,0 +1,13 @@ + +文件上传 + +
+ 文件1:
+ 文件2:
+ + 说明 :
+ + +
+ + diff --git a/douyu-examples/WEB-INF/src/FileUpload.java b/douyu-examples/WEB-INF/src/FileUpload.java new file mode 100644 index 0000000..cf7bc51 --- /dev/null +++ b/douyu-examples/WEB-INF/src/FileUpload.java @@ -0,0 +1,59 @@ +import java.io.File; +import java.io.PrintWriter; + +import douyu.mvc.Context; +import douyu.mvc.Controller; + +import douyu.http.HttpRequest; +import douyu.http.UploadedFile; + +@Controller +public class FileUpload { + public void index(Context c) { + c.out("FileUpload.html"); + } + + public void upload(HttpRequest request, UploadedFile[] uploadedFiles, UploadedFile file1, + String description, PrintWriter out) { + + out.println("说明: "+description); + out.println("file1: "+request.getUploadedFile("file1")); + out.println("uploadedFiles.length: "+uploadedFiles.length); + out.println(); + + if(uploadedFiles != null) { + for(UploadedFile uf : uploadedFiles) { + //注意这里,file1与uploadedFiles中的某一个元素指向同一个对象 + if(file1 == uf) { + out.println("这是文件1:"); + out.println(); + } + + out.println("大小 : "+uf.getSize()+" 字节"); + out.println("类型 : "+uf.getContentType()); + out.println("文件名: "+uf.getSimpleName()); + out.println("全名 : "+uf.getFullName()); + out.println("路径名: "+uf.getPathName()); + + out.println(); + out.println("文件内容:"); + out.println("--------------------------------------"); + out.println(uf.getContent()); + out.println("--------------------------------------"); + + out.println(); + + File file = new File("E:/Douyu/douyu-examples-classes/uploadedFiles", uf.getSimpleName()); + + try { + uf.saveTo(file); + out.println("已保存到: "+file); + } catch(Exception e) { + out.println("出错了: "+e); + } + out.println(); + out.println(); + } + } + } +} diff --git a/douyu-examples/WEB-INF/src/FileUpload.vm b/douyu-examples/WEB-INF/src/FileUpload.vm new file mode 100644 index 0000000..ec04511 --- /dev/null +++ b/douyu-examples/WEB-INF/src/FileUpload.vm @@ -0,0 +1,13 @@ + +文件上传 + +
+ 文件1:
+ 文件2:
+ + 说明 :
+ + +
+ + \ No newline at end of file diff --git a/douyu-examples/WEB-INF/src/FormExample.java b/douyu-examples/WEB-INF/src/FormExample.java new file mode 100644 index 0000000..b3f28ea --- /dev/null +++ b/douyu-examples/WEB-INF/src/FormExample.java @@ -0,0 +1,7 @@ +@douyu.mvc.Controller +public class FormExample { + public void save(String name, int age, java.io.PrintWriter out) { + out.println("name = " + name); + out.println("age = " + age); + } +} \ No newline at end of file diff --git a/douyu-examples/WEB-INF/src/HelloWorld.java b/douyu-examples/WEB-INF/src/HelloWorld.java new file mode 100644 index 0000000..2580736 --- /dev/null +++ b/douyu-examples/WEB-INF/src/HelloWorld.java @@ -0,0 +1,9 @@ +import java.io.PrintWriter; +import douyu.mvc.Controller; + +@Controller +public class HelloWorld { + public void index(PrintWriter out) { + out.println("Hello Douyu World!"); + } +} \ No newline at end of file diff --git a/douyu-examples/WEB-INF/src/LocalStrings.properties b/douyu-examples/WEB-INF/src/LocalStrings.properties new file mode 100644 index 0000000..c647d1d --- /dev/null +++ b/douyu-examples/WEB-INF/src/LocalStrings.properties @@ -0,0 +1,53 @@ +# 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. + +# $Id: LocalStrings.properties 467217 2006-10-24 03:14:34Z markt $ + +# Default localized resources for example servlets +# This locale is en_US + +helloworld.title=Hello World! + +requestinfo.title=Request Information Example +requestinfo.label.method=Method: +requestinfo.label.requesturi=Request URI: +requestinfo.label.protocol=Protocol: +requestinfo.label.pathinfo=Path Info: +requestinfo.label.remoteaddr=Remote Address: + +requestheader.title=Request Header Example + +requestparams.title=Request Parameters Example +requestparams.params-in-req=Parameters in this request: +requestparams.no-params=No Parameters, Please enter some +requestparams.firstname=First Name: +requestparams.lastname=Last Name: + +cookies.title=Cookies Example +cookies.cookies=Your browser is sending the following cookies: +cookies.no-cookies=Your browser isn't sending any cookies +cookies.make-cookie=Create a cookie to send to your browser +cookies.name=Name: +cookies.value=Value: +cookies.set=You just sent the following cookie to your browser: + +sessions.title=Sessions Example +sessions.id=Session ID: +sessions.created=Created: +sessions.lastaccessed=Last Accessed: +sessions.data=The following data is in your session: +sessions.adddata=Add data to your session +sessions.dataname=Name of Session Attribute: +sessions.datavalue=Value of Session Attribute: diff --git a/douyu-examples/WEB-INF/src/ModelInjectExample.java b/douyu-examples/WEB-INF/src/ModelInjectExample.java new file mode 100644 index 0000000..7d50e75 --- /dev/null +++ b/douyu-examples/WEB-INF/src/ModelInjectExample.java @@ -0,0 +1,13 @@ +import java.io.PrintWriter; + +import douyu.mvc.Controller; +import models.Consumer; + +@Controller +public class ModelInjectExample { + //ModelInjectExample?consumer.name=zhh&consumer.address.country=china&consumer.address.city=hangzhou + + public void index(Consumer consumer, PrintWriter out) { + out.println("consumer=" + consumer); + } +} diff --git a/douyu-examples/WEB-INF/src/RequestHeaderExample.java b/douyu-examples/WEB-INF/src/RequestHeaderExample.java new file mode 100644 index 0000000..cc641b8 --- /dev/null +++ b/douyu-examples/WEB-INF/src/RequestHeaderExample.java @@ -0,0 +1,76 @@ +///* +//* 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. +//*/ +///* $Id: RequestHeaderExample.java 982412 2010-08-04 21:55:19Z markt $ +// * +// */ +// +//import java.io.PrintWriter; +//import java.util.Enumeration; +//import java.util.ResourceBundle; +// +//import javax.servlet.http.HttpServletRequest; +//import javax.servlet.http.HttpServletResponse; +// +//import util.HTMLFilter; +// +///** +// * Example servlet showing request headers +// * +// * @author James Duncan Davidson +// */ +//@douyu.mvc.Controller +//public class RequestHeaderExample { +// private static final ResourceBundle RB = ResourceBundle.getBundle("LocalStrings"); +// +// public void index(HttpServletRequest request, HttpServletResponse response, PrintWriter out) { +// response.setContentType("text/html"); +// out.println(""); +// out.println(""); +// out.println(""); +// +// String title = RB.getString("requestheader.title"); +// out.println("" + title + ""); +// out.println(""); +// out.println(""); +// +// // all links relative +// +// // XXX +// // making these absolute till we work out the +// // addition of a PathInfo issue +// +// out.println(""); +// out.println("\"view"); +// out.println(""); +// out.println("\"return\""); +// +// out.println("

" + title + "

"); +// out.println(""); +// Enumeration e = request.getHeaderNames(); +// while (e.hasMoreElements()) { +// String headerName = e.nextElement(); +// String headerValue = request.getHeader(headerName); +// out.println(""); +// } +// out.println("
"); +// out.println(HTMLFilter.filter(headerName)); +// out.println(""); +// out.println(HTMLFilter.filter(headerValue)); +// out.println("
"); +// } +// +//} diff --git a/douyu-examples/WEB-INF/src/RequestInfoExample.java b/douyu-examples/WEB-INF/src/RequestInfoExample.java new file mode 100644 index 0000000..63f8150 --- /dev/null +++ b/douyu-examples/WEB-INF/src/RequestInfoExample.java @@ -0,0 +1,96 @@ +///* +//* 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. +//*/ +///* $Id: RequestInfoExample.java 982412 2010-08-04 21:55:19Z markt $ +// * +// */ +// +//import java.io.PrintWriter; +//import java.util.ResourceBundle; +// +//import javax.servlet.http.HttpServletRequest; +//import javax.servlet.http.HttpServletResponse; +// +//import util.HTMLFilter; +// +///** +// * Example servlet showing request information. +// * +// * @author James Duncan Davidson +// */ +//@douyu.mvc.Controller +//public class RequestInfoExample { +// +// private static final ResourceBundle RB = ResourceBundle.getBundle("LocalStrings"); +// +// public void index(HttpRequest request, HttpResponse response, PrintWriter out) { +// response.setContentType("text/html"); +// out.println(""); +// out.println(""); +// out.println(""); +// +// String title = RB.getString("requestinfo.title"); +// out.println("" + title + ""); +// out.println(""); +// out.println(""); +// +// // img stuff not req'd for source code html showing +// // all links relative! +// +// // XXX +// // making these absolute till we work out the +// // addition of a PathInfo issue +// +// out.println(""); +// out.println("\"view"); +// out.println(""); +// out.println("\"return\""); +// +// out.println("

" + title + "

"); +// out.println("
"); +// out.println(RB.getString("requestinfo.label.method")); +// out.println(""); +// out.println(request.getMethod()); +// out.println("
"); +// out.println(RB.getString("requestinfo.label.requesturi")); +// out.println(""); +// out.println(HTMLFilter.filter(request.getRequestURI())); +// out.println("
"); +// out.println(RB.getString("requestinfo.label.protocol")); +// out.println(""); +// out.println(request.getProtocol()); +// out.println("
"); +// out.println(RB.getString("requestinfo.label.pathinfo")); +// out.println(""); +// out.println(HTMLFilter.filter(request.getPathInfo())); +// out.println("
"); +// out.println(RB.getString("requestinfo.label.remoteaddr")); +// +// String cipherSuite = (String) request.getAttribute("javax.servlet.request.cipher_suite"); +// out.println(""); +// out.println(request.getRemoteAddr()); +// out.println("
"); +// +// if (cipherSuite != null) { +// out.println(""); +// out.println("SSLCipherSuite:"); +// out.println(""); +// out.println(""); +// out.println(request.getAttribute("javax.servlet.request.cipher_suite")); +// out.println(""); +// } +// } +//} diff --git a/douyu-examples/WEB-INF/src/RequestParamExample.java b/douyu-examples/WEB-INF/src/RequestParamExample.java new file mode 100644 index 0000000..6a8c61e --- /dev/null +++ b/douyu-examples/WEB-INF/src/RequestParamExample.java @@ -0,0 +1,89 @@ +/* +* 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. +*/ +/* $Id: RequestParamExample.java 982412 2010-08-04 21:55:19Z markt $ + * + */ + +import java.io.PrintWriter; +import java.util.ResourceBundle; + +import douyu.http.HttpResponse; + +import util.HTMLFilter; + +/** + * Example servlet showing request headers + * + * @author James Duncan Davidson + */ + +@douyu.mvc.Controller +public class RequestParamExample { + + private static final ResourceBundle RB = ResourceBundle.getBundle("LocalStrings"); + + public void index(HttpResponse response, PrintWriter out, String firstName, String lastName) { + response.setContentType("text/html"); + out.println(""); + out.println(""); + out.println(""); + + String title = RB.getString("requestparams.title"); + out.println("" + title + ""); + out.println(""); + out.println(""); + + // img stuff not req'd for source code html showing + + // all links relative + + // XXX + // making these absolute till we work out the + // addition of a PathInfo issue + + out.println(""); + out.println("\"view"); + out.println(""); + out.println("\"return\""); + + out.println("

" + title + "

"); + out.println(RB.getString("requestparams.params-in-req") + "
"); + if (firstName != null || lastName != null) { + out.println(RB.getString("requestparams.firstname")); + out.println(" = " + HTMLFilter.filter(firstName) + "
"); + out.println(RB.getString("requestparams.lastname")); + out.println(" = " + HTMLFilter.filter(lastName)); + } else { + out.println(RB.getString("requestparams.no-params")); + } + out.println("

"); + out.print("

"); + out.println(RB.getString("requestparams.firstname")); + out.println(""); + out.println("
"); + out.println(RB.getString("requestparams.lastname")); + out.println(""); + out.println("
"); + out.println(""); + out.println("
"); + + out.println(""); + out.println(""); + } +} diff --git a/douyu-examples/WEB-INF/src/ViewTest.ftl b/douyu-examples/WEB-INF/src/ViewTest.ftl new file mode 100644 index 0000000..499ebba --- /dev/null +++ b/douyu-examples/WEB-INF/src/ViewTest.ftl @@ -0,0 +1,14 @@ + + +ViewManager Test + + + +

+FreeMaker: invoke douyu.mvc.ViewManager.out(String viewFileName): +
+name = ${name!}
+age = ${age!}
+ + + \ No newline at end of file diff --git a/douyu-examples/WEB-INF/src/ViewTest.vm b/douyu-examples/WEB-INF/src/ViewTest.vm new file mode 100644 index 0000000..b8eaa4b --- /dev/null +++ b/douyu-examples/WEB-INF/src/ViewTest.vm @@ -0,0 +1,14 @@ + + +ViewManager Test + + + +

+Veloctiy: invoke douyu.mvc.ViewManager.out(String viewFileName): +
+name = ${name}
+age = ${age}
+ + + \ No newline at end of file diff --git a/douyu-examples/WEB-INF/src/WebSocketExample.html b/douyu-examples/WEB-INF/src/WebSocketExample.html new file mode 100644 index 0000000..848e616 --- /dev/null +++ b/douyu-examples/WEB-INF/src/WebSocketExample.html @@ -0,0 +1,110 @@ + + + WebSocket Chat + + + +
+
+
+ Username:  +
+ +
+ + +

+This is a demonstration of the Douyu websocket server(adapted from Jetty). +

+ + + + diff --git a/douyu-examples/WEB-INF/src/WebSocketExample.java b/douyu-examples/WEB-INF/src/WebSocketExample.java new file mode 100644 index 0000000..1140415 --- /dev/null +++ b/douyu-examples/WEB-INF/src/WebSocketExample.java @@ -0,0 +1,54 @@ +import java.io.IOException; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import douyu.http.WebSocket; +import douyu.mvc.Context; +import douyu.mvc.Controller; + +@Controller +public class WebSocketExample { + public void index(Context c) { + c.out("WebSocketExample.html"); + } + + public void join(Context c) { + c.setWebSocket(new MyWebSocket()); + } + + public static class MyWebSocket implements WebSocket { + private final static Set members = new CopyOnWriteArraySet(); + + private Outbound outbound; + + @Override + public void onConnect(Outbound outbound) { + this.outbound = outbound; + members.add(this); + } + + @Override + public void onDisconnect() { + members.remove(this); + } + + @Override + public void onMessage(int type, String data) { + if (data.indexOf("disconnect") >= 0) + outbound.close(); + else { + for (MyWebSocket member : members) { + try { + member.outbound.send(type, data); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + @Override + public void onMessage(int type, byte[] data) { + } + } +} diff --git a/douyu-examples/WEB-INF/src/models/Address.java b/douyu-examples/WEB-INF/src/models/Address.java new file mode 100644 index 0000000..4e69a23 --- /dev/null +++ b/douyu-examples/WEB-INF/src/models/Address.java @@ -0,0 +1,18 @@ +package models; + +import douyu.mvc.Model; + +@Model +public class Address { + private String country; + private String city; + + public void set(String country, String city) { + this.country = country; + this.city = city; + } + + public String toString() { + return "Address[country=" + country + ", city=" + city + "]"; + } +} diff --git a/douyu-examples/WEB-INF/src/models/Consumer.java b/douyu-examples/WEB-INF/src/models/Consumer.java new file mode 100644 index 0000000..66c0c08 --- /dev/null +++ b/douyu-examples/WEB-INF/src/models/Consumer.java @@ -0,0 +1,18 @@ +package models; + +import douyu.mvc.Model; + +@Model +public class Consumer { + private String name; + private Address address; + + public void set(String name, Address address) { + this.name = name; + this.address = address; + } + + public String toString() { + return "Consumer[name=" + name + ", address=" + address + "]"; + } +} diff --git a/douyu-examples/WEB-INF/src/models/MyModel.java b/douyu-examples/WEB-INF/src/models/MyModel.java new file mode 100644 index 0000000..00492bf --- /dev/null +++ b/douyu-examples/WEB-INF/src/models/MyModel.java @@ -0,0 +1,25 @@ +package models; + +import java.io.PrintWriter; + +import douyu.mvc.Model; + +@Model +public class MyModel { + private int f1; + private String f2; + private MySubModel subModel; + + public void set(int f1, String f2, MySubModel subModel, PrintWriter out) { + this.f1 = f1; + this.f2 = f2; + this.subModel = subModel; + + out.println("invoke MyModel.set(): subModel.getParentModel()==this : " + (subModel.getParentModel() == this)); + out.println(); + } + + public String toString() { + return "MyModel[f1=" + f1 + ", f2=" + f2 + ", subModel=" + subModel + "]"; + } +} diff --git a/douyu-examples/WEB-INF/src/models/MySubModel.java b/douyu-examples/WEB-INF/src/models/MySubModel.java new file mode 100644 index 0000000..c4252ce --- /dev/null +++ b/douyu-examples/WEB-INF/src/models/MySubModel.java @@ -0,0 +1,25 @@ +package models; + +import douyu.mvc.Model; + +@Model +public class MySubModel { + + private int f1; + private String f2; + private MyModel parentModel; + + public void set(int f1, String f2, MyModel parentModel) { + this.f1 = f1; + this.f2 = f2; + this.parentModel = parentModel; + } + + public MyModel getParentModel() { + return parentModel; + } + + public String toString() { + return "MySubModel[f1=" + f1 + ", f2=" + f2 + "]"; + } +} diff --git a/douyu-examples/WEB-INF/src/util/HTMLFilter.java b/douyu-examples/WEB-INF/src/util/HTMLFilter.java new file mode 100644 index 0000000..019aa71 --- /dev/null +++ b/douyu-examples/WEB-INF/src/util/HTMLFilter.java @@ -0,0 +1,69 @@ +/* +* 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 util; + +/** + * HTML filter utility. + * + * @author Craig R. McClanahan + * @author Tim Tye + * @version $Id: HTMLFilter.java 939315 2010-04-29 14:11:01Z kkolinko $ + */ + +public final class HTMLFilter { + + + /** + * Filter the specified message string for characters that are sensitive + * in HTML. This avoids potential attacks caused by including JavaScript + * codes in the request URL that is often reported in error messages. + * + * @param message The message string to be filtered + */ + public static String filter(String message) { + + if (message == null) + return (null); + + char content[] = new char[message.length()]; + message.getChars(0, message.length(), content, 0); + StringBuilder result = new StringBuilder(content.length + 50); + for (int i = 0; i < content.length; i++) { + switch (content[i]) { + case '<': + result.append("<"); + break; + case '>': + result.append(">"); + break; + case '&': + result.append("&"); + break; + case '"': + result.append("""); + break; + default: + result.append(content[i]); + } + } + return (result.toString()); + + } + + +} + diff --git a/douyu-examples/WEB-INF/web.xml b/douyu-examples/WEB-INF/web.xml new file mode 100644 index 0000000..384fe40 --- /dev/null +++ b/douyu-examples/WEB-INF/web.xml @@ -0,0 +1,97 @@ + + + + + Douyu Examples + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Douyu Controller Filter + org.douyu.mvc.ControllerFilter + true + + + + + srcDir + src + + + + classesDir + classes + + + + isDevMode + true + + + + javacEncoding + UTF-8 + + + + viewManagerProviderConfig + + org.douyu.plugins.jsp.JspViewManagerProvider=jsp|jspx; + org.douyu.plugins.velocity.VelocityViewManagerProvider=vm; + org.douyu.plugins.freemarker.FreeMarkerViewManagerProvider=ftl + + + + + + Douyu Controller Filter + /* + REQUEST + + ASYNC + + diff --git a/douyu-examples/form.html b/douyu-examples/form.html new file mode 100644 index 0000000..29e97e3 --- /dev/null +++ b/douyu-examples/form.html @@ -0,0 +1,17 @@ + + + + +Form Example + + + + +
+ :
+ : + +
+ + + diff --git a/douyu-examples/index.html b/douyu-examples/index.html new file mode 100644 index 0000000..54f0c78 --- /dev/null +++ b/douyu-examples/index.html @@ -0,0 +1,88 @@ + + + + +Douyu Examples + + + +Douyu +Samples +
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
HelloWorld
FormExample
Model Inject Example
DefaultAction
RequestParamExample
RequestInfoExample
RequestHeaderExample
CookieExample
SessionExample
FileUpload (Tomcat7/Jetty8)
SPVelocityFreeMaker Example
+ +
+Async +Samples (Only Tomcat7) +
  + + + + + + + + + + + + + + + + + + + + + + +
Async0
Async1
Async2
Async3
AsyncStock
+ + + diff --git a/douyu-examples/jsp/ViewTest.jsp b/douyu-examples/jsp/ViewTest.jsp new file mode 100644 index 0000000..58cf550 --- /dev/null +++ b/douyu-examples/jsp/ViewTest.jsp @@ -0,0 +1,17 @@ + + + +ViewManager Test + + + +

+JSP: invoke douyu.mvc.ViewManager.out(String viewFileName): +
+name = ${name}
+age = ${age}
+ + + + + diff --git a/douyu-examples/jsp/async/async1.jsp b/douyu-examples/jsp/async/async1.jsp new file mode 100644 index 0000000..b7e3da6 --- /dev/null +++ b/douyu-examples/jsp/async/async1.jsp @@ -0,0 +1,26 @@ +<%-- + 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. +--%> +<%@page session="false"%> +Output from async1.jsp +Type is <%=request.getDispatcherType()%> +<% +System.out.println("Inside Async 1"); + if (request.isAsyncStarted()) { + request.getAsyncContext().complete(); + } +%> +Completed async request at <%=new java.sql.Date(System.currentTimeMillis())%> \ No newline at end of file diff --git a/douyu-examples/jsp/async/async1.jsp.html b/douyu-examples/jsp/async/async1.jsp.html new file mode 100644 index 0000000..9a52e71 --- /dev/null +++ b/douyu-examples/jsp/async/async1.jsp.html @@ -0,0 +1,28 @@ +
+<%--
+ 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.
+--%>
+<%@page session="false"%>
+Output from async1.jsp
+Type is <%=request.getDispatcherType()%>
+<%
+System.out.println("Inside Async 1");
+  if (request.isAsyncStarted()) {
+    request.getAsyncContext().complete();
+  }
+%>
+Completed async request at <%=new java.sql.Date(System.currentTimeMillis())%>
+
diff --git a/douyu-examples/jsp/async/async3.jsp b/douyu-examples/jsp/async/async3.jsp new file mode 100644 index 0000000..fd54054 --- /dev/null +++ b/douyu-examples/jsp/async/async3.jsp @@ -0,0 +1,20 @@ +<%-- + 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. +--%> +<%@page session="false"%> +Output from async3.jsp +Type is <%=request.getDispatcherType()%> +Completed async 3 request at <%=new java.sql.Date(System.currentTimeMillis())%> \ No newline at end of file diff --git a/douyu-examples/jsp/async/async3.jsp.html b/douyu-examples/jsp/async/async3.jsp.html new file mode 100644 index 0000000..671a409 --- /dev/null +++ b/douyu-examples/jsp/async/async3.jsp.html @@ -0,0 +1,22 @@ +
+<%--
+ 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.
+--%>
+<%@page session="false"%>
+Output from async3.jsp
+Type is <%=request.getDispatcherType()%>
+Completed async 3 request at <%=new java.sql.Date(System.currentTimeMillis())%>
+
diff --git a/douyu-examples/jsp/async/index.jsp b/douyu-examples/jsp/async/index.jsp new file mode 100644 index 0000000..5f53107 --- /dev/null +++ b/douyu-examples/jsp/async/index.jsp @@ -0,0 +1,69 @@ +<%-- + 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. +--%> +<%@page session="false"%> + +
+Use cases:
+
+1. Simple dispatch 
+ - servlet does startAsync()
+ - background thread calls ctx.dispatch() 
+   "> Async 0 
+ 
+2. Simple dispatch
+ - servlet does startAsync()
+ - background thread calls dispatch(/path/to/jsp)
+   "> Async 1 
+ 
+3. Simple dispatch
+ - servlet does startAsync()
+ - background thread calls writes and calls complete()
+   "> Async 2 
+
+4. Simple dispatch
+ - servlet does a startAsync()
+ - servlet calls dispatch(/path/to/jsp)
+ - servlet calls complete()
+   "> Async 3 
+
+3. Timeout s1
+ - servlet does a startAsync()
+ - servlet does a setAsyncTimeout
+ - returns - waits for timeout to happen should return error page 
+ 
+4. Timeout s2
+ - servlet does a startAsync()
+ - servlet does a setAsyncTimeout
+ - servlet does a addAsyncListener
+ - returns - waits for timeout to happen and listener invoked 
+ 
+5. Dispatch to asyncSupported=false servlet
+ - servlet1 does a startAsync()
+ - servlet1 dispatches to dispatch(/servlet2)
+ - the container calls complete() after servlet2 is complete
+ - TODO
+ 
+6. Chained dispatch
+ - servlet1 does a startAsync
+ - servlet1 does a dispatch to servlet2 (asyncsupported=true)
+ - servlet2 does a dispatch to servlet3 (asyncsupported=true)
+ - servlet3 does a dispatch to servlet4 (asyncsupported=false) 
+ 
+ 
+7. Stock ticker
+   "> StockTicker 
+
\ No newline at end of file diff --git a/douyu-examples/jsp/async/index.jsp.html b/douyu-examples/jsp/async/index.jsp.html new file mode 100644 index 0000000..98e794c --- /dev/null +++ b/douyu-examples/jsp/async/index.jsp.html @@ -0,0 +1,71 @@ +
+<%--
+ 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.
+--%>
+<%@page session="false"%>
+
+<pre>
+Use cases:
+
+1. Simple dispatch 
+ - servlet does startAsync()
+ - background thread calls ctx.dispatch() 
+   <a href="<%=response.encodeURL("/examples/async/async0")%>"> Async 0 </a>
+ 
+2. Simple dispatch
+ - servlet does startAsync()
+ - background thread calls dispatch(/path/to/jsp)
+   <a href="<%=response.encodeURL("/examples/async/async1")%>"> Async 1 </a>
+ 
+3. Simple dispatch
+ - servlet does startAsync()
+ - background thread calls writes and calls complete()
+   <a href="<%=response.encodeURL("/examples/async/async2")%>"> Async 2 </a>
+
+4. Simple dispatch
+ - servlet does a startAsync()
+ - servlet calls dispatch(/path/to/jsp)
+ - servlet calls complete()
+   <a href="<%=response.encodeURL("/examples/async/async3")%>"> Async 3 </a>
+
+3. Timeout s1
+ - servlet does a startAsync()
+ - servlet does a setAsyncTimeout
+ - returns - waits for timeout to happen should return error page 
+ 
+4. Timeout s2
+ - servlet does a startAsync()
+ - servlet does a setAsyncTimeout
+ - servlet does a addAsyncListener
+ - returns - waits for timeout to happen and listener invoked 
+ 
+5. Dispatch to asyncSupported=false servlet
+ - servlet1 does a startAsync()
+ - servlet1 dispatches to dispatch(/servlet2)
+ - the container calls complete() after servlet2 is complete
+ - TODO
+ 
+6. Chained dispatch
+ - servlet1 does a startAsync
+ - servlet1 does a dispatch to servlet2 (asyncsupported=true)
+ - servlet2 does a dispatch to servlet3 (asyncsupported=true)
+ - servlet3 does a dispatch to servlet4 (asyncsupported=false) 
+ 
+ 
+7. Stock ticker
+   <a href="<%=response.encodeURL("/examples/async/stock")%>"> StockTicker </a>
+</pre>
+
diff --git a/douyu-http/pom.xml b/douyu-http/pom.xml new file mode 100644 index 0000000..52d64db --- /dev/null +++ b/douyu-http/pom.xml @@ -0,0 +1,27 @@ + + 4.0.0 + + com.codefollower.douyu + douyu + 0.1.0 + + + douyu-http + jar + ${douyu.version} + douyu http + + + + com.codefollower.douyu + douyu-core + ${douyu.version} + + + com.codefollower.douyu + douyu-netty + ${douyu.version} + + + diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/AbstractDiskHttpData.java b/douyu-http/src/main/java/com/codefollower/douyu/http/AbstractDiskHttpData.java new file mode 100644 index 0000000..c09b431 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/AbstractDiskHttpData.java @@ -0,0 +1,356 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; + +/** + * Abstract Disk HttpData implementation + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @author Frederic Bregier + * + */ +public abstract class AbstractDiskHttpData extends AbstractHttpData { + + protected File file = null; + + private boolean isRenamed = false; + + private FileChannel fileChannel = null; + + public AbstractDiskHttpData(String name, Charset charset, long size) + throws NullPointerException, IllegalArgumentException { + super(name, charset, size); + } + + /** + * + * @return the real DiskFilename (basename) + */ + protected abstract String getDiskFilename(); + /** + * + * @return the default prefix + */ + protected abstract String getPrefix(); + /** + * + * @return the default base Directory + */ + protected abstract String getBaseDirectory(); + /** + * + * @return the default postfix + */ + protected abstract String getPostfix(); + /** + * + * @return True if the file should be deleted on Exit by default + */ + protected abstract boolean deleteOnExit(); + + /** + * + * @return a new Temp File from getDiskFilename(), default prefix, postfix and baseDirectory + * @throws IOException + */ + private File tempFile() throws IOException { + String newpostfix = null; + String diskFilename = getDiskFilename(); + if (diskFilename != null) { + newpostfix = "_" + diskFilename; + } else { + newpostfix = getPostfix(); + } + File tmpFile; + if (getBaseDirectory() == null) { + // create a temporary file + tmpFile = File.createTempFile(getPrefix(), newpostfix); + } else { + tmpFile = File.createTempFile(getPrefix(), newpostfix, new File( + getBaseDirectory())); + } + if (deleteOnExit()) { + tmpFile.deleteOnExit(); + } + return tmpFile; + } + + @Override + public void setContent(ChannelBuffer buffer) throws IOException { + if (buffer == null) { + throw new NullPointerException("buffer"); + } + size = buffer.readableBytes(); + if (definedSize > 0 && definedSize < size) { + throw new IOException("Out of size: " + size + " > " + definedSize); + } + if (file == null) { + file = tempFile(); + } + if (buffer.readableBytes() == 0) { + // empty file + file.createNewFile(); + return; + } + FileOutputStream outputStream = new FileOutputStream(file); + FileChannel localfileChannel = outputStream.getChannel(); + ByteBuffer byteBuffer = buffer.toByteBuffer(); + int written = 0; + while (written < size) { + written += localfileChannel.write(byteBuffer); + localfileChannel.force(false); + } + buffer.readerIndex(buffer.readerIndex() + written); + localfileChannel.close(); + completed = true; + } + + @Override + public void addContent(ChannelBuffer buffer, boolean last) + throws IOException { + if (buffer != null) { + int localsize = buffer.readableBytes(); + if (definedSize > 0 && definedSize < size + localsize) { + throw new IOException("Out of size: " + (size + localsize) + + " > " + definedSize); + } + ByteBuffer byteBuffer = buffer.toByteBuffer(); + int written = 0; + if (file == null) { + file = tempFile(); + } + if (fileChannel == null) { + FileOutputStream outputStream = new FileOutputStream(file); + fileChannel = outputStream.getChannel(); + } + while (written < localsize) { + written += fileChannel.write(byteBuffer); + fileChannel.force(false); + } + size += localsize; + buffer.readerIndex(buffer.readerIndex() + written); + } + if (last) { + if (file == null) { + file = tempFile(); + } + if (fileChannel == null) { + FileOutputStream outputStream = new FileOutputStream(file); + fileChannel = outputStream.getChannel(); + } + fileChannel.close(); + fileChannel = null; + completed = true; + } else { + if (buffer == null) { + throw new NullPointerException("buffer"); + } + } + } + + @Override + public void setContent(File file) throws IOException { + if (this.file != null) { + delete(); + } + this.file = file; + size = file.length(); + isRenamed = true; + completed = true; + } + + @Override + public void setContent(InputStream inputStream) throws IOException { + if (inputStream == null) { + throw new NullPointerException("inputStream"); + } + if (file != null) { + delete(); + } + file = tempFile(); + FileOutputStream outputStream = new FileOutputStream(file); + FileChannel localfileChannel = outputStream.getChannel(); + byte[] bytes = new byte[4096*4]; + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + int read = inputStream.read(bytes); + int written = 0; + while (read > 0) { + byteBuffer.position(read).flip(); + written += localfileChannel.write(byteBuffer); + localfileChannel.force(false); + read = inputStream.read(bytes); + } + size = written; + if (definedSize > 0 && definedSize < size) { + file.delete(); + file = null; + throw new IOException("Out of size: " + size + " > " + definedSize); + } + isRenamed = true; + completed = true; + } + + @Override + public void delete() { + if (! isRenamed) { + if (file != null) { + file.delete(); + } + } + } + + @Override + public byte[] get() throws IOException { + if (file == null) { + return new byte[0]; + } + return readFrom(file); + } + + @Override + public ChannelBuffer getChannelBuffer() throws IOException { + if (file == null) { + return ChannelBuffers.EMPTY_BUFFER; + } + byte[] array = readFrom(file); + return ChannelBuffers.wrappedBuffer(array); + } + + @Override + public ChannelBuffer getChunk(int length) throws IOException { + if (file == null || length == 0) { + return ChannelBuffers.EMPTY_BUFFER; + } + if (fileChannel == null) { + FileInputStream inputStream = new FileInputStream(file); + fileChannel = inputStream.getChannel(); + } + int read = 0; + ByteBuffer byteBuffer = ByteBuffer.allocate(length); + while (read < length) { + int readnow = fileChannel.read(byteBuffer); + if (readnow == -1) { + fileChannel.close(); + fileChannel = null; + break; + } else { + read += readnow; + } + } + if (read == 0) { + return ChannelBuffers.EMPTY_BUFFER; + } + byteBuffer.flip(); + ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(byteBuffer); + buffer.readerIndex(0); + buffer.writerIndex(read); + return buffer; + } + + @Override + public String getString() throws IOException { + return getString(HttpCodecUtil.DEFAULT_CHARSET); + } + + @Override + public String getString(Charset encoding) throws IOException { + if (file == null) { + return ""; + } + if (encoding == null) { + byte[] array = readFrom(file); + return new String(array, HttpCodecUtil.DEFAULT_CHARSET); + } + byte[] array = readFrom(file); + return new String(array, encoding); + } + + @Override + public boolean isInMemory() { + return false; + } + + @Override + public boolean renameTo(File dest) throws IOException { + if (dest == null) { + throw new NullPointerException("dest"); + } + if (!file.renameTo(dest)) { + // must copy + FileInputStream inputStream = new FileInputStream(file); + FileOutputStream outputStream = new FileOutputStream(dest); + FileChannel in = inputStream.getChannel(); + FileChannel out = outputStream.getChannel(); + long destsize = in.transferTo(0, size, out); + if (destsize == size) { + file.delete(); + file = dest; + isRenamed = true; + return true; + } else { + dest.delete(); + return false; + } + } + file = dest; + isRenamed = true; + return true; + } + + /** + * Utility function + * @param src + * @return the array of bytes + * @throws IOException + */ + private byte[] readFrom(File src) throws IOException { + long srcsize = src.length(); + if (srcsize > Integer.MAX_VALUE) { + throw new IllegalArgumentException( + "File too big to be loaded in memory"); + } + FileInputStream inputStream = new FileInputStream(src); + FileChannel fileChannel = inputStream.getChannel(); + byte[] array = new byte[(int) srcsize]; + ByteBuffer byteBuffer = ByteBuffer.wrap(array); + int read = 0; + while (read < srcsize) { + read += fileChannel.read(byteBuffer); + } + fileChannel.close(); + return array; + } + + @Override + public File getFile() throws IOException { + return file; + } + +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/AbstractHttpData.java b/douyu-http/src/main/java/com/codefollower/douyu/http/AbstractHttpData.java new file mode 100644 index 0000000..027d556 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/AbstractHttpData.java @@ -0,0 +1,108 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.nio.charset.Charset; + +/** + * Abstract HttpData implementation + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @author Frederic Bregier + * + */ +public abstract class AbstractHttpData implements HttpData { + + protected final String name; + + protected long definedSize = 0; + + protected long size = 0; + + protected Charset charset = HttpCodecUtil.DEFAULT_CHARSET; + + protected boolean completed = false; + + public AbstractHttpData(String name, Charset charset, long size) + throws NullPointerException, IllegalArgumentException { + if (name == null) { + throw new NullPointerException("name"); + } + name = name.trim(); + if (name.length() == 0) { + throw new IllegalArgumentException("empty name"); + } + + for (int i = 0; i < name.length(); i ++) { + char c = name.charAt(i); + if (c > 127) { + throw new IllegalArgumentException( + "name contains non-ascii character: " + name); + } + + // Check prohibited characters. + switch (c) { + case '=': + case ',': + case ';': + case ' ': + case '\t': + case '\r': + case '\n': + case '\f': + case 0x0b: // Vertical tab + throw new IllegalArgumentException( + "name contains one of the following prohibited characters: " + + "=,; \\t\\r\\n\\v\\f: " + name); + } + } + this.name = name; + if (charset != null) { + setCharset(charset); + } + definedSize = size; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isCompleted() { + return completed; + } + + @Override + public Charset getCharset() { + return charset; + } + + @Override + public void setCharset(Charset charset) { + if (charset == null) { + throw new NullPointerException("charset"); + } + this.charset = charset; + } + + @Override + public long length() { + return size; + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/AbstractMemoryHttpData.java b/douyu-http/src/main/java/com/codefollower/douyu/http/AbstractMemoryHttpData.java new file mode 100644 index 0000000..3bcffa5 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/AbstractMemoryHttpData.java @@ -0,0 +1,235 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; + +/** + * Abstract Memory HttpData implementation + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @author Frederic Bregier + * + */ +public abstract class AbstractMemoryHttpData extends AbstractHttpData { + + private ChannelBuffer channelBuffer = null; + + private int chunkPosition = 0; + + protected boolean isRenamed = false; + + public AbstractMemoryHttpData(String name, Charset charset, long size) + throws NullPointerException, IllegalArgumentException { + super(name, charset, size); + } + + @Override + public void setContent(ChannelBuffer buffer) throws IOException { + if (buffer == null) { + throw new NullPointerException("buffer"); + } + long localsize = buffer.readableBytes(); + if (definedSize > 0 && definedSize < localsize) { + throw new IOException("Out of size: " + localsize + " > " + + definedSize); + } + channelBuffer = buffer; + size = localsize; + completed = true; + } + + @Override + public void setContent(InputStream inputStream) throws IOException { + if (inputStream == null) { + throw new NullPointerException("inputStream"); + } + ChannelBuffer buffer = ChannelBuffers.dynamicBuffer(); + byte[] bytes = new byte[4096*4]; + int read = inputStream.read(bytes); + int written = 0; + while (read > 0) { + buffer.writeBytes(bytes); + written += read; + read = inputStream.read(bytes); + } + size = written; + if (definedSize > 0 && definedSize < size) { + throw new IOException("Out of size: " + size + " > " + definedSize); + } + channelBuffer = buffer; + completed = true; + } + + @Override + public void addContent(ChannelBuffer buffer, boolean last) + throws IOException { + if (buffer != null) { + long localsize = buffer.readableBytes(); + if (definedSize > 0 && definedSize < size + localsize) { + throw new IOException("Out of size: " + (size + localsize) + + " > " + definedSize); + } + size += localsize; + if (channelBuffer == null) { + channelBuffer = buffer; + } else { + channelBuffer = ChannelBuffers.wrappedBuffer( + channelBuffer, buffer); + } + } + if (last) { + completed = true; + } else { + if (buffer == null) { + throw new NullPointerException("buffer"); + } + } + } + + @Override + public void setContent(File file) throws IOException { + if (file == null) { + throw new NullPointerException("file"); + } + long newsize = file.length(); + if (newsize > Integer.MAX_VALUE) { + throw new IllegalArgumentException( + "File too big to be loaded in memory"); + } + FileInputStream inputStream = new FileInputStream(file); + FileChannel fileChannel = inputStream.getChannel(); + byte[] array = new byte[(int) newsize]; + ByteBuffer byteBuffer = ByteBuffer.wrap(array); + int read = 0; + while (read < newsize) { + read += fileChannel.read(byteBuffer); + } + fileChannel.close(); + byteBuffer.flip(); + channelBuffer = ChannelBuffers.wrappedBuffer(byteBuffer); + size = newsize; + completed = true; + } + + @Override + public void delete() { + // nothing to do + } + + @Override + public byte[] get() { + if (channelBuffer == null) { + return new byte[0]; + } + byte[] array = new byte[channelBuffer.readableBytes()]; + channelBuffer.getBytes(channelBuffer.readerIndex(), array); + return array; + } + + @Override + public String getString() { + return getString(HttpCodecUtil.DEFAULT_CHARSET); + } + + @Override + public String getString(Charset encoding) { + if (channelBuffer == null) { + return ""; + } + if (encoding == null) { + return getString(HttpCodecUtil.DEFAULT_CHARSET); + } + return channelBuffer.toString(encoding); + } + + /** + * Utility to go from a In Memory FileUpload + * to a Disk (or another implementation) FileUpload + * @return the attached ChannelBuffer containing the actual bytes + */ + @Override + public ChannelBuffer getChannelBuffer() { + return channelBuffer; + } + + @Override + public ChannelBuffer getChunk(int length) throws IOException { + if (channelBuffer == null || length == 0 || channelBuffer.readableBytes() == 0) { + chunkPosition = 0; + return ChannelBuffers.EMPTY_BUFFER; + } + int sizeLeft = channelBuffer.readableBytes() - chunkPosition; + if (sizeLeft == 0) { + chunkPosition = 0; + return ChannelBuffers.EMPTY_BUFFER; + } + int sliceLength = length; + if (sizeLeft < length) { + sliceLength = sizeLeft; + } + ChannelBuffer chunk = channelBuffer.slice(chunkPosition, sliceLength); + chunkPosition += sliceLength; + return chunk; + } + + @Override + public boolean isInMemory() { + return true; + } + + @Override + public boolean renameTo(File dest) throws IOException { + if (dest == null) { + throw new NullPointerException("dest"); + } + if (channelBuffer == null) { + // empty file + dest.createNewFile(); + isRenamed = true; + return true; + } + int length = channelBuffer.readableBytes(); + FileOutputStream outputStream = new FileOutputStream(dest); + FileChannel fileChannel = outputStream.getChannel(); + ByteBuffer byteBuffer = channelBuffer.toByteBuffer(); + int written = 0; + while (written < length) { + written += fileChannel.write(byteBuffer); + fileChannel.force(false); + } + fileChannel.close(); + isRenamed = true; + return written == length; + } + + @Override + public File getFile() throws IOException { + throw new IOException("Not represented by a file"); + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/Attribute.java b/douyu-http/src/main/java/com/codefollower/douyu/http/Attribute.java new file mode 100644 index 0000000..38706e0 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/Attribute.java @@ -0,0 +1,40 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.io.IOException; + +/** + * Attribute interface + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @author Frederic Bregier + * + */ +public interface Attribute extends HttpData { + /** + * Returns the value of this HttpData. + */ + String getValue() throws IOException; + + /** + * Sets the value of this HttpData. + * @param value + */ + void setValue(String value) throws IOException; +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/CaseIgnoringComparator.java b/douyu-http/src/main/java/com/codefollower/douyu/http/CaseIgnoringComparator.java new file mode 100644 index 0000000..d0780f6 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/CaseIgnoringComparator.java @@ -0,0 +1,45 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.io.Serializable; +import java.util.Comparator; + +/** + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @version $Rev$, $Date$ + */ +final class CaseIgnoringComparator implements Comparator, Serializable { + + private static final long serialVersionUID = 4582133183775373862L; + + static final CaseIgnoringComparator INSTANCE = new CaseIgnoringComparator(); + + private CaseIgnoringComparator() { + super(); + } + + @Override + public int compare(String o1, String o2) { + return o1.compareToIgnoreCase(o2); + } + + private Object readResolve() { + return INSTANCE; + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/Constants.java b/douyu-http/src/main/java/com/codefollower/douyu/http/Constants.java new file mode 100644 index 0000000..f4bc459 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/Constants.java @@ -0,0 +1,33 @@ +/* + * 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 com.codefollower.douyu.http; + +public class Constants { + + /** + * Name of the system property containing + * the tomcat instance installation path + */ + public static final String CATALINA_BASE_PROP = "catalina.base"; + + + /** + * Has security been turned on? + */ + public static final boolean IS_SECURITY_ENABLED = + (System.getSecurityManager() != null); +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/Cookie.java b/douyu-http/src/main/java/com/codefollower/douyu/http/Cookie.java new file mode 100644 index 0000000..cd83d83 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/Cookie.java @@ -0,0 +1,156 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.util.Set; + +/** + * An HTTP Cookie. + * + * @author The Netty Project + * @author Trustin Lee + * @author Andy Taylor (andy.taylor@jboss.org) + * @version $Rev$, $Date$ + */ +public interface Cookie extends Comparable { + + /** + * Returns the name of this cookie. + */ + String getName(); + + /** + * Returns the value of this cookie. + */ + String getValue(); + + /** + * Sets the value of this cookie. + */ + void setValue(String value); + + /** + * Returns the domain of this cookie. + */ + String getDomain(); + + /** + * Sets the domain of this cookie. + */ + void setDomain(String domain); + + /** + * Returns the path of this cookie. + */ + String getPath(); + + /** + * Sets the path of this cookie. + */ + void setPath(String path); + + /** + * Returns the comment of this cookie. + */ + String getComment(); + + /** + * Sets the comment of this cookie. + */ + void setComment(String comment); + + /** + * Returns the max age of this cookie in seconds. + */ + int getMaxAge(); + + /** + * Sets the max age of this cookie in seconds. If {@code 0} is specified, + * this cookie will be removed by browser because it will be expired + * immediately. If {@code -1} is specified, this cookie will be removed + * when a user terminates browser. + */ + void setMaxAge(int maxAge); + + /** + * Returns the version of this cookie. + */ + int getVersion(); + + /** + * Sets the version of this cookie. + */ + void setVersion(int version); + + /** + * Returns the secure flag of this cookie. + */ + boolean isSecure(); + + /** + * Sets the secure flag of this cookie. + */ + void setSecure(boolean secure); + + /** + * Returns if this cookie cannot be accessed through client side script. + * This flag works only if the browser supports it. For more information, + * see here. + */ + boolean isHttpOnly(); + + /** + * Sets if this cookie cannot be accessed through client side script. + * This flag works only if the browser supports it. For more information, + * see here. + */ + void setHttpOnly(boolean httpOnly); + + /** + * Returns the comment URL of this cookie. + */ + String getCommentUrl(); + + /** + * Sets the comment URL of this cookie. + */ + void setCommentUrl(String commentUrl); + + /** + * Returns the discard flag of this cookie. + */ + boolean isDiscard(); + + /** + * Sets the discard flag of this cookie. + */ + void setDiscard(boolean discard); + + /** + * Returns the ports of this cookie. + */ + Set getPorts(); + + /** + * Sets the ports of this cookie. + */ + void setPorts(int... ports); + + /** + * Sets the ports of this cookie. + */ + void setPorts(Iterable ports); +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/CookieDecoder.java b/douyu-http/src/main/java/com/codefollower/douyu/http/CookieDecoder.java new file mode 100644 index 0000000..a360105 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/CookieDecoder.java @@ -0,0 +1,240 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Decodes an HTTP header value into {@link Cookie}s. This decoder can decode + * the HTTP cookie version 0, 1, and 2. + * + *
+ * {@link HttpRequest} req = ...;
+ * String value = req.getHeader("Cookie");
+ * Set<{@link Cookie}> cookies = new {@link CookieDecoder}().decode(value);
+ * 
+ * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @version $Rev$, $Date$ + * @see CookieEncoder + * + * @apiviz.stereotype utility + * @apiviz.has org.jboss.netty.handler.codec.http.Cookie oneway - - decodes + */ +public class CookieDecoder { + + private final static Pattern PATTERN = + Pattern.compile("(?:\\s|[;,])*\\$*([^;=]+)(?:=(?:[\"']((?:\\\\.|[^\"])*)[\"']|([^;,]*)))?(\\s*(?:[;,]+\\s*|$))"); + + private final static String COMMA = ","; + + /** + * Creates a new decoder. + */ + public CookieDecoder() { + super(); + } + + /** + * Decodes the specified HTTP header value into {@link Cookie}s. + * + * @return the decoded {@link Cookie}s + */ + public Set decode(String header) { + List names = new ArrayList(8); + List values = new ArrayList(8); + extractKeyValuePairs(header, names, values); + + if (names.isEmpty()) { + return Collections.emptySet(); + } + + int i; + int version = 0; + + // $Version is the only attribute that can appear before the actual + // cookie name-value pair. + if (names.get(0).equalsIgnoreCase(CookieHeaderNames.VERSION)) { + try { + version = Integer.parseInt(values.get(0)); + } catch (NumberFormatException e) { + // Ignore. + } + i = 1; + } else { + i = 0; + } + + if (names.size() <= i) { + // There's a version attribute, but nothing more. + return Collections.emptySet(); + } + + Set cookies = new TreeSet(); + for (; i < names.size(); i ++) { + String name = names.get(i); + String value = values.get(i); + if (value == null) { + value = ""; + } + + Cookie c = new DefaultCookie(name, value); + cookies.add(c); + + boolean discard = false; + boolean secure = false; + boolean httpOnly = false; + String comment = null; + String commentURL = null; + String domain = null; + String path = null; + int maxAge = -1; + List ports = new ArrayList(2); + + for (int j = i + 1; j < names.size(); j++, i++) { + name = names.get(j); + value = values.get(j); + + if (CookieHeaderNames.DISCARD.equalsIgnoreCase(name)) { + discard = true; + } else if (CookieHeaderNames.SECURE.equalsIgnoreCase(name)) { + secure = true; + } else if (CookieHeaderNames.HTTPONLY.equalsIgnoreCase(name)) { + httpOnly = true; + } else if (CookieHeaderNames.COMMENT.equalsIgnoreCase(name)) { + comment = value; + } else if (CookieHeaderNames.COMMENTURL.equalsIgnoreCase(name)) { + commentURL = value; + } else if (CookieHeaderNames.DOMAIN.equalsIgnoreCase(name)) { + domain = value; + } else if (CookieHeaderNames.PATH.equalsIgnoreCase(name)) { + path = value; + } else if (CookieHeaderNames.EXPIRES.equalsIgnoreCase(name)) { + try { + long maxAgeMillis = + new HttpHeaderDateFormat().parse(value).getTime() - + System.currentTimeMillis(); + if (maxAgeMillis <= 0) { + maxAge = 0; + } else { + maxAge = (int) (maxAgeMillis / 1000) + + (maxAgeMillis % 1000 != 0? 1 : 0); + } + } catch (ParseException e) { + // Ignore. + } + } else if (CookieHeaderNames.MAX_AGE.equalsIgnoreCase(name)) { + maxAge = Integer.parseInt(value); + } else if (CookieHeaderNames.VERSION.equalsIgnoreCase(name)) { + version = Integer.parseInt(value); + } else if (CookieHeaderNames.PORT.equalsIgnoreCase(name)) { + String[] portList = value.split(COMMA); + for (String s1: portList) { + try { + ports.add(Integer.valueOf(s1)); + } catch (NumberFormatException e) { + // Ignore. + } + } + } else { + break; + } + } + + c.setVersion(version); + c.setMaxAge(maxAge); + c.setPath(path); + c.setDomain(domain); + c.setSecure(secure); + c.setHttpOnly(httpOnly); + if (version > 0) { + c.setComment(comment); + } + if (version > 1) { + c.setCommentUrl(commentURL); + c.setPorts(ports); + c.setDiscard(discard); + } + } + + return cookies; + } + + private void extractKeyValuePairs( + String header, List names, List values) { + Matcher m = PATTERN.matcher(header); + int pos = 0; + String name = null; + String value = null; + String separator = null; + while (m.find(pos)) { + pos = m.end(); + + // Extract name and value pair from the match. + String newName = m.group(1); + String newValue = m.group(3); + if (newValue == null) { + newValue = decodeValue(m.group(2)); + } + String newSeparator = m.group(4); + + if (name == null) { + name = newName; + value = newValue == null? "" : newValue; + separator = newSeparator; + continue; + } + + if (newValue == null && + !CookieHeaderNames.DISCARD.equalsIgnoreCase(newName) && + !CookieHeaderNames.SECURE.equalsIgnoreCase(newName) && + !CookieHeaderNames.HTTPONLY.equalsIgnoreCase(newName)) { + value = value + separator + newName; + separator = newSeparator; + continue; + } + + names.add(name); + values.add(value); + + name = newName; + value = newValue; + separator = newSeparator; + } + + // The last entry + if (name != null) { + names.add(name); + values.add(value); + } + } + + private String decodeValue(String value) { + if (value == null) { + return value; + } + return value.replace("\\\"", "\"").replace("\\\\", "\\"); + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/CookieEncoder.java b/douyu-http/src/main/java/com/codefollower/douyu/http/CookieEncoder.java new file mode 100644 index 0000000..337f70e --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/CookieEncoder.java @@ -0,0 +1,259 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.util.Date; +import java.util.Set; +import java.util.TreeSet; + +/** + * Encodes {@link Cookie}s into an HTTP header value. This encoder can encode + * the HTTP cookie version 0, 1, and 2. + *

+ * This encoder is stateful. It maintains an internal data structure that + * holds the {@link Cookie}s added by the {@link #addCookie(String, String)} + * method. Once {@link #encode()} is called, all added {@link Cookie}s are + * encoded into an HTTP header value and all {@link Cookie}s in the internal + * data structure are removed so that the encoder can start over. + *

+ * // Client-side example
+ * {@link HttpRequest} req = ...;
+ * {@link CookieEncoder} encoder = new {@link CookieEncoder}(false);
+ * encoder.addCookie("JSESSIONID", "1234");
+ * res.setHeader("Cookie", encoder.encode());
+ *
+ * // Server-side example
+ * {@link HttpResponse} res = ...;
+ * {@link CookieEncoder} encoder = new {@link CookieEncoder}(true);
+ * encoder.addCookie("JSESSIONID", "1234");
+ * res.setHeader("Set-Cookie", encoder.encode());
+ * 
+ * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @version $Rev$, $Date$ + * @see CookieDecoder + * + * @apiviz.stereotype utility + * @apiviz.has org.jboss.netty.handler.codec.http.Cookie oneway - - encodes + */ +public class CookieEncoder { + + private final Set cookies = new TreeSet(); + private final boolean server; + + /** + * Creates a new encoder. + * + * @param server {@code true} if and only if this encoder is supposed to + * encode server-side cookies. {@code false} if and only if + * this encoder is supposed to encode client-side cookies. + */ + public CookieEncoder(boolean server) { + this.server = server; + } + + /** + * Adds a new {@link Cookie} created with the specified name and value to + * this encoder. + */ + public void addCookie(String name, String value) { + cookies.add(new DefaultCookie(name, value)); + } + + /** + * Adds the specified {@link Cookie} to this encoder. + */ + public void addCookie(Cookie cookie) { + cookies.add(cookie); + } + + /** + * Encodes the {@link Cookie}s which were added by {@link #addCookie(Cookie)} + * so far into an HTTP header value. If no {@link Cookie}s were added, + * an empty string is returned. + */ + public String encode() { + String answer; + if (server) { + answer = encodeServerSide(); + } else { + answer = encodeClientSide(); + } + cookies.clear(); + return answer; + } + + private String encodeServerSide() { + StringBuilder sb = new StringBuilder(); + + for (Cookie cookie: cookies) { + add(sb, cookie.getName(), cookie.getValue()); + + if (cookie.getMaxAge() >= 0) { + if (cookie.getVersion() == 0) { + addUnquoted(sb, CookieHeaderNames.EXPIRES, + new HttpHeaderDateFormat().format( + new Date(System.currentTimeMillis() + + cookie.getMaxAge() * 1000L))); + } else { + add(sb, CookieHeaderNames.MAX_AGE, cookie.getMaxAge()); + } + } + + if (cookie.getPath() != null) { + if (cookie.getVersion() > 0) { + add(sb, CookieHeaderNames.PATH, cookie.getPath()); + } else { + addUnquoted(sb, CookieHeaderNames.PATH, cookie.getPath()); + } + } + + if (cookie.getDomain() != null) { + if (cookie.getVersion() > 0) { + add(sb, CookieHeaderNames.DOMAIN, cookie.getDomain()); + } else { + addUnquoted(sb, CookieHeaderNames.DOMAIN, cookie.getDomain()); + } + } + if (cookie.isSecure()) { + sb.append(CookieHeaderNames.SECURE); + sb.append((char) HttpCodecUtil.SEMICOLON); + } + if (cookie.isHttpOnly()) { + sb.append(CookieHeaderNames.HTTPONLY); + sb.append((char) HttpCodecUtil.SEMICOLON); + } + if (cookie.getVersion() >= 1) { + if (cookie.getComment() != null) { + add(sb, CookieHeaderNames.COMMENT, cookie.getComment()); + } + + add(sb, CookieHeaderNames.VERSION, 1); + + if (cookie.getCommentUrl() != null) { + addQuoted(sb, CookieHeaderNames.COMMENTURL, cookie.getCommentUrl()); + } + + if(!cookie.getPorts().isEmpty()) { + sb.append(CookieHeaderNames.PORT); + sb.append((char) HttpCodecUtil.EQUALS); + sb.append((char) HttpCodecUtil.DOUBLE_QUOTE); + for (int port: cookie.getPorts()) { + sb.append(port); + sb.append((char) HttpCodecUtil.COMMA); + } + sb.setCharAt(sb.length() - 1, (char) HttpCodecUtil.DOUBLE_QUOTE); + sb.append((char) HttpCodecUtil.SEMICOLON); + } + if (cookie.isDiscard()) { + sb.append(CookieHeaderNames.DISCARD); + sb.append((char) HttpCodecUtil.SEMICOLON); + } + } + } + + sb.setLength(sb.length() - 1); + return sb.toString(); + } + + private String encodeClientSide() { + StringBuilder sb = new StringBuilder(); + + for (Cookie cookie: cookies) { + if (cookie.getVersion() >= 1) { + add(sb, '$' + CookieHeaderNames.VERSION, 1); + } + + add(sb, cookie.getName(), cookie.getValue()); + + if (cookie.getPath() != null) { + add(sb, '$' + CookieHeaderNames.PATH, cookie.getPath()); + } + + if (cookie.getDomain() != null) { + add(sb, '$' + CookieHeaderNames.DOMAIN, cookie.getDomain()); + } + + if (cookie.getVersion() >= 1) { + if(!cookie.getPorts().isEmpty()) { + sb.append('$'); + sb.append(CookieHeaderNames.PORT); + sb.append((char) HttpCodecUtil.EQUALS); + sb.append((char) HttpCodecUtil.DOUBLE_QUOTE); + for (int port: cookie.getPorts()) { + sb.append(port); + sb.append((char) HttpCodecUtil.COMMA); + } + sb.setCharAt(sb.length() - 1, (char) HttpCodecUtil.DOUBLE_QUOTE); + sb.append((char) HttpCodecUtil.SEMICOLON); + } + } + } + + sb.setLength(sb.length() - 1); + return sb.toString(); + } + + private static void add(StringBuilder sb, String name, String val) { + if (val == null) { + addQuoted(sb, name, ""); + return; + } + + for (int i = 0; i < val.length(); i ++) { + char c = val.charAt(i); + switch (c) { + case '\t': case ' ': case '"': case '(': case ')': case ',': + case '/': case ':': case ';': case '<': case '=': case '>': + case '?': case '@': case '[': case '\\': case ']': + case '{': case '}': + addQuoted(sb, name, val); + return; + } + } + + addUnquoted(sb, name, val); + } + + private static void addUnquoted(StringBuilder sb, String name, String val) { + sb.append(name); + sb.append((char) HttpCodecUtil.EQUALS); + sb.append(val); + sb.append((char) HttpCodecUtil.SEMICOLON); + } + + private static void addQuoted(StringBuilder sb, String name, String val) { + if (val == null) { + val = ""; + } + + sb.append(name); + sb.append((char) HttpCodecUtil.EQUALS); + sb.append((char) HttpCodecUtil.DOUBLE_QUOTE); + sb.append(val.replace("\\", "\\\\").replace("\"", "\\\"")); + sb.append((char) HttpCodecUtil.DOUBLE_QUOTE); + sb.append((char) HttpCodecUtil.SEMICOLON); + } + + private static void add(StringBuilder sb, String name, int val) { + sb.append(name); + sb.append((char) HttpCodecUtil.EQUALS); + sb.append(val); + sb.append((char) HttpCodecUtil.SEMICOLON); + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/CookieHeaderNames.java b/douyu-http/src/main/java/com/codefollower/douyu/http/CookieHeaderNames.java new file mode 100644 index 0000000..b307215 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/CookieHeaderNames.java @@ -0,0 +1,50 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +/** + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @version $Rev$, $Date$ + */ +final class CookieHeaderNames { + static final String PATH = "Path"; + + static final String EXPIRES = "Expires"; + + static final String MAX_AGE = "Max-Age"; + + static final String DOMAIN = "Domain"; + + static final String SECURE = "Secure"; + + static final String HTTPONLY = "HTTPOnly"; + + static final String COMMENT = "Comment"; + + static final String COMMENTURL = "CommentURL"; + + static final String DISCARD = "Discard"; + + static final String PORT = "Port"; + + static final String VERSION = "Version"; + + private CookieHeaderNames() { + // Unused. + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultCookie.java b/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultCookie.java new file mode 100644 index 0000000..73673fa --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultCookie.java @@ -0,0 +1,376 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.util.Collections; +import java.util.Set; +import java.util.TreeSet; + + + +/** + * The default {@link Cookie} implementation. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @version $Rev$, $Date$ + */ +public class DefaultCookie implements Cookie { + + private static final Set RESERVED_NAMES = new TreeSet(CaseIgnoringComparator.INSTANCE); + + static { + RESERVED_NAMES.add("Domain"); + RESERVED_NAMES.add("Path"); + RESERVED_NAMES.add("Comment"); + RESERVED_NAMES.add("CommentURL"); + RESERVED_NAMES.add("Discard"); + RESERVED_NAMES.add("Port"); + RESERVED_NAMES.add("Max-Age"); + RESERVED_NAMES.add("Expires"); + RESERVED_NAMES.add("Version"); + RESERVED_NAMES.add("Secure"); + RESERVED_NAMES.add("HTTPOnly"); + } + + private final String name; + private String value; + private String domain; + private String path; + private String comment; + private String commentUrl; + private boolean discard; + private Set ports = Collections.emptySet(); + private Set unmodifiablePorts = ports; + private int maxAge = -1; + private int version; + private boolean secure; + private boolean httpOnly; + + /** + * Creates a new cookie with the specified name and value. + */ + public DefaultCookie(String name, String value) { + if (name == null) { + throw new NullPointerException("name"); + } + name = name.trim(); + if (name.length() == 0) { + throw new IllegalArgumentException("empty name"); + } + + for (int i = 0; i < name.length(); i ++) { + char c = name.charAt(i); + if (c > 127) { + throw new IllegalArgumentException( + "name contains non-ascii character: " + name); + } + + // Check prohibited characters. + switch (c) { + case '\t': case '\n': case 0x0b: case '\f': case '\r': + case ' ': case ',': case ';': case '=': + throw new IllegalArgumentException( + "name contains one of the following prohibited characters: " + + "=,; \\t\\r\\n\\v\\f: " + name); + } + } + + if (RESERVED_NAMES.contains(name)) { + throw new IllegalArgumentException("reserved name: " + name); + } + + this.name = name; + setValue(value); + } + + @Override + public String getName() { + return name; + } + + @Override + public String getValue() { + return value; + } + + @Override + public void setValue(String value) { + if (value == null) { + throw new NullPointerException("value"); + } + this.value = value; + } + + @Override + public String getDomain() { + return domain; + } + + @Override + public void setDomain(String domain) { + this.domain = validateValue("domain", domain); + } + + @Override + public String getPath() { + return path; + } + + @Override + public void setPath(String path) { + this.path = validateValue("path", path); + } + + @Override + public String getComment() { + return comment; + } + + @Override + public void setComment(String comment) { + this.comment = validateValue("comment", comment); + } + + @Override + public String getCommentUrl() { + return commentUrl; + } + + @Override + public void setCommentUrl(String commentUrl) { + this.commentUrl = validateValue("commentUrl", commentUrl); + } + + @Override + public boolean isDiscard() { + return discard; + } + + @Override + public void setDiscard(boolean discard) { + this.discard = discard; + } + + @Override + public Set getPorts() { + if (unmodifiablePorts == null) { + unmodifiablePorts = Collections.unmodifiableSet(ports); + } + return unmodifiablePorts; + } + + @Override + public void setPorts(int... ports) { + if (ports == null) { + throw new NullPointerException("ports"); + } + + int[] portsCopy = ports.clone(); + if (portsCopy.length == 0) { + unmodifiablePorts = this.ports = Collections.emptySet(); + } else { + Set newPorts = new TreeSet(); + for (int p: portsCopy) { + if (p <= 0 || p > 65535) { + throw new IllegalArgumentException("port out of range: " + p); + } + newPorts.add(Integer.valueOf(p)); + } + this.ports = newPorts; + unmodifiablePorts = null; + } + } + + @Override + public void setPorts(Iterable ports) { + Set newPorts = new TreeSet(); + for (int p: ports) { + if (p <= 0 || p > 65535) { + throw new IllegalArgumentException("port out of range: " + p); + } + newPorts.add(Integer.valueOf(p)); + } + if (newPorts.isEmpty()) { + unmodifiablePorts = this.ports = Collections.emptySet(); + } else { + this.ports = newPorts; + unmodifiablePorts = null; + } + } + + @Override + public int getMaxAge() { + return maxAge; + } + + @Override + public void setMaxAge(int maxAge) { + if (maxAge < -1) { + throw new IllegalArgumentException( + "maxAge must be either -1, 0, or a positive integer: " + + maxAge); + } + this.maxAge = maxAge; + } + + @Override + public int getVersion() { + return version; + } + + @Override + public void setVersion(int version) { + this.version = version; + } + + @Override + public boolean isSecure() { + return secure; + } + + @Override + public void setSecure(boolean secure) { + this.secure = secure; + } + + @Override + public boolean isHttpOnly() { + return httpOnly; + } + + @Override + public void setHttpOnly(boolean httpOnly) { + this.httpOnly = httpOnly; + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Cookie)) { + return false; + } + + Cookie that = (Cookie) o; + if (!getName().equalsIgnoreCase(that.getName())) { + return false; + } + + if (getPath() == null && that.getPath() != null) { + return false; + } else if (that.getPath() == null) { + return false; + } + if (!getPath().equals(that.getPath())) { + return false; + } + + if (getDomain() == null && that.getDomain() != null) { + return false; + } else if (that.getDomain() == null) { + return false; + } + if (!getDomain().equalsIgnoreCase(that.getDomain())) { + return false; + } + + return true; + } + + @Override + public int compareTo(Cookie c) { + int v; + v = getName().compareToIgnoreCase(c.getName()); + if (v != 0) { + return v; + } + + if (getPath() == null && c.getPath() != null) { + return -1; + } else if (c.getPath() == null) { + return 1; + } + v = getPath().compareTo(c.getPath()); + if (v != 0) { + return v; + } + + if (getDomain() == null && c.getDomain() != null) { + return -1; + } else if (c.getDomain() == null) { + return 1; + } + v = getDomain().compareToIgnoreCase(c.getDomain()); + return v; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(getName()); + buf.append('='); + buf.append(getValue()); + if (getDomain() != null) { + buf.append(", domain="); + buf.append(getDomain()); + } + if (getPath() != null) { + buf.append(", path="); + buf.append(getPath()); + } + if (getComment() != null) { + buf.append(", comment="); + buf.append(getComment()); + } + if (getMaxAge() >= 0) { + buf.append(", maxAge="); + buf.append(getMaxAge()); + buf.append('s'); + } + if (isSecure()) { + buf.append(", secure"); + } + if (isHttpOnly()) { + buf.append(", HTTPOnly"); + } + return buf.toString(); + } + + private static String validateValue(String name, String value) { + if (value == null) { + return null; + } + value = value.trim(); + if (value.length() == 0) { + return null; + } + for (int i = 0; i < value.length(); i ++) { + char c = value.charAt(i); + switch (c) { + case '\r': case '\n': case '\f': case 0x0b: case ';': + throw new IllegalArgumentException( + name + " contains one of the following prohibited characters: " + + ";\\r\\n\\f\\v (" + value + ')'); + } + } + return value; + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultHttpChunk.java b/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultHttpChunk.java new file mode 100644 index 0000000..1521a34 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultHttpChunk.java @@ -0,0 +1,58 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; + +/** + * The default {@link HttpChunk} implementation. + * + * @author The Netty Project + * @author Trustin Lee + * @version $Rev$, $Date$ + */ +public class DefaultHttpChunk implements HttpChunk { + + private ChannelBuffer content; + private boolean last; + + /** + * Creates a new instance with the specified chunk content. If an empty + * buffer is specified, this chunk becomes the 'end of content' marker. + */ + public DefaultHttpChunk(ChannelBuffer content) { + setContent(content); + } + + @Override + public ChannelBuffer getContent() { + return content; + } + + @Override + public void setContent(ChannelBuffer content) { + if (content == null) { + throw new NullPointerException("content"); + } + last = !content.readable(); + this.content = content; + } + + @Override + public boolean isLast() { + return last; + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultHttpChunkTrailer.java b/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultHttpChunkTrailer.java new file mode 100644 index 0000000..2f6bc76 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultHttpChunkTrailer.java @@ -0,0 +1,111 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; + +/** + * The default {@link HttpChunkTrailer} implementation. + * + * @author The Netty Project + * @author Trustin Lee + * @version $Rev$, $Date$ + */ +public class DefaultHttpChunkTrailer implements HttpChunkTrailer { + + private final HttpHeaders headers = new HttpHeaders() { + @Override + void validateHeaderName(String name) { + super.validateHeaderName(name); + if (name.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH) || + name.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING) || + name.equalsIgnoreCase(HttpHeaders.Names.TRAILER)) { + throw new IllegalArgumentException( + "prohibited trailing header: " + name); + } + } + }; + + @Override + public boolean isLast() { + return true; + } + + @Override + public void addHeader(final String name, final Object value) { + headers.addHeader(name, value); + } + + @Override + public void setHeader(final String name, final Object value) { + headers.setHeader(name, value); + } + + @Override + public void setHeader(final String name, final Iterable values) { + headers.setHeader(name, values); + } + + @Override + public void removeHeader(final String name) { + headers.removeHeader(name); + } + + @Override + public void clearHeaders() { + headers.clearHeaders(); + } + + @Override + public String getHeader(final String name) { + return headers.getHeader(name); + } + + @Override + public List getHeaders(final String name) { + return headers.getHeaders(name); + } + + @Override + public List> getHeaders() { + return headers.getHeaders(); + } + + @Override + public boolean containsHeader(final String name) { + return headers.containsHeader(name); + } + + @Override + public Set getHeaderNames() { + return headers.getHeaderNames(); + } + + @Override + public ChannelBuffer getContent() { + return ChannelBuffers.EMPTY_BUFFER; + } + + @Override + public void setContent(ChannelBuffer content) { + throw new IllegalStateException("read-only"); + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultHttpDataFactory.java b/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultHttpDataFactory.java new file mode 100644 index 0000000..13feaad --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultHttpDataFactory.java @@ -0,0 +1,202 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Default factory giving Attribute and FileUpload according to constructor + * + * Attribute and FileUpload could be :
+ * - MemoryAttribute, DiskAttribute or MixedAttribute
+ * - MemoryFileUpload, DiskFileUpload or MixedFileUpload
+ * according to the constructor. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @author Frederic Bregier + * + */ +public class DefaultHttpDataFactory implements HttpDataFactory { + /** + * Proposed default MINSIZE as 16 KB. + */ + public static long MINSIZE = 0x4000; + + private boolean useDisk = false; + + private boolean checkSize = false; + + private long minSize = 0L; + + /** + * Keep all HttpDatas until cleanAllHttpDatas() is called. + */ + private ConcurrentHashMap> requestFileDeleteMap = + new ConcurrentHashMap>(); + /** + * HttpData will be in memory if less than default size (16KB). + * The type will be Mixed. + */ + public DefaultHttpDataFactory() { + useDisk = false; + checkSize = true; + this.minSize = MINSIZE; + } + + /** + * HttpData will be always on Disk if useDisk is True, else always in Memory if False + * @param useDisk + */ + public DefaultHttpDataFactory(boolean useDisk) { + this.useDisk = useDisk; + checkSize = false; + } + + /** + * HttpData will be on Disk if the size of the file is greater than minSize, else it + * will be in memory. The type will be Mixed. + * @param minSize + */ + public DefaultHttpDataFactory(long minSize) { + useDisk = false; + checkSize = true; + this.minSize = minSize; + } + + /** + * + * @param request + * @return the associated list of Files for the request + */ + private List getList(HttpRequest request) { + List list = requestFileDeleteMap.get(request); + if (list == null) { + list = new ArrayList(); + requestFileDeleteMap.put(request, list); + } + return list; + } + + @Override + public Attribute createAttribute(HttpRequest request, String name) throws NullPointerException, + IllegalArgumentException { + if (useDisk) { + Attribute attribute = new DiskAttribute(name); + List fileToDelete = getList(request); + fileToDelete.add(attribute); + return attribute; + } else if (checkSize) { + Attribute attribute = new MixedAttribute(name, minSize); + List fileToDelete = getList(request); + fileToDelete.add(attribute); + return attribute; + } + return new MemoryAttribute(name); + } + + /* (non-Javadoc) + * @see org.jboss.netty.handler.codec.http2.HttpDataFactory#createAttribute(java.lang.String, java.lang.String) + */ + @Override + public Attribute createAttribute(HttpRequest request, String name, String value) + throws NullPointerException, IllegalArgumentException { + if (useDisk) { + Attribute attribute; + try { + attribute = new DiskAttribute(name, value); + } catch (IOException e) { + // revert to Mixed mode + attribute = new MixedAttribute(name, value, minSize); + } + List fileToDelete = getList(request); + fileToDelete.add(attribute); + return attribute; + } else if (checkSize) { + Attribute attribute = new MixedAttribute(name, value, minSize); + List fileToDelete = getList(request); + fileToDelete.add(attribute); + return attribute; + } + try { + return new MemoryAttribute(name, value); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + /* (non-Javadoc) + * @see org.jboss.netty.handler.codec.http2.HttpDataFactory#createFileUpload(java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public FileUpload createFileUpload(HttpRequest request, String name, String filename, + String contentType, String contentTransferEncoding, Charset charset, + long size) throws NullPointerException, IllegalArgumentException { + if (useDisk) { + FileUpload fileUpload = new DiskFileUpload(name, filename, contentType, + contentTransferEncoding, charset, size); + List fileToDelete = getList(request); + fileToDelete.add(fileUpload); + return fileUpload; + } else if (checkSize) { + FileUpload fileUpload = new MixedFileUpload(name, filename, contentType, + contentTransferEncoding, charset, size, minSize); + List fileToDelete = getList(request); + fileToDelete.add(fileUpload); + return fileUpload; + } + return new MemoryFileUpload(name, filename, contentType, + contentTransferEncoding, charset, size); + } + + @Override + public void removeHttpDataFromClean(HttpRequest request, InterfaceHttpData data) { + if (data instanceof HttpData) { + List fileToDelete = getList(request); + fileToDelete.remove(data); + } + } + + @Override + public void cleanRequestHttpDatas(HttpRequest request) { + List fileToDelete = requestFileDeleteMap.remove(request); + if (fileToDelete != null) { + for (HttpData data: fileToDelete) { + data.delete(); + } + fileToDelete.clear(); + } + } + + @Override + public void cleanAllHttpDatas() { + for (HttpRequest request : requestFileDeleteMap.keySet()) { + List fileToDelete = requestFileDeleteMap.get(request); + if (fileToDelete != null) { + for (HttpData data: fileToDelete) { + data.delete(); + } + fileToDelete.clear(); + } + requestFileDeleteMap.remove(request); + } + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultHttpMessage.java b/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultHttpMessage.java new file mode 100644 index 0000000..2e6031a --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultHttpMessage.java @@ -0,0 +1,173 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; +import com.codefollower.douyu.netty.util.internal.StringUtil; + +/** + * The default {@link HttpMessage} implementation. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @version $Rev$, $Date$ + */ +public class DefaultHttpMessage implements HttpMessage { + + private final HttpHeaders headers = new HttpHeaders(); + private HttpVersion version; + private ChannelBuffer content = ChannelBuffers.EMPTY_BUFFER; + private boolean chunked; + + /** + * Creates a new instance. + */ + protected DefaultHttpMessage(final HttpVersion version) { + setProtocolVersion(version); + } + + @Override + public void addHeader(final String name, final Object value) { + headers.addHeader(name, value); + } + + @Override + public void setHeader(final String name, final Object value) { + headers.setHeader(name, value); + } + + @Override + public void setHeader(final String name, final Iterable values) { + headers.setHeader(name, values); + } + + @Override + public void removeHeader(final String name) { + headers.removeHeader(name); + } + + @Override + public boolean isChunked() { + if (chunked) { + return true; + } else { + return HttpCodecUtil.isTransferEncodingChunked(this); + } + } + + @Override + public void setChunked(boolean chunked) { + this.chunked = chunked; + if (chunked) { + setContent(ChannelBuffers.EMPTY_BUFFER); + } + } + + @Override + public void clearHeaders() { + headers.clearHeaders(); + } + + @Override + public void setContent(ChannelBuffer content) { + if (content == null) { + content = ChannelBuffers.EMPTY_BUFFER; + } + if (content.readable() && isChunked()) { + throw new IllegalArgumentException( + "non-empty content disallowed if this.chunked == true"); + } + this.content = content; + } + + @Override + public String getHeader(final String name) { + List values = getHeaders(name); + return values.size() > 0 ? values.get(0) : null; + } + + @Override + public List getHeaders(final String name) { + return headers.getHeaders(name); + } + + @Override + public List> getHeaders() { + return headers.getHeaders(); + } + + @Override + public boolean containsHeader(final String name) { + return headers.containsHeader(name); + } + + @Override + public Set getHeaderNames() { + return headers.getHeaderNames(); + } + + @Override + public HttpVersion getProtocolVersion() { + return version; + } + + @Override + public void setProtocolVersion(HttpVersion version) { + if (version == null) { + throw new NullPointerException("version"); + } + this.version = version; + } + + @Override + public ChannelBuffer getContent() { + return content; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(getClass().getSimpleName()); + buf.append("(version: "); + buf.append(getProtocolVersion().getText()); + buf.append(", keepAlive: "); + buf.append(HttpHeaders.isKeepAlive(this)); + buf.append(", chunked: "); + buf.append(isChunked()); + buf.append(')'); + buf.append(StringUtil.NEWLINE); + appendHeaders(buf); + + // Remove the last newline. + buf.setLength(buf.length() - StringUtil.NEWLINE.length()); + return buf.toString(); + } + + void appendHeaders(StringBuilder buf) { + for (Map.Entry e: getHeaders()) { + buf.append(e.getKey()); + buf.append(": "); + buf.append(e.getValue()); + buf.append(StringUtil.NEWLINE); + } + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultHttpRequest.java b/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultHttpRequest.java new file mode 100644 index 0000000..cede6ae --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultHttpRequest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import com.codefollower.douyu.netty.util.internal.StringUtil; + +/** + * The default {@link HttpRequest} implementation. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @version $Rev$, $Date$ + */ +public class DefaultHttpRequest extends DefaultHttpMessage implements HttpRequest { + + private HttpMethod method; + private String uri; + + /** + * Creates a new instance. + * + * @param httpVersion the HTTP version of the request + * @param method the HTTP method of the request + * @param uri the URI or path of the request + */ + public DefaultHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri) { + super(httpVersion); + setMethod(method); + setUri(uri); + } + + @Override + public HttpMethod getMethod() { + return method; + } + + @Override + public void setMethod(HttpMethod method) { + if (method == null) { + throw new NullPointerException("method"); + } + this.method = method; + } + + @Override + public String getUri() { + return uri; + } + + @Override + public void setUri(String uri) { + if (uri == null) { + throw new NullPointerException("uri"); + } + this.uri = uri; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(getClass().getSimpleName()); + buf.append("(chunked: "); + buf.append(isChunked()); + buf.append(')'); + buf.append(StringUtil.NEWLINE); + buf.append(getMethod().toString()); + buf.append(' '); + buf.append(getUri()); + buf.append(' '); + buf.append(getProtocolVersion().getText()); + buf.append(StringUtil.NEWLINE); + appendHeaders(buf); + + // Remove the last newline. + buf.setLength(buf.length() - StringUtil.NEWLINE.length()); + return buf.toString(); + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultHttpResponse.java b/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultHttpResponse.java new file mode 100644 index 0000000..b318672 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/DefaultHttpResponse.java @@ -0,0 +1,74 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import com.codefollower.douyu.netty.util.internal.StringUtil; + +/** + * The default {@link HttpResponse} implementation. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @version $Rev$, $Date$ + */ +public class DefaultHttpResponse extends DefaultHttpMessage implements HttpResponse { + + private HttpResponseStatus status; + + /** + * Creates a new instance. + * + * @param version the HTTP version of this response + * @param status the status of this response + */ + public DefaultHttpResponse(HttpVersion version, HttpResponseStatus status) { + super(version); + setStatus(status); + } + + @Override + public HttpResponseStatus getStatus() { + return status; + } + + @Override + public void setStatus(HttpResponseStatus status) { + if (status == null) { + throw new NullPointerException("status"); + } + this.status = status; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(getClass().getSimpleName()); + buf.append("(chunked: "); + buf.append(isChunked()); + buf.append(')'); + buf.append(StringUtil.NEWLINE); + buf.append(getProtocolVersion().getText()); + buf.append(' '); + buf.append(getStatus().toString()); + buf.append(StringUtil.NEWLINE); + appendHeaders(buf); + + // Remove the last newline. + buf.setLength(buf.length() - StringUtil.NEWLINE.length()); + return buf.toString(); + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/DiskAttribute.java b/douyu-http/src/main/java/com/codefollower/douyu/http/DiskAttribute.java new file mode 100644 index 0000000..4f83bd9 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/DiskAttribute.java @@ -0,0 +1,153 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.io.IOException; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; + +/** + * Disk implementation of Attributes + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @author Frederic Bregier + * + */ +public class DiskAttribute extends AbstractDiskHttpData implements Attribute { + public static String baseDirectory = null; + + public static boolean deleteOnExitTemporaryFile = true; + + public static String prefix = "Attr_"; + + public static String postfix = ".att"; + + /** + * Constructor used for huge Attribute + * @param name + */ + public DiskAttribute(String name) { + super(name, HttpCodecUtil.DEFAULT_CHARSET, 0); + } + /** + * + * @param name + * @param value + * @throws NullPointerException + * @throws IllegalArgumentException + * @throws IOException + */ + public DiskAttribute(String name, String value) + throws NullPointerException, IllegalArgumentException, IOException { + super(name, HttpCodecUtil.DEFAULT_CHARSET, 0); // Attribute have no default size + setValue(value); + } + + @Override + public HttpDataType getHttpDataType() { + return HttpDataType.Attribute; + } + + @Override + public String getValue() throws IOException { + byte [] bytes = get(); + return new String(bytes, charset); + } + + @Override + public void setValue(String value) throws IOException { + if (value == null) { + throw new NullPointerException("value"); + } + byte [] bytes = value.getBytes(charset); + ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(bytes); + if (definedSize > 0) { + definedSize = buffer.readableBytes(); + } + setContent(buffer); + } + + @Override + public void addContent(ChannelBuffer buffer, boolean last) throws IOException { + int localsize = buffer.readableBytes(); + if (definedSize > 0 && definedSize < size + localsize) { + definedSize = size + localsize; + } + super.addContent(buffer, last); + } + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Attribute)) { + return false; + } + Attribute attribute = (Attribute) o; + return getName().equalsIgnoreCase(attribute.getName()); + } + + @Override + public int compareTo(InterfaceHttpData arg0) { + if (!(arg0 instanceof Attribute)) { + throw new ClassCastException("Cannot compare " + getHttpDataType() + + " with " + arg0.getHttpDataType()); + } + return compareTo((Attribute) arg0); + } + + public int compareTo(Attribute o) { + return getName().compareToIgnoreCase(o.getName()); + } + + @Override + public String toString() { + try { + return getName() + "=" + getValue(); + } catch (IOException e) { + return getName() + "=IoException"; + } + } + + @Override + protected boolean deleteOnExit() { + return deleteOnExitTemporaryFile; + } + + @Override + protected String getBaseDirectory() { + return baseDirectory; + } + + @Override + protected String getDiskFilename() { + return getName()+postfix; + } + + @Override + protected String getPostfix() { + return postfix; + } + + @Override + protected String getPrefix() { + return prefix; + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/DiskFileUpload.java b/douyu-http/src/main/java/com/codefollower/douyu/http/DiskFileUpload.java new file mode 100644 index 0000000..4627526 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/DiskFileUpload.java @@ -0,0 +1,173 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.io.File; +import java.nio.charset.Charset; + +/** + * Disk FileUpload implementation that stores file into real files + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @author Frederic Bregier + * + */ +public class DiskFileUpload extends AbstractDiskHttpData implements FileUpload { + public static String baseDirectory = null; + + public static boolean deleteOnExitTemporaryFile = true; + + public static String prefix = "FUp_"; + + public static String postfix = ".tmp"; + + private String filename = null; + + private String contentType = null; + + private String contentTransferEncoding = null; + + public DiskFileUpload(String name, String filename, String contentType, + String contentTransferEncoding, Charset charset, long size) + throws NullPointerException, IllegalArgumentException { + super(name, charset, size); + setFilename(filename); + setContentType(contentType); + setContentTransferEncoding(contentTransferEncoding); + } + + @Override + public HttpDataType getHttpDataType() { + return HttpDataType.FileUpload; + } + + /* (non-Javadoc) + * @see org.jboss.netty.handler.codec.http2.FileUpload#getFilename() + */ + @Override + public String getFilename() { + return filename; + } + + /* (non-Javadoc) + * @see org.jboss.netty.handler.codec.http2.FileUpload#setFilename(java.lang.String) + */ + @Override + public void setFilename(String filename) { + if (filename == null) { + throw new NullPointerException("filename"); + } + this.filename = filename; + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Attribute)) { + return false; + } + Attribute attribute = (Attribute) o; + return getName().equalsIgnoreCase(attribute.getName()); + } + + @Override + public int compareTo(InterfaceHttpData arg0) { + if (!(arg0 instanceof FileUpload)) { + throw new ClassCastException("Cannot compare " + getHttpDataType() + + " with " + arg0.getHttpDataType()); + } + return compareTo((FileUpload) arg0); + } + + public int compareTo(FileUpload o) { + int v; + v = getName().compareToIgnoreCase(o.getName()); + if (v != 0) { + return v; + } + // TODO should we compare size ? + return v; + } + + @Override + public void setContentType(String contentType) { + if (contentType == null) { + throw new NullPointerException("contentType"); + } + this.contentType = contentType; + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public String getContentTransferEncoding() { + return contentTransferEncoding; + } + + @Override + public void setContentTransferEncoding(String contentTransferEncoding) { + this.contentTransferEncoding = contentTransferEncoding; + } + + @Override + public String toString() { + return HttpPostBodyUtil.CONTENT_DISPOSITION+": "+ + HttpPostBodyUtil.FORM_DATA+"; "+HttpPostBodyUtil.NAME+"=\"" + getName() + + "\"; "+HttpPostBodyUtil.FILENAME+"=\"" + filename + "\"\r\n" + + HttpHeaders.Names.CONTENT_TYPE+": " + contentType + + (charset != null? "; "+HttpHeaders.Values.CHARSET+"=" + charset + "\r\n" : "\r\n") + + HttpHeaders.Names.CONTENT_LENGTH+": " + length() + "\r\n" + + "Completed: " + isCompleted() + + "\r\nIsInMemory: " + isInMemory() + "\r\nRealFile: " + + file.getAbsolutePath() + " DefaultDeleteAfter: " + + deleteOnExitTemporaryFile; + } + + @Override + protected boolean deleteOnExit() { + return deleteOnExitTemporaryFile; + } + + @Override + protected String getBaseDirectory() { + return baseDirectory; + } + + @Override + protected String getDiskFilename() { + File file = new File(filename); + return file.getName(); + } + + @Override + protected String getPostfix() { + return postfix; + } + + @Override + protected String getPrefix() { + return prefix; + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/DouyuHttpRequest.java b/douyu-http/src/main/java/com/codefollower/douyu/http/DouyuHttpRequest.java new file mode 100644 index 0000000..99c756e --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/DouyuHttpRequest.java @@ -0,0 +1,286 @@ +package com.codefollower.douyu.http; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; + +import douyu.http.UploadedFile; + +public class DouyuHttpRequest extends DefaultHttpRequest implements douyu.http.HttpRequest { + private Map> params; + private Map> uploadedFiles; + + protected HashMap attributes = new HashMap(); + + private String queryString; + private int serverPort = -1; + private String serverName; + + String protocol; + + String requestURI; + String remoteAddr; + String remoteHost; + String localName; + int localPort; + int remotePort; + + String remoteUser; + String authType; + String scheme; + + public String getScheme() { + return scheme; + } + + public void setScheme(String scheme) { + this.scheme = scheme; + } + + public String getAuthType() { + return authType; + } + + public void setAuthType(String authType) { + this.authType = authType; + } + + public String getRemoteUser() { + return remoteUser; + } + + public void setRemoteUser(String remoteUser) { + this.remoteUser = remoteUser; + } + + public int getRemotePort() { + return remotePort; + } + + public void setRemotePort(int remotePort) { + this.remotePort = remotePort; + } + + boolean isSSL; + + int contentLength = -1; + String contentType; + String instanceId; + + public String getInstanceId() { + return instanceId; + } + + public void setInstanceId(String instanceId) { + this.instanceId = instanceId; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public int getContentLength() { + return contentLength; + } + + public void setContentLength(int contentLength) { + this.contentLength = contentLength; + } + + public String getRemoteAddr() { + return remoteAddr; + } + + public void setRemoteAddr(String remoteAddr) { + this.remoteAddr = remoteAddr; + } + + public String getRemoteHost() { + return remoteHost; + } + + public void setRemoteHost(String remoteHost) { + this.remoteHost = remoteHost; + } + + public String getLocalName() { + return localName; + } + + public void setLocalName(String localName) { + this.localName = localName; + } + + public int getLocalPort() { + return localPort; + } + + public void setLocalPort(int localPort) { + this.localPort = localPort; + } + + public boolean isSSL() { + return isSSL; + } + + public void setSSL(boolean isSSL) { + this.isSSL = isSSL; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public String getQueryString() { + return queryString; + } + + public void setQueryString(String queryString) { + this.queryString = queryString; + } + + public int getServerPort() { + return serverPort; + } + + public void setServerPort(int serverPort) { + this.serverPort = serverPort; + } + + public String getServerName() { + return serverName; + } + + public void setServerName(String serverName) { + this.serverName = serverName; + } + + public DouyuHttpRequest() { + super(HttpVersion.HTTP_1_1, com.codefollower.douyu.http.HttpMethod.GET, "/"); + } + + public DouyuHttpRequest(HttpVersion httpVersion, com.codefollower.douyu.http.HttpMethod method, String uri) throws Exception { + super(httpVersion, method, uri); + QueryStringDecoder queryStringDecoder = new QueryStringDecoder(uri); + Map> params = queryStringDecoder.getParameters(); + this.params = new HashMap>(); + this.params.putAll(params); + } + + @Override + public douyu.http.HttpMethod getHttpMethod() { + return douyu.http.HttpMethod.valueOf(getMethod().getName()); + } + + @Override + public String getProtocol() { + return getProtocolVersion().getText(); + } + + @Override + public void setAttribute(String name, Object value) { + attributes.put(name, value); + } + + @Override + public Object getAttribute(String name) { + return attributes.get(name); + } + + @Override + public String getParameter(String name) { + List values = params.get(name); + if (values == null || values.isEmpty()) + return null; + else + return values.get(0); + } + + @Override + public String[] getParameterValues(String name) { + List values = params.get(name); + if (values == null || values.isEmpty()) + return null; + else + return values.toArray(new String[values.size()]); + } + + public void setRequestURI(String requestURI) { + this.requestURI = requestURI; + } + + @Override + public String getRequestURI() { + if (requestURI == null) + return getUri(); + return requestURI; + } + + @Override + public UploadedFile getUploadedFile(String name) { + List values = uploadedFiles.get(name); + if (values == null || values.isEmpty()) + return null; + else + return values.get(0); + } + + @Override + public UploadedFile[] getUploadedFiles() { + if (uploadedFiles == null || uploadedFiles.isEmpty()) + return null; + else { + List values = new ArrayList(uploadedFiles.size()); + for (List list : uploadedFiles.values()) { + if (list != null) { + values.addAll(list); + } + } + return values.toArray(new UploadedFile[values.size()]); + // return uploadedFiles.values().toArray(new + // UploadedFile[uploadedFiles.size()]); + } + + } + + public void addParameter(String name, String value) { + List values = params.get(name); + if (values == null) + values = new ArrayList(1); + + values.add(value); + params.put(name, values); + } + + public void addUploadedFile(String name, UploadedFile file) { + if (uploadedFiles == null) + uploadedFiles = new HashMap>(1); + List values = uploadedFiles.get(name); + if (values == null) + values = new ArrayList(1); + + values.add(file); + uploadedFiles.put(name, values); + } + + // 覆盖父类的content,父类的setContent不能放chunk,但是解析ajp协议时必须把所有chunk组装成一个整体 + private ChannelBuffer content = ChannelBuffers.EMPTY_BUFFER; + + @Override + public void setContent(ChannelBuffer content) { + this.content = content; + } + + @Override + public ChannelBuffer getContent() { + return content; + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/DouyuHttpRequestDecoder.java b/douyu-http/src/main/java/com/codefollower/douyu/http/DouyuHttpRequestDecoder.java new file mode 100644 index 0000000..68eb9a7 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/DouyuHttpRequestDecoder.java @@ -0,0 +1,26 @@ +package com.codefollower.douyu.http; + + +public class DouyuHttpRequestDecoder extends HttpMessageDecoder { + + /** + * Creates a new instance with the default + * {@code maxInitialLineLength (4096}}, {@code maxHeaderSize (8192)}, and + * {@code maxChunkSize (8192)}. + */ + public DouyuHttpRequestDecoder() { + super(); + } + + /** + * Creates a new instance with the specified parameters. + */ + public DouyuHttpRequestDecoder(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) { + super(maxInitialLineLength, maxHeaderSize, maxChunkSize); + } + + @Override + protected HttpMessage createMessage(String[] initialLine) throws Exception { + return new DouyuHttpRequest(HttpVersion.valueOf(initialLine[2]), HttpMethod.valueOf(initialLine[0]), initialLine[1]); + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/DouyuHttpResponse.java b/douyu-http/src/main/java/com/codefollower/douyu/http/DouyuHttpResponse.java new file mode 100644 index 0000000..89560cb --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/DouyuHttpResponse.java @@ -0,0 +1,75 @@ +package com.codefollower.douyu.http; + +import static com.codefollower.douyu.http.HttpResponseStatus.OK; +import static com.codefollower.douyu.http.HttpVersion.HTTP_1_1; + +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBufferOutputStream; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; + +public class DouyuHttpResponse extends DefaultHttpResponse implements douyu.http.HttpResponse { + private String contentType; + private String charset; + private PrintWriter writer; + private ChannelBuffer content = ChannelBuffers.dynamicBuffer(); + + private StringWriter sw = new StringWriter(); + + public DouyuHttpResponse() { + this(HTTP_1_1, OK); + } + + public DouyuHttpResponse(HttpVersion version, HttpResponseStatus status) { + super(version, status); + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public PrintWriter getWriter() throws Exception { + if (writer == null) { + if (charset != null) { + writer = new PrintWriter(new OutputStreamWriter(new ChannelBufferOutputStream(content), charset)); + } else { + // writer = new PrintWriter(new OutputStreamWriter(new + // ChannelBufferOutputStream(content), "utf-8")); + writer = new PrintWriter(sw); + } + } + return writer; + } + + @Override + public void setCharacterEncoding(String charset) { + this.charset = charset; + } + + @Override + public void setContentType(String contentType) { + this.contentType = contentType; + } + + @Override + public void sendError(int status, String message) { + } + + @Override + public ChannelBuffer getContent() { + try { + content.clear(); + content.writeBytes(sw.toString().getBytes("utf-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return content; + } + +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/DouyuOutbound.java b/douyu-http/src/main/java/com/codefollower/douyu/http/DouyuOutbound.java new file mode 100644 index 0000000..3cc2273 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/DouyuOutbound.java @@ -0,0 +1,56 @@ +package com.codefollower.douyu.http; + +import java.io.IOException; + + +import com.codefollower.douyu.http.websocket.DefaultWebSocketFrame; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; +import com.codefollower.douyu.netty.channel.Channel; + +public class DouyuOutbound implements douyu.http.WebSocket.Outbound { + private Channel channel; + + public DouyuOutbound(Channel channel) { + this.channel = channel; + } + + @Override + public void close() { + channel.close(); + } + + @Override + public boolean isOpen() { + return channel.isOpen(); + } + + @Override + public void send(String data) throws IOException { + checkClosed(); + channel.write(new DefaultWebSocketFrame(data)); + } + + @Override + public void send(int type, String data) throws IOException { + checkClosed(); + channel.write(new DefaultWebSocketFrame(type, ChannelBuffers + .copiedBuffer(data, com.codefollower.douyu.netty.util.CharsetUtil.UTF_8))); + } + + @Override + public void send(int type, byte[] data) throws IOException { + send(type, data, 0, data.length); + } + + @Override + public void send(int type, byte[] data, int offset, int length) throws IOException { + checkClosed(); + channel.write(new DefaultWebSocketFrame(type, ChannelBuffers.wrappedBuffer(data, offset, length))); + } + + private void checkClosed() throws IOException { + if (!isOpen()) + throw new IOException("WebSocket is closed"); + } + +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/DouyuUploadedFile.java b/douyu-http/src/main/java/com/codefollower/douyu/http/DouyuUploadedFile.java new file mode 100644 index 0000000..e98db0a --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/DouyuUploadedFile.java @@ -0,0 +1,91 @@ +package com.codefollower.douyu.http; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; + + +import douyu.http.UploadedFile; + +public class DouyuUploadedFile implements UploadedFile { + private FileUpload fileUpload; + + public DouyuUploadedFile(FileUpload fileUpload) { + this.fileUpload = fileUpload; + } + + @Override + public byte[] getBytes() { + try { + return fileUpload.get(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + } + + @Override + public String getContent() { + try { + return fileUpload.getString(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + } + + @Override + public String getContent(String encoding) { + try { + return fileUpload.getString(Charset.forName(encoding)); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + } + + @Override + public String getContentType() { + return fileUpload.getContentType(); + } + + @Override + public String getFullName() { + return fileUpload.getFilename(); + } + + @Override + public String getPathName() { + return fileUpload.getFilename(); + } + + @Override + public String getSimpleName() { + return fileUpload.getFilename(); + } + + @Override + public long getSize() { + try { + return fileUpload.get().length; + } catch (IOException e) { + e.printStackTrace(); + return 0; + } + } + + @Override + public void saveTo(String file) throws Exception { + fileUpload.renameTo(new File(file)); + + } + + @Override + public void saveTo(File file) throws Exception { + fileUpload.renameTo(file); + } + +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/FileUpload.java b/douyu-http/src/main/java/com/codefollower/douyu/http/FileUpload.java new file mode 100644 index 0000000..2e70c6d --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/FileUpload.java @@ -0,0 +1,66 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +/** + * FileUpload interface that could be in memory, on temporary file or any other implementations. + * + * Most methods are inspired from java.io.File API. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @author Frederic Bregier + * + */ +public interface FileUpload extends HttpData { + /** + * Returns the original filename in the client's filesystem, + * as provided by the browser (or other client software). + * @return the original filename + */ + public String getFilename(); + + /** + * Set the original filename + * @param filename + */ + public void setFilename(String filename); + + /** + * Set the Content Type passed by the browser if defined + * @param contentType Content Type to set - must be not null + */ + public void setContentType(String contentType); + + /** + * Returns the content type passed by the browser or null if not defined. + * @return the content type passed by the browser or null if not defined. + */ + public String getContentType(); + + /** + * Set the Content-Transfer-Encoding type from String as 7bit, 8bit or binary + * @param contentTransferEncoding + */ + public void setContentTransferEncoding(String contentTransferEncoding); + + /** + * Returns the Content-Transfer-Encoding + * @return the Content-Transfer-Encoding + */ + public String getContentTransferEncoding(); +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpChunk.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpChunk.java new file mode 100644 index 0000000..81baeec --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpChunk.java @@ -0,0 +1,130 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; +import com.codefollower.douyu.netty.channel.ChannelPipeline; + +/** + * An HTTP chunk which is used for HTTP chunked transfer-encoding. + * {@link HttpMessageDecoder} generates {@link HttpChunk} after + * {@link HttpMessage} when the content is large or the encoding of the content + * is 'chunked. If you prefer not to receive {@link HttpChunk} in your handler, + * please {@link HttpChunkAggregator} after {@link HttpMessageDecoder} in the + * {@link ChannelPipeline}. + * + * @author The Netty Project + * @author Trustin Lee + * @version $Rev$, $Date$ + * + * @apiviz.landmark + */ +public interface HttpChunk { + + /** + * The 'end of content' marker in chunked encoding. + */ + static HttpChunkTrailer LAST_CHUNK = new HttpChunkTrailer() { + @Override + public ChannelBuffer getContent() { + return ChannelBuffers.EMPTY_BUFFER; + } + + @Override + public void setContent(ChannelBuffer content) { + throw new IllegalStateException("read-only"); + } + + @Override + public boolean isLast() { + return true; + } + + @Override + public void addHeader(String name, Object value) { + throw new IllegalStateException("read-only"); + } + + @Override + public void clearHeaders() { + // NOOP + } + + @Override + public boolean containsHeader(String name) { + return false; + } + + @Override + public String getHeader(String name) { + return null; + } + + @Override + public Set getHeaderNames() { + return Collections.emptySet(); + } + + @Override + public List getHeaders(String name) { + return Collections.emptyList(); + } + + @Override + public List> getHeaders() { + return Collections.emptyList(); + } + + @Override + public void removeHeader(String name) { + // NOOP + } + + @Override + public void setHeader(String name, Object value) { + throw new IllegalStateException("read-only"); + } + + @Override + public void setHeader(String name, Iterable values) { + throw new IllegalStateException("read-only"); + } + }; + + /** + * Returns {@code true} if and only if this chunk is the 'end of content' + * marker. + */ + boolean isLast(); + + /** + * Returns the content of this chunk. If this is the 'end of content' + * marker, {@link ChannelBuffers#EMPTY_BUFFER} will be returned. + */ + ChannelBuffer getContent(); + + /** + * Sets the content of this chunk. If an empty buffer is specified, + * this chunk becomes the 'end of content' marker. + */ + void setContent(ChannelBuffer content); +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpChunkAggregator.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpChunkAggregator.java new file mode 100644 index 0000000..3c7727e --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpChunkAggregator.java @@ -0,0 +1,165 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import static com.codefollower.douyu.http.HttpHeaders.*; +import static com.codefollower.douyu.netty.channel.Channels.*; + +import java.util.List; +import java.util.Map.Entry; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; +import com.codefollower.douyu.netty.channel.ChannelHandler; +import com.codefollower.douyu.netty.channel.ChannelHandlerContext; +import com.codefollower.douyu.netty.channel.ChannelPipeline; +import com.codefollower.douyu.netty.channel.Channels; +import com.codefollower.douyu.netty.channel.MessageEvent; +import com.codefollower.douyu.netty.channel.SimpleChannelUpstreamHandler; +import com.codefollower.douyu.netty.handler.codec.frame.TooLongFrameException; +import com.codefollower.douyu.netty.util.CharsetUtil; + +/** + * A {@link ChannelHandler} that aggregates an {@link HttpMessage} + * and its following {@link HttpChunk}s into a single {@link HttpMessage} with + * no following {@link HttpChunk}s. It is useful when you don't want to take + * care of HTTP messages whose transfer encoding is 'chunked'. Insert this + * handler after {@link HttpMessageDecoder} in the {@link ChannelPipeline}: + *
+ * {@link ChannelPipeline} p = ...;
+ * ...
+ * p.addLast("decoder", new {@link HttpRequestDecoder}());
+ * p.addLast("aggregator", new {@link HttpChunkAggregator}(1048576));
+ * ...
+ * p.addLast("encoder", new {@link HttpResponseEncoder}());
+ * p.addLast("handler", new HttpRequestHandler());
+ * 
+ * + * @author The Netty Project + * @author Trustin Lee + * @version $Rev$, $Date$ + * + * @apiviz.landmark + * @apiviz.has org.jboss.netty.handler.codec.http.HttpChunk oneway - - filters out + */ +public class HttpChunkAggregator extends SimpleChannelUpstreamHandler { + + private static final ChannelBuffer CONTINUE = ChannelBuffers.copiedBuffer( + "HTTP/1.1 100 Continue\r\n\r\n", CharsetUtil.US_ASCII); + + private final int maxContentLength; + private HttpMessage currentMessage; + + /** + * Creates a new instance. + * + * @param maxContentLength + * the maximum length of the aggregated content. + * If the length of the aggregated content exceeds this value, + * a {@link TooLongFrameException} will be raised. + */ + public HttpChunkAggregator(int maxContentLength) { + if (maxContentLength <= 0) { + throw new IllegalArgumentException( + "maxContentLength must be a positive integer: " + + maxContentLength); + } + this.maxContentLength = maxContentLength; + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + + Object msg = e.getMessage(); + HttpMessage currentMessage = this.currentMessage; + + if (msg instanceof HttpMessage) { + HttpMessage m = (HttpMessage) msg; + + // Handle the 'Expect: 100-continue' header if necessary. + // TODO: Respond with 413 Request Entity Too Large + // and discard the traffic or close the connection. + // No need to notify the upstream handlers - just log. + // If decoding a response, just throw an exception. + if (is100ContinueExpected(m)) { + write(ctx, succeededFuture(ctx.getChannel()), CONTINUE.duplicate()); + } + + if (m.isChunked()) { + // A chunked message - remove 'Transfer-Encoding' header, + // initialize the cumulative buffer, and wait for incoming chunks. + List encodings = m.getHeaders(HttpHeaders.Names.TRANSFER_ENCODING); + encodings.remove(HttpHeaders.Values.CHUNKED); + if (encodings.isEmpty()) { + m.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING); + } + m.setChunked(false); + m.setContent(ChannelBuffers.dynamicBuffer(e.getChannel().getConfig().getBufferFactory())); + this.currentMessage = m; + } else { + // Not a chunked message - pass through. + this.currentMessage = null; + ctx.sendUpstream(e); + } + } else if (msg instanceof HttpChunk) { + // Sanity check + if (currentMessage == null) { + throw new IllegalStateException( + "received " + HttpChunk.class.getSimpleName() + + " without " + HttpMessage.class.getSimpleName()); + } + + // Merge the received chunk into the content of the current message. + HttpChunk chunk = (HttpChunk) msg; + ChannelBuffer content = currentMessage.getContent(); + + if (content.readableBytes() > maxContentLength - chunk.getContent().readableBytes()) { + // TODO: Respond with 413 Request Entity Too Large + // and discard the traffic or close the connection. + // No need to notify the upstream handlers - just log. + // If decoding a response, just throw an exception. + throw new TooLongFrameException( + "HTTP content length exceeded " + maxContentLength + + " bytes."); + } + + content.writeBytes(chunk.getContent()); + if (chunk.isLast()) { + this.currentMessage = null; + + // Merge trailing headers into the message. + if (chunk instanceof HttpChunkTrailer) { + HttpChunkTrailer trailer = (HttpChunkTrailer) chunk; + for (Entry header: trailer.getHeaders()) { + currentMessage.setHeader(header.getKey(), header.getValue()); + } + } + + // Set the 'Content-Length' header. + currentMessage.setHeader( + HttpHeaders.Names.CONTENT_LENGTH, + String.valueOf(content.readableBytes())); + + // All done - generate the event. + Channels.fireMessageReceived(ctx, currentMessage, e.getRemoteAddress()); + } + } else { + // Neither HttpMessage or HttpChunk + ctx.sendUpstream(e); + } + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpChunkTrailer.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpChunkTrailer.java new file mode 100644 index 0000000..cb0ed9b --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpChunkTrailer.java @@ -0,0 +1,103 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * The last {@link HttpChunk} which has trailing headers. + * + * @author The Netty Project + * @author Trustin Lee + * @version $Rev$, $Date$ + */ +public interface HttpChunkTrailer extends HttpChunk { + + /** + * Always returns {@code true}. + */ + @Override + boolean isLast(); + + /** + * Returns the trailing header value with the specified header name. + * If there are more than one trailing header value for the specified + * header name, the first value is returned. + * + * @return the header value or {@code null} if there is no such header + * + */ + String getHeader(String name); + + /** + * Returns the trailing header values with the specified header name. + * + * @return the {@link List} of header values. An empty list if there is no + * such header. + */ + List getHeaders(String name); + + /** + * Returns the all header names and values that this trailer contains. + * + * @return the {@link List} of the header name-value pairs. An empty list + * if there is no header in this trailer. + */ + List> getHeaders(); + + /** + * Returns {@code true} if and only if there is a trailing header with + * the specified header name. + */ + boolean containsHeader(String name); + + /** + * Returns the {@link Set} of all trailing header names that this trailer + * contains. + */ + Set getHeaderNames(); + + /** + * Adds a new trailing header with the specified name and value. + */ + void addHeader(String name, Object value); + + /** + * Sets a new trailing header with the specified name and value. + * If there is an existing trailing header with the same name, the existing + * one is removed. + */ + void setHeader(String name, Object value); + + /** + * Sets a new trailing header with the specified name and values. + * If there is an existing trailing header with the same name, the existing + * one is removed. + */ + void setHeader(String name, Iterable values); + + /** + * Removes the trailing header with the specified name. + */ + void removeHeader(String name); + + /** + * Removes all trailing headers from this trailer. + */ + void clearHeaders(); +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpCodecUtil.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpCodecUtil.java new file mode 100644 index 0000000..738af4a --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpCodecUtil.java @@ -0,0 +1,176 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.nio.charset.Charset; +import java.util.List; + +import com.codefollower.douyu.netty.util.CharsetUtil; + +/** + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @version $Rev$, $Date$ + */ +class HttpCodecUtil { + //space ' ' + static final byte SP = 32; + + //tab ' ' + static final byte HT = 9; + + /** + * Carriage return + */ + static final byte CR = 13; + + /** + * Equals '=' + */ + static final byte EQUALS = 61; + + /** + * Line feed character + */ + static final byte LF = 10; + + /** + * carriage return line feed + */ + static final byte[] CRLF = new byte[] { CR, LF }; + + /** + * Colon ':' + */ + static final byte COLON = 58; + + /** + * Semicolon ';' + */ + static final byte SEMICOLON = 59; + + /** + * comma ',' + */ + static final byte COMMA = 44; + + static final byte DOUBLE_QUOTE = '"'; + + static final Charset DEFAULT_CHARSET = CharsetUtil.UTF_8; + + private HttpCodecUtil() { + super(); + } + + static void validateHeaderName(String name) { + if (name == null) { + throw new NullPointerException("name"); + } + for (int i = 0; i < name.length(); i ++) { + char c = name.charAt(i); + if (c > 127) { + throw new IllegalArgumentException( + "name contains non-ascii character: " + name); + } + + // Check prohibited characters. + switch (c) { + case '\t': case '\n': case 0x0b: case '\f': case '\r': + case ' ': case ',': case ':': case ';': case '=': + throw new IllegalArgumentException( + "name contains one of the following prohibited characters: " + + "=,;: \\t\\r\\n\\v\\f: " + name); + } + } + } + + static void validateHeaderValue(String value) { + if (value == null) { + throw new NullPointerException("value"); + } + + // 0 - the previous character was neither CR nor LF + // 1 - the previous character was CR + // 2 - the previous character was LF + int state = 0; + + for (int i = 0; i < value.length(); i ++) { + char c = value.charAt(i); + + // Check the absolutely prohibited characters. + switch (c) { + case 0x0b: // Vertical tab + throw new IllegalArgumentException( + "value contains a prohibited character '\\v': " + value); + case '\f': + throw new IllegalArgumentException( + "value contains a prohibited character '\\f': " + value); + } + + // Check the CRLF (HT | SP) pattern + switch (state) { + case 0: + switch (c) { + case '\r': + state = 1; + break; + case '\n': + state = 2; + break; + } + break; + case 1: + switch (c) { + case '\n': + state = 2; + break; + default: + throw new IllegalArgumentException( + "Only '\\n' is allowed after '\\r': " + value); + } + break; + case 2: + switch (c) { + case '\t': case ' ': + state = 0; + break; + default: + throw new IllegalArgumentException( + "Only ' ' and '\\t' are allowed after '\\n': " + value); + } + } + } + + if (state != 0) { + throw new IllegalArgumentException( + "value must not end with '\\r' or '\\n':" + value); + } + } + + static boolean isTransferEncodingChunked(HttpMessage m) { + List chunked = m.getHeaders(HttpHeaders.Names.TRANSFER_ENCODING); + if (chunked.isEmpty()) { + return false; + } + + for (String v: chunked) { + if (v.equalsIgnoreCase(HttpHeaders.Values.CHUNKED)) { + return true; + } + } + return false; + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpContentCompressor.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpContentCompressor.java new file mode 100644 index 0000000..97bbbda --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpContentCompressor.java @@ -0,0 +1,99 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.handler.codec.compression.ZlibEncoder; +import com.codefollower.douyu.netty.handler.codec.compression.ZlibWrapper; +import com.codefollower.douyu.netty.handler.codec.embedder.EncoderEmbedder; + +/** + * Compresses an {@link HttpMessage} and an {@link HttpChunk} in {@code gzip} or + * {@code deflate} encoding while respecting the {@code "Accept-Encoding"} header. + * If there is no matching encoding, no compression is done. For more + * information on how this handler modifies the message, please refer to + * {@link HttpContentEncoder}. + * + * @author The Netty Project + * @author Trustin Lee + * @version $Rev$, $Date$ + */ +public class HttpContentCompressor extends HttpContentEncoder { + + private final int compressionLevel; + + /** + * Creates a new handler with the default compression level (6). + */ + public HttpContentCompressor() { + this(6); + } + + /** + * Creates a new handler with the specified compression level. + * + * @param compressionLevel + * {@code 1} yields the fastest compression and {@code 9} yields the + * best compression. {@code 0} means no compression. The default + * compression level is {@code 6}. + */ + public HttpContentCompressor(int compressionLevel) { + if (compressionLevel < 0 || compressionLevel > 9) { + throw new IllegalArgumentException( + "compressionLevel: " + compressionLevel + + " (expected: 0-9)"); + } + this.compressionLevel = compressionLevel; + } + + @Override + protected EncoderEmbedder newContentEncoder(String acceptEncoding) throws Exception { + ZlibWrapper wrapper = determineWrapper(acceptEncoding); + if (wrapper == null) { + return null; + } + + return new EncoderEmbedder(new ZlibEncoder(wrapper, compressionLevel)); + } + + @Override + protected String getTargetContentEncoding(String acceptEncoding) throws Exception { + ZlibWrapper wrapper = determineWrapper(acceptEncoding); + if (wrapper == null) { + return null; + } + + switch (wrapper) { + case GZIP: + return "gzip"; + case ZLIB: + return "deflate"; + default: + throw new Error(); + } + } + + private ZlibWrapper determineWrapper(String acceptEncoding) { + // FIXME: Use the Q value. + if (acceptEncoding.indexOf("gzip") >= 0) { + return ZlibWrapper.GZIP; + } + if (acceptEncoding.indexOf("deflate") >= 0) { + return ZlibWrapper.ZLIB; + } + return null; + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpContentDecoder.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpContentDecoder.java new file mode 100644 index 0000000..06df675 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpContentDecoder.java @@ -0,0 +1,176 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; +import com.codefollower.douyu.netty.channel.ChannelHandlerContext; +import com.codefollower.douyu.netty.channel.Channels; +import com.codefollower.douyu.netty.channel.MessageEvent; +import com.codefollower.douyu.netty.channel.SimpleChannelUpstreamHandler; +import com.codefollower.douyu.netty.handler.codec.embedder.DecoderEmbedder; + +/** + * Decodes the content of the received {@link HttpRequest} and {@link HttpChunk}. + * The original content is replaced with the new content decoded by the + * {@link DecoderEmbedder}, which is created by {@link #newContentDecoder(String)}. + * Once decoding is finished, the value of the 'Content-Encoding' + * header is set to the target content encoding, as returned by {@link #getTargetContentEncoding(String)}. + * Also, the 'Content-Length' header is updated to the length of the + * decoded content. If the content encoding of the original is not supported + * by the decoder, {@link #newContentDecoder(String)} should return {@code null} + * so that no decoding occurs (i.e. pass-through). + *

+ * Please note that this is an abstract class. You have to extend this class + * and implement {@link #newContentDecoder(String)} properly to make this class + * functional. For example, refer to the source code of {@link HttpContentDecompressor}. + *

+ * This handler must be placed after {@link HttpMessageDecoder} in the pipeline + * so that this handler can intercept HTTP requests after {@link HttpMessageDecoder} + * converts {@link ChannelBuffer}s into HTTP requests. + * + * @author The Netty Project + * @author Trustin Lee + * @version $Rev$, $Date$ + */ +public abstract class HttpContentDecoder extends SimpleChannelUpstreamHandler { + + private DecoderEmbedder decoder; + + /** + * Creates a new instance. + */ + protected HttpContentDecoder() { + super(); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { + Object msg = e.getMessage(); + if (msg instanceof HttpResponse && ((HttpResponse) msg).getStatus().getCode() == 100) { + // 100-continue response must be passed through. + ctx.sendDownstream(e); + } else if (msg instanceof HttpMessage) { + HttpMessage m = (HttpMessage) msg; + + decoder = null; + + // Determine the content encoding. + String contentEncoding = m.getHeader(HttpHeaders.Names.CONTENT_ENCODING); + if (contentEncoding != null) { + contentEncoding = contentEncoding.trim(); + } else { + contentEncoding = HttpHeaders.Values.IDENTITY; + } + + boolean hasContent = m.isChunked() || m.getContent().readable(); + if (hasContent && (decoder = newContentDecoder(contentEncoding)) != null) { + // Decode the content and remove or replace the existing headers + // so that the message looks like a decoded message. + m.setHeader( + HttpHeaders.Names.CONTENT_ENCODING, + getTargetContentEncoding(contentEncoding)); + + if (!m.isChunked()) { + ChannelBuffer content = m.getContent(); + // Decode the content + content = ChannelBuffers.wrappedBuffer( + decode(content), finishDecode()); + + // Replace the content. + m.setContent(content); + if (m.containsHeader(HttpHeaders.Names.CONTENT_LENGTH)) { + m.setHeader( + HttpHeaders.Names.CONTENT_LENGTH, + Integer.toString(content.readableBytes())); + } + } + } + + // Because HttpMessage is a mutable object, we can simply forward the received event. + ctx.sendUpstream(e); + } else if (msg instanceof HttpChunk) { + HttpChunk c = (HttpChunk) msg; + ChannelBuffer content = c.getContent(); + + // Decode the chunk if necessary. + if (decoder != null) { + if (!c.isLast()) { + content = decode(content); + if (content.readable()) { + c.setContent(content); + ctx.sendUpstream(e); + } + } else { + ChannelBuffer lastProduct = finishDecode(); + + // Generate an additional chunk if the decoder produced + // the last product on closure, + if (lastProduct.readable()) { + Channels.fireMessageReceived( + ctx, new DefaultHttpChunk(lastProduct), e.getRemoteAddress()); + } + + // Emit the last chunk. + ctx.sendUpstream(e); + } + } else { + ctx.sendUpstream(e); + } + } else { + ctx.sendUpstream(e); + } + } + + /** + * Returns a new {@link DecoderEmbedder} that decodes the HTTP message + * content encoded in the specified contentEncoding. + * + * @param contentEncoding the value of the {@code "Content-Encoding"} header + * @return a new {@link DecoderEmbedder} if the specified encoding is supported. + * {@code null} otherwise (alternatively, you can throw an exception + * to block unknown encoding). + */ + protected abstract DecoderEmbedder newContentDecoder(String contentEncoding) throws Exception; + + /** + * Returns the expected content encoding of the decoded content. + * This method returns {@code "identity"} by default, which is the case for + * most decoders. + * + * @param contentEncoding the value of the {@code "Content-Encoding"} header + * @return the expected content encoding of the new content + */ + protected String getTargetContentEncoding(String contentEncoding) throws Exception { + return HttpHeaders.Values.IDENTITY; + } + + private ChannelBuffer decode(ChannelBuffer buf) { + decoder.offer(buf); + return ChannelBuffers.wrappedBuffer(decoder.pollAll(new ChannelBuffer[decoder.size()])); + } + + private ChannelBuffer finishDecode() { + ChannelBuffer result; + if (decoder.finish()) { + result = ChannelBuffers.wrappedBuffer(decoder.pollAll(new ChannelBuffer[decoder.size()])); + } else { + result = ChannelBuffers.EMPTY_BUFFER; + } + decoder = null; + return result; + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpContentDecompressor.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpContentDecompressor.java new file mode 100644 index 0000000..dcf819a --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpContentDecompressor.java @@ -0,0 +1,45 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.handler.codec.compression.ZlibDecoder; +import com.codefollower.douyu.netty.handler.codec.compression.ZlibWrapper; +import com.codefollower.douyu.netty.handler.codec.embedder.DecoderEmbedder; + +/** + * Decompresses an {@link HttpMessage} and an {@link HttpChunk} compressed in + * {@code gzip} or {@code deflate} encoding. For more information on how this + * handler modifies the message, please refer to {@link HttpContentDecoder}. + * + * @author The Netty Project + * @author Trustin Lee + * @version $Rev$, $Date$ + */ +public class HttpContentDecompressor extends HttpContentDecoder { + @Override + protected DecoderEmbedder newContentDecoder(String contentEncoding) throws Exception { + if ("gzip".equalsIgnoreCase(contentEncoding) || "x-gzip".equalsIgnoreCase(contentEncoding)) { + return new DecoderEmbedder(new ZlibDecoder(ZlibWrapper.GZIP)); + } else if ("deflate".equalsIgnoreCase(contentEncoding) || "x-deflate".equalsIgnoreCase(contentEncoding)) { + // To be strict, 'deflate' means ZLIB, but some servers were not implemented correctly. + return new DecoderEmbedder(new ZlibDecoder(ZlibWrapper.ZLIB_OR_NONE)); + } + + // 'identity' or unsupported + return null; + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpContentEncoder.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpContentEncoder.java new file mode 100644 index 0000000..b76bd02 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpContentEncoder.java @@ -0,0 +1,198 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.util.Queue; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; +import com.codefollower.douyu.netty.channel.ChannelHandlerContext; +import com.codefollower.douyu.netty.channel.Channels; +import com.codefollower.douyu.netty.channel.MessageEvent; +import com.codefollower.douyu.netty.channel.SimpleChannelHandler; +import com.codefollower.douyu.netty.handler.codec.embedder.EncoderEmbedder; +import com.codefollower.douyu.netty.util.internal.LinkedTransferQueue; + +/** + * Encodes the content of the outbound {@link HttpResponse} and {@link HttpChunk}. + * The original content is replaced with the new content encoded by the + * {@link EncoderEmbedder}, which is created by {@link #newContentEncoder(String)}. + * Once encoding is finished, the value of the 'Content-Encoding' header + * is set to the target content encoding, as returned by {@link #getTargetContentEncoding(String)}. + * Also, the 'Content-Length' header is updated to the length of the + * encoded content. If there is no supported encoding in the + * corresponding {@link HttpRequest}'s {@code "Accept-Encoding"} header, + * {@link #newContentEncoder(String)} should return {@code null} so that no + * encoding occurs (i.e. pass-through). + *

+ * Please note that this is an abstract class. You have to extend this class + * and implement {@link #newContentEncoder(String)} and {@link #getTargetContentEncoding(String)} + * properly to make this class functional. For example, refer to the source + * code of {@link HttpContentCompressor}. + *

+ * This handler must be placed after {@link HttpMessageEncoder} in the pipeline + * so that this handler can intercept HTTP responses before {@link HttpMessageEncoder} + * converts them into {@link ChannelBuffer}s. + * + * @author The Netty Project + * @author Trustin Lee + * @version $Rev$, $Date$ + */ +public abstract class HttpContentEncoder extends SimpleChannelHandler { + + private final Queue acceptEncodingQueue = new LinkedTransferQueue(); + private volatile EncoderEmbedder encoder; + + /** + * Creates a new instance. + */ + protected HttpContentEncoder() { + super(); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + Object msg = e.getMessage(); + if (!(msg instanceof HttpMessage)) { + ctx.sendUpstream(e); + return; + } + + HttpMessage m = (HttpMessage) msg; + String acceptedEncoding = m.getHeader(HttpHeaders.Names.ACCEPT_ENCODING); + if (acceptedEncoding == null) { + acceptedEncoding = HttpHeaders.Values.IDENTITY; + } + boolean offered = acceptEncodingQueue.offer(acceptedEncoding); + assert offered; + + ctx.sendUpstream(e); + } + + @Override + public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + + Object msg = e.getMessage(); + if (msg instanceof HttpResponse && ((HttpResponse) msg).getStatus().getCode() == 100) { + // 100-continue response must be passed through. + ctx.sendDownstream(e); + } else if (msg instanceof HttpMessage) { + HttpMessage m = (HttpMessage) msg; + + encoder = null; + + // Determine the content encoding. + String acceptEncoding = acceptEncodingQueue.poll(); + if (acceptEncoding == null) { + throw new IllegalStateException("cannot send more responses than requests"); + } + + if ((encoder = newContentEncoder(acceptEncoding)) != null) { + // Encode the content and remove or replace the existing headers + // so that the message looks like a decoded message. + m.setHeader( + HttpHeaders.Names.CONTENT_ENCODING, + getTargetContentEncoding(acceptEncoding)); + + if (!m.isChunked()) { + ChannelBuffer content = m.getContent(); + // Encode the content. + content = ChannelBuffers.wrappedBuffer( + encode(content), finishEncode()); + + // Replace the content. + m.setContent(content); + if (m.containsHeader(HttpHeaders.Names.CONTENT_LENGTH)) { + m.setHeader( + HttpHeaders.Names.CONTENT_LENGTH, + Integer.toString(content.readableBytes())); + } + } + } + + // Because HttpMessage is a mutable object, we can simply forward the write request. + ctx.sendDownstream(e); + } else if (msg instanceof HttpChunk) { + HttpChunk c = (HttpChunk) msg; + ChannelBuffer content = c.getContent(); + + // Encode the chunk if necessary. + if (encoder != null) { + if (!c.isLast()) { + content = encode(content); + if (content.readable()) { + c.setContent(content); + ctx.sendDownstream(e); + } + } else { + ChannelBuffer lastProduct = finishEncode(); + + // Generate an additional chunk if the decoder produced + // the last product on closure, + if (lastProduct.readable()) { + Channels.write( + ctx, Channels.succeededFuture(e.getChannel()), new DefaultHttpChunk(lastProduct), e.getRemoteAddress()); + } + + // Emit the last chunk. + ctx.sendDownstream(e); + } + } else { + ctx.sendDownstream(e); + } + } else { + ctx.sendDownstream(e); + } + } + + /** + * Returns a new {@link EncoderEmbedder} that encodes the HTTP message + * content. + * + * @param acceptEncoding + * the value of the {@code "Accept-Encoding"} header + * + * @return a new {@link EncoderEmbedder} if there is a supported encoding + * in {@code acceptEncoding}. {@code null} otherwise. + */ + protected abstract EncoderEmbedder newContentEncoder(String acceptEncoding) throws Exception; + + /** + * Returns the expected content encoding of the encoded content. + * + * @param acceptEncoding the value of the {@code "Accept-Encoding"} header + * @return the expected content encoding of the new content + */ + protected abstract String getTargetContentEncoding(String acceptEncoding) throws Exception; + + private ChannelBuffer encode(ChannelBuffer buf) { + encoder.offer(buf); + return ChannelBuffers.wrappedBuffer(encoder.pollAll(new ChannelBuffer[encoder.size()])); + } + + private ChannelBuffer finishEncode() { + ChannelBuffer result; + if (encoder.finish()) { + result = ChannelBuffers.wrappedBuffer(encoder.pollAll(new ChannelBuffer[encoder.size()])); + } else { + result = ChannelBuffers.EMPTY_BUFFER; + } + encoder = null; + return result; + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpData.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpData.java new file mode 100644 index 0000000..b6f26bf --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpData.java @@ -0,0 +1,158 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; + +/** + * Extended interface for InterfaceHttpData + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @author Frederic Bregier + * + */ +public interface HttpData extends InterfaceHttpData { + /** + * Set the content from the ChannelBuffer (erase any previous data) + * @param buffer must be not null + * @exception IOException + */ + public void setContent(ChannelBuffer buffer) throws IOException; + + /** + * Add the content from the ChannelBuffer + * @param buffer must be not null except if last is set to False + * @param last True of the buffer is the last one + * @exception IOException + */ + public void addContent(ChannelBuffer buffer, boolean last) + throws IOException; + + /** + * Set the content from the file (erase any previous data) + * @param file must be not null + * @exception IOException + */ + public void setContent(File file) throws IOException; + + /** + * Set the content from the inputStream (erase any previous data) + * @param inputStream must be not null + * @exception IOException + */ + public void setContent(InputStream inputStream) throws IOException; + + /** + * + * @return True if the InterfaceHttpData is completed (all data are stored) + */ + public boolean isCompleted(); + + /** + * Returns the size in byte of the InterfaceHttpData + * @return the size of the InterfaceHttpData + */ + public long length(); + + /** + * Deletes the underlying storage for a file item, + * including deleting any associated temporary disk file. + */ + public void delete(); + + /** + * Returns the contents of the file item as an array of bytes. + * @return the contents of the file item as an array of bytes. + * @exception IOException + */ + public byte[] get() throws IOException; + + /** + * Returns the content of the file item as a ChannelBuffer + * @return the content of the file item as a ChannelBuffer + * @throws IOException + */ + public ChannelBuffer getChannelBuffer() throws IOException; + + /** + * Returns a ChannelBuffer for the content from the current position with at most length read + * bytes, increasing the current position of the Bytes read. Once it arrives at the end, + * it returns an EMPTY_BUFFER and it resets the current position to 0. + * @param length + * @return a ChannelBuffer for the content from the current position or + * an EMPTY_BUFFER if there is no more data to return + * @throws IOException + */ + public ChannelBuffer getChunk(int length) throws IOException; + + /** + * Returns the contents of the file item as a String, using the default character encoding. + * @return the contents of the file item as a String, using the default character encoding. + * @exception IOException + */ + public String getString() throws IOException; + + /** + * Returns the contents of the file item as a String, using the specified charset. + * @param encoding the charset to use + * @return the contents of the file item as a String, using the specified charset. + * @exception IOException + */ + public String getString(Charset encoding) throws IOException; + + /** + * Set the Charset passed by the browser if defined + * @param charset Charset to set - must be not null + */ + public void setCharset(Charset charset); + + /** + * Returns the Charset passed by the browser or null if not defined. + * @return the Charset passed by the browser or null if not defined. + */ + public Charset getCharset(); + + /** + * A convenience method to write an uploaded item to disk. + * If a previous one exists, it will be deleted. + * Once this method is called, if successful, the new file will be out of the cleaner + * of the factory that creates the original InterfaceHttpData object. + * @param dest destination file - must be not null + * @return True if the write is successful + * @exception IOException + */ + public boolean renameTo(File dest) throws IOException; + + /** + * Provides a hint as to whether or not the file contents will be read from memory. + * @return True if the file contents is in memory. + */ + public boolean isInMemory(); + + /** + * + * @return the associated File if this data is represented in a file + * @exception IOException if this data is not represented by a file + */ + public File getFile() throws IOException; + +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpDataFactory.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpDataFactory.java new file mode 100644 index 0000000..82942b5 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpDataFactory.java @@ -0,0 +1,86 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.nio.charset.Charset; + +/** + * Interface to enable creation of InterfaceHttpData objects + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @author Frederic Bregier + * + */ +public interface HttpDataFactory { + /** + * + * @param request associated request + * @param name + * @return a new Attribute with no value + * @throws NullPointerException + * @throws IllegalArgumentException + */ + public Attribute createAttribute(HttpRequest request, String name) + throws NullPointerException, IllegalArgumentException; + + /** + * + * @param request associated request + * @param name + * @param value + * @return a new Attribute + * @throws NullPointerException + * @throws IllegalArgumentException + */ + public Attribute createAttribute(HttpRequest request, String name, String value) + throws NullPointerException, IllegalArgumentException; + + /** + * + * @param request associated request + * @param name + * @param filename + * @param contentType + * @param charset + * @param size the size of the Uploaded file + * @return a new FileUpload + */ + public FileUpload createFileUpload(HttpRequest request, String name, String filename, + String contentType, String contentTransferEncoding, Charset charset, + long size) throws NullPointerException, IllegalArgumentException; + + /** + * Remove the given InterfaceHttpData from clean list (will not delete the file, except if the file + * is still a temporary one as setup at construction) + * @param request associated request + * @param data + */ + public void removeHttpDataFromClean(HttpRequest request, InterfaceHttpData data); + + /** + * Remove all InterfaceHttpData from virtual File storage from clean list for the request + * + * @param request associated request + */ + public void cleanRequestHttpDatas(HttpRequest request); + + /** + * Remove all InterfaceHttpData from virtual File storage from clean list for all requests + */ + public void cleanAllHttpDatas(); +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpHeaderDateFormat.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpHeaderDateFormat.java new file mode 100644 index 0000000..4d4e167 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpHeaderDateFormat.java @@ -0,0 +1,92 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** + * This DateFormat decodes 3 formats of {@link Date}, but only encodes the one, + * the first: + *

    + *
  • Sun, 06 Nov 1994 08:49:37 GMT: standard specification, the only one with + * valid generation
  • + *
  • Sun, 06 Nov 1994 08:49:37 GMT: obsolete specification
  • + *
  • Sun Nov 6 08:49:37 1994: obsolete specification
  • + *
+ * + * @author The Netty Project + * @author Trustin Lee + * @author Rogiel Josias Sulzbach + * @version $Rev$, $Date$ + */ +final class HttpHeaderDateFormat extends SimpleDateFormat { + private static final long serialVersionUID = -925286159755905325L; + + private final SimpleDateFormat format1 = new HttpHeaderDateFormatObsolete1(); + private final SimpleDateFormat format2 = new HttpHeaderDateFormatObsolete2(); + + /** + * Standard date format

+ * Sun, 06 Nov 1994 08:49:37 GMT -> E, d MMM yyyy HH:mm:ss z + */ + HttpHeaderDateFormat() { + super("E, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH); + setTimeZone(TimeZone.getTimeZone("GMT")); + } + + @Override + public Date parse(String text, ParsePosition pos) { + Date date = super.parse(text, pos); + if (date == null) { + date = format1.parse(text, pos); + } + if (date == null) { + date = format2.parse(text, pos); + } + return date; + } + + /** + * First obsolete format

+ * Sunday, 06-Nov-94 08:49:37 GMT -> E, d-MMM-y HH:mm:ss z + */ + private final class HttpHeaderDateFormatObsolete1 extends SimpleDateFormat { + private static final long serialVersionUID = -3178072504225114298L; + + HttpHeaderDateFormatObsolete1() { + super("E, dd-MMM-y HH:mm:ss z", Locale.ENGLISH); + setTimeZone(TimeZone.getTimeZone("GMT")); + } + } + + /** + * Second obsolete format + *

+ * Sun Nov 6 08:49:37 1994 -> EEE, MMM d HH:mm:ss yyyy + */ + private final class HttpHeaderDateFormatObsolete2 extends SimpleDateFormat { + private static final long serialVersionUID = 3010674519968303714L; + + HttpHeaderDateFormatObsolete2() { + super("E MMM d HH:mm:ss yyyy", Locale.ENGLISH); + setTimeZone(TimeZone.getTimeZone("GMT")); + } + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpHeaders.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpHeaders.java new file mode 100644 index 0000000..f342ed2 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpHeaders.java @@ -0,0 +1,1220 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.text.ParseException; +import java.util.Calendar; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + + +/** + * Provides the constants for the standard HTTP header names and values and + * commonly used utility methods that accesses an {@link HttpMessage}. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @version $Rev$, $Date$ + * + * @apiviz.landmark + * @apiviz.stereotype static + */ +public class HttpHeaders { + + /** + * Standard HTTP header names. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @version $Rev$, $Date$ + * + * @apiviz.stereotype static + */ + public static final class Names { + /** + * {@code "Accept"} + */ + public static final String ACCEPT = "Accept"; + /** + * {@code "Accept-Charset"} + */ + public static final String ACCEPT_CHARSET = "Accept-Charset"; + /** + * {@code "Accept-Encoding"} + */ + public static final String ACCEPT_ENCODING= "Accept-Encoding"; + /** + * {@code "Accept-Language"} + */ + public static final String ACCEPT_LANGUAGE = "Accept-Language"; + /** + * {@code "Accept-Ranges"} + */ + public static final String ACCEPT_RANGES= "Accept-Ranges"; + /** + * {@code "Accept-Patch"} + */ + public static final String ACCEPT_PATCH= "Accept-Patch"; + /** + * {@code "Age"} + */ + public static final String AGE = "Age"; + /** + * {@code "Allow"} + */ + public static final String ALLOW = "Allow"; + /** + * {@code "Authorization"} + */ + public static final String AUTHORIZATION = "Authorization"; + /** + * {@code "Cache-Control"} + */ + public static final String CACHE_CONTROL = "Cache-Control"; + /** + * {@code "Connection"} + */ + public static final String CONNECTION = "Connection"; + /** + * {@code "Content-Base"} + */ + public static final String CONTENT_BASE = "Content-Base"; + /** + * {@code "Content-Encoding"} + */ + public static final String CONTENT_ENCODING = "Content-Encoding"; + /** + * {@code "Content-Language"} + */ + public static final String CONTENT_LANGUAGE= "Content-Language"; + /** + * {@code "Content-Length"} + */ + public static final String CONTENT_LENGTH = "Content-Length"; + /** + * {@code "Content-Location"} + */ + public static final String CONTENT_LOCATION = "Content-Location"; + /** + * {@code "Content-Transfer-Encoding"} + */ + public static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding"; + /** + * {@code "Content-MD5"} + */ + public static final String CONTENT_MD5 = "Content-MD5"; + /** + * {@code "Content-Range"} + */ + public static final String CONTENT_RANGE = "Content-Range"; + /** + * {@code "Content-Type"} + */ + public static final String CONTENT_TYPE= "Content-Type"; + /** + * {@code "Cookie"} + */ + public static final String COOKIE = "Cookie"; + /** + * {@code "Date"} + */ + public static final String DATE = "Date"; + /** + * {@code "ETag"} + */ + public static final String ETAG = "ETag"; + /** + * {@code "Expect"} + */ + public static final String EXPECT = "Expect"; + /** + * {@code "Expires"} + */ + public static final String EXPIRES = "Expires"; + /** + * {@code "From"} + */ + public static final String FROM = "From"; + /** + * {@code "Host"} + */ + public static final String HOST = "Host"; + /** + * {@code "If-Match"} + */ + public static final String IF_MATCH = "If-Match"; + /** + * {@code "If-Modified-Since"} + */ + public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; + /** + * {@code "If-None-Match"} + */ + public static final String IF_NONE_MATCH = "If-None-Match"; + /** + * {@code "If-Range"} + */ + public static final String IF_RANGE= "If-Range"; + /** + * {@code "If-Unmodified-Since"} + */ + public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; + /** + * {@code "Last-Modified"} + */ + public static final String LAST_MODIFIED = "Last-Modified"; + /** + * {@code "Location"} + */ + public static final String LOCATION = "Location"; + /** + * {@code "Max-Forwards"} + */ + public static final String MAX_FORWARDS = "Max-Forwards"; + /** + * {@code "Origin"} + */ + public static final String ORIGIN = "Origin"; + /** + * {@code "Pragma"} + */ + public static final String PRAGMA = "Pragma"; + /** + * {@code "Proxy-Authenticate"} + */ + public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate"; + /** + * {@code "Proxy-Authorization"} + */ + public static final String PROXY_AUTHORIZATION = "Proxy-Authorization"; + /** + * {@code "Range"} + */ + public static final String RANGE = "Range"; + /** + * {@code "Referer"} + */ + public static final String REFERER = "Referer"; + /** + * {@code "Retry-After"} + */ + public static final String RETRY_AFTER = "Retry-After"; + /** + * {@code "Sec-WebSocket-Key1"} + */ + public static final String SEC_WEBSOCKET_KEY1 = "Sec-WebSocket-Key1"; + /** + * {@code "Sec-WebSocket-Key2"} + */ + public static final String SEC_WEBSOCKET_KEY2 = "Sec-WebSocket-Key2"; + /** + * {@code "Sec-WebSocket-Location"} + */ + public static final String SEC_WEBSOCKET_LOCATION = "Sec-WebSocket-Location"; + /** + * {@code "Sec-WebSocket-Origin"} + */ + public static final String SEC_WEBSOCKET_ORIGIN = "Sec-WebSocket-Origin"; + /** + * {@code "Sec-WebSocket-Protocol"} + */ + public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol"; + /** + * {@code "Server"} + */ + public static final String SERVER = "Server"; + /** + * {@code "Set-Cookie"} + */ + public static final String SET_COOKIE = "Set-Cookie"; + /** + * {@code "Set-Cookie2"} + */ + public static final String SET_COOKIE2 = "Set-Cookie2"; + /** + * {@code "TE"} + */ + public static final String TE = "TE"; + /** + * {@code "Trailer"} + */ + public static final String TRAILER = "Trailer"; + /** + * {@code "Transfer-Encoding"} + */ + public static final String TRANSFER_ENCODING = "Transfer-Encoding"; + /** + * {@code "Upgrade"} + */ + public static final String UPGRADE = "Upgrade"; + /** + * {@code "User-Agent"} + */ + public static final String USER_AGENT = "User-Agent"; + /** + * {@code "Vary"} + */ + public static final String VARY = "Vary"; + /** + * {@code "Via"} + */ + public static final String VIA = "Via"; + /** + * {@code "Warning"} + */ + public static final String WARNING = "Warning"; + /** + * {@code "WebSocket-Location"} + */ + public static final String WEBSOCKET_LOCATION = "WebSocket-Location"; + /** + * {@code "WebSocket-Origin"} + */ + public static final String WEBSOCKET_ORIGIN = "WebSocket-Origin"; + /** + * {@code "WebSocket-Protocol"} + */ + public static final String WEBSOCKET_PROTOCOL = "WebSocket-Protocol"; + /** + * {@code "WWW-Authenticate"} + */ + public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; + + private Names() { + super(); + } + } + + /** + * Standard HTTP header values. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @version $Rev$, $Date$ + * + * @apiviz.stereotype static + */ + public static final class Values { + /** + * {@code "application/x-www-form-urlencoded"} + */ + public static final String APPLICATION_X_WWW_FORM_URLENCODED = + "application/x-www-form-urlencoded"; + /** + * {@code "base64"} + */ + public static final String BASE64 = "base64"; + /** + * {@code "binary"} + */ + public static final String BINARY = "binary"; + /** + * {@code "boundary"} + */ + static final String BOUNDARY = "boundary"; + /** + * {@code "bytes"} + */ + public static final String BYTES = "bytes"; + /** + * {@code "charset"} + */ + public static final String CHARSET = "charset"; + /** + * {@code "chunked"} + */ + public static final String CHUNKED = "chunked"; + /** + * {@code "close"} + */ + public static final String CLOSE = "close"; + /** + * {@code "compress"} + */ + public static final String COMPRESS = "compress"; + /** + * {@code "100-continue"} + */ + public static final String CONTINUE = "100-continue"; + /** + * {@code "deflate"} + */ + public static final String DEFLATE = "deflate"; + /** + * {@code "gzip"} + */ + public static final String GZIP = "gzip"; + /** + * {@code "identity"} + */ + public static final String IDENTITY = "identity"; + /** + * {@code "keep-alive"} + */ + public static final String KEEP_ALIVE = "keep-alive"; + /** + * {@code "max-age"} + */ + public static final String MAX_AGE = "max-age"; + /** + * {@code "max-stale"} + */ + public static final String MAX_STALE = "max-stale"; + /** + * {@code "min-fresh"} + */ + public static final String MIN_FRESH = "min-fresh"; + /** + * {@code "multipart/form-data"} + */ + static final String MULTIPART_FORM_DATA = "multipart/form-data"; + /** + * {@code "must-revalidate"} + */ + public static final String MUST_REVALIDATE = "must-revalidate"; + /** + * {@code "no-cache"} + */ + public static final String NO_CACHE = "no-cache"; + /** + * {@code "no-store"} + */ + public static final String NO_STORE = "no-store"; + /** + * {@code "no-transform"} + */ + public static final String NO_TRANSFORM = "no-transform"; + /** + * {@code "none"} + */ + public static final String NONE = "none"; + /** + * {@code "only-if-cached"} + */ + public static final String ONLY_IF_CACHED = "only-if-cached"; + /** + * {@code "private"} + */ + public static final String PRIVATE = "private"; + /** + * {@code "proxy-revalidate"} + */ + public static final String PROXY_REVALIDATE = "proxy-revalidate"; + /** + * {@code "public"} + */ + public static final String PUBLIC = "public"; + /** + * {@code "quoted-printable"} + */ + public static final String QUOTED_PRINTABLE = "quoted-printable"; + /** + * {@code "s-maxage"} + */ + public static final String S_MAXAGE = "s-maxage"; + /** + * {@code "trailers"} + */ + public static final String TRAILERS = "trailers"; + /** + * {@code "Upgrade"} + */ + public static final String UPGRADE = "Upgrade"; + /** + * {@code "WebSocket"} + */ + public static final String WEBSOCKET = "WebSocket"; + + private Values() { + super(); + } + } + + + /** + * Returns {@code true} if and only if the connection can remain open and + * thus 'kept alive'. This methods respects the value of the + * {@code "Connection"} header first and then the return value of + * {@link HttpVersion#isKeepAliveDefault()}. + */ + public static boolean isKeepAlive(HttpMessage message) { + String connection = message.getHeader(Names.CONNECTION); + if (Values.CLOSE.equalsIgnoreCase(connection)) { + return false; + } + + if (message.getProtocolVersion().isKeepAliveDefault()) { + return !Values.CLOSE.equalsIgnoreCase(connection); + } else { + return Values.KEEP_ALIVE.equalsIgnoreCase(connection); + } + } + + /** + * Sets the value of the {@code "Connection"} header depending on the + * protocol version of the specified message. This method sets or removes + * the {@code "Connection"} header depending on what the default keep alive + * mode of the message's protocol version is, as specified by + * {@link HttpVersion#isKeepAliveDefault()}. + *

    + *
  • If the connection is kept alive by default: + *
      + *
    • set to {@code "close"} if {@code keepAlive} is {@code false}.
    • + *
    • remove otherwise.
    • + *
  • + *
  • If the connection is closed by default: + *
      + *
    • set to {@code "keep-alive"} if {@code keepAlive} is {@code true}.
    • + *
    • remove otherwise.
    • + *
  • + *
+ */ + public static void setKeepAlive(HttpMessage message, boolean keepAlive) { + if (message.getProtocolVersion().isKeepAliveDefault()) { + if (keepAlive) { + message.removeHeader(Names.CONNECTION); + } else { + message.setHeader(Names.CONNECTION, Values.CLOSE); + } + } else { + if (keepAlive) { + message.setHeader(Names.CONNECTION, Values.KEEP_ALIVE); + } else { + message.removeHeader(Names.CONNECTION); + } + } + } + + /** + * Returns the header value with the specified header name. If there are + * more than one header value for the specified header name, the first + * value is returned. + * + * @return the header value or {@code null} if there is no such header + */ + public static String getHeader(HttpMessage message, String name) { + return message.getHeader(name); + } + + /** + * Returns the header value with the specified header name. If there are + * more than one header value for the specified header name, the first + * value is returned. + * + * @return the header value or the {@code defaultValue} if there is no such + * header + */ + public static String getHeader(HttpMessage message, String name, String defaultValue) { + String value = message.getHeader(name); + if (value == null) { + return defaultValue; + } + return value; + } + + /** + * Sets a new header with the specified name and value. If there is an + * existing header with the same name, the existing header is removed. + * If the specified value is not a {@link String}, it is converted into a + * {@link String} by {@link Object#toString()}, except for {@link Date} + * and {@link Calendar} which are formatted to the date format defined in + * RFC2616. + */ + public static void setHeader(HttpMessage message, String name, Object value) { + message.setHeader(name, value); + } + + /** + * Sets a new header with the specified name and values. If there is an + * existing header with the same name, the existing header is removed. + * This method can be represented approximately as the following code: + *
+     * removeHeader(message, name);
+     * for (Object v: values) {
+     *     if (v == null) {
+     *         break;
+     *     }
+     *     addHeader(message, name, v);
+     * }
+     * 
+ */ + public static void setHeader(HttpMessage message, String name, Iterable values) { + message.setHeader(name, values); + } + + /** + * Adds a new header with the specified name and value. + * If the specified value is not a {@link String}, it is converted into a + * {@link String} by {@link Object#toString()}, except for {@link Date} + * and {@link Calendar} which are formatted to the date format defined in + * RFC2616. + */ + public static void addHeader(HttpMessage message, String name, Object value) { + message.addHeader(name, value); + } + + /** + * Removes the header with the specified name. + */ + public static void removeHeader(HttpMessage message, String name) { + message.removeHeader(name); + } + + /** + * Removes all headers from the specified message. + */ + public static void clearHeaders(HttpMessage message) { + message.clearHeaders(); + } + + /** + * Returns the integer header value with the specified header name. If + * there are more than one header value for the specified header name, the + * first value is returned. + * + * @return the header value + * @throws NumberFormatException + * if there is no such header or the header value is not a number + */ + public static int getIntHeader(HttpMessage message, String name) { + String value = getHeader(message, name); + if (value == null) { + throw new NumberFormatException("header not found: " + name); + } + return Integer.parseInt(value); + } + + /** + * Returns the integer header value with the specified header name. If + * there are more than one header value for the specified header name, the + * first value is returned. + * + * @return the header value or the {@code defaultValue} if there is no such + * header or the header value is not a number + */ + public static int getIntHeader(HttpMessage message, String name, int defaultValue) { + String value = getHeader(message, name); + if (value == null) { + return defaultValue; + } + + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + /** + * Sets a new integer header with the specified name and value. If there + * is an existing header with the same name, the existing header is removed. + */ + public static void setIntHeader(HttpMessage message, String name, int value) { + message.setHeader(name, value); + } + + /** + * Sets a new integer header with the specified name and values. If there + * is an existing header with the same name, the existing header is removed. + */ + public static void setIntHeader(HttpMessage message, String name, Iterable values) { + message.setHeader(name, values); + } + + /** + * Adds a new integer header with the specified name and value. + */ + public static void addIntHeader(HttpMessage message, String name, int value) { + message.addHeader(name, value); + } + + /** + * Returns the date header value with the specified header name. If + * there are more than one header value for the specified header name, the + * first value is returned. + * + * @return the header value + * @throws ParseException + * if there is no such header or the header value is not a formatted date + */ + public static Date getDateHeader(HttpMessage message, String name) throws ParseException { + String value = getHeader(message, name); + if (value == null) { + throw new ParseException("header not found: " + name, 0); + } + return new HttpHeaderDateFormat().parse(value); + } + + /** + * Returns the date header value with the specified header name. If + * there are more than one header value for the specified header name, the + * first value is returned. + * + * @return the header value or the {@code defaultValue} if there is no such + * header or the header value is not a formatted date + */ + public static Date getDateHeader(HttpMessage message, String name, Date defaultValue) { + final String value = getHeader(message, name); + if (value == null) { + return defaultValue; + } + + try { + return new HttpHeaderDateFormat().parse(value); + } catch (ParseException e) { + return defaultValue; + } + } + + /** + * Sets a new date header with the specified name and value. If there + * is an existing header with the same name, the existing header is removed. + * The specified value is formatted as defined in + * RFC2616 + */ + public static void setDateHeader(HttpMessage message, String name, Date value) { + if (value != null) { + message.setHeader(name, new HttpHeaderDateFormat().format(value)); + } else { + message.setHeader(name, null); + } + + } + + /** + * Sets a new date header with the specified name and values. If there + * is an existing header with the same name, the existing header is removed. + * The specified values are formatted as defined in + * RFC2616 + */ + public static void setDateHeader(HttpMessage message, String name, Iterable values) { + message.setHeader(name, values); + } + + /** + * Adds a new date header with the specified name and value. The specified + * value is formatted as defined in RFC2616 + */ + public static void addDateHeader(HttpMessage message, String name, Date value) { + message.addHeader(name, value); + } + + /** + * Returns the length of the content. Please note that this value is + * not retrieved from {@link HttpMessage#getContent()} but from the + * {@code "Content-Length"} header, and thus they are independent from each + * other. + * + * @return the content length + * + * @throws NumberFormatException + * if the message does not have the {@code "Content-Length"} header + * or its value is not a number + */ + public static long getContentLength(HttpMessage message) { + String value = getHeader(message, Names.CONTENT_LENGTH); + if (value != null) { + return Long.parseLong(value); + } + + // We know the content length if it's a Web Socket message even if + // Content-Length header is missing. + long webSocketContentLength = getWebSocketContentLength(message); + if (webSocketContentLength >= 0) { + return webSocketContentLength; + } + + // Otherwise we don't. + throw new NumberFormatException("header not found: " + Names.CONTENT_LENGTH); + } + + /** + * Returns the length of the content. Please note that this value is + * not retrieved from {@link HttpMessage#getContent()} but from the + * {@code "Content-Length"} header, and thus they are independent from each + * other. + * + * @return the content length or {@code defaultValue} if this message does + * not have the {@code "Content-Length"} header or its value is not + * a number + */ + public static long getContentLength(HttpMessage message, long defaultValue) { + String contentLength = message.getHeader(Names.CONTENT_LENGTH); + if (contentLength != null) { + try { + return Long.parseLong(contentLength); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + // We know the content length if it's a Web Socket message even if + // Content-Length header is missing. + long webSocketContentLength = getWebSocketContentLength(message); + if (webSocketContentLength >= 0) { + return webSocketContentLength; + } + + // Otherwise we don't. + return defaultValue; + } + + /** + * Returns the content length of the specified web socket message. If the + * specified message is not a web socket message, {@code -1} is returned. + */ + private static int getWebSocketContentLength(HttpMessage message) { + // WebSockset messages have constant content-lengths. + if (message instanceof HttpRequest) { + HttpRequest req = (HttpRequest) message; + if (HttpMethod.GET.equals(req.getMethod()) && + req.containsHeader(Names.SEC_WEBSOCKET_KEY1) && + req.containsHeader(Names.SEC_WEBSOCKET_KEY2)) { + return 8; + } + } else if (message instanceof HttpResponse) { + HttpResponse res = (HttpResponse) message; + if (res.getStatus().getCode() == 101 && + res.containsHeader(Names.SEC_WEBSOCKET_ORIGIN) && + res.containsHeader(Names.SEC_WEBSOCKET_LOCATION)) { + return 16; + } + } + + // Not a web socket message + return -1; + } + + /** + * Sets the {@code "Content-Length"} header. + */ + public static void setContentLength(HttpMessage message, long length) { + message.setHeader(Names.CONTENT_LENGTH, length); + } + + /** + * Returns the value of the {@code "Host"} header. + */ + public static String getHost(HttpMessage message) { + return message.getHeader(Names.HOST); + } + + /** + * Returns the value of the {@code "Host"} header. If there is no such + * header, the {@code defaultValue} is returned. + */ + public static String getHost(HttpMessage message, String defaultValue) { + return getHeader(message, Names.HOST, defaultValue); + } + + /** + * Sets the {@code "Host"} header. + */ + public static void setHost(HttpMessage message, String value) { + message.setHeader(Names.HOST, value); + } + + + /** + * Returns the value of the {@code "Date"} header. + * + * @throws ParseException + * if there is no such header or the header value is not a formatted date + */ + public static Date getDate(HttpMessage message) throws ParseException { + return getDateHeader(message, Names.DATE); + } + + /** + * Returns the value of the {@code "Date"} header. If there is no such + * header or the header is not a formatted date, the {@code defaultValue} + * is returned. + */ + public static Date getDate(HttpMessage message, Date defaultValue) { + return getDateHeader(message, Names.DATE, defaultValue); + } + + /** + * Sets the {@code "Date"} header. + */ + public static void setDate(HttpMessage message, Date value) { + if (value != null) { + message.setHeader(Names.DATE, new HttpHeaderDateFormat().format(value)); + } else { + message.setHeader(Names.DATE, null); + } + } + + /** + * Returns {@code true} if and only if the specified message contains the + * {@code "Expect: 100-continue"} header. + */ + public static boolean is100ContinueExpected(HttpMessage message) { + // Expect: 100-continue is for requests only. + if (!(message instanceof HttpRequest)) { + return false; + } + + // It works only on HTTP/1.1 or later. + if (message.getProtocolVersion().compareTo(HttpVersion.HTTP_1_1) < 0) { + return false; + } + + // In most cases, there will be one or zero 'Expect' header. + String value = message.getHeader(Names.EXPECT); + if (value == null) { + return false; + } + if (Values.CONTINUE.equalsIgnoreCase(value)) { + return true; + } + + // Multiple 'Expect' headers. Search through them. + for (String v: message.getHeaders(Names.EXPECT)) { + if (Values.CONTINUE.equalsIgnoreCase(v)) { + return true; + } + } + return false; + } + + /** + * Sets the {@code "Expect: 100-continue"} header to the specified message. + * If there is any existing {@code "Expect"} header, they are replaced with + * the new one. + */ + public static void set100ContinueExpected(HttpMessage message) { + set100ContinueExpected(message, true); + } + + /** + * Sets or removes the {@code "Expect: 100-continue"} header to / from the + * specified message. If the specified {@code value} is {@code true}, + * the {@code "Expect: 100-continue"} header is set and all other previous + * {@code "Expect"} headers are removed. Otherwise, all {@code "Expect"} + * headers are removed completely. + */ + public static void set100ContinueExpected(HttpMessage message, boolean set) { + if (set) { + message.setHeader(Names.EXPECT, Values.CONTINUE); + } else { + message.removeHeader(Names.EXPECT); + } + } + + private static final int BUCKET_SIZE = 17; + + private static int hash(String name) { + int h = 0; + for (int i = name.length() - 1; i >= 0; i --) { + char c = name.charAt(i); + if (c >= 'A' && c <= 'Z') { + c += 32; + } + h = 31 * h + c; + } + + if (h > 0) { + return h; + } else if (h == Integer.MIN_VALUE) { + return Integer.MAX_VALUE; + } else { + return -h; + } + } + + private static boolean eq(String name1, String name2) { + int nameLen = name1.length(); + if (nameLen != name2.length()) { + return false; + } + + for (int i = nameLen - 1; i >= 0; i --) { + char c1 = name1.charAt(i); + char c2 = name2.charAt(i); + if (c1 != c2) { + if (c1 >= 'A' && c1 <= 'Z') { + c1 += 32; + } + if (c2 >= 'A' && c2 <= 'Z') { + c2 += 32; + } + if (c1 != c2) { + return false; + } + } + } + return true; + } + + private static int index(int hash) { + return hash % BUCKET_SIZE; + } + + private final Entry[] entries = new Entry[BUCKET_SIZE]; + private final Entry head = new Entry(-1, null, null); + + HttpHeaders() { + head.before = head.after = head; + } + + void validateHeaderName(String name) { + HttpCodecUtil.validateHeaderName(name); + } + + void addHeader(final String name, final Object value) { + validateHeaderName(name); + String strVal = toString(value); + HttpCodecUtil.validateHeaderValue(strVal); + int h = hash(name); + int i = index(h); + addHeader0(h, i, name, strVal); + } + + private void addHeader0(int h, int i, final String name, final String value) { + // Update the hash table. + Entry e = entries[i]; + Entry newEntry; + entries[i] = newEntry = new Entry(h, name, value); + newEntry.next = e; + + // Update the linked list. + newEntry.addBefore(head); + } + + void removeHeader(final String name) { + if (name == null) { + throw new NullPointerException("name"); + } + int h = hash(name); + int i = index(h); + removeHeader0(h, i, name); + } + + private void removeHeader0(int h, int i, String name) { + Entry e = entries[i]; + if (e == null) { + return; + } + + for (;;) { + if (e.hash == h && eq(name, e.key)) { + e.remove(); + Entry next = e.next; + if (next != null) { + entries[i] = next; + e = next; + } else { + entries[i] = null; + return; + } + } else { + break; + } + } + + for (;;) { + Entry next = e.next; + if (next == null) { + break; + } + if (next.hash == h && eq(name, next.key)) { + e.next = next.next; + next.remove(); + } else { + e = next; + } + } + } + + void setHeader(final String name, final Object value) { + validateHeaderName(name); + String strVal = toString(value); + HttpCodecUtil.validateHeaderValue(strVal); + int h = hash(name); + int i = index(h); + removeHeader0(h, i, name); + addHeader0(h, i, name, strVal); + } + + void setHeader(final String name, final Iterable values) { + if (values == null) { + throw new NullPointerException("values"); + } + + validateHeaderName(name); + + int h = hash(name); + int i = index(h); + + removeHeader0(h, i, name); + for (Object v: values) { + if (v == null) { + break; + } + String strVal = toString(v); + HttpCodecUtil.validateHeaderValue(strVal); + addHeader0(h, i, name, strVal); + } + } + + void clearHeaders() { + for (int i = 0; i < entries.length; i ++) { + entries[i] = null; + } + head.before = head.after = head; + } + + String getHeader(final String name) { + if (name == null) { + throw new NullPointerException("name"); + } + + int h = hash(name); + int i = index(h); + Entry e = entries[i]; + while (e != null) { + if (e.hash == h && eq(name, e.key)) { + return e.value; + } + + e = e.next; + } + return null; + } + + List getHeaders(final String name) { + if (name == null) { + throw new NullPointerException("name"); + } + + LinkedList values = new LinkedList(); + + int h = hash(name); + int i = index(h); + Entry e = entries[i]; + while (e != null) { + if (e.hash == h && eq(name, e.key)) { + values.addFirst(e.value); + } + e = e.next; + } + return values; + } + + List> getHeaders() { + List> all = + new LinkedList>(); + + Entry e = head.after; + while (e != head) { + all.add(e); + e = e.after; + } + return all; + } + + boolean containsHeader(String name) { + return getHeader(name) != null; + } + + Set getHeaderNames() { + Set names = + new TreeSet(CaseIgnoringComparator.INSTANCE); + + Entry e = head.after; + while (e != head) { + names.add(e.key); + e = e.after; + } + return names; + } + + private static String toString(Object value) { + if (value == null) { + return null; + } + if (value instanceof String) { + return (String) value; + } + if (value instanceof Number) { + return value.toString(); + } + if (value instanceof Date) { + return new HttpHeaderDateFormat().format((Date) value); + } + if (value instanceof Calendar) { + return new HttpHeaderDateFormat().format(((Calendar) value).getTime()); + } + return value.toString(); + } + + private static final class Entry implements Map.Entry { + final int hash; + final String key; + String value; + Entry next; + Entry before, after; + + Entry(int hash, String key, String value) { + this.hash = hash; + this.key = key; + this.value = value; + } + + void remove() { + before.after = after; + after.before = before; + } + + void addBefore(Entry e) { + after = e; + before = e.before; + before.after = this; + after.before = this; + } + + @Override + public String getKey() { + return key; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String setValue(String value) { + if (value == null) { + throw new NullPointerException("value"); + } + HttpCodecUtil.validateHeaderValue(value); + String oldValue = this.value; + this.value = value; + return oldValue; + } + + @Override + public String toString() { + return key + "=" + value; + } + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpMessage.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpMessage.java new file mode 100644 index 0000000..7857e2b --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpMessage.java @@ -0,0 +1,173 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; + +/** + * An HTTP message which provides common properties for {@link HttpRequest} and + * {@link HttpResponse}. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @version $Rev$, $Date$ + * + * @see HttpHeaders + * + * @apiviz.landmark + * @apiviz.has org.jboss.netty.handler.codec.http.HttpChunk oneway - - is followed by + */ +public interface HttpMessage { + + /** + * Returns the header value with the specified header name. If there are + * more than one header value for the specified header name, the first + * value is returned. + * + * @return the header value or {@code null} if there is no such header + * + */ + String getHeader(String name); + + /** + * Returns the header values with the specified header name. + * + * @return the {@link List} of header values. An empty list if there is no + * such header. + */ + List getHeaders(String name); + + /** + * Returns the all header names and values that this message contains. + * + * @return the {@link List} of the header name-value pairs. An empty list + * if there is no header in this message. + */ + List> getHeaders(); + + /** + * Returns {@code true} if and only if there is a header with the specified + * header name. + */ + boolean containsHeader(String name); + + /** + * Returns the {@link Set} of all header names that this message contains. + */ + Set getHeaderNames(); + + /** + * Returns the protocol version of this message. + */ + HttpVersion getProtocolVersion(); + + /** + * Sets the protocol version of this message. + */ + void setProtocolVersion(HttpVersion version); + + /** + * Returns the content of this message. If there is no content or + * {@link #isChunked()} returns {@code true}, an + * {@link ChannelBuffers#EMPTY_BUFFER} is returned. + */ + ChannelBuffer getContent(); + + /** + * Sets the content of this message. If {@code null} is specified, + * the content of this message will be set to {@link ChannelBuffers#EMPTY_BUFFER}. + */ + void setContent(ChannelBuffer content); + + /** + * Adds a new header with the specified name and value. + * If the specified value is not a {@link String}, it is converted into a + * {@link String} by {@link Object#toString()}, except for {@link Date} + * and {@link Calendar} which are formatted to the date format defined in + * RFC2616. + */ + void addHeader(String name, Object value); + + /** + * Sets a new header with the specified name and value. If there is an + * existing header with the same name, the existing header is removed. + * If the specified value is not a {@link String}, it is converted into a + * {@link String} by {@link Object#toString()}, except for {@link Date} + * and {@link Calendar} which are formatted to the date format defined in + * RFC2616. + */ + void setHeader(String name, Object value); + + /** + * Sets a new header with the specified name and values. If there is an + * existing header with the same name, the existing header is removed. + * This method can be represented approximately as the following code: + *
+     * m.removeHeader(name);
+     * for (Object v: values) {
+     *     if (v == null) {
+     *         break;
+     *     }
+     *     m.addHeader(name, v);
+     * }
+     * 
+ */ + void setHeader(String name, Iterable values); + + /** + * Removes the header with the specified name. + */ + void removeHeader(String name); + + /** + * Removes all headers from this message. + */ + void clearHeaders(); + + /** + * Returns {@code true} if and only if this message does not have any + * content but the {@link HttpChunk}s, which is generated by + * {@link HttpMessageDecoder} consecutively, contain the actual content. + *

+ * Please note that this method will keep returning {@code true} if the + * {@code "Transfer-Encoding"} of this message is {@code "chunked"}, even if + * you attempt to override this property by calling {@link #setChunked(boolean)} + * with {@code false}. + */ + boolean isChunked(); + + /** + * Sets if this message does not have any content but the + * {@link HttpChunk}s, which is generated by {@link HttpMessageDecoder} + * consecutively, contain the actual content. + *

+ * If this method is called with {@code true}, the content of this message + * becomes {@link ChannelBuffers#EMPTY_BUFFER}. + *

+ * Even if this method is called with {@code false}, {@link #isChunked()} + * will keep returning {@code true} if the {@code "Transfer-Encoding"} of + * this message is {@code "chunked"}. + */ + void setChunked(boolean chunked); +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpMessageDecoder.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpMessageDecoder.java new file mode 100644 index 0000000..c580824 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpMessageDecoder.java @@ -0,0 +1,668 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.util.List; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; +import com.codefollower.douyu.netty.channel.Channel; +import com.codefollower.douyu.netty.channel.ChannelHandlerContext; +import com.codefollower.douyu.netty.channel.ChannelPipeline; +import com.codefollower.douyu.netty.handler.codec.frame.TooLongFrameException; +import com.codefollower.douyu.netty.handler.codec.replay.ReplayingDecoder; + +/** + * Decodes {@link ChannelBuffer}s into {@link HttpMessage}s and + * {@link HttpChunk}s. + * + *

Parameters that prevents excessive memory consumption

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
NameMeaning
{@code maxInitialLineLength}The maximum length of the initial line + * (e.g. {@code "GET / HTTP/1.0"} or {@code "HTTP/1.0 200 OK"}) + * If the length of the initial line exceeds this value, a + * {@link TooLongFrameException} will be raised.
{@code maxHeaderSize}The maximum length of all headers. If the sum of the length of each + * header exceeds this value, a {@link TooLongFrameException} will be raised.
{@code maxChunkSize}The maximum length of the content or each chunk. If the content length + * (or the length of each chunk) exceeds this value, the content or chunk + * will be split into multiple {@link HttpChunk}s whose length is + * {@code maxChunkSize} at maximum.
+ * + *

Chunked Content

+ * + * If the content of an HTTP message is greater than {@code maxChunkSize} or + * the transfer encoding of the HTTP message is 'chunked', this decoder + * generates one {@link HttpMessage} instance and its following + * {@link HttpChunk}s per single HTTP message to avoid excessive memory + * consumption. For example, the following HTTP message: + *
+ * GET / HTTP/1.1
+ * Transfer-Encoding: chunked
+ *
+ * 1a
+ * abcdefghijklmnopqrstuvwxyz
+ * 10
+ * 1234567890abcdef
+ * 0
+ * Content-MD5: ...
+ * [blank line]
+ * 
+ * triggers {@link HttpRequestDecoder} to generate 4 objects: + *
    + *
  1. An {@link HttpRequest} whose {@link HttpMessage#isChunked() chunked} + * property is {@code true},
  2. + *
  3. The first {@link HttpChunk} whose content is {@code 'abcdefghijklmnopqrstuvwxyz'},
  4. + *
  5. The second {@link HttpChunk} whose content is {@code '1234567890abcdef'}, and
  6. + *
  7. An {@link HttpChunkTrailer} which marks the end of the content.
  8. + *
+ * + * If you prefer not to handle {@link HttpChunk}s by yourself for your + * convenience, insert {@link HttpChunkAggregator} after this decoder in the + * {@link ChannelPipeline}. However, please note that your server might not + * be as memory efficient as without the aggregator. + * + *

Extensibility

+ * + * Please note that this decoder is designed to be extended to implement + * a protocol derived from HTTP, such as + * RTSP and + * ICAP. + * To implement the decoder of such a derived protocol, extend this class and + * implement all abstract methods properly. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @version $Rev$, $Date$ + * + * @apiviz.landmark + */ +public abstract class HttpMessageDecoder extends ReplayingDecoder { + + private final int maxInitialLineLength; + private final int maxHeaderSize; + private final int maxChunkSize; + private HttpMessage message; + private ChannelBuffer content; + private long chunkSize; + private int headerSize; + + /** + * The internal state of {@link HttpMessageDecoder}. + * Internal use only. + * + * @author The Netty Project + * @author Trustin Lee + * @version $Rev$, $Date$ + * + * @apiviz.exclude + */ + protected static enum State { + SKIP_CONTROL_CHARS, + READ_INITIAL, + READ_HEADER, + READ_VARIABLE_LENGTH_CONTENT, + READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS, + READ_FIXED_LENGTH_CONTENT, + READ_FIXED_LENGTH_CONTENT_AS_CHUNKS, + READ_CHUNK_SIZE, + READ_CHUNKED_CONTENT, + READ_CHUNKED_CONTENT_AS_CHUNKS, + READ_CHUNK_DELIMITER, + READ_CHUNK_FOOTER; + } + + /** + * Creates a new instance with the default + * {@code maxInitialLineLength (4096}}, {@code maxHeaderSize (8192)}, and + * {@code maxChunkSize (8192)}. + */ + protected HttpMessageDecoder() { + this(4096, 8192, 8192); + } + + /** + * Creates a new instance with the specified parameters. + */ + protected HttpMessageDecoder( + int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) { + + super(State.SKIP_CONTROL_CHARS, true); + + if (maxInitialLineLength <= 0) { + throw new IllegalArgumentException( + "maxInitialLineLength must be a positive integer: " + + maxInitialLineLength); + } + if (maxHeaderSize <= 0) { + throw new IllegalArgumentException( + "maxHeaderSize must be a positive integer: " + + maxHeaderSize); + } + if (maxChunkSize < 0) { + throw new IllegalArgumentException( + "maxChunkSize must be a positive integer: " + + maxChunkSize); + } + this.maxInitialLineLength = maxInitialLineLength; + this.maxHeaderSize = maxHeaderSize; + this.maxChunkSize = maxChunkSize; + } + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, State state) throws Exception { + switch (state) { + case SKIP_CONTROL_CHARS: { + try { + skipControlCharacters(buffer); + checkpoint(State.READ_INITIAL); + } finally { + checkpoint(); + } + } + case READ_INITIAL: { + String[] initialLine = splitInitialLine(readLine(buffer, maxInitialLineLength)); + if (initialLine.length < 3) { + // Invalid initial line - ignore. + checkpoint(State.SKIP_CONTROL_CHARS); + return null; + } + + message = createMessage(initialLine); + checkpoint(State.READ_HEADER); + } + case READ_HEADER: { + State nextState = readHeaders(buffer); + checkpoint(nextState); + if (nextState == State.READ_CHUNK_SIZE) { + // Chunked encoding + message.setChunked(true); + // Generate HttpMessage first. HttpChunks will follow. + return message; + } else { + long contentLength = HttpHeaders.getContentLength(message, -1); + if (contentLength == 0 || contentLength == -1) { + content = ChannelBuffers.EMPTY_BUFFER; + return reset(); + } + + switch (nextState) { + case READ_FIXED_LENGTH_CONTENT: + if (contentLength > maxChunkSize || HttpHeaders.is100ContinueExpected(message)) { + // Generate HttpMessage first. HttpChunks will follow. + checkpoint(State.READ_FIXED_LENGTH_CONTENT_AS_CHUNKS); + message.setChunked(true); + // chunkSize will be decreased as the READ_FIXED_LENGTH_CONTENT_AS_CHUNKS + // state reads data chunk by chunk. + chunkSize = HttpHeaders.getContentLength(message, -1); + return message; + } + break; + case READ_VARIABLE_LENGTH_CONTENT: + if (buffer.readableBytes() > maxChunkSize || HttpHeaders.is100ContinueExpected(message)) { + // Generate HttpMessage first. HttpChunks will follow. + checkpoint(State.READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS); + message.setChunked(true); + return message; + } + break; + default: + throw new IllegalStateException("Unexpected state: " + nextState); + } + } + // We return null here, this forces decode to be called again where we will decode the content + return null; + } + case READ_VARIABLE_LENGTH_CONTENT: { + if (content == null) { + content = ChannelBuffers.dynamicBuffer(channel.getConfig().getBufferFactory()); + } + //this will cause a replay error until the channel is closed where this will read what's left in the buffer + content.writeBytes(buffer.readBytes(buffer.readableBytes())); + return reset(); + } + case READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS: { + // Keep reading data as a chunk until the end of connection is reached. + int chunkSize = Math.min(maxChunkSize, buffer.readableBytes()); + HttpChunk chunk = new DefaultHttpChunk(buffer.readBytes(chunkSize)); + + if (!buffer.readable()) { + // Reached to the end of the connection. + reset(); + if (!chunk.isLast()) { + // Append the last chunk. + return new Object[] { chunk, HttpChunk.LAST_CHUNK }; + } + } + return chunk; + } + case READ_FIXED_LENGTH_CONTENT: { + //we have a content-length so we just read the correct number of bytes + readFixedLengthContent(buffer); + return reset(); + } + case READ_FIXED_LENGTH_CONTENT_AS_CHUNKS: { + long chunkSize = this.chunkSize; + HttpChunk chunk; + if (chunkSize > maxChunkSize) { + chunk = new DefaultHttpChunk(buffer.readBytes(maxChunkSize)); + chunkSize -= maxChunkSize; + } else { + assert chunkSize <= Integer.MAX_VALUE; + chunk = new DefaultHttpChunk(buffer.readBytes((int) chunkSize)); + chunkSize = 0; + } + this.chunkSize = chunkSize; + + if (chunkSize == 0) { + // Read all content. + reset(); + if (!chunk.isLast()) { + // Append the last chunk. + return new Object[] { chunk, HttpChunk.LAST_CHUNK }; + } + } + return chunk; + } + /** + * everything else after this point takes care of reading chunked content. basically, read chunk size, + * read chunk, read and ignore the CRLF and repeat until 0 + */ + case READ_CHUNK_SIZE: { + String line = readLine(buffer, maxInitialLineLength); + int chunkSize = getChunkSize(line); + this.chunkSize = chunkSize; + if (chunkSize == 0) { + checkpoint(State.READ_CHUNK_FOOTER); + return null; + } else if (chunkSize > maxChunkSize) { + // A chunk is too large. Split them into multiple chunks again. + checkpoint(State.READ_CHUNKED_CONTENT_AS_CHUNKS); + } else { + checkpoint(State.READ_CHUNKED_CONTENT); + } + } + case READ_CHUNKED_CONTENT: { + assert chunkSize <= Integer.MAX_VALUE; + HttpChunk chunk = new DefaultHttpChunk(buffer.readBytes((int) chunkSize)); + checkpoint(State.READ_CHUNK_DELIMITER); + return chunk; + } + case READ_CHUNKED_CONTENT_AS_CHUNKS: { + long chunkSize = this.chunkSize; + HttpChunk chunk; + if (chunkSize > maxChunkSize) { + chunk = new DefaultHttpChunk(buffer.readBytes(maxChunkSize)); + chunkSize -= maxChunkSize; + } else { + assert chunkSize <= Integer.MAX_VALUE; + chunk = new DefaultHttpChunk(buffer.readBytes((int) chunkSize)); + chunkSize = 0; + } + this.chunkSize = chunkSize; + + if (chunkSize == 0) { + // Read all content. + checkpoint(State.READ_CHUNK_DELIMITER); + } + + if (!chunk.isLast()) { + return chunk; + } + } + case READ_CHUNK_DELIMITER: { + for (;;) { + byte next = buffer.readByte(); + if (next == HttpCodecUtil.CR) { + if (buffer.readByte() == HttpCodecUtil.LF) { + checkpoint(State.READ_CHUNK_SIZE); + return null; + } + } else if (next == HttpCodecUtil.LF) { + checkpoint(State.READ_CHUNK_SIZE); + return null; + } + } + } + case READ_CHUNK_FOOTER: { + HttpChunkTrailer trailer = readTrailingHeaders(buffer); + if (maxChunkSize == 0) { + // Chunked encoding disabled. + return reset(); + } else { + reset(); + // The last chunk, which is empty + return trailer; + } + } + default: { + throw new Error("Shouldn't reach here."); + } + + } + } + + private Object reset() { + HttpMessage message = this.message; + ChannelBuffer content = this.content; + + if (content != null) { + message.setContent(content); + this.content = null; + } + this.message = null; + + checkpoint(State.SKIP_CONTROL_CHARS); + return message; + } + + private void skipControlCharacters(ChannelBuffer buffer) { + for (;;) { + char c = (char) buffer.readUnsignedByte(); + if (!Character.isISOControl(c) && + !Character.isWhitespace(c)) { + buffer.readerIndex(buffer.readerIndex() - 1); + break; + } + } + } + + private void readFixedLengthContent(ChannelBuffer buffer) { + long length = HttpHeaders.getContentLength(message, -1); + assert length <= Integer.MAX_VALUE; + + if (content == null) { + content = buffer.readBytes((int) length); + } else { + content.writeBytes(buffer.readBytes((int) length)); + } + } + + private State readHeaders(ChannelBuffer buffer) throws TooLongFrameException { + headerSize = 0; + final HttpMessage message = this.message; + String line = readHeader(buffer); + String name = null; + String value = null; + if (line.length() != 0) { + message.clearHeaders(); + do { + char firstChar = line.charAt(0); + if (name != null && (firstChar == ' ' || firstChar == '\t')) { + value = value + ' ' + line.trim(); + } else { + if (name != null) { + message.addHeader(name, value); + } + String[] header = splitHeader(line); + name = header[0]; + value = header[1]; + } + + line = readHeader(buffer); + } while (line.length() != 0); + + // Add the last header. + if (name != null) { + message.addHeader(name, value); + } + } + + State nextState; + + if (message.isChunked()) { + // HttpMessage.isChunked() returns true when either: + // 1) HttpMessage.setChunked(true) was called or + // 2) 'Transfer-Encoding' is 'chunked'. + // Because this decoder did not call HttpMessage.setChunked(true) + // yet, HttpMessage.isChunked() should return true only when + // 'Transfer-Encoding' is 'chunked'. + nextState = State.READ_CHUNK_SIZE; + } else if (HttpHeaders.getContentLength(message, -1) >= 0) { + nextState = State.READ_FIXED_LENGTH_CONTENT; + } else { + nextState = State.READ_VARIABLE_LENGTH_CONTENT; + } + return nextState; + } + + private HttpChunkTrailer readTrailingHeaders(ChannelBuffer buffer) throws TooLongFrameException { + headerSize = 0; + String line = readHeader(buffer); + String lastHeader = null; + if (line.length() != 0) { + HttpChunkTrailer trailer = new DefaultHttpChunkTrailer(); + do { + char firstChar = line.charAt(0); + if (lastHeader != null && (firstChar == ' ' || firstChar == '\t')) { + List current = trailer.getHeaders(lastHeader); + if (current.size() != 0) { + int lastPos = current.size() - 1; + String newString = current.get(lastPos) + line.trim(); + current.set(lastPos, newString); + } else { + // Content-Length, Transfer-Encoding, or Trailer + } + } else { + String[] header = splitHeader(line); + String name = header[0]; + if (!name.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH) && + !name.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING) && + !name.equalsIgnoreCase(HttpHeaders.Names.TRAILER)) { + trailer.addHeader(name, header[1]); + } + lastHeader = name; + } + + line = readHeader(buffer); + } while (line.length() != 0); + + return trailer; + } + + return HttpChunk.LAST_CHUNK; + } + + private String readHeader(ChannelBuffer buffer) throws TooLongFrameException { + StringBuilder sb = new StringBuilder(64); + int headerSize = this.headerSize; + + loop: + for (;;) { + char nextByte = (char) buffer.readByte(); + headerSize ++; + + switch (nextByte) { + case HttpCodecUtil.CR: + nextByte = (char) buffer.readByte(); + headerSize ++; + if (nextByte == HttpCodecUtil.LF) { + break loop; + } + break; + case HttpCodecUtil.LF: + break loop; + } + + // Abort decoding if the header part is too large. + if (headerSize >= maxHeaderSize) { + // TODO: Respond with Bad Request and discard the traffic + // or close the connection. + // No need to notify the upstream handlers - just log. + // If decoding a response, just throw an exception. + throw new TooLongFrameException( + "HTTP header is larger than " + + maxHeaderSize + " bytes."); + + } + + sb.append(nextByte); + } + + this.headerSize = headerSize; + return sb.toString(); + } + + protected abstract HttpMessage createMessage(String[] initialLine) throws Exception; + + private int getChunkSize(String hex) { + hex = hex.trim(); + for (int i = 0; i < hex.length(); i ++) { + char c = hex.charAt(i); + if (c == ';' || Character.isWhitespace(c) || Character.isISOControl(c)) { + hex = hex.substring(0, i); + break; + } + } + + return Integer.parseInt(hex, 16); + } + + private String readLine(ChannelBuffer buffer, int maxLineLength) throws TooLongFrameException { + StringBuilder sb = new StringBuilder(64); + int lineLength = 0; + while (true) { + byte nextByte = buffer.readByte(); + if (nextByte == HttpCodecUtil.CR) { + nextByte = buffer.readByte(); + if (nextByte == HttpCodecUtil.LF) { + return sb.toString(); + } + } + else if (nextByte == HttpCodecUtil.LF) { + return sb.toString(); + } + else { + if (lineLength >= maxLineLength) { + // TODO: Respond with Bad Request and discard the traffic + // or close the connection. + // No need to notify the upstream handlers - just log. + // If decoding a response, just throw an exception. + throw new TooLongFrameException( + "An HTTP line is larger than " + maxLineLength + + " bytes."); + } + lineLength ++; + sb.append((char) nextByte); + } + } + } + + private String[] splitInitialLine(String sb) { + int aStart; + int aEnd; + int bStart; + int bEnd; + int cStart; + int cEnd; + + aStart = findNonWhitespace(sb, 0); + aEnd = findWhitespace(sb, aStart); + + bStart = findNonWhitespace(sb, aEnd); + bEnd = findWhitespace(sb, bStart); + + cStart = findNonWhitespace(sb, bEnd); + cEnd = findEndOfString(sb); + + return new String[] { + sb.substring(aStart, aEnd), + sb.substring(bStart, bEnd), + cStart < cEnd? sb.substring(cStart, cEnd) : "" }; + } + + private String[] splitHeader(String sb) { + final int length = sb.length(); + int nameStart; + int nameEnd; + int colonEnd; + int valueStart; + int valueEnd; + + nameStart = findNonWhitespace(sb, 0); + for (nameEnd = nameStart; nameEnd < length; nameEnd ++) { + char ch = sb.charAt(nameEnd); + if (ch == ':' || Character.isWhitespace(ch)) { + break; + } + } + + for (colonEnd = nameEnd; colonEnd < length; colonEnd ++) { + if (sb.charAt(colonEnd) == ':') { + colonEnd ++; + break; + } + } + + valueStart = findNonWhitespace(sb, colonEnd); + if (valueStart == length) { + return new String[] { + sb.substring(nameStart, nameEnd), + "" + }; + } + + valueEnd = findEndOfString(sb); + return new String[] { + sb.substring(nameStart, nameEnd), + sb.substring(valueStart, valueEnd) + }; + } + + private int findNonWhitespace(String sb, int offset) { + int result; + for (result = offset; result < sb.length(); result ++) { + if (!Character.isWhitespace(sb.charAt(result))) { + break; + } + } + return result; + } + + private int findWhitespace(String sb, int offset) { + int result; + for (result = offset; result < sb.length(); result ++) { + if (Character.isWhitespace(sb.charAt(result))) { + break; + } + } + return result; + } + + private int findEndOfString(String sb) { + int result; + for (result = sb.length(); result > 0; result --) { + if (!Character.isWhitespace(sb.charAt(result - 1))) { + break; + } + } + return result; + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpMessageEncoder.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpMessageEncoder.java new file mode 100644 index 0000000..675402b --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpMessageEncoder.java @@ -0,0 +1,166 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import static com.codefollower.douyu.http.HttpCodecUtil.*; +import static com.codefollower.douyu.netty.buffer.ChannelBuffers.*; + +import java.io.UnsupportedEncodingException; +import java.util.Map; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; +import com.codefollower.douyu.netty.channel.Channel; +import com.codefollower.douyu.netty.channel.ChannelHandlerContext; +import com.codefollower.douyu.netty.handler.codec.oneone.OneToOneEncoder; +import com.codefollower.douyu.netty.util.CharsetUtil; + +/** + * Encodes an {@link HttpMessage} or an {@link HttpChunk} into + * a {@link ChannelBuffer}. + * + *

Extensibility

+ * + * Please note that this encoder is designed to be extended to implement + * a protocol derived from HTTP, such as + * RTSP and + * ICAP. + * To implement the encoder of such a derived protocol, extend this class and + * implement all abstract methods properly. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @version $Rev$, $Date$ + * + * @apiviz.landmark + */ +public abstract class HttpMessageEncoder extends OneToOneEncoder { + + private static final ChannelBuffer LAST_CHUNK = + copiedBuffer("0\r\n\r\n", CharsetUtil.US_ASCII); + + private volatile boolean chunked; + + /** + * Creates a new instance. + */ + protected HttpMessageEncoder() { + super(); + } + + @Override + protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { + if (msg instanceof HttpMessage) { + HttpMessage m = (HttpMessage) msg; + boolean chunked = this.chunked = HttpCodecUtil.isTransferEncodingChunked(m); + ChannelBuffer header = ChannelBuffers.dynamicBuffer( + channel.getConfig().getBufferFactory()); + encodeInitialLine(header, m); + encodeHeaders(header, m); + header.writeByte(CR); + header.writeByte(LF); + + ChannelBuffer content = m.getContent(); + //System.out.println(new String(header.array())); + if (!content.readable()) { + return header; // no content + } else if (chunked) { + throw new IllegalArgumentException( + "HttpMessage.content must be empty " + + "if Transfer-Encoding is chunked."); + } else { + //System.out.println(new String(content.array())); + return wrappedBuffer(header, content); + } + } + + if (msg instanceof HttpChunk) { + HttpChunk chunk = (HttpChunk) msg; + if (chunked) { + if (chunk.isLast()) { + chunked = false; + if (chunk instanceof HttpChunkTrailer) { + ChannelBuffer trailer = ChannelBuffers.dynamicBuffer( + channel.getConfig().getBufferFactory()); + trailer.writeByte((byte) '0'); + trailer.writeByte(CR); + trailer.writeByte(LF); + encodeTrailingHeaders(trailer, (HttpChunkTrailer) chunk); + trailer.writeByte(CR); + trailer.writeByte(LF); + return trailer; + } else { + return LAST_CHUNK.duplicate(); + } + } else { + ChannelBuffer content = chunk.getContent(); + int contentLength = content.readableBytes(); + + return wrappedBuffer( + copiedBuffer( + Integer.toHexString(contentLength), + CharsetUtil.US_ASCII), + wrappedBuffer(CRLF), + content.slice(content.readerIndex(), contentLength), + wrappedBuffer(CRLF)); + } + } else { + if (chunk.isLast()) { + return null; + } else { + return chunk.getContent(); + } + } + + } + + // Unknown message type. + return msg; + } + + private void encodeHeaders(ChannelBuffer buf, HttpMessage message) { + try { + for (Map.Entry h: message.getHeaders()) { + encodeHeader(buf, h.getKey(), h.getValue()); + } + } catch (UnsupportedEncodingException e) { + throw (Error) new Error().initCause(e); + } + } + + private void encodeTrailingHeaders(ChannelBuffer buf, HttpChunkTrailer trailer) { + try { + for (Map.Entry h: trailer.getHeaders()) { + encodeHeader(buf, h.getKey(), h.getValue()); + } + } catch (UnsupportedEncodingException e) { + throw (Error) new Error().initCause(e); + } + } + + private void encodeHeader(ChannelBuffer buf, String header, String value) + throws UnsupportedEncodingException { + buf.writeBytes(header.getBytes("ASCII")); + buf.writeByte(COLON); + buf.writeByte(SP); + buf.writeBytes(value.getBytes("ASCII")); + buf.writeByte(CR); + buf.writeByte(LF); + } + + protected abstract void encodeInitialLine(ChannelBuffer buf, HttpMessage message) throws Exception; +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpMethod.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpMethod.java new file mode 100644 index 0000000..b811f16 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpMethod.java @@ -0,0 +1,183 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.util.HashMap; +import java.util.Map; + +/** + * The request method of HTTP or its derived protocols, such as + * RTSP and + * ICAP. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @version $Rev$, $Date$ + * + * @apiviz.exclude + */ +public class HttpMethod implements Comparable { + /** + * The OPTIONS method represents a request for information about the communication options available on the request/response + * chain identified by the Request-URI. This method allows the client to determine the options and/or requirements + * associated with a resource, or the capabilities of a server, without implying a resource action or initiating a + * resource retrieval. + */ + public static final HttpMethod OPTIONS = new HttpMethod("OPTIONS"); + + /** + * The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI. + * If the Request-URI refers to a data-producing process, it is the produced data which shall be returned as the entity + * in the response and not the source text of the process, unless that text happens to be the output of the process. + */ + public static final HttpMethod GET = new HttpMethod("GET"); + + /** + * The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response. + */ + public static final HttpMethod HEAD = new HttpMethod("HEAD"); + + /** + * The POST method is used to request that the origin server accept the entity enclosed in the request as a new + * subordinate of the resource identified by the Request-URI in the Request-Line. + */ + public static final HttpMethod POST = new HttpMethod("POST"); + + /** + * The PUT method requests that the enclosed entity be stored under the supplied Request-URI. + */ + public static final HttpMethod PUT = new HttpMethod("PUT"); + + /** + * The PATCH method requests that a set of changes described in the + * request entity be applied to the resource identified by the Request-URI. + */ + public static final HttpMethod PATCH = new HttpMethod("PATCH"); + + /** + * The DELETE method requests that the origin server delete the resource identified by the Request-URI. + */ + public static final HttpMethod DELETE = new HttpMethod("DELETE"); + + /** + * The TRACE method is used to invoke a remote, application-layer loop- back of the request message. + */ + public static final HttpMethod TRACE = new HttpMethod("TRACE"); + + /** + * This specification reserves the method name CONNECT for use with a proxy that can dynamically switch to being a tunnel + */ + public static final HttpMethod CONNECT = new HttpMethod("CONNECT"); + + private static final Map methodMap = + new HashMap(); + + static { + methodMap.put(OPTIONS.toString(), OPTIONS); + methodMap.put(GET.toString(), GET); + methodMap.put(HEAD.toString(), HEAD); + methodMap.put(POST.toString(), POST); + methodMap.put(PUT.toString(), PUT); + methodMap.put(PATCH.toString(), PATCH); + methodMap.put(DELETE.toString(), DELETE); + methodMap.put(TRACE.toString(), TRACE); + methodMap.put(CONNECT.toString(), CONNECT); + } + + /** + * Returns the {@link HttpMethod} represented by the specified name. + * If the specified name is a standard HTTP method name, a cached instance + * will be returned. Otherwise, a new instance will be returned. + */ + public static HttpMethod valueOf(String name) { + if (name == null) { + throw new NullPointerException("name"); + } + + name = name.trim().toUpperCase(); + if (name.length() == 0) { + throw new IllegalArgumentException("empty name"); + } + + HttpMethod result = methodMap.get(name); + if (result != null) { + return result; + } else { + return new HttpMethod(name); + } + } + + private final String name; + + /** + * Creates a new HTTP method with the specified name. You will not need to + * create a new method unless you are implementing a protocol derived from + * HTTP, such as + * RTSP and + * ICAP + */ + public HttpMethod(String name) { + if (name == null) { + throw new NullPointerException("name"); + } + + name = name.trim().toUpperCase(); + if (name.length() == 0) { + throw new IllegalArgumentException("empty name"); + } + + for (int i = 0; i < name.length(); i ++) { + if (Character.isISOControl(name.charAt(i)) || + Character.isWhitespace(name.charAt(i))) { + throw new IllegalArgumentException("invalid character in name"); + } + } + + this.name = name; + } + + /** + * Returns the name of this method. + */ + public String getName() { + return name; + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof HttpMethod)) { + return false; + } + + HttpMethod that = (HttpMethod) o; + return getName().equals(that.getName()); + } + + @Override + public String toString() { + return getName(); + } + + @Override + public int compareTo(HttpMethod o) { + return getName().compareTo(o.getName()); + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpPostBodyUtil.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpPostBodyUtil.java new file mode 100644 index 0000000..613be6f --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpPostBodyUtil.java @@ -0,0 +1,187 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.nio.charset.Charset; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.util.CharsetUtil; + +/** + * Shared Static object between HttpMessageDecoder, HttpPostRequestDecoder and HttpPostRequestEncoder + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @author Frederic Bregier + * + */ +public class HttpPostBodyUtil { + public static int chunkSize = 8096; + /** + * HTTP content disposition header name. + */ + public static final String CONTENT_DISPOSITION = "Content-Disposition"; + + public static final String NAME = "name"; + + public static final String FILENAME = "filename"; + + /** + * Content-disposition value for form data. + */ + public static final String FORM_DATA = "form-data"; + + /** + * Content-disposition value for file attachment. + */ + public static final String ATTACHMENT = "attachment"; + + /** + * Content-disposition value for file attachment. + */ + public static final String FILE = "file"; + + /** + * HTTP content type body attribute for multiple uploads. + */ + public static final String MULTIPART_MIXED = "multipart/mixed"; + + /** + * Charset for 8BIT + */ + public static final Charset ISO_8859_1 = CharsetUtil.ISO_8859_1; + + /** + * Charset for 7BIT + */ + public static final Charset US_ASCII = CharsetUtil.US_ASCII; + + /** + * Default Content-Type in binary form + */ + public static final String DEFAULT_BINARY_CONTENT_TYPE = "application/octet-stream"; + + /** + * Default Content-Type in Text form + */ + public static final String DEFAULT_TEXT_CONTENT_TYPE = "text/plain"; + + /** + * Allowed mechanism for multipart + * mechanism := "7bit" + / "8bit" + / "binary" + Not allowed: "quoted-printable" + / "base64" + */ + public static enum TransferEncodingMechanism { + /** + * Default encoding + */ + BIT7("7bit"), + /** + * Short lines but not in ASCII - no encoding + */ + BIT8("8bit"), + /** + * Could be long text not in ASCII - no encoding + */ + BINARY("binary"); + + public String value; + + private TransferEncodingMechanism(String value) { + this.value = value; + } + + private TransferEncodingMechanism() { + value = name(); + } + + @Override + public String toString() { + return value; + } + } + + private HttpPostBodyUtil() { + super(); + } + + //Some commons methods between HttpPostRequestDecoder and HttpMessageDecoder + /** + * Skip control Characters + * @param buffer + */ + static void skipControlCharacters(ChannelBuffer buffer) { + for (;;) { + char c = (char) buffer.readUnsignedByte(); + if (!Character.isISOControl(c) && !Character.isWhitespace(c)) { + buffer.readerIndex(buffer.readerIndex() - 1); + break; + } + } + } + + /** + * Find the first non whitespace + * @param sb + * @param offset + * @return the rank of the first non whitespace + */ + static int findNonWhitespace(String sb, int offset) { + int result; + for (result = offset; result < sb.length(); result ++) { + if (!Character.isWhitespace(sb.charAt(result))) { + break; + } + } + return result; + } + + /** + * Find the first whitespace + * @param sb + * @param offset + * @return the rank of the first whitespace + */ + static int findWhitespace(String sb, int offset) { + int result; + for (result = offset; result < sb.length(); result ++) { + if (Character.isWhitespace(sb.charAt(result))) { + break; + } + } + return result; + } + + /** + * Find the end of String + * @param sb + * @return the rank of the end of string + */ + static int findEndOfString(String sb) { + int result; + for (result = sb.length(); result > 0; result --) { + if (!Character.isWhitespace(sb.charAt(result - 1))) { + break; + } + } + return result; + } + +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpPostRequestDecoder.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpPostRequestDecoder.java new file mode 100644 index 0000000..cfd2198 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpPostRequestDecoder.java @@ -0,0 +1,1530 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + + +import com.codefollower.douyu.http.HttpPostBodyUtil.TransferEncodingMechanism; +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; + +/** + * This decoder will decode Body and can handle POST BODY. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @author Frederic Bregier + * + */ +public class HttpPostRequestDecoder { + /** + * Factory used to create InterfaceHttpData + */ + private final HttpDataFactory factory; + + /** + * Request to decode + */ + private final HttpRequest request; + + /** + * Default charset to use + */ + private final Charset charset; + + /** + * Does request have a body to decode + */ + private boolean bodyToDecode = false; + + /** + * Does the last chunk already received + */ + private boolean isLastChunk = false; + + /** + * HttpDatas from Body + */ + private final List bodyListHttpData = new ArrayList(); + + /** + * HttpDatas as Map from Body + */ + private final Map> bodyMapHttpData = new TreeMap>( + CaseIgnoringComparator.INSTANCE); + + /** + * The current channelBuffer + */ + private ChannelBuffer undecodedChunk = null; + + /** + * Does this request is a Multipart request + */ + private boolean isMultipart = false; + + /** + * Body HttpDatas current position + */ + private int bodyListHttpDataRank = 0; + + /** + * If multipart, this is the boundary for the flobal multipart + */ + private String multipartDataBoundary = null; + + /** + * If multipart, there could be internal multiparts (mixed) to the global multipart. + * Only one level is allowed. + */ + private String multipartMixedBoundary = null; + + /** + * Current status + */ + private MultiPartStatus currentStatus = MultiPartStatus.NOTSTARTED; + + /** + * Used in Multipart + */ + private Map currentFieldAttributes = null; + + /** + * The current FileUpload that is currently in decode process + */ + private FileUpload currentFileUpload = null; + + /** + * The current Attribute that is currently in decode process + */ + private Attribute currentAttribute = null; + + /** + * + * @param request the request to decode + * @throws NullPointerException for request + * @throws IncompatibleDataDecoderException if the request has no body to decode + * @throws ErrorDataDecoderException if the default charset was wrong when decoding or other errors + */ + public HttpPostRequestDecoder(HttpRequest request) + throws ErrorDataDecoderException, IncompatibleDataDecoderException, + NullPointerException { + this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), + request, HttpCodecUtil.DEFAULT_CHARSET); + } + + /** + * + * @param factory the factory used to create InterfaceHttpData + * @param request the request to decode + * @throws NullPointerException for request or factory + * @throws IncompatibleDataDecoderException if the request has no body to decode + * @throws ErrorDataDecoderException if the default charset was wrong when decoding or other errors + */ + public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request) + throws ErrorDataDecoderException, IncompatibleDataDecoderException, + NullPointerException { + this(factory, request, HttpCodecUtil.DEFAULT_CHARSET); + } + + /** + * + * @param factory the factory used to create InterfaceHttpData + * @param request the request to decode + * @param charset the charset to use as default + * @throws NullPointerException for request or charset or factory + * @throws IncompatibleDataDecoderException if the request has no body to decode + * @throws ErrorDataDecoderException if the default charset was wrong when decoding or other errors + */ + public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request, + Charset charset) throws ErrorDataDecoderException, + IncompatibleDataDecoderException, NullPointerException { + if (factory == null) { + throw new NullPointerException("factory"); + } + if (request == null) { + throw new NullPointerException("request"); + } + if (charset == null) { + throw new NullPointerException("charset"); + } + this.request = request; + HttpMethod method = request.getMethod(); + if (method.equals(HttpMethod.POST) || method.equals(HttpMethod.PUT)) { + bodyToDecode = true; + } + this.charset = charset; + this.factory = factory; + // Fill default values + if (this.request.containsHeader(HttpHeaders.Names.CONTENT_TYPE)) { + checkMultipart(this.request.getHeader(HttpHeaders.Names.CONTENT_TYPE)); + } else { + isMultipart = false; + } + if (!bodyToDecode) { + throw new IncompatibleDataDecoderException("No Body to decode"); + } + if (!this.request.isChunked()) { + undecodedChunk = this.request.getContent(); + isLastChunk = true; + parseBody(); + } + } + + /** + * states follow + * NOTSTARTED PREAMBLE ( + * (HEADERDELIMITER DISPOSITION (FIELD | FILEUPLOAD))* + * (HEADERDELIMITER DISPOSITION MIXEDPREAMBLE + * (MIXEDDELIMITER MIXEDDISPOSITION MIXEDFILEUPLOAD)+ + * MIXEDCLOSEDELIMITER)* + * CLOSEDELIMITER)+ EPILOGUE + * + * First status is: NOSTARTED + + Content-type: multipart/form-data, boundary=AaB03x => PREAMBLE in Header + + --AaB03x => HEADERDELIMITER + content-disposition: form-data; name="field1" => DISPOSITION + + Joe Blow => FIELD + --AaB03x => HEADERDELIMITER + content-disposition: form-data; name="pics" => DISPOSITION + Content-type: multipart/mixed, boundary=BbC04y + + --BbC04y => MIXEDDELIMITER + Content-disposition: attachment; filename="file1.txt" => MIXEDDISPOSITION + Content-Type: text/plain + + ... contents of file1.txt ... => MIXEDFILEUPLOAD + --BbC04y => MIXEDDELIMITER + Content-disposition: file; filename="file2.gif" => MIXEDDISPOSITION + Content-type: image/gif + Content-Transfer-Encoding: binary + + ...contents of file2.gif... => MIXEDFILEUPLOAD + --BbC04y-- => MIXEDCLOSEDELIMITER + --AaB03x-- => CLOSEDELIMITER + + Once CLOSEDELIMITER is found, last status is EPILOGUE + * + * @author frederic bregier + * + */ + private static enum MultiPartStatus { + NOTSTARTED, + PREAMBLE, + HEADERDELIMITER, + DISPOSITION, + FIELD, + FILEUPLOAD, + MIXEDPREAMBLE, + MIXEDDELIMITER, + MIXEDDISPOSITION, + MIXEDFILEUPLOAD, + MIXEDCLOSEDELIMITER, + CLOSEDELIMITER, + PREEPILOGUE, + EPILOGUE; + } + + /** + * Check from the request ContentType if this request is a Multipart request. + * @param contentType + * @throws ErrorDataDecoderException + */ + private void checkMultipart(String contentType) + throws ErrorDataDecoderException { + // Check if Post using "multipart/form-data; boundary=--89421926422648" + String[] headerContentType = splitHeaderContentType(contentType); + if (headerContentType[0].toLowerCase().startsWith( + HttpHeaders.Values.MULTIPART_FORM_DATA) && + headerContentType[1].toLowerCase().startsWith( + HttpHeaders.Values.BOUNDARY)) { + String[] boundary = headerContentType[1].split("="); + if (boundary.length != 2) { + throw new ErrorDataDecoderException("Needs a boundary value"); + } + multipartDataBoundary = "--" + boundary[1]; + isMultipart = true; + currentStatus = MultiPartStatus.HEADERDELIMITER; + } else { + isMultipart = false; + } + } + + /** + * True if this request is a Multipart request + * @return True if this request is a Multipart request + */ + public boolean isMultipart() { + return isMultipart; + } + + /** + * This method returns a List of all HttpDatas from body.
+ * + * If chunked, all chunks must have been offered using offer() method. + * If not, NotEnoughDataDecoderException will be raised. + * + * @return the list of HttpDatas from Body part for POST method + * @throws NotEnoughDataDecoderException Need more chunks + */ + public List getBodyHttpDatas() + throws NotEnoughDataDecoderException { + if (!isLastChunk) { + throw new NotEnoughDataDecoderException(); + } + return bodyListHttpData; + } + + /** + * This method returns a List of all HttpDatas with the given name from body.
+ * + * If chunked, all chunks must have been offered using offer() method. + * If not, NotEnoughDataDecoderException will be raised. + + * @param name + * @return All Body HttpDatas with the given name (ignore case) + * @throws NotEnoughDataDecoderException need more chunks + */ + public List getBodyHttpDatas(String name) + throws NotEnoughDataDecoderException { + if (!isLastChunk) { + throw new NotEnoughDataDecoderException(); + } + return bodyMapHttpData.get(name); + } + + /** + * This method returns the first InterfaceHttpData with the given name from body.
+ * + * If chunked, all chunks must have been offered using offer() method. + * If not, NotEnoughDataDecoderException will be raised. + * + * @param name + * @return The first Body InterfaceHttpData with the given name (ignore case) + * @throws NotEnoughDataDecoderException need more chunks + */ + public InterfaceHttpData getBodyHttpData(String name) + throws NotEnoughDataDecoderException { + if (!isLastChunk) { + throw new NotEnoughDataDecoderException(); + } + List list = bodyMapHttpData.get(name); + if (list != null) { + return list.get(0); + } + return null; + } + + /** + * Initialized the internals from a new chunk + * @param chunk the new received chunk + * @throws ErrorDataDecoderException if there is a problem with the charset decoding or + * other errors + */ + public void offer(HttpChunk chunk) throws ErrorDataDecoderException { + ChannelBuffer chunked = chunk.getContent(); + if (undecodedChunk == null) { + undecodedChunk = chunked; + } else { + //undecodedChunk = ChannelBuffers.wrappedBuffer(undecodedChunk, chunk.getContent()); + // less memory usage + undecodedChunk = ChannelBuffers.wrappedBuffer( + undecodedChunk, chunked); + } + if (chunk.isLast()) { + isLastChunk = true; + } + parseBody(); + } + + /** + * True if at current status, there is an available decoded InterfaceHttpData from the Body. + * + * This method works for chunked and not chunked request. + * + * @return True if at current status, there is a decoded InterfaceHttpData + * @throws EndOfDataDecoderException No more data will be available + */ + public boolean hasNext() throws EndOfDataDecoderException { + if (currentStatus == MultiPartStatus.EPILOGUE) { + // OK except if end of list + if (bodyListHttpDataRank >= bodyListHttpData.size()) { + throw new EndOfDataDecoderException(); + } + } + return bodyListHttpData.size() > 0 && + bodyListHttpDataRank < bodyListHttpData.size(); + } + + /** + * Returns the next available InterfaceHttpData or null if, at the time it is called, there is no more + * available InterfaceHttpData. A subsequent call to offer(httpChunk) could enable more data. + * + * @return the next available InterfaceHttpData or null if none + * @throws EndOfDataDecoderException No more data will be available + */ + public InterfaceHttpData next() throws EndOfDataDecoderException { + if (hasNext()) { + return bodyListHttpData.get(bodyListHttpDataRank++); + } + return null; + } + + /** + * This method will parse as much as possible data and fill the list and map + * @throws ErrorDataDecoderException if there is a problem with the charset decoding or + * other errors + */ + private void parseBody() throws ErrorDataDecoderException { + if (currentStatus == MultiPartStatus.PREEPILOGUE || + currentStatus == MultiPartStatus.EPILOGUE) { + if (isLastChunk) { + currentStatus = MultiPartStatus.EPILOGUE; + } + return; + } + if (isMultipart) { + parseBodyMultipart(); + } else { + parseBodyAttributes(); + } + } + + /** + * Utility function to add a new decoded data + * @param data + */ + private void addHttpData(InterfaceHttpData data) { + if (data == null) { + return; + } + List datas = bodyMapHttpData.get(data.getName()); + if (datas == null) { + datas = new ArrayList(1); + bodyMapHttpData.put(data.getName(), datas); + } + datas.add(data); + bodyListHttpData.add(data); + } + + /** + * This method fill the map and list with as much Attribute as possible from Body in + * not Multipart mode. + * + * @throws ErrorDataDecoderException if there is a problem with the charset decoding or + * other errors + */ + private void parseBodyAttributes() throws ErrorDataDecoderException { + int firstpos = undecodedChunk.readerIndex(); + int currentpos = firstpos; + int equalpos = firstpos; + int ampersandpos = firstpos; + if (currentStatus == MultiPartStatus.NOTSTARTED) { + currentStatus = MultiPartStatus.DISPOSITION; + } + boolean contRead = true; + try { + while (undecodedChunk.readable() && contRead) { + char read = (char) undecodedChunk.readUnsignedByte(); + currentpos++; + switch (currentStatus) { + case DISPOSITION:// search '=' + if (read == '=') { + currentStatus = MultiPartStatus.FIELD; + equalpos = currentpos-1; + String key = decodeAttribute( + undecodedChunk.toString(firstpos, equalpos-firstpos, charset), + charset); + currentAttribute = factory.createAttribute(request, key); + firstpos = currentpos; + } + break; + case FIELD:// search '&' or end of line + if (read == '&') { + currentStatus = MultiPartStatus.DISPOSITION; + ampersandpos = currentpos-1; + setFinalBuffer(undecodedChunk.slice(firstpos, ampersandpos-firstpos)); + firstpos = currentpos; + contRead = true; + } else if (read == HttpCodecUtil.CR) { + if (undecodedChunk.readable()) { + read = (char) undecodedChunk.readUnsignedByte(); + currentpos++; + if (read == HttpCodecUtil.LF) { + currentStatus = MultiPartStatus.PREEPILOGUE; + ampersandpos = currentpos-2; + setFinalBuffer( + undecodedChunk.slice(firstpos, ampersandpos-firstpos)); + firstpos = currentpos; + contRead = false; + } else { + // Error + contRead = false; + throw new ErrorDataDecoderException("Bad end of line"); + } + } else { + currentpos--; + } + } else if (read == HttpCodecUtil.LF) { + currentStatus = MultiPartStatus.PREEPILOGUE; + ampersandpos = currentpos-1; + setFinalBuffer( + undecodedChunk.slice(firstpos, ampersandpos-firstpos)); + firstpos = currentpos; + contRead = false; + } + break; + default: + // just stop + contRead = false; + } + } + if (isLastChunk && currentAttribute != null) { + // special case + ampersandpos = currentpos; + if (ampersandpos > firstpos) { + setFinalBuffer( + undecodedChunk.slice(firstpos, ampersandpos-firstpos)); + } else if (! currentAttribute.isCompleted()) { + setFinalBuffer(ChannelBuffers.EMPTY_BUFFER); + } + firstpos = currentpos; + currentStatus = MultiPartStatus.EPILOGUE; + return; + } + if (contRead && currentAttribute != null) { + // reset index except if to continue in case of FIELD status + if (currentStatus == MultiPartStatus.FIELD) { + currentAttribute.addContent( + undecodedChunk.slice(firstpos, currentpos-firstpos), + false); + firstpos = currentpos; + } + undecodedChunk.readerIndex(firstpos); + } else { + // end of line so keep index + } + } catch (ErrorDataDecoderException e) { + // error while decoding + undecodedChunk.readerIndex(firstpos); + throw e; + } catch (IOException e) { + // error while decoding + undecodedChunk.readerIndex(firstpos); + throw new ErrorDataDecoderException(e); + } + } + + private void setFinalBuffer(ChannelBuffer buffer) throws ErrorDataDecoderException, IOException { + currentAttribute.addContent(buffer, true); + String value = decodeAttribute( + currentAttribute.getChannelBuffer().toString(charset), + charset); + currentAttribute.setValue(value); + addHttpData(currentAttribute); + currentAttribute = null; + } + + /** + * Decode component + * @param s + * @param charset + * @return the decoded component + * @throws ErrorDataDecoderException + */ + private static String decodeAttribute(String s, Charset charset) + throws ErrorDataDecoderException { + if (s == null) { + return ""; + } + try { + return URLDecoder.decode(s, charset.name()); + } catch (UnsupportedEncodingException e) { + throw new ErrorDataDecoderException(charset.toString(), e); + } + } + + /** + * Parse the Body for multipart + * + * @throws ErrorDataDecoderException if there is a problem with the charset decoding or other errors + */ + private void parseBodyMultipart() throws ErrorDataDecoderException { + if (undecodedChunk == null || undecodedChunk.readableBytes() == 0) { + // nothing to decode + return; + } + InterfaceHttpData data = decodeMultipart(currentStatus); + while (data != null) { + addHttpData(data); + if (currentStatus == MultiPartStatus.PREEPILOGUE || + currentStatus == MultiPartStatus.EPILOGUE) { + break; + } + data = decodeMultipart(currentStatus); + } + } + + /** + * Decode a multipart request by pieces
+ *
+ * NOTSTARTED PREAMBLE (
+ * (HEADERDELIMITER DISPOSITION (FIELD | FILEUPLOAD))*
+ * (HEADERDELIMITER DISPOSITION MIXEDPREAMBLE
+ * (MIXEDDELIMITER MIXEDDISPOSITION MIXEDFILEUPLOAD)+
+ * MIXEDCLOSEDELIMITER)*
+ * CLOSEDELIMITER)+ EPILOGUE
+ * + * Inspired from HttpMessageDecoder + * + * @param state + * @return the next decoded InterfaceHttpData or null if none until now. + * @throws ErrorDataDecoderException if an error occurs + */ + private InterfaceHttpData decodeMultipart(MultiPartStatus state) + throws ErrorDataDecoderException { + switch (state) { + case NOTSTARTED: + throw new ErrorDataDecoderException( + "Should not be called with the current status"); + case PREAMBLE: + // Content-type: multipart/form-data, boundary=AaB03x + throw new ErrorDataDecoderException( + "Should not be called with the current status"); + case HEADERDELIMITER: { + // --AaB03x or --AaB03x-- + return findMultipartDelimiter(multipartDataBoundary, + MultiPartStatus.DISPOSITION, MultiPartStatus.PREEPILOGUE); + } + case DISPOSITION: { + // content-disposition: form-data; name="field1" + // content-disposition: form-data; name="pics"; filename="file1.txt" + // and other immediate values like + // Content-type: image/gif + // Content-Type: text/plain + // Content-Type: text/plain; charset=ISO-8859-1 + // Content-Transfer-Encoding: binary + // The following line implies a change of mode (mixed mode) + // Content-type: multipart/mixed, boundary=BbC04y + return findMultipartDisposition(); + } + case FIELD: { + // Now get value according to Content-Type and Charset + Charset localCharset = null; + Attribute charsetAttribute = currentFieldAttributes + .get(HttpHeaders.Values.CHARSET); + if (charsetAttribute != null) { + try { + localCharset = Charset.forName(charsetAttribute.getValue()); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + } + Attribute nameAttribute = currentFieldAttributes + .get(HttpPostBodyUtil.NAME); + if (currentAttribute == null) { + try { + currentAttribute = factory.createAttribute(request, nameAttribute + .getValue()); + } catch (NullPointerException e) { + throw new ErrorDataDecoderException(e); + } catch (IllegalArgumentException e) { + throw new ErrorDataDecoderException(e); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + if (localCharset != null) { + currentAttribute.setCharset(localCharset); + } + } + // load data + try { + loadFieldMultipart(multipartDataBoundary); + } catch (NotEnoughDataDecoderException e) { + return null; + } + Attribute finalAttribute = currentAttribute; + currentAttribute = null; + currentFieldAttributes = null; + // ready to load the next one + currentStatus = MultiPartStatus.HEADERDELIMITER; + return finalAttribute; + } + case FILEUPLOAD: { + // eventually restart from existing FileUpload + return getFileUpload(multipartDataBoundary); + } + case MIXEDDELIMITER: { + // --AaB03x or --AaB03x-- + // Note that currentFieldAttributes exists + return findMultipartDelimiter(multipartMixedBoundary, + MultiPartStatus.MIXEDDISPOSITION, + MultiPartStatus.HEADERDELIMITER); + } + case MIXEDDISPOSITION: { + return findMultipartDisposition(); + } + case MIXEDFILEUPLOAD: { + // eventually restart from existing FileUpload + return getFileUpload(multipartMixedBoundary); + } + case PREEPILOGUE: + return null; + case EPILOGUE: + return null; + default: + throw new ErrorDataDecoderException("Shouldn't reach here."); + } + } + + /** + * Find the next Multipart Delimiter + * @param delimiter delimiter to find + * @param dispositionStatus the next status if the delimiter is a start + * @param closeDelimiterStatus the next status if the delimiter is a close delimiter + * @return the next InterfaceHttpData if any + * @throws ErrorDataDecoderException + */ + private InterfaceHttpData findMultipartDelimiter(String delimiter, + MultiPartStatus dispositionStatus, + MultiPartStatus closeDelimiterStatus) + throws ErrorDataDecoderException { + // --AaB03x or --AaB03x-- + int readerIndex = undecodedChunk.readerIndex(); + HttpPostBodyUtil.skipControlCharacters(undecodedChunk); + skipOneLine(); + String newline; + try { + newline = readLine(); + } catch (NotEnoughDataDecoderException e) { + undecodedChunk.readerIndex(readerIndex); + return null; + } + if (newline.equals(delimiter)) { + currentStatus = dispositionStatus; + return decodeMultipart(dispositionStatus); + } else if (newline.equals(delimiter + "--")) { + // CLOSEDELIMITER or MIXED CLOSEDELIMITER found + currentStatus = closeDelimiterStatus; + if (currentStatus == MultiPartStatus.HEADERDELIMITER) { + // MIXEDCLOSEDELIMITER + // end of the Mixed part + currentFieldAttributes = null; + return decodeMultipart(MultiPartStatus.HEADERDELIMITER); + } + return null; + } + undecodedChunk.readerIndex(readerIndex); + throw new ErrorDataDecoderException("No Multipart delimiter found"); + } + + /** + * Find the next Disposition + * @return the next InterfaceHttpData if any + * @throws ErrorDataDecoderException + */ + private InterfaceHttpData findMultipartDisposition() + throws ErrorDataDecoderException { + int readerIndex = undecodedChunk.readerIndex(); + if (currentStatus == MultiPartStatus.DISPOSITION) { + currentFieldAttributes = new TreeMap( + CaseIgnoringComparator.INSTANCE); + } + // read many lines until empty line with newline found! Store all data + while (!skipOneLine()) { + HttpPostBodyUtil.skipControlCharacters(undecodedChunk); + String newline; + try { + newline = readLine(); + } catch (NotEnoughDataDecoderException e) { + undecodedChunk.readerIndex(readerIndex); + return null; + } + String[] contents = splitMultipartHeader(newline); + if (contents[0].equalsIgnoreCase(HttpPostBodyUtil.CONTENT_DISPOSITION)) { + boolean checkSecondArg = false; + if (currentStatus == MultiPartStatus.DISPOSITION) { + checkSecondArg = contents[1] + .equalsIgnoreCase(HttpPostBodyUtil.FORM_DATA); + } else { + checkSecondArg = contents[1] + .equalsIgnoreCase(HttpPostBodyUtil.ATTACHMENT) || + contents[1] + .equalsIgnoreCase(HttpPostBodyUtil.FILE); + } + if (checkSecondArg) { + // read next values and store them in the map as Attribute + for (int i = 2; i < contents.length; i ++) { + String[] values = contents[i].split("="); + Attribute attribute; + try { + attribute = factory.createAttribute(request, values[0].trim(), + decodeAttribute(cleanString(values[1]), charset)); + } catch (NullPointerException e) { + throw new ErrorDataDecoderException(e); + } catch (IllegalArgumentException e) { + throw new ErrorDataDecoderException(e); + } + currentFieldAttributes.put(attribute.getName(), + attribute); + } + } + } else if (contents[0] + .equalsIgnoreCase(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING)) { + Attribute attribute; + try { + attribute = factory.createAttribute(request, + HttpHeaders.Names.CONTENT_TRANSFER_ENCODING, + cleanString(contents[1])); + } catch (NullPointerException e) { + throw new ErrorDataDecoderException(e); + } catch (IllegalArgumentException e) { + throw new ErrorDataDecoderException(e); + } + currentFieldAttributes.put( + HttpHeaders.Names.CONTENT_TRANSFER_ENCODING, attribute); + } else if (contents[0] + .equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH)) { + Attribute attribute; + try { + attribute = factory.createAttribute(request, + HttpHeaders.Names.CONTENT_LENGTH, + cleanString(contents[1])); + } catch (NullPointerException e) { + throw new ErrorDataDecoderException(e); + } catch (IllegalArgumentException e) { + throw new ErrorDataDecoderException(e); + } + currentFieldAttributes.put(HttpHeaders.Names.CONTENT_LENGTH, + attribute); + } else if (contents[0].equalsIgnoreCase(HttpHeaders.Names.CONTENT_TYPE)) { + // Take care of possible "multipart/mixed" + if (contents[1].equalsIgnoreCase(HttpPostBodyUtil.MULTIPART_MIXED)) { + if (currentStatus == MultiPartStatus.DISPOSITION) { + String[] values = contents[2].split("="); + multipartMixedBoundary = "--" + values[1]; + currentStatus = MultiPartStatus.MIXEDDELIMITER; + return decodeMultipart(MultiPartStatus.MIXEDDELIMITER); + } else { + throw new ErrorDataDecoderException( + "Mixed Multipart found in a previous Mixed Multipart"); + } + } else { + for (int i = 1; i < contents.length; i ++) { + if (contents[i].toLowerCase().startsWith( + HttpHeaders.Values.CHARSET)) { + String[] values = contents[i].split("="); + Attribute attribute; + try { + attribute = factory.createAttribute(request, + HttpHeaders.Values.CHARSET, + cleanString(values[1])); + } catch (NullPointerException e) { + throw new ErrorDataDecoderException(e); + } catch (IllegalArgumentException e) { + throw new ErrorDataDecoderException(e); + } + currentFieldAttributes.put(HttpHeaders.Values.CHARSET, + attribute); + } else { + Attribute attribute; + try { + attribute = factory.createAttribute(request, + contents[0].trim(), + decodeAttribute(cleanString(contents[i]), charset)); + } catch (NullPointerException e) { + throw new ErrorDataDecoderException(e); + } catch (IllegalArgumentException e) { + throw new ErrorDataDecoderException(e); + } + currentFieldAttributes.put(attribute.getName(), + attribute); + } + } + } + } else { + throw new ErrorDataDecoderException("Unknown Params: " + + newline); + } + } + // Is it a FileUpload + Attribute filenameAttribute = currentFieldAttributes + .get(HttpPostBodyUtil.FILENAME); + if (currentStatus == MultiPartStatus.DISPOSITION) { + if (filenameAttribute != null) { + // FileUpload + currentStatus = MultiPartStatus.FILEUPLOAD; + // do not change the buffer position + return decodeMultipart(MultiPartStatus.FILEUPLOAD); + } else { + // Field + currentStatus = MultiPartStatus.FIELD; + // do not change the buffer position + return decodeMultipart(MultiPartStatus.FIELD); + } + } else { + if (filenameAttribute != null) { + // FileUpload + currentStatus = MultiPartStatus.MIXEDFILEUPLOAD; + // do not change the buffer position + return decodeMultipart(MultiPartStatus.MIXEDFILEUPLOAD); + } else { + // Field is not supported in MIXED mode + throw new ErrorDataDecoderException("Filename not found"); + } + } + } + + /** + * Get the FileUpload (new one or current one) + * @param delimiter the delimiter to use + * @return the InterfaceHttpData if any + * @throws ErrorDataDecoderException + */ + private InterfaceHttpData getFileUpload(String delimiter) + throws ErrorDataDecoderException { + // eventually restart from existing FileUpload + // Now get value according to Content-Type and Charset + Attribute encoding = currentFieldAttributes + .get(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING); + Charset localCharset = charset; + // Default + TransferEncodingMechanism mechanism = TransferEncodingMechanism.BIT7; + if (encoding != null) { + String code; + try { + code = encoding.getValue().toLowerCase(); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BIT7.value)) { + localCharset = HttpPostBodyUtil.US_ASCII; + } else if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BIT8.value)) { + localCharset = HttpPostBodyUtil.ISO_8859_1; + mechanism = TransferEncodingMechanism.BIT8; + } else if (code + .equals(HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value)) { + // no real charset, so let the default + mechanism = TransferEncodingMechanism.BINARY; + } else { + throw new ErrorDataDecoderException( + "TransferEncoding Unknown: " + code); + } + } + Attribute charsetAttribute = currentFieldAttributes + .get(HttpHeaders.Values.CHARSET); + if (charsetAttribute != null) { + try { + localCharset = Charset.forName(charsetAttribute.getValue()); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + } + if (currentFileUpload == null) { + Attribute filenameAttribute = currentFieldAttributes + .get(HttpPostBodyUtil.FILENAME); + Attribute nameAttribute = currentFieldAttributes + .get(HttpPostBodyUtil.NAME); + Attribute contentTypeAttribute = currentFieldAttributes + .get(HttpHeaders.Names.CONTENT_TYPE); + if (contentTypeAttribute == null) { + throw new ErrorDataDecoderException( + "Content-Type is absent but required"); + } + Attribute lengthAttribute = currentFieldAttributes + .get(HttpHeaders.Names.CONTENT_LENGTH); + long size; + try { + size = lengthAttribute != null? Long.parseLong(lengthAttribute + .getValue()) : 0L; + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } catch (NumberFormatException e) { + size = 0; + } + try { + currentFileUpload = factory.createFileUpload( + request, + nameAttribute.getValue(), filenameAttribute.getValue(), + contentTypeAttribute.getValue(), mechanism.value, + localCharset, size); + } catch (NullPointerException e) { + throw new ErrorDataDecoderException(e); + } catch (IllegalArgumentException e) { + throw new ErrorDataDecoderException(e); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + } + // load data as much as possible + try { + readFileUploadByteMultipart(delimiter); + } catch (NotEnoughDataDecoderException e) { + // do not change the buffer position + // since some can be already saved into FileUpload + // So do not change the currentStatus + return null; + } + if (currentFileUpload.isCompleted()) { + // ready to load the next one + if (currentStatus == MultiPartStatus.FILEUPLOAD) { + currentStatus = MultiPartStatus.HEADERDELIMITER; + currentFieldAttributes = null; + } else { + currentStatus = MultiPartStatus.MIXEDDELIMITER; + cleanMixedAttributes(); + } + FileUpload fileUpload = currentFileUpload; + currentFileUpload = null; + return fileUpload; + } + // do not change the buffer position + // since some can be already saved into FileUpload + // So do not change the currentStatus + return null; + } + + /** + * Clean all HttpDatas (on Disk) for the current request. + * + */ + public void cleanFiles() { + factory.cleanRequestHttpDatas(request); + } + + /** + * Remove the given FileUpload from the list of FileUploads to clean + */ + public void removeHttpDataFromClean(InterfaceHttpData data) { + factory.removeHttpDataFromClean(request, data); + } + + /** + * Remove all Attributes that should be cleaned between two FileUpload in Mixed mode + */ + private void cleanMixedAttributes() { + currentFieldAttributes.remove(HttpHeaders.Values.CHARSET); + currentFieldAttributes.remove(HttpHeaders.Names.CONTENT_LENGTH); + currentFieldAttributes.remove(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING); + currentFieldAttributes.remove(HttpHeaders.Names.CONTENT_TYPE); + currentFieldAttributes.remove(HttpPostBodyUtil.FILENAME); + } + + /** + * Read one line up to the CRLF or LF + * @return the String from one line + * @throws NotEnoughDataDecoderException Need more chunks and + * reset the readerInder to the previous value + */ + private String readLine() throws NotEnoughDataDecoderException { + int readerIndex = undecodedChunk.readerIndex(); + try { + StringBuilder sb = new StringBuilder(64); + while (undecodedChunk.readable()) { + byte nextByte = undecodedChunk.readByte(); + if (nextByte == HttpCodecUtil.CR) { + nextByte = undecodedChunk.readByte(); + if (nextByte == HttpCodecUtil.LF) { + return sb.toString(); + } + } else if (nextByte == HttpCodecUtil.LF) { + return sb.toString(); + } else { + sb.append((char) nextByte); + } + } + } catch (IndexOutOfBoundsException e) { + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(e); + } + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(); + } + + /** + * Read a FileUpload data as Byte (Binary) and add the bytes directly to the + * FileUpload. If the delimiter is found, the FileUpload is completed. + * @param delimiter + * @throws NotEnoughDataDecoderException Need more chunks but + * do not reset the readerInder since some values will be already added to the FileOutput + * @throws ErrorDataDecoderException write IO error occurs with the FileUpload + */ + private void readFileUploadByteMultipart(String delimiter) + throws NotEnoughDataDecoderException, ErrorDataDecoderException { + int readerIndex = undecodedChunk.readerIndex(); + // found the decoder limit + boolean newLine = true; + int index = 0; + int lastPosition = undecodedChunk.readerIndex(); + boolean found = false; + while (undecodedChunk.readable()) { + byte nextByte = undecodedChunk.readByte(); + if (newLine) { + // Check the delimiter + if (nextByte == delimiter.codePointAt(index)) { + index ++; + if (delimiter.length() == index) { + found = true; + break; + } + continue; + } else { + newLine = false; + index = 0; + // continue until end of line + if (nextByte == HttpCodecUtil.CR) { + if (undecodedChunk.readable()) { + nextByte = undecodedChunk.readByte(); + if (nextByte == HttpCodecUtil.LF) { + newLine = true; + index = 0; + lastPosition = undecodedChunk.readerIndex() - 2; + } + } + } else if (nextByte == HttpCodecUtil.LF) { + newLine = true; + index = 0; + lastPosition = undecodedChunk.readerIndex() - 1; + } else { + // save last valid position + lastPosition = undecodedChunk.readerIndex(); + } + } + } else { + // continue until end of line + if (nextByte == HttpCodecUtil.CR) { + if (undecodedChunk.readable()) { + nextByte = undecodedChunk.readByte(); + if (nextByte == HttpCodecUtil.LF) { + newLine = true; + index = 0; + lastPosition = undecodedChunk.readerIndex() - 2; + } + } + } else if (nextByte == HttpCodecUtil.LF) { + newLine = true; + index = 0; + lastPosition = undecodedChunk.readerIndex() - 1; + } else { + // save last valid position + lastPosition = undecodedChunk.readerIndex(); + } + } + } + ChannelBuffer buffer = undecodedChunk.slice(readerIndex, lastPosition - + readerIndex); + if (found) { + // found so lastPosition is correct and final + try { + currentFileUpload.addContent(buffer, true); + // just before the CRLF and delimiter + undecodedChunk.readerIndex(lastPosition); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + } else { + // possibly the delimiter is partially found but still the last position is OK + try { + currentFileUpload.addContent(buffer, false); + // last valid char (not CR, not LF, not beginning of delimiter) + undecodedChunk.readerIndex(lastPosition); + throw new NotEnoughDataDecoderException(); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + } + } + + /** + * Load the field value from a Multipart request + * @throws NotEnoughDataDecoderException Need more chunks + * @throws ErrorDataDecoderException + */ + private void loadFieldMultipart(String delimiter) + throws NotEnoughDataDecoderException, ErrorDataDecoderException { + int readerIndex = undecodedChunk.readerIndex(); + try { + // found the decoder limit + boolean newLine = true; + int index = 0; + int lastPosition = undecodedChunk.readerIndex(); + boolean found = false; + while (undecodedChunk.readable()) { + byte nextByte = undecodedChunk.readByte(); + if (newLine) { + // Check the delimiter + if (nextByte == delimiter.codePointAt(index)) { + index ++; + if (delimiter.length() == index) { + found = true; + break; + } + continue; + } else { + newLine = false; + index = 0; + // continue until end of line + if (nextByte == HttpCodecUtil.CR) { + if (undecodedChunk.readable()) { + nextByte = undecodedChunk.readByte(); + if (nextByte == HttpCodecUtil.LF) { + newLine = true; + index = 0; + lastPosition = undecodedChunk.readerIndex() - 2; + } + } + } else if (nextByte == HttpCodecUtil.LF) { + newLine = true; + index = 0; + lastPosition = undecodedChunk.readerIndex() - 1; + } else { + lastPosition = undecodedChunk.readerIndex(); + } + } + } else { + // continue until end of line + if (nextByte == HttpCodecUtil.CR) { + if (undecodedChunk.readable()) { + nextByte = undecodedChunk.readByte(); + if (nextByte == HttpCodecUtil.LF) { + newLine = true; + index = 0; + lastPosition = undecodedChunk.readerIndex() - 2; + } + } + } else if (nextByte == HttpCodecUtil.LF) { + newLine = true; + index = 0; + lastPosition = undecodedChunk.readerIndex() - 1; + } else { + lastPosition = undecodedChunk.readerIndex(); + } + } + } + if (found) { + // found so lastPosition is correct + // but position is just after the delimiter (either close delimiter or simple one) + // so go back of delimiter size + try { + currentAttribute.addContent( + undecodedChunk.slice(readerIndex, lastPosition-readerIndex), + true); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + undecodedChunk.readerIndex(lastPosition); + } else { + try { + currentAttribute.addContent( + undecodedChunk.slice(readerIndex, lastPosition-readerIndex), + false); + } catch (IOException e) { + throw new ErrorDataDecoderException(e); + } + undecodedChunk.readerIndex(lastPosition); + throw new NotEnoughDataDecoderException(); + } + } catch (IndexOutOfBoundsException e) { + undecodedChunk.readerIndex(readerIndex); + throw new NotEnoughDataDecoderException(e); + } + } + + /** + * Clean the String from any unallowed character + * @return the cleaned String + */ + private String cleanString(String field) { + StringBuilder sb = new StringBuilder(field.length()); + int i = 0; + for (i = 0; i < field.length(); i ++) { + char nextChar = field.charAt(i); + if (nextChar == HttpCodecUtil.COLON) { + sb.append(HttpCodecUtil.SP); + } else if (nextChar == HttpCodecUtil.COMMA) { + sb.append(HttpCodecUtil.SP); + } else if (nextChar == HttpCodecUtil.EQUALS) { + sb.append(HttpCodecUtil.SP); + } else if (nextChar == HttpCodecUtil.SEMICOLON) { + sb.append(HttpCodecUtil.SP); + } else if (nextChar == HttpCodecUtil.HT) { + sb.append(HttpCodecUtil.SP); + } else if (nextChar == HttpCodecUtil.DOUBLE_QUOTE) { + // nothing added, just removes it + } else { + sb.append(nextChar); + } + } + return sb.toString().trim(); + } + + /** + * Skip one empty line + * @return True if one empty line was skipped + */ + private boolean skipOneLine() { + if (!undecodedChunk.readable()) { + return false; + } + byte nextByte = undecodedChunk.readByte(); + if (nextByte == HttpCodecUtil.CR) { + if (!undecodedChunk.readable()) { + undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1); + return false; + } + nextByte = undecodedChunk.readByte(); + if (nextByte == HttpCodecUtil.LF) { + return true; + } + undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 2); + return false; + } else if (nextByte == HttpCodecUtil.LF) { + return true; + } + undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1); + return false; + } + + /** + * Split the very first line (Content-Type value) in 2 Strings + * @param sb + * @return the array of 2 Strings + */ + private String[] splitHeaderContentType(String sb) { + int size = sb.length(); + int aStart; + int aEnd; + int bStart; + int bEnd; + aStart = HttpPostBodyUtil.findNonWhitespace(sb, 0); + aEnd = HttpPostBodyUtil.findWhitespace(sb, aStart); + if (aEnd >= size) { + return new String[] { sb, "" }; + } + if (sb.charAt(aEnd) == ';') { + aEnd --; + } + bStart = HttpPostBodyUtil.findNonWhitespace(sb, aEnd); + bEnd = HttpPostBodyUtil.findEndOfString(sb); + return new String[] { sb.substring(aStart, aEnd), + sb.substring(bStart, bEnd) }; + } + + /** + * Split one header in Multipart + * @param sb + * @return an array of String where rank 0 is the name of the header, follows by several + * values that were separated by ';' or ',' + */ + private String[] splitMultipartHeader(String sb) { + ArrayList headers = new ArrayList(1); + int nameStart; + int nameEnd; + int colonEnd; + int valueStart; + int valueEnd; + nameStart = HttpPostBodyUtil.findNonWhitespace(sb, 0); + for (nameEnd = nameStart; nameEnd < sb.length(); nameEnd ++) { + char ch = sb.charAt(nameEnd); + if (ch == ':' || Character.isWhitespace(ch)) { + break; + } + } + for (colonEnd = nameEnd; colonEnd < sb.length(); colonEnd ++) { + if (sb.charAt(colonEnd) == ':') { + colonEnd ++; + break; + } + } + valueStart = HttpPostBodyUtil.findNonWhitespace(sb, colonEnd); + valueEnd = HttpPostBodyUtil.findEndOfString(sb); + headers.add(sb.substring(nameStart, nameEnd)); + String svalue = sb.substring(valueStart, valueEnd); + String[] values = null; + if (svalue.indexOf(";") >= 0) { + values = svalue.split(";"); + } else { + values = svalue.split(","); + } + for (String value: values) { + headers.add(value.trim()); + } + String[] array = new String[headers.size()]; + for (int i = 0; i < headers.size(); i ++) { + array[i] = headers.get(i); + } + return array; + } + + /** + * Exception when try reading data from request in chunked format, and not enough + * data are available (need more chunks) + * + * @author frederic bregier + * + */ + public static class NotEnoughDataDecoderException extends Exception { + /** + * + */ + private static final long serialVersionUID = -7846841864603865638L; + + /** + * + */ + public NotEnoughDataDecoderException() { + super(); + } + + /** + * @param arg0 + */ + public NotEnoughDataDecoderException(String arg0) { + super(arg0); + } + + /** + * @param arg0 + */ + public NotEnoughDataDecoderException(Throwable arg0) { + super(arg0); + } + + /** + * @param arg0 + * @param arg1 + */ + public NotEnoughDataDecoderException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + } + + /** + * Exception when the body is fully decoded, even if there is still data + * + * @author frederic bregier + * + */ + public static class EndOfDataDecoderException extends Exception { + /** + * + */ + private static final long serialVersionUID = 1336267941020800769L; + + /** + * + */ + public EndOfDataDecoderException() { + super(); + } + } + + /** + * Exception when an error occurs while decoding + * + * @author frederic bregier + * + */ + public static class ErrorDataDecoderException extends Exception { + /** + * + */ + private static final long serialVersionUID = 5020247425493164465L; + + /** + * + */ + public ErrorDataDecoderException() { + super(); + } + + /** + * @param arg0 + */ + public ErrorDataDecoderException(String arg0) { + super(arg0); + } + + /** + * @param arg0 + */ + public ErrorDataDecoderException(Throwable arg0) { + super(arg0); + } + + /** + * @param arg0 + * @param arg1 + */ + public ErrorDataDecoderException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + } + + /** + * Exception when an unappropriated method was called on a request + * + * @author frederic bregier + * + */ + public class IncompatibleDataDecoderException extends Exception { + /** + * + */ + private static final long serialVersionUID = -953268047926250267L; + + /** + * + */ + public IncompatibleDataDecoderException() { + super(); + } + + /** + * @param arg0 + */ + public IncompatibleDataDecoderException(String arg0) { + super(arg0); + } + + /** + * @param arg0 + */ + public IncompatibleDataDecoderException(Throwable arg0) { + super(arg0); + } + + /** + * @param arg0 + * @param arg1 + */ + public IncompatibleDataDecoderException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpRequest.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpRequest.java new file mode 100644 index 0000000..f4d0b35 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpRequest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + + +/** + * An HTTP request. + * + *

Accessing Query Parameters and Cookie

+ *

+ * Unlike the Servlet API, a query string is constructed and decomposed by + * {@link QueryStringEncoder} and {@link QueryStringDecoder}. {@link Cookie} + * support is also provided separately via {@link CookieEncoder} and + * {@link CookieDecoder}. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @version $Rev$, $Date$ + * + * @see HttpResponse + * @see CookieEncoder + * @see CookieDecoder + */ +public interface HttpRequest extends HttpMessage { + + /** + * Returns the method of this request. + */ + HttpMethod getMethod(); + + /** + * Sets the method of this request. + */ + void setMethod(HttpMethod method); + + /** + * Returns the URI (or path) of this request. + */ + String getUri(); + + /** + * Sets the URI (or path) of this request. + */ + void setUri(String uri); +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpRequestDecoder.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpRequestDecoder.java new file mode 100644 index 0000000..4593ab0 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpRequestDecoder.java @@ -0,0 +1,84 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.channel.ChannelPipeline; +import com.codefollower.douyu.netty.handler.codec.frame.TooLongFrameException; + + +/** + * Decodes {@link ChannelBuffer}s into {@link HttpRequest}s and {@link HttpChunk}s. + * + *

Parameters that prevents excessive memory consumption

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
NameMeaning
{@code maxInitialLineLength}The maximum length of the initial line (e.g. {@code "GET / HTTP/1.0"}) + * If the length of the initial line exceeds this value, a + * {@link TooLongFrameException} will be raised.
{@code maxHeaderSize}The maximum length of all headers. If the sum of the length of each + * header exceeds this value, a {@link TooLongFrameException} will be raised.
{@code maxChunkSize}The maximum length of the content or each chunk. If the content length + * exceeds this value, the transfer encoding of the decoded request will be + * converted to 'chunked' and the content will be split into multiple + * {@link HttpChunk}s. If the transfer encoding of the HTTP request is + * 'chunked' already, each chunk will be split into smaller chunks if the + * length of the chunk exceeds this value. If you prefer not to handle + * {@link HttpChunk}s in your handler, insert {@link HttpChunkAggregator} + * after this decoder in the {@link ChannelPipeline}.
+ * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @version $Rev$, $Date$ + */ +public class HttpRequestDecoder extends HttpMessageDecoder { + + /** + * Creates a new instance with the default + * {@code maxInitialLineLength (4096}}, {@code maxHeaderSize (8192)}, and + * {@code maxChunkSize (8192)}. + */ + public HttpRequestDecoder() { + super(); + } + + /** + * Creates a new instance with the specified parameters. + */ + public HttpRequestDecoder( + int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) { + super(maxInitialLineLength, maxHeaderSize, maxChunkSize); + } + + @Override + protected HttpMessage createMessage(String[] initialLine) throws Exception{ + return new DefaultHttpRequest( + HttpVersion.valueOf(initialLine[2]), HttpMethod.valueOf(initialLine[0]), initialLine[1]); + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpResponse.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpResponse.java new file mode 100644 index 0000000..5007634 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpResponse.java @@ -0,0 +1,47 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + + +/** + * An HTTP response. + * + *

Accessing Cookie

+ *

+ * Unlike the Servlet API, {@link Cookie} support is provided separately via + * {@link CookieEncoder} and {@link CookieDecoder}. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @version $Rev$, $Date$ + * + * @see HttpRequest + * @see CookieEncoder + * @see CookieDecoder + */ +public interface HttpResponse extends HttpMessage { + + /** + * Returns the status of this response. + */ + HttpResponseStatus getStatus(); + + /** + * Sets the status of this response. + */ + void setStatus(HttpResponseStatus status); +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpResponseEncoder.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpResponseEncoder.java new file mode 100644 index 0000000..b99eca5 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpResponseEncoder.java @@ -0,0 +1,51 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import static com.codefollower.douyu.http.HttpCodecUtil.*; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; + +/** + * Encodes an {@link HttpResponse} or an {@link HttpChunk} into + * a {@link ChannelBuffer}. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @version $Rev$, $Date$ + */ +public class HttpResponseEncoder extends HttpMessageEncoder { + + /** + * Creates a new instance. + */ + public HttpResponseEncoder() { + super(); + } + + @Override + protected void encodeInitialLine(ChannelBuffer buf, HttpMessage message) throws Exception { + HttpResponse response = (HttpResponse) message; + buf.writeBytes(response.getProtocolVersion().toString().getBytes("ASCII")); + buf.writeByte(SP); + buf.writeBytes(String.valueOf(response.getStatus().getCode()).getBytes("ASCII")); + buf.writeByte(SP); + buf.writeBytes(String.valueOf(response.getStatus().getReasonPhrase()).getBytes("ASCII")); + buf.writeByte(CR); + buf.writeByte(LF); + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpResponseStatus.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpResponseStatus.java new file mode 100644 index 0000000..5b0a043 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpResponseStatus.java @@ -0,0 +1,486 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +/** + * The response code and its description of HTTP or its derived protocols, such as + * RTSP and + * ICAP. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @version $Rev$, $Date$ + * + * @apiviz.exclude + */ +public class HttpResponseStatus implements Comparable { + + /** + * 100 Continue + */ + public static final HttpResponseStatus CONTINUE = new HttpResponseStatus(100, "Continue"); + + /** + * 101 Switching Protocols + */ + public static final HttpResponseStatus SWITCHING_PROTOCOLS = new HttpResponseStatus(101, "Switching Protocols"); + + /** + * 102 Processing (WebDAV, RFC2518) + */ + public static final HttpResponseStatus PROCESSING = new HttpResponseStatus(102, "Processing"); + + /** + * 200 OK + */ + public static final HttpResponseStatus OK = new HttpResponseStatus(200, "OK"); + + /** + * 201 Created + */ + public static final HttpResponseStatus CREATED = new HttpResponseStatus(201, "Created"); + + /** + * 202 Accepted + */ + public static final HttpResponseStatus ACCEPTED = new HttpResponseStatus(202, "Accepted"); + + /** + * 203 Non-Authoritative Information (since HTTP/1.1) + */ + public static final HttpResponseStatus NON_AUTHORITATIVE_INFORMATION = new HttpResponseStatus(203, "Non-Authoritative Information"); + + /** + * 204 No Content + */ + public static final HttpResponseStatus NO_CONTENT = new HttpResponseStatus(204, "No Content"); + + /** + * 205 Reset Content + */ + public static final HttpResponseStatus RESET_CONTENT = new HttpResponseStatus(205, "Reset Content"); + + /** + * 206 Partial Content + */ + public static final HttpResponseStatus PARTIAL_CONTENT = new HttpResponseStatus(206, "Partial Content"); + + /** + * 207 Multi-Status (WebDAV, RFC2518) + */ + public static final HttpResponseStatus MULTI_STATUS = new HttpResponseStatus(207, "Multi-Status"); + + /** + * 300 Multiple Choices + */ + public static final HttpResponseStatus MULTIPLE_CHOICES = new HttpResponseStatus(300, "Multiple Choices"); + + /** + * 301 Moved Permanently + */ + public static final HttpResponseStatus MOVED_PERMANENTLY = new HttpResponseStatus(301, "Moved Permanently"); + + /** + * 302 Found + */ + public static final HttpResponseStatus FOUND = new HttpResponseStatus(302, "Found"); + + /** + * 303 See Other (since HTTP/1.1) + */ + public static final HttpResponseStatus SEE_OTHER = new HttpResponseStatus(303, "See Other"); + + /** + * 304 Not Modified + */ + public static final HttpResponseStatus NOT_MODIFIED = new HttpResponseStatus(304, "Not Modified"); + + /** + * 305 Use Proxy (since HTTP/1.1) + */ + public static final HttpResponseStatus USE_PROXY = new HttpResponseStatus(305, "Use Proxy"); + + /** + * 307 Temporary Redirect (since HTTP/1.1) + */ + public static final HttpResponseStatus TEMPORARY_REDIRECT = new HttpResponseStatus(307, "Temporary Redirect"); + + /** + * 400 Bad Request + */ + public static final HttpResponseStatus BAD_REQUEST = new HttpResponseStatus(400, "Bad Request"); + + /** + * 401 Unauthorized + */ + public static final HttpResponseStatus UNAUTHORIZED = new HttpResponseStatus(401, "Unauthorized"); + + /** + * 402 Payment Required + */ + public static final HttpResponseStatus PAYMENT_REQUIRED = new HttpResponseStatus(402, "Payment Required"); + + /** + * 403 Forbidden + */ + public static final HttpResponseStatus FORBIDDEN = new HttpResponseStatus(403, "Forbidden"); + + /** + * 404 Not Found + */ + public static final HttpResponseStatus NOT_FOUND = new HttpResponseStatus(404, "Not Found"); + + /** + * 405 Method Not Allowed + */ + public static final HttpResponseStatus METHOD_NOT_ALLOWED = new HttpResponseStatus(405, "Method Not Allowed"); + + /** + * 406 Not Acceptable + */ + public static final HttpResponseStatus NOT_ACCEPTABLE = new HttpResponseStatus(406, "Not Acceptable"); + + /** + * 407 Proxy Authentication Required + */ + public static final HttpResponseStatus PROXY_AUTHENTICATION_REQUIRED = new HttpResponseStatus(407, "Proxy Authentication Required"); + + /** + * 408 Request Timeout + */ + public static final HttpResponseStatus REQUEST_TIMEOUT = new HttpResponseStatus(408, "Request Timeout"); + + /** + * 409 Conflict + */ + public static final HttpResponseStatus CONFLICT = new HttpResponseStatus(409, "Conflict"); + + /** + * 410 Gone + */ + public static final HttpResponseStatus GONE = new HttpResponseStatus(410, "Gone"); + + /** + * 411 Length Required + */ + public static final HttpResponseStatus LENGTH_REQUIRED = new HttpResponseStatus(411, "Length Required"); + + /** + * 412 Precondition Failed + */ + public static final HttpResponseStatus PRECONDITION_FAILED = new HttpResponseStatus(412, "Precondition Failed"); + + /** + * 413 Request Entity Too Large + */ + public static final HttpResponseStatus REQUEST_ENTITY_TOO_LARGE = new HttpResponseStatus(413, "Request Entity Too Large"); + + /** + * 414 Request-URI Too Long + */ + public static final HttpResponseStatus REQUEST_URI_TOO_LONG = new HttpResponseStatus(414, "Request-URI Too Long"); + + /** + * 415 Unsupported Media Type + */ + public static final HttpResponseStatus UNSUPPORTED_MEDIA_TYPE = new HttpResponseStatus(415, "Unsupported Media Type"); + + /** + * 416 Requested Range Not Satisfiable + */ + public static final HttpResponseStatus REQUESTED_RANGE_NOT_SATISFIABLE = new HttpResponseStatus(416, "Requested Range Not Satisfiable"); + + /** + * 417 Expectation Failed + */ + public static final HttpResponseStatus EXPECTATION_FAILED = new HttpResponseStatus(417, "Expectation Failed"); + + /** + * 422 Unprocessable Entity (WebDAV, RFC4918) + */ + public static final HttpResponseStatus UNPROCESSABLE_ENTITY = new HttpResponseStatus(422, "Unprocessable Entity"); + + /** + * 423 Locked (WebDAV, RFC4918) + */ + public static final HttpResponseStatus LOCKED = new HttpResponseStatus(423, "Locked"); + + /** + * 424 Failed Dependency (WebDAV, RFC4918) + */ + public static final HttpResponseStatus FAILED_DEPENDENCY = new HttpResponseStatus(424, "Failed Dependency"); + + /** + * 425 Unordered Collection (WebDAV, RFC3648) + */ + public static final HttpResponseStatus UNORDERED_COLLECTION = new HttpResponseStatus(425, "Unordered Collection"); + + /** + * 426 Upgrade Required (RFC2817) + */ + public static final HttpResponseStatus UPGRADE_REQUIRED = new HttpResponseStatus(426, "Upgrade Required"); + + /** + * 500 Internal Server Error + */ + public static final HttpResponseStatus INTERNAL_SERVER_ERROR = new HttpResponseStatus(500, "Internal Server Error"); + + /** + * 501 Not Implemented + */ + public static final HttpResponseStatus NOT_IMPLEMENTED = new HttpResponseStatus(501, "Not Implemented"); + + /** + * 502 Bad Gateway + */ + public static final HttpResponseStatus BAD_GATEWAY = new HttpResponseStatus(502, "Bad Gateway"); + + /** + * 503 Service Unavailable + */ + public static final HttpResponseStatus SERVICE_UNAVAILABLE = new HttpResponseStatus(503, "Service Unavailable"); + + /** + * 504 Gateway Timeout + */ + public static final HttpResponseStatus GATEWAY_TIMEOUT = new HttpResponseStatus(504, "Gateway Timeout"); + + /** + * 505 HTTP Version Not Supported + */ + public static final HttpResponseStatus HTTP_VERSION_NOT_SUPPORTED = new HttpResponseStatus(505, "HTTP Version Not Supported"); + + /** + * 506 Variant Also Negotiates (RFC2295) + */ + public static final HttpResponseStatus VARIANT_ALSO_NEGOTIATES = new HttpResponseStatus(506, "Variant Also Negotiates"); + + /** + * 507 Insufficient Storage (WebDAV, RFC4918) + */ + public static final HttpResponseStatus INSUFFICIENT_STORAGE = new HttpResponseStatus(507, "Insufficient Storage"); + + /** + * 510 Not Extended (RFC2774) + */ + public static final HttpResponseStatus NOT_EXTENDED = new HttpResponseStatus(510, "Not Extended"); + + /** + * Returns the {@link HttpResponseStatus} represented by the specified code. + * If the specified code is a standard HTTP status code, a cached instance + * will be returned. Otherwise, a new instance will be returned. + */ + public static HttpResponseStatus valueOf(int code) { + switch (code) { + case 100: + return CONTINUE; + case 101: + return SWITCHING_PROTOCOLS; + case 102: + return PROCESSING; + case 200: + return OK; + case 201: + return CREATED; + case 202: + return ACCEPTED; + case 203: + return NON_AUTHORITATIVE_INFORMATION; + case 204: + return NO_CONTENT; + case 205: + return RESET_CONTENT; + case 206: + return PARTIAL_CONTENT; + case 207: + return MULTI_STATUS; + case 300: + return MULTIPLE_CHOICES; + case 301: + return MOVED_PERMANENTLY; + case 302: + return FOUND; + case 303: + return SEE_OTHER; + case 304: + return NOT_MODIFIED; + case 305: + return USE_PROXY; + case 307: + return TEMPORARY_REDIRECT; + case 400: + return BAD_REQUEST; + case 401: + return UNAUTHORIZED; + case 402: + return PAYMENT_REQUIRED; + case 403: + return FORBIDDEN; + case 404: + return NOT_FOUND; + case 405: + return METHOD_NOT_ALLOWED; + case 406: + return NOT_ACCEPTABLE; + case 407: + return PROXY_AUTHENTICATION_REQUIRED; + case 408: + return REQUEST_TIMEOUT; + case 409: + return CONFLICT; + case 410: + return GONE; + case 411: + return LENGTH_REQUIRED; + case 412: + return PRECONDITION_FAILED; + case 413: + return REQUEST_ENTITY_TOO_LARGE; + case 414: + return REQUEST_URI_TOO_LONG; + case 415: + return UNSUPPORTED_MEDIA_TYPE; + case 416: + return REQUESTED_RANGE_NOT_SATISFIABLE; + case 417: + return EXPECTATION_FAILED; + case 422: + return UNPROCESSABLE_ENTITY; + case 423: + return LOCKED; + case 424: + return FAILED_DEPENDENCY; + case 425: + return UNORDERED_COLLECTION; + case 426: + return UPGRADE_REQUIRED; + case 500: + return INTERNAL_SERVER_ERROR; + case 501: + return NOT_IMPLEMENTED; + case 502: + return BAD_GATEWAY; + case 503: + return SERVICE_UNAVAILABLE; + case 504: + return GATEWAY_TIMEOUT; + case 505: + return HTTP_VERSION_NOT_SUPPORTED; + case 506: + return VARIANT_ALSO_NEGOTIATES; + case 507: + return INSUFFICIENT_STORAGE; + case 510: + return NOT_EXTENDED; + } + + final String reasonPhrase; + + if (code < 100) { + reasonPhrase = "Unknown Status"; + } else if (code < 200) { + reasonPhrase = "Informational"; + } else if (code < 300) { + reasonPhrase = "Successful"; + } else if (code < 400) { + reasonPhrase = "Redirection"; + } else if (code < 500) { + reasonPhrase = "Client Error"; + } else if (code < 600) { + reasonPhrase = "Server Error"; + } else { + reasonPhrase = "Unknown Status"; + } + + return new HttpResponseStatus(code, reasonPhrase + " (" + code + ')'); + } + + private final int code; + + private final String reasonPhrase; + + /** + * Creates a new instance with the specified {@code code} and its + * {@code reasonPhrase}. + */ + public HttpResponseStatus(int code, String reasonPhrase) { + if (code < 0) { + throw new IllegalArgumentException( + "code: " + code + " (expected: 0+)"); + } + + if (reasonPhrase == null) { + throw new NullPointerException("reasonPhrase"); + } + + for (int i = 0; i < reasonPhrase.length(); i ++) { + char c = reasonPhrase.charAt(i); + // Check prohibited characters. + switch (c) { + case '\n': case '\r': + throw new IllegalArgumentException( + "reasonPhrase contains one of the following prohibited characters: " + + "\\r\\n: " + reasonPhrase); + } + } + + this.code = code; + this.reasonPhrase = reasonPhrase; + } + + /** + * Returns the code of this status. + */ + public int getCode() { + return code; + } + + /** + * Returns the reason phrase of this status. + */ + public String getReasonPhrase() { + return reasonPhrase; + } + + @Override + public int hashCode() { + return getCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof HttpResponseStatus)) { + return false; + } + + return getCode() == ((HttpResponseStatus) o).getCode(); + } + + @Override + public int compareTo(HttpResponseStatus o) { + return getCode() - o.getCode(); + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(reasonPhrase.length() + 5); + buf.append(code); + buf.append(' '); + buf.append(reasonPhrase); + return buf.toString(); + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpServerCodec.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpServerCodec.java new file mode 100644 index 0000000..76e6dc0 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpServerCodec.java @@ -0,0 +1,70 @@ +/* + * Copyright 2010 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import com.codefollower.douyu.netty.channel.ChannelDownstreamHandler; +import com.codefollower.douyu.netty.channel.ChannelEvent; +import com.codefollower.douyu.netty.channel.ChannelHandlerContext; +import com.codefollower.douyu.netty.channel.ChannelUpstreamHandler; + +/** + * A combination of {@link HttpRequestDecoder} and {@link HttpResponseEncoder} + * which enables easier server side HTTP implementation. + * + * @author The Netty Project + * @author Trustin Lee + * @version $Rev$, $Date$ + * + * @see HttpClientCodec + * + * @apiviz.has org.jboss.netty.handler.codec.http.HttpRequestDecoder + * @apiviz.has org.jboss.netty.handler.codec.http.HttpResponseEncoder + */ +public class HttpServerCodec implements ChannelUpstreamHandler, + ChannelDownstreamHandler { + + private final HttpRequestDecoder decoder; + private final HttpResponseEncoder encoder = new HttpResponseEncoder(); + + /** + * Creates a new instance with the default decoder options + * ({@code maxInitialLineLength (4096}}, {@code maxHeaderSize (8192)}, and + * {@code maxChunkSize (8192)}). + */ + public HttpServerCodec() { + this(4096, 8192, 8192); + } + + /** + * Creates a new instance with the specified decoder options. + */ + public HttpServerCodec( + int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) { + decoder = new HttpRequestDecoder(maxInitialLineLength, maxHeaderSize, maxChunkSize); + } + + @Override + public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) + throws Exception { + decoder.handleUpstream(ctx, e); + } + + @Override + public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent e) + throws Exception { + encoder.handleDownstream(ctx, e); + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/HttpVersion.java b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpVersion.java new file mode 100644 index 0000000..9f5e399 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/HttpVersion.java @@ -0,0 +1,230 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * The version of HTTP or its derived protocols, such as + * RTSP and + * ICAP. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @version $Rev$, $Date$ + * + * @apiviz.exclude + */ +public class HttpVersion implements Comparable { + + private static final Pattern VERSION_PATTERN = + Pattern.compile("(\\S+)/(\\d+)\\.(\\d+)"); + + /** + * HTTP/1.0 + */ + public static final HttpVersion HTTP_1_0 = new HttpVersion("HTTP", 1, 0, false); + + /** + * HTTP/1.1 + */ + public static final HttpVersion HTTP_1_1 = new HttpVersion("HTTP", 1, 1, true); + + /** + * Returns an existing or new {@link HttpVersion} instance which matches to + * the specified protocol version string. If the specified {@code text} is + * equal to {@code "HTTP/1.0"}, {@link #HTTP_1_0} will be returned. If the + * specified {@code text} is equal to {@code "HTTP/1.1"}, {@link #HTTP_1_1} + * will be returned. Otherwise, a new {@link HttpVersion} instance will be + * returned. + */ + public static HttpVersion valueOf(String text) { + if (text == null) { + throw new NullPointerException("text"); + } + + text = text.trim().toUpperCase(); + if (text.equals("HTTP/1.1")) { + return HTTP_1_1; + } + if (text.equals("HTTP/1.0")) { + return HTTP_1_0; + } + return new HttpVersion(text, true); + } + + private final String protocolName; + private final int majorVersion; + private final int minorVersion; + private final String text; + private final boolean keepAliveDefault; + + /** + * Creates a new HTTP version with the specified version string. You will + * not need to create a new instance unless you are implementing a protocol + * derived from HTTP, such as + * RTSP and + * ICAP. + * + * @param keepAliveDefault + * {@code true} if and only if the connection is kept alive unless + * the {@code "Connection"} header is set to {@code "close"} explicitly. + */ + public HttpVersion(String text, boolean keepAliveDefault) { + if (text == null) { + throw new NullPointerException("text"); + } + + text = text.trim().toUpperCase(); + if (text.length() == 0) { + throw new IllegalArgumentException("empty text"); + } + + Matcher m = VERSION_PATTERN.matcher(text); + if (!m.matches()) { + throw new IllegalArgumentException("invalid version format: " + text); + } + + protocolName = m.group(1); + majorVersion = Integer.parseInt(m.group(2)); + minorVersion = Integer.parseInt(m.group(3)); + this.text = protocolName + '/' + majorVersion + '.' + minorVersion; + this.keepAliveDefault = keepAliveDefault; + } + + /** + * Creates a new HTTP version with the specified protocol name and version + * numbers. You will not need to create a new instance unless you are + * implementing a protocol derived from HTTP, such as + * RTSP and + * ICAP + * + * @param keepAliveDefault + * {@code true} if and only if the connection is kept alive unless + * the {@code "Connection"} header is set to {@code "close"} explicitly. + */ + public HttpVersion( + String protocolName, int majorVersion, int minorVersion, + boolean keepAliveDefault) { + if (protocolName == null) { + throw new NullPointerException("protocolName"); + } + + protocolName = protocolName.trim().toUpperCase(); + if (protocolName.length() == 0) { + throw new IllegalArgumentException("empty protocolName"); + } + + for (int i = 0; i < protocolName.length(); i ++) { + if (Character.isISOControl(protocolName.charAt(i)) || + Character.isWhitespace(protocolName.charAt(i))) { + throw new IllegalArgumentException("invalid character in protocolName"); + } + } + + if (majorVersion < 0) { + throw new IllegalArgumentException("negative majorVersion"); + } + if (minorVersion < 0) { + throw new IllegalArgumentException("negative minorVersion"); + } + + this.protocolName = protocolName; + this.majorVersion = majorVersion; + this.minorVersion = minorVersion; + text = protocolName + '/' + majorVersion + '.' + minorVersion; + this.keepAliveDefault = keepAliveDefault; + } + + /** + * Returns the name of the protocol such as {@code "HTTP"} in {@code "HTTP/1.0"}. + */ + public String getProtocolName() { + return protocolName; + } + + /** + * Returns the name of the protocol such as {@code 1} in {@code "HTTP/1.0"}. + */ + public int getMajorVersion() { + return majorVersion; + } + + /** + * Returns the name of the protocol such as {@code 0} in {@code "HTTP/1.0"}. + */ + public int getMinorVersion() { + return minorVersion; + } + + /** + * Returns the full protocol version text such as {@code "HTTP/1.0"}. + */ + public String getText() { + return text; + } + + /** + * Returns {@code true} if and only if the connection is kept alive unless + * the {@code "Connection"} header is set to {@code "close"} explicitly. + */ + public boolean isKeepAliveDefault() { + return keepAliveDefault; + } + + /** + * Returns the full protocol version text such as {@code "HTTP/1.0"}. + */ + @Override + public String toString() { + return getText(); + } + + @Override + public int hashCode() { + return (getProtocolName().hashCode() * 31 + getMajorVersion()) * 31 + + getMinorVersion(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof HttpVersion)) { + return false; + } + + HttpVersion that = (HttpVersion) o; + return getMinorVersion() == that.getMinorVersion() && + getMajorVersion() == that.getMajorVersion() && + getProtocolName().equals(that.getProtocolName()); + } + + @Override + public int compareTo(HttpVersion o) { + int v = getProtocolName().compareTo(o.getProtocolName()); + if (v != 0) { + return v; + } + + v = getMajorVersion() - o.getMajorVersion(); + if (v != 0) { + return v; + } + + return getMinorVersion() - o.getMinorVersion(); + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/InterfaceHttpData.java b/douyu-http/src/main/java/com/codefollower/douyu/http/InterfaceHttpData.java new file mode 100644 index 0000000..bc025ea --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/InterfaceHttpData.java @@ -0,0 +1,41 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +/** + * Interface for all Objects that could be encoded/decoded using HttpPostRequestEncoder/Decoder + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @author Frederic Bregier + * + */ +public interface InterfaceHttpData extends Comparable { + public static enum HttpDataType { + Attribute, FileUpload, InternalAttribute; + } + + /** + * Returns the name of this InterfaceHttpData. + */ + String getName(); + + /** + * + * @return The HttpDataType + */ + HttpDataType getHttpDataType(); +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/InternalAttribute.java b/douyu-http/src/main/java/com/codefollower/douyu/http/InternalAttribute.java new file mode 100644 index 0000000..fc290bf --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/InternalAttribute.java @@ -0,0 +1,111 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.util.ArrayList; +import java.util.List; + +/** + * This Attribute is only for Encoder use to insert special command between object if needed + * (like Multipart Mixed mode) + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @author Frederic Bregier + * + */ +public class InternalAttribute implements InterfaceHttpData { + protected List value = new ArrayList(); + + @Override + public HttpDataType getHttpDataType() { + return HttpDataType.InternalAttribute; + } + + public List getValue() { + return value; + } + + public void addValue(String value) { + if (value == null) { + throw new NullPointerException("value"); + } + this.value.add(value); + } + + public void addValue(String value, int rank) { + if (value == null) { + throw new NullPointerException("value"); + } + this.value.add(rank, value); + } + + public void setValue(String value, int rank) { + if (value == null) { + throw new NullPointerException("value"); + } + this.value.set(rank, value); + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Attribute)) { + return false; + } + Attribute attribute = (Attribute) o; + return getName().equalsIgnoreCase(attribute.getName()); + } + + @Override + public int compareTo(InterfaceHttpData arg0) { + if (!(arg0 instanceof InternalAttribute)) { + throw new ClassCastException("Cannot compare " + getHttpDataType() + + " with " + arg0.getHttpDataType()); + } + return compareTo((InternalAttribute) arg0); + } + + public int compareTo(InternalAttribute o) { + return getName().compareToIgnoreCase(o.getName()); + } + + public int size() { + int size = 0; + for (String elt : value) { + size += elt.length(); + } + return size; + } + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + for (String elt : value) { + result.append(elt); + } + return result.toString(); + } + + @Override + public String getName() { + return "InternalAttribute"; + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/MemoryAttribute.java b/douyu-http/src/main/java/com/codefollower/douyu/http/MemoryAttribute.java new file mode 100644 index 0000000..3ee110c --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/MemoryAttribute.java @@ -0,0 +1,114 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.io.IOException; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; + +/** + * Memory implementation of Attributes + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @author Frederic Bregier + * + */ +public class MemoryAttribute extends AbstractMemoryHttpData implements Attribute { + + public MemoryAttribute(String name) { + super(name, HttpCodecUtil.DEFAULT_CHARSET, 0); + } + /** + * + * @param name + * @param value + * @throws NullPointerException + * @throws IllegalArgumentException + * @throws IOException + */ + public MemoryAttribute(String name, String value) + throws NullPointerException, IllegalArgumentException, IOException { + super(name, HttpCodecUtil.DEFAULT_CHARSET, 0); // Attribute have no default size + setValue(value); + } + + @Override + public HttpDataType getHttpDataType() { + return HttpDataType.Attribute; + } + + @Override + public String getValue() { + return getChannelBuffer().toString(charset); + } + + @Override + public void setValue(String value) throws IOException { + if (value == null) { + throw new NullPointerException("value"); + } + byte [] bytes = value.getBytes(charset); + ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(bytes); + if (definedSize > 0) { + definedSize = buffer.readableBytes(); + } + setContent(buffer); + } + + @Override + public void addContent(ChannelBuffer buffer, boolean last) throws IOException { + int localsize = buffer.readableBytes(); + if (definedSize > 0 && definedSize < size + localsize) { + definedSize = size + localsize; + } + super.addContent(buffer, last); + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Attribute)) { + return false; + } + Attribute attribute = (Attribute) o; + return getName().equalsIgnoreCase(attribute.getName()); + } + + @Override + public int compareTo(InterfaceHttpData arg0) { + if (!(arg0 instanceof Attribute)) { + throw new ClassCastException("Cannot compare " + getHttpDataType() + + " with " + arg0.getHttpDataType()); + } + return compareTo((Attribute) arg0); + } + + public int compareTo(Attribute o) { + return getName().compareToIgnoreCase(o.getName()); + } + + @Override + public String toString() { + return getName() + "=" + getValue(); + } + +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/MemoryFileUpload.java b/douyu-http/src/main/java/com/codefollower/douyu/http/MemoryFileUpload.java new file mode 100644 index 0000000..1362a9a --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/MemoryFileUpload.java @@ -0,0 +1,133 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.nio.charset.Charset; + +/** + * Default FileUpload implementation that stores file into memory.

+ * + * Warning: be aware of the memory limitation. + * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @author Frederic Bregier + * + */ +public class MemoryFileUpload extends AbstractMemoryHttpData implements FileUpload { + + private String filename = null; + + private String contentType = null; + + private String contentTransferEncoding = null; + + public MemoryFileUpload(String name, String filename, String contentType, + String contentTransferEncoding, Charset charset, long size) + throws NullPointerException, IllegalArgumentException { + super(name, charset, size); + setFilename(filename); + setContentType(contentType); + setContentTransferEncoding(contentTransferEncoding); + } + + @Override + public HttpDataType getHttpDataType() { + return HttpDataType.FileUpload; + } + + @Override + public String getFilename() { + return filename; + } + + @Override + public void setFilename(String filename) { + if (filename == null) { + throw new NullPointerException("filename"); + } + this.filename = filename; + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Attribute)) { + return false; + } + Attribute attribute = (Attribute) o; + return getName().equalsIgnoreCase(attribute.getName()); + } + + @Override + public int compareTo(InterfaceHttpData arg0) { + if (!(arg0 instanceof FileUpload)) { + throw new ClassCastException("Cannot compare " + getHttpDataType() + + " with " + arg0.getHttpDataType()); + } + return compareTo((FileUpload) arg0); + } + + public int compareTo(FileUpload o) { + int v; + v = getName().compareToIgnoreCase(o.getName()); + if (v != 0) { + return v; + } + // TODO should we compare size for instance ? + return v; + } + + @Override + public void setContentType(String contentType) { + if (contentType == null) { + throw new NullPointerException("contentType"); + } + this.contentType = contentType; + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public String getContentTransferEncoding() { + return contentTransferEncoding; + } + + @Override + public void setContentTransferEncoding(String contentTransferEncoding) { + this.contentTransferEncoding = contentTransferEncoding; + } + + @Override + public String toString() { + return HttpPostBodyUtil.CONTENT_DISPOSITION+": "+ + HttpPostBodyUtil.FORM_DATA+"; "+HttpPostBodyUtil.NAME+"=\"" + getName() + + "\"; "+HttpPostBodyUtil.FILENAME+"=\"" + filename + "\"\r\n" + + HttpHeaders.Names.CONTENT_TYPE+": " + contentType + + (charset != null? "; "+HttpHeaders.Values.CHARSET+"=" + charset + "\r\n" : "\r\n") + + HttpHeaders.Names.CONTENT_LENGTH+": " + length() + "\r\n" + + "Completed: " + isCompleted() + + "\r\nIsInMemory: " + isInMemory(); + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/MixedAttribute.java b/douyu-http/src/main/java/com/codefollower/douyu/http/MixedAttribute.java new file mode 100644 index 0000000..58d2bbc --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/MixedAttribute.java @@ -0,0 +1,216 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; + +/** + * Mixed implementation using both in Memory and in File with a limit of size + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @author Frederic Bregier + * + */ +public class MixedAttribute implements Attribute { + private Attribute attribute = null; + + private long limitSize = 0; + + public MixedAttribute(String name, + long limitSize) throws NullPointerException, + IllegalArgumentException { + this.limitSize = limitSize; + attribute = new MemoryAttribute(name); + } + + public MixedAttribute(String name, String value, + long limitSize) throws NullPointerException, + IllegalArgumentException { + this.limitSize = limitSize; + if (value.length() > this.limitSize) { + try { + attribute = new DiskAttribute(name, value); + } catch (IOException e) { + // revert to Memory mode + try { + attribute = new MemoryAttribute(name, value); + } catch (IOException e1) { + throw new IllegalArgumentException(e); + } + } + } else { + try { + attribute = new MemoryAttribute(name, value); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + } + + @Override + public void addContent(ChannelBuffer buffer, boolean last) + throws IOException { + if (attribute instanceof MemoryAttribute) { + if (attribute.length() + buffer.readableBytes() > limitSize) { + DiskAttribute diskAttribute = new DiskAttribute(attribute + .getName()); + diskAttribute.addContent(((MemoryAttribute) attribute) + .getChannelBuffer(), last); + attribute = diskAttribute; + } + } + attribute.addContent(buffer, last); + } + + @Override + public void delete() { + attribute.delete(); + } + + @Override + public byte[] get() throws IOException { + return attribute.get(); + } + + @Override + public ChannelBuffer getChannelBuffer() throws IOException { + return attribute.getChannelBuffer(); + } + + @Override + public Charset getCharset() { + return attribute.getCharset(); + } + + @Override + public String getString() throws IOException { + return attribute.getString(); + } + + @Override + public String getString(Charset encoding) throws IOException { + return attribute.getString(encoding); + } + + @Override + public boolean isCompleted() { + return attribute.isCompleted(); + } + + @Override + public boolean isInMemory() { + return attribute.isInMemory(); + } + + @Override + public long length() { + return attribute.length(); + } + + @Override + public boolean renameTo(File dest) throws IOException { + return attribute.renameTo(dest); + } + + @Override + public void setCharset(Charset charset) { + attribute.setCharset(charset); + } + + @Override + public void setContent(ChannelBuffer buffer) throws IOException { + if (buffer.readableBytes() > limitSize) { + if (attribute instanceof MemoryAttribute) { + // change to Disk + DiskAttribute diskAttribute = new DiskAttribute(attribute + .getName()); + attribute = diskAttribute; + } + } + attribute.setContent(buffer); + } + + @Override + public void setContent(File file) throws IOException { + if (file.length() > limitSize) { + if (attribute instanceof MemoryAttribute) { + // change to Disk + DiskAttribute diskAttribute = new DiskAttribute(attribute + .getName()); + attribute = diskAttribute; + } + } + attribute.setContent(file); + } + + @Override + public void setContent(InputStream inputStream) throws IOException { + if (attribute instanceof MemoryAttribute) { + // change to Disk even if we don't know the size + DiskAttribute diskAttribute = new DiskAttribute(attribute + .getName()); + attribute = diskAttribute; + } + attribute.setContent(inputStream); + } + + @Override + public HttpDataType getHttpDataType() { + return attribute.getHttpDataType(); + } + + @Override + public String getName() { + return attribute.getName(); + } + + @Override + public int compareTo(InterfaceHttpData o) { + return attribute.compareTo(o); + } + + @Override + public String toString() { + return "Mixed: " + attribute.toString(); + } + + @Override + public String getValue() throws IOException { + return attribute.getValue(); + } + + @Override + public void setValue(String value) throws IOException { + attribute.setValue(value); + } + + @Override + public ChannelBuffer getChunk(int length) throws IOException { + return attribute.getChunk(length); + } + + @Override + public File getFile() throws IOException { + return attribute.getFile(); + } + +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/MixedFileUpload.java b/douyu-http/src/main/java/com/codefollower/douyu/http/MixedFileUpload.java new file mode 100644 index 0000000..e26ac05 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/MixedFileUpload.java @@ -0,0 +1,234 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; + +/** + * Mixed implementation using both in Memory and in File with a limit of size + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @author Frederic Bregier + * + */ +public class MixedFileUpload implements FileUpload { + private FileUpload fileUpload = null; + + private long limitSize = 0; + + private long definedSize = 0; + + public MixedFileUpload(String name, String filename, String contentType, + String contentTransferEncoding, Charset charset, long size, + long limitSize) throws NullPointerException, + IllegalArgumentException { + this.limitSize = limitSize; + if (size > this.limitSize) { + fileUpload = new DiskFileUpload(name, filename, contentType, + contentTransferEncoding, charset, size); + } else { + fileUpload = new MemoryFileUpload(name, filename, contentType, + contentTransferEncoding, charset, size); + } + definedSize = size; + } + + @Override + public void addContent(ChannelBuffer buffer, boolean last) + throws IOException { + if (fileUpload instanceof MemoryFileUpload) { + if (fileUpload.length() + buffer.readableBytes() > limitSize) { + DiskFileUpload diskFileUpload = new DiskFileUpload(fileUpload + .getName(), fileUpload.getFilename(), fileUpload + .getContentType(), fileUpload + .getContentTransferEncoding(), fileUpload.getCharset(), + definedSize); + diskFileUpload.addContent(((MemoryFileUpload) fileUpload) + .getChannelBuffer(), last); + fileUpload = diskFileUpload; + } + } + fileUpload.addContent(buffer, last); + } + + @Override + public void delete() { + fileUpload.delete(); + } + + @Override + public byte[] get() throws IOException { + return fileUpload.get(); + } + + @Override + public ChannelBuffer getChannelBuffer() throws IOException { + return fileUpload.getChannelBuffer(); + } + + @Override + public Charset getCharset() { + return fileUpload.getCharset(); + } + + @Override + public String getContentType() { + return fileUpload.getContentType(); + } + + @Override + public String getContentTransferEncoding() { + return fileUpload.getContentTransferEncoding(); + } + + @Override + public String getFilename() { + return fileUpload.getFilename(); + } + + @Override + public String getString() throws IOException { + return fileUpload.getString(); + } + + @Override + public String getString(Charset encoding) throws IOException { + return fileUpload.getString(encoding); + } + + @Override + public boolean isCompleted() { + return fileUpload.isCompleted(); + } + + @Override + public boolean isInMemory() { + return fileUpload.isInMemory(); + } + + @Override + public long length() { + return fileUpload.length(); + } + + @Override + public boolean renameTo(File dest) throws IOException { + return fileUpload.renameTo(dest); + } + + @Override + public void setCharset(Charset charset) { + fileUpload.setCharset(charset); + } + + @Override + public void setContent(ChannelBuffer buffer) throws IOException { + if (buffer.readableBytes() > limitSize) { + if (fileUpload instanceof MemoryFileUpload) { + // change to Disk + DiskFileUpload diskFileUpload = new DiskFileUpload(fileUpload + .getName(), fileUpload.getFilename(), fileUpload + .getContentType(), fileUpload + .getContentTransferEncoding(), fileUpload.getCharset(), + definedSize); + fileUpload = diskFileUpload; + } + } + fileUpload.setContent(buffer); + } + + @Override + public void setContent(File file) throws IOException { + if (file.length() > limitSize) { + if (fileUpload instanceof MemoryFileUpload) { + // change to Disk + DiskFileUpload diskFileUpload = new DiskFileUpload(fileUpload + .getName(), fileUpload.getFilename(), fileUpload + .getContentType(), fileUpload + .getContentTransferEncoding(), fileUpload.getCharset(), + definedSize); + fileUpload = diskFileUpload; + } + } + fileUpload.setContent(file); + } + + @Override + public void setContent(InputStream inputStream) throws IOException { + if (fileUpload instanceof MemoryFileUpload) { + // change to Disk + DiskFileUpload diskFileUpload = new DiskFileUpload(fileUpload + .getName(), fileUpload.getFilename(), fileUpload + .getContentType(), fileUpload + .getContentTransferEncoding(), fileUpload.getCharset(), + definedSize); + fileUpload = diskFileUpload; + } + fileUpload.setContent(inputStream); + } + + @Override + public void setContentType(String contentType) { + fileUpload.setContentType(contentType); + } + + @Override + public void setContentTransferEncoding(String contentTransferEncoding) { + fileUpload.setContentTransferEncoding(contentTransferEncoding); + } + + @Override + public void setFilename(String filename) { + fileUpload.setFilename(filename); + } + + @Override + public HttpDataType getHttpDataType() { + return fileUpload.getHttpDataType(); + } + + @Override + public String getName() { + return fileUpload.getName(); + } + + @Override + public int compareTo(InterfaceHttpData o) { + return fileUpload.compareTo(o); + } + + @Override + public String toString() { + return "Mixed: " + fileUpload.toString(); + } + + @Override + public ChannelBuffer getChunk(int length) throws IOException { + return fileUpload.getChunk(length); + } + + @Override + public File getFile() throws IOException { + return fileUpload.getFile(); + } + +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/QueryStringDecoder.java b/douyu-http/src/main/java/com/codefollower/douyu/http/QueryStringDecoder.java new file mode 100644 index 0000000..809434f --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/QueryStringDecoder.java @@ -0,0 +1,299 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.codefollower.douyu.netty.util.CharsetUtil; + +/** + * Splits an HTTP query string into a path string and key-value parameter pairs. + * This decoder is for one time use only. Create a new instance for each URI: + *

+ * {@link QueryStringDecoder} decoder = new {@link QueryStringDecoder}("/hello?recipient=world");
+ * assert decoder.getPath().equals("/hello");
+ * assert decoder.getParameters().get("recipient").equals("world");
+ * 
+ * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @author Benoit Sigoure + * @version $Rev$, $Date$ + * + * @see QueryStringEncoder + * + * @apiviz.stereotype utility + * @apiviz.has org.jboss.netty.handler.codec.http.HttpRequest oneway - - decodes + */ +public class QueryStringDecoder { + + private final Charset charset; + private final String uri; + private String path; + private Map> params; + + /** + * Creates a new decoder that decodes the specified URI. The decoder will + * assume that the query string is encoded in UTF-8. + */ + public QueryStringDecoder(String uri) { + this(uri, HttpCodecUtil.DEFAULT_CHARSET); + } + + /** + * Creates a new decoder that decodes the specified URI encoded in the + * specified charset. + */ + public QueryStringDecoder(String uri, Charset charset) { + if (uri == null) { + throw new NullPointerException("uri"); + } + if (charset == null) { + throw new NullPointerException("charset"); + } + + this.uri = uri; + this.charset = charset; + } + + /** + * Creates a new decoder that decodes the specified URI. The decoder will + * assume that the query string is encoded in UTF-8. + */ + public QueryStringDecoder(URI uri) { + this(uri, HttpCodecUtil.DEFAULT_CHARSET); + } + + /** + * Creates a new decoder that decodes the specified URI encoded in the + * specified charset. + */ + public QueryStringDecoder(URI uri, Charset charset){ + if (uri == null) { + throw new NullPointerException("uri"); + } + if (charset == null) { + throw new NullPointerException("charset"); + } + + this.uri = uri.toASCIIString(); + this.charset = charset; + } + + /** + * Returns the decoded path string of the URI. + */ + public String getPath() { + if (path == null) { + int pathEndPos = uri.indexOf('?'); + if (pathEndPos < 0) { + path = uri; + } + else { + return path = uri.substring(0, pathEndPos); + } + } + return path; + } + + /** + * Returns the decoded key-value parameter pairs of the URI. + */ + public Map> getParameters() { + if (params == null) { + int pathLength = getPath().length(); + if (uri.length() == pathLength) { + return Collections.emptyMap(); + } + params = decodeParams(uri.substring(pathLength + 1)); + } + return params; + } + + private Map> decodeParams(String s) { + Map> params = new LinkedHashMap>(); + String name = null; + int pos = 0; // Beginning of the unprocessed region + int i; // End of the unprocessed region + char c = 0; // Current character + for (i = 0; i < s.length(); i++) { + c = s.charAt(i); + if (c == '=' && name == null) { + if (pos != i) { + name = decodeComponent(s.substring(pos, i), charset); + } + pos = i + 1; + } else if (c == '&') { + if (name == null && pos != i) { + // We haven't seen an `=' so far but moved forward. + // Must be a param of the form '&a&' so add it with + // an empty value. + addParam(params, decodeComponent(s.substring(pos, i), charset), ""); + } else if (name != null) { + addParam(params, name, decodeComponent(s.substring(pos, i), charset)); + name = null; + } + pos = i + 1; + } + } + + if (pos != i) { // Are there characters we haven't dealt with? + if (name == null) { // Yes and we haven't seen any `='. + addParam(params, decodeComponent(s.substring(pos, i), charset), ""); + } else { // Yes and this must be the last value. + addParam(params, name, decodeComponent(s.substring(pos, i), charset)); + } + } else if (name != null) { // Have we seen a name without value? + addParam(params, name, ""); + } + + return params; + } + + /** + * Decodes a bit of an URL encoded by a browser. + *

+ * This is equivalent to calling {@link #decodeComponent(String, Charset)} + * with the UTF-8 charset (recommended to comply with RFC 3986, Section 2). + * @param s The string to decode (can be empty). + * @return The decoded string, or {@code s} if there's nothing to decode. + * If the string to decode is {@code null}, returns an empty string. + * @throws IllegalArgumentException if the string contains a malformed + * escape sequence. + */ + public static String decodeComponent(final String s) { + return decodeComponent(s, HttpCodecUtil.DEFAULT_CHARSET); + } + + /** + * Decodes a bit of an URL encoded by a browser. + *

+ * The string is expected to be encoded as per RFC 3986, Section 2. + * This is the encoding used by JavaScript functions {@code encodeURI} + * and {@code encodeURIComponent}, but not {@code escape}. For example + * in this encoding, é (in Unicode {@code U+00E9} or in UTF-8 + * {@code 0xC3 0xA9}) is encoded as {@code %C3%A9} or {@code %c3%a9}. + *

+ * This is essentially equivalent to calling + * {@link URLDecoder#decode(String, String) URLDecoder.decode}(s, charset.name()) + * except that it's over 2x faster and generates less garbage for the GC. + * Actually this function doesn't allocate any memory if there's nothing + * to decode, the argument itself is returned. + * @param s The string to decode (can be empty). + * @param charset The charset to use to decode the string (should really + * be {@link CharsetUtil#UTF_8}. + * @return The decoded string, or {@code s} if there's nothing to decode. + * If the string to decode is {@code null}, returns an empty string. + * @throws IllegalArgumentException if the string contains a malformed + * escape sequence. + */ + @SuppressWarnings("fallthrough") + public static String decodeComponent(final String s, + final Charset charset) { + if (s == null) { + return ""; + } + final int size = s.length(); + boolean modified = false; + for (int i = 0; i < size; i++) { + final char c = s.charAt(i); + switch (c) { + case '%': + i++; // We can skip at least one char, e.g. `%%'. + // Fall through. + case '+': + modified = true; + break; + } + } + if (!modified) { + return s; + } + final byte[] buf = new byte[size]; + int pos = 0; // position in `buf'. + for (int i = 0; i < size; i++) { + char c = s.charAt(i); + switch (c) { + case '+': + buf[pos++] = ' '; // "+" -> " " + break; + case '%': + if (i == size - 1) { + throw new IllegalArgumentException("unterminated escape" + + " sequence at end of string: " + s); + } + c = s.charAt(++i); + if (c == '%') { + buf[pos++] = '%'; // "%%" -> "%" + break; + } else if (i == size - 1) { + throw new IllegalArgumentException("partial escape" + + " sequence at end of string: " + s); + } + c = decodeHexNibble(c); + final char c2 = decodeHexNibble(s.charAt(++i)); + if (c == Character.MAX_VALUE || c2 == Character.MAX_VALUE) { + throw new IllegalArgumentException( + "invalid escape sequence `%" + s.charAt(i - 1) + + s.charAt(i) + "' at index " + (i - 2) + + " of: " + s); + } + c = (char) (c * 16 + c2); + // Fall through. + default: + buf[pos++] = (byte) c; + break; + } + } + return new String(buf, 0, pos, charset); + } + + /** + * Helper to decode half of a hexadecimal number from a string. + * @param c The ASCII character of the hexadecimal number to decode. + * Must be in the range {@code [0-9a-fA-F]}. + * @return The hexadecimal value represented in the ASCII character + * given, or {@link Character#MAX_VALUE} if the character is invalid. + */ + private static char decodeHexNibble(final char c) { + if ('0' <= c && c <= '9') { + return (char) (c - '0'); + } else if ('a' <= c && c <= 'f') { + return (char) (c - 'a' + 10); + } else if ('A' <= c && c <= 'F') { + return (char) (c - 'A' + 10); + } else { + return Character.MAX_VALUE; + } + } + + private static void addParam(Map> params, String name, String value) { + List values = params.get(name); + if (values == null) { + values = new ArrayList(1); // Often there's only 1 value. + params.put(name, values); + } + values.add(value); + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/QueryStringEncoder.java b/douyu-http/src/main/java/com/codefollower/douyu/http/QueryStringEncoder.java new file mode 100644 index 0000000..874f76a --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/QueryStringEncoder.java @@ -0,0 +1,141 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; +import java.util.ArrayList; +import java.util.List; + +/** + * Creates an URL-encoded URI from a path string and key-value parameter pairs. + * This encoder is for one time use only. Create a new instance for each URI. + * + *

+ * {@link QueryStringEncoder} encoder = new {@link QueryStringDecoder}("/hello");
+ * encoder.addParam("recipient", "world");
+ * assert encoder.toString().equals("/hello?recipient=world");
+ * 
+ * + * @author The Netty Project + * @author Andy Taylor (andy.taylor@jboss.org) + * @author Trustin Lee + * @version $Rev$, $Date$ + * + * @see QueryStringDecoder + * + * @apiviz.stereotype utility + * @apiviz.has org.jboss.netty.handler.codec.http.HttpRequest oneway - - encodes + */ +public class QueryStringEncoder { + + private final Charset charset; + private final String uri; + private final List params = new ArrayList(); + + /** + * Creates a new encoder that encodes a URI that starts with the specified + * path string. The encoder will encode the URI in UTF-8. + */ + public QueryStringEncoder(String uri) { + this(uri, HttpCodecUtil.DEFAULT_CHARSET); + } + + /** + * Creates a new encoder that encodes a URI that starts with the specified + * path string in the specified charset. + */ + public QueryStringEncoder(String uri, Charset charset) { + if (uri == null) { + throw new NullPointerException("uri"); + } + if (charset == null) { + throw new NullPointerException("charset"); + } + + this.uri = uri; + this.charset = charset; + } + + /** + * Adds a parameter with the specified name and value to this encoder. + */ + public void addParam(String name, String value) { + if (name == null) { + throw new NullPointerException("name"); + } + if (value == null) { + throw new NullPointerException("value"); + } + params.add(new Param(name, value)); + } + + /** + * Returns the URL-encoded URI object which was created from the path string + * specified in the constructor and the parameters added by + * {@link #addParam(String, String)} method. + */ + public URI toUri() throws URISyntaxException { + return new URI(toString()); + } + + /** + * Returns the URL-encoded URI which was created from the path string + * specified in the constructor and the parameters added by + * {@link #addParam(String, String)} method. + */ + @Override + public String toString() { + if (params.isEmpty()) { + return uri; + } else { + StringBuilder sb = new StringBuilder(uri).append("?"); + for (int i = 0; i < params.size(); i++) { + Param param = params.get(i); + sb.append(encodeComponent(param.name, charset)); + sb.append("="); + sb.append(encodeComponent(param.value, charset)); + if(i != params.size() - 1) { + sb.append("&"); + } + } + return sb.toString(); + } + } + + private static String encodeComponent(String s, Charset charset) { + try { + return URLEncoder.encode(s, charset.name()).replaceAll("\\+", "%20"); + } catch (UnsupportedEncodingException e) { + throw new UnsupportedCharsetException(charset.name()); + } + } + + private static final class Param { + + final String name; + final String value; + + Param(String name, String value) { + this.value = value; + this.name = name; + } + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/package-info.java b/douyu-http/src/main/java/com/codefollower/douyu/http/package-info.java new file mode 100644 index 0000000..7fcf82d --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/package-info.java @@ -0,0 +1,29 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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. + */ + +/** + * Encoder, decoder and their related message types for HTTP. + * + * @apiviz.exclude ^java\.lang\. + * @apiviz.exclude OneToOne(Encoder|Decoder)$ + * @apiviz.exclude \.HttpHeaders\. + * @apiviz.exclude \.codec\.replay\. + * @apiviz.exclude \.(Simple)?Channel[A-Za-z]*Handler$ + * @apiviz.exclude \.Rtsp + * @apiviz.exclude \.Default + * @apiviz.exclude \.Http(Client|Server)Codec$ + */ +package com.codefollower.douyu.http; diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/DefaultServerSocketFactory.java b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/DefaultServerSocketFactory.java new file mode 100644 index 0000000..d97620b --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/DefaultServerSocketFactory.java @@ -0,0 +1,63 @@ +/* + * 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 com.codefollower.douyu.http.ssl; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; + +/** + * Default server socket factory. Doesn't do much except give us + * plain old server sockets. + * + * @author db@eng.sun.com + * @author Harish Prabandham + */ +public class DefaultServerSocketFactory implements ServerSocketFactory { + + public DefaultServerSocketFactory() { + } + + @Override + public ServerSocket createSocket (int port) throws IOException { + return new ServerSocket (port); + } + + @Override + public ServerSocket createSocket (int port, int backlog) + throws IOException { + return new ServerSocket (port, backlog); + } + + @Override + public ServerSocket createSocket (int port, int backlog, + InetAddress ifAddress) throws IOException { + return new ServerSocket (port, backlog, ifAddress); + } + + @Override + public Socket acceptSocket(ServerSocket socket) throws IOException { + return socket.accept(); + } + + @Override + public void handshake(Socket sock) throws IOException { + // NOOP + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/SSLConfig.java b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/SSLConfig.java new file mode 100644 index 0000000..aee32a7 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/SSLConfig.java @@ -0,0 +1,284 @@ +package com.codefollower.douyu.http.ssl; + +import java.io.File; +import java.util.ArrayList; +import java.util.StringTokenizer; + +import javax.net.ssl.KeyManagerFactory; + + +import com.codefollower.douyu.http.Constants; +import com.codefollower.douyu.logging.InternalLogger; +import com.codefollower.douyu.logging.InternalLoggerFactory; + +public class SSLConfig { + private static final InternalLogger log = InternalLoggerFactory.getInstance(SSLConfig.class); + + public String adjustRelativePath(String path, String relativeTo) { + String newPath = path; + File f = new File(newPath); + if (!f.isAbsolute()) { + newPath = relativeTo + File.separator + newPath; + f = new File(newPath); + } + if (!f.exists()) { + log.warn("configured file:[" + newPath + "] does not exist."); + } + return newPath; + } + + // -------------------- SSL related properties -------------------- + + private String algorithm = KeyManagerFactory.getDefaultAlgorithm(); + + public String getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(String s) { + this.algorithm = s; + } + + private String clientAuth = "false"; + + public String getClientAuth() { + return clientAuth; + } + + public void setClientAuth(String s) { + this.clientAuth = s; + } + + private String keystoreFile = System.getProperty("user.home") + "/.keystore"; + + public String getKeystoreFile() { + return keystoreFile; + } + + public void setKeystoreFile(String s) { + String file = adjustRelativePath(s, System.getProperty(Constants.CATALINA_BASE_PROP)); + this.keystoreFile = file; + } + + private String keystorePass = null; + + public String getKeystorePass() { + return keystorePass; + } + + public void setKeystorePass(String s) { + this.keystorePass = s; + } + + private String keystoreType = "JKS"; + + public String getKeystoreType() { + return keystoreType; + } + + public void setKeystoreType(String s) { + this.keystoreType = s; + } + + private String keystoreProvider = null; + + public String getKeystoreProvider() { + return keystoreProvider; + } + + public void setKeystoreProvider(String s) { + this.keystoreProvider = s; + } + + private String sslProtocol = "TLS"; + + public String getSslProtocol() { + return sslProtocol; + } + + public void setSslProtocol(String s) { + sslProtocol = s; + } + + // Note: Some implementations use the comma separated string, some use + // the array + private String ciphers = null; + private String[] ciphersarr = new String[0]; + + public String[] getCiphersArray() { + return this.ciphersarr; + } + + public String getCiphers() { + return ciphers; + } + + public void setCiphers(String s) { + ciphers = s; + if (s == null) + ciphersarr = new String[0]; + else { + StringTokenizer t = new StringTokenizer(s, ","); + ciphersarr = new String[t.countTokens()]; + for (int i = 0; i < ciphersarr.length; i++) + ciphersarr[i] = t.nextToken(); + } + } + + private String keyAlias = null; + + public String getKeyAlias() { + return keyAlias; + } + + public void setKeyAlias(String s) { + keyAlias = s; + } + + private String keyPass = null; + + public String getKeyPass() { + return keyPass; + } + + public void setKeyPass(String s) { + this.keyPass = s; + } + + private String truststoreFile = System.getProperty("javax.net.ssl.trustStore"); + + public String getTruststoreFile() { + return truststoreFile; + } + + public void setTruststoreFile(String s) { + if (s == null) { + this.truststoreFile = null; + } else { + String file = adjustRelativePath(s, System.getProperty(Constants.CATALINA_BASE_PROP)); + this.truststoreFile = file; + } + } + + private String truststorePass = System.getProperty("javax.net.ssl.trustStorePassword"); + + public String getTruststorePass() { + return truststorePass; + } + + public void setTruststorePass(String truststorePass) { + this.truststorePass = truststorePass; + } + + private String truststoreType = System.getProperty("javax.net.ssl.trustStoreType"); + + public String getTruststoreType() { + return truststoreType; + } + + public void setTruststoreType(String truststoreType) { + this.truststoreType = truststoreType; + } + + private String truststoreProvider = null; + + public String getTruststoreProvider() { + return truststoreProvider; + } + + public void setTruststoreProvider(String truststoreProvider) { + this.truststoreProvider = truststoreProvider; + } + + private String truststoreAlgorithm = null; + + public String getTruststoreAlgorithm() { + return truststoreAlgorithm; + } + + public void setTruststoreAlgorithm(String truststoreAlgorithm) { + this.truststoreAlgorithm = truststoreAlgorithm; + } + + private String trustManagerClassName = null; + + public String getTrustManagerClassName() { + return trustManagerClassName; + } + + public void setTrustManagerClassName(String trustManagerClassName) { + this.trustManagerClassName = trustManagerClassName; + } + + private String crlFile = null; + + public String getCrlFile() { + return crlFile; + } + + public void setCrlFile(String crlFile) { + this.crlFile = crlFile; + } + + private String trustMaxCertLength = null; + + public String getTrustMaxCertLength() { + return trustMaxCertLength; + } + + public void setTrustMaxCertLength(String trustMaxCertLength) { + this.trustMaxCertLength = trustMaxCertLength; + } + + private String sessionCacheSize = null; + + public String getSessionCacheSize() { + return sessionCacheSize; + } + + public void setSessionCacheSize(String s) { + sessionCacheSize = s; + } + + private String sessionTimeout = "86400"; + + public String getSessionTimeout() { + return sessionTimeout; + } + + public void setSessionTimeout(String s) { + sessionTimeout = s; + } + + private String allowUnsafeLegacyRenegotiation = null; + + public String getAllowUnsafeLegacyRenegotiation() { + return allowUnsafeLegacyRenegotiation; + } + + public void setAllowUnsafeLegacyRenegotiation(String s) { + allowUnsafeLegacyRenegotiation = s; + } + + private String[] sslEnabledProtocolsarr = new String[0]; + + public String[] getSslEnabledProtocolsArray() { + return this.sslEnabledProtocolsarr; + } + + public void setSslEnabledProtocols(String s) { + if (s == null) { + this.sslEnabledProtocolsarr = new String[0]; + } else { + ArrayList sslEnabledProtocols = new ArrayList(); + StringTokenizer t = new StringTokenizer(s, ","); + while (t.hasMoreTokens()) { + String p = t.nextToken().trim(); + if (p.length() > 0) { + sslEnabledProtocols.add(p); + } + } + sslEnabledProtocolsarr = sslEnabledProtocols.toArray(new String[sslEnabledProtocols.size()]); + } + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/SSLImplementation.java b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/SSLImplementation.java new file mode 100644 index 0000000..0affb35 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/SSLImplementation.java @@ -0,0 +1,95 @@ +/* + * 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 com.codefollower.douyu.http.ssl; + +import java.net.Socket; + +import javax.net.ssl.SSLSession; + + +import com.codefollower.douyu.http.ssl.jsse.JSSEImplementation; +import com.codefollower.douyu.logging.InternalLogger; +import com.codefollower.douyu.logging.InternalLoggerFactory; + +/* SSLImplementation: + + Abstract factory and base class for all SSL implementations. + + @author EKR + */ +public abstract class SSLImplementation { + private static final InternalLogger logger = InternalLoggerFactory.getInstance(SSLImplementation.class); + + // The default implementations in our search path + private static final String JSSEImplementationClass = + "org.apache.tomcat.util.net.jsse.JSSEImplementation"; + + private static final String[] implementations = { JSSEImplementationClass }; + + public static SSLImplementation getInstance() throws ClassNotFoundException { + for (int i = 0; i < implementations.length; i++) { + try { + SSLImplementation impl = getInstance(implementations[i]); + return impl; + } catch (Exception e) { + if (logger.isDebugEnabled()) + logger.debug("Error creating " + implementations[i], e); + } + } + + // If we can't instantiate any of these + throw new ClassNotFoundException("Can't find any SSL implementation"); + } + + public static SSLImplementation getInstance(String className) + throws ClassNotFoundException { + if (className == null) + return getInstance(); + + try { + // Workaround for the J2SE 1.4.x classloading problem (under + // Solaris). + // Class.forName(..) fails without creating class using new. + // This is an ugly workaround. + if (JSSEImplementationClass.equals(className)) { + return new JSSEImplementation(); + } + Class clazz = Class.forName(className); + return (SSLImplementation) clazz.newInstance(); + } catch (Exception e) { + if (logger.isDebugEnabled()) + logger + .debug("Error loading SSL Implementation " + className, + e); + throw new ClassNotFoundException( + "Error loading SSL Implementation " + className + " :" + + e.toString()); + } + } + + public abstract String getImplementationName(); + + public abstract ServerSocketFactory getServerSocketFactory( + SSLConfig config); + + public abstract SSLSupport getSSLSupport(Socket sock); + + public abstract SSLSupport getSSLSupport(SSLSession session); + + public abstract SSLUtil getSSLUtil(SSLConfig config); +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/SSLSessionManager.java b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/SSLSessionManager.java new file mode 100644 index 0000000..2fd9e03 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/SSLSessionManager.java @@ -0,0 +1,31 @@ +/* + * 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 com.codefollower.douyu.http.ssl; + +/** + * Defines an interface used to manage SSL sessions. The manager operates on a + * single session. + * + * $Id: SSLSessionManager.java 881567 2009-11-17 22:13:30Z markt $ + */ +public interface SSLSessionManager { + /** + * Invalidate the SSL session + */ + public void invalidateSession(); +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/SSLSupport.java b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/SSLSupport.java new file mode 100644 index 0000000..a6f4fbd --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/SSLSupport.java @@ -0,0 +1,137 @@ +/* + * 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 com.codefollower.douyu.http.ssl; + +import java.io.IOException; + +/** + * Defines an interface to interact with SSL sessions. + */ +public interface SSLSupport { + /** + * The Request attribute key for the cipher suite. + */ + public static final String CIPHER_SUITE_KEY = + "javax.servlet.request.cipher_suite"; + + /** + * The Request attribute key for the key size. + */ + public static final String KEY_SIZE_KEY = "javax.servlet.request.key_size"; + + /** + * The Request attribute key for the client certificate chain. + */ + public static final String CERTIFICATE_KEY = + "javax.servlet.request.X509Certificate"; + + /** + * The Request attribute key for the session id. + * This one is a Tomcat extension to the Servlet spec. + */ + public static final String SESSION_ID_KEY = + "javax.servlet.request.ssl_session"; + + /** + * The request attribute key for the session manager. + * This one is a Tomcat extension to the Servlet spec. + */ + public static final String SESSION_MGR = + "javax.servlet.request.ssl_session_mgr"; + + + /** + * A mapping table to determine the number of effective bits in the key + * when using a cipher suite containing the specified cipher name. The + * underlying data came from the TLS Specification (RFC 2246), Appendix C. + */ + static final CipherData ciphers[] = { + new CipherData("_WITH_NULL_", 0), + new CipherData("_WITH_IDEA_CBC_", 128), + new CipherData("_WITH_RC2_CBC_40_", 40), + new CipherData("_WITH_RC4_40_", 40), + new CipherData("_WITH_RC4_128_", 128), + new CipherData("_WITH_DES40_CBC_", 40), + new CipherData("_WITH_DES_CBC_", 56), + new CipherData("_WITH_3DES_EDE_CBC_", 168), + new CipherData("_WITH_AES_128_CBC_", 128), + new CipherData("_WITH_AES_256_CBC_", 256) + }; + + /** + * The cipher suite being used on this connection. + */ + public String getCipherSuite() throws IOException; + + /** + * The client certificate chain (if any). + */ + public Object[] getPeerCertificateChain() + throws IOException; + + /** + * The client certificate chain (if any). + * @param force If true, then re-negotiate the + * connection if necessary. + */ + public Object[] getPeerCertificateChain(boolean force) + throws IOException; + + /** + * Get the keysize. + * + * What we're supposed to put here is ill-defined by the + * Servlet spec (S 4.7 again). There are at least 4 potential + * values that might go here: + * + * (a) The size of the encryption key + * (b) The size of the MAC key + * (c) The size of the key-exchange key + * (d) The size of the signature key used by the server + * + * Unfortunately, all of these values are nonsensical. + **/ + public Integer getKeySize() + throws IOException; + + /** + * The current session Id. + */ + public String getSessionId() + throws IOException; + /** + * Simple data class that represents the cipher being used, along with the + * corresponding effective key size. The specified phrase must appear in the + * name of the cipher suite to be recognized. + */ + + final class CipherData { + + public String phrase = null; + + public int keySize = 0; + + public CipherData(String phrase, int keySize) { + this.phrase = phrase; + this.keySize = keySize; + } + + } + +} + diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/SSLUtil.java b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/SSLUtil.java new file mode 100644 index 0000000..35e9ac2 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/SSLUtil.java @@ -0,0 +1,33 @@ +/* + * 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 com.codefollower.douyu.http.ssl; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSessionContext; +import javax.net.ssl.TrustManager; + +public interface SSLUtil { + + public SSLContext createSSLContext() throws Exception; + + public KeyManager[] getKeyManagers() throws Exception; + + public TrustManager[] getTrustManagers() throws Exception; + + public void configureSessionContext(SSLSessionContext sslSessionContext); +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/ServerSocketFactory.java b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/ServerSocketFactory.java new file mode 100644 index 0000000..e872125 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/ServerSocketFactory.java @@ -0,0 +1,97 @@ +/* + * 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 com.codefollower.douyu.http.ssl; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; + +/** + * The common interface through which the {@link JIoEndpoint} interacts with + * both non-SSL and SSL sockets. + */ +public interface ServerSocketFactory { + + /** + * Returns a server socket which uses all network interfaces on the host, + * and is bound to a the specified port. The socket is configured with the + * socket options (such as accept timeout) given to this factory. + * + * @param port + * the port to listen to + * @exception IOException + * for networking errors + * @exception InstantiationException + * for construction errors + */ + ServerSocket createSocket(int port) throws IOException, + InstantiationException; + + /** + * Returns a server socket which uses all network interfaces on the host, is + * bound to a the specified port, and uses the specified connection backlog. + * The socket is configured with the socket options (such as accept timeout) + * given to this factory. + * + * @param port + * the port to listen to + * @param backlog + * how many connections are queued + * @exception IOException + * for networking errors + * @exception InstantiationException + * for construction errors + */ + ServerSocket createSocket(int port, int backlog) throws IOException, + InstantiationException; + + /** + * Returns a server socket which uses only the specified network interface + * on the local host, is bound to a the specified port, and uses the + * specified connection backlog. The socket is configured with the socket + * options (such as accept timeout) given to this factory. + * + * @param port + * the port to listen to + * @param backlog + * how many connections are queued + * @param ifAddress + * the network interface address to use + * @exception IOException + * for networking errors + * @exception InstantiationException + * for construction errors + */ + ServerSocket createSocket(int port, int backlog, InetAddress ifAddress) + throws IOException, InstantiationException; + + /** + * Wrapper function for accept(). This allows us to trap and translate + * exceptions if necessary. + * + * @exception IOException + */ + Socket acceptSocket(ServerSocket socket) throws IOException; + + /** + * Triggers the SSL handshake. This will be a no-op for non-SSL sockets. + * + * @exception IOException + */ + void handshake(Socket sock) throws IOException; +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/jsse/JSSEImplementation.java b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/jsse/JSSEImplementation.java new file mode 100644 index 0000000..c347538 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/jsse/JSSEImplementation.java @@ -0,0 +1,63 @@ +/* + * 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 com.codefollower.douyu.http.ssl.jsse; + +import java.net.Socket; + +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; + +import com.codefollower.douyu.http.ssl.SSLConfig; +import com.codefollower.douyu.http.ssl.SSLSupport; +import com.codefollower.douyu.http.ssl.SSLUtil; +import com.codefollower.douyu.http.ssl.ServerSocketFactory; + +/* JSSEImplementation: + + Concrete implementation class for JSSE + + @author EKR +*/ + +public class JSSEImplementation extends com.codefollower.douyu.http.ssl.SSLImplementation { + + @Override + public String getImplementationName(){ + return "JSSE"; + } + + @Override + public ServerSocketFactory getServerSocketFactory(SSLConfig config) { + return new JSSESocketFactory(config); + } + + @Override + public SSLSupport getSSLSupport(Socket s) { + return new JSSESupport((SSLSocket) s); + } + + @Override + public SSLSupport getSSLSupport(SSLSession session) { + return new JSSESupport(session); + } + + @Override + public SSLUtil getSSLUtil(SSLConfig config) { + return new JSSESocketFactory(config); + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/jsse/JSSEKeyManager.java b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/jsse/JSSEKeyManager.java new file mode 100644 index 0000000..a9c4a19 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/jsse/JSSEKeyManager.java @@ -0,0 +1,151 @@ +/* + * 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 com.codefollower.douyu.http.ssl.jsse; + +import java.net.Socket; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +import javax.net.ssl.X509KeyManager; + +/** + * X509KeyManager which allows selection of a specific keypair and certificate + * chain (identified by their keystore alias name) to be used by the server to + * authenticate itself to SSL clients. + * + * @author Jan Luehe + */ +public final class JSSEKeyManager implements X509KeyManager { + + private X509KeyManager delegate; + private String serverKeyAlias; + + /** + * Constructor. + * + * @param mgr The X509KeyManager used as a delegate + * @param serverKeyAlias The alias name of the server's keypair and + * supporting certificate chain + */ + public JSSEKeyManager(X509KeyManager mgr, String serverKeyAlias) { + this.delegate = mgr; + this.serverKeyAlias = serverKeyAlias; + } + + /** + * Choose an alias to authenticate the client side of a secure socket, + * given the public key type and the list of certificate issuer authorities + * recognized by the peer (if any). + * + * @param keyType The key algorithm type name(s), ordered with the + * most-preferred key type first + * @param issuers The list of acceptable CA issuer subject names, or null + * if it does not matter which issuers are used + * @param socket The socket to be used for this connection. This parameter + * can be null, in which case this method will return the most generic + * alias to use + * + * @return The alias name for the desired key, or null if there are no + * matches + */ + @Override + public String chooseClientAlias(String[] keyType, Principal[] issuers, + Socket socket) { + return delegate.chooseClientAlias(keyType, issuers, socket); + } + + /** + * Returns this key manager's server key alias that was provided in the + * constructor. + * + * @param keyType The key algorithm type name (ignored) + * @param issuers The list of acceptable CA issuer subject names, or null + * if it does not matter which issuers are used (ignored) + * @param socket The socket to be used for this connection. This parameter + * can be null, in which case this method will return the most generic + * alias to use (ignored) + * + * @return Alias name for the desired key + */ + @Override + public String chooseServerAlias(String keyType, Principal[] issuers, + Socket socket) { + return serverKeyAlias; + } + + /** + * Returns the certificate chain associated with the given alias. + * + * @param alias The alias name + * + * @return Certificate chain (ordered with the user's certificate first + * and the root certificate authority last), or null if the alias can't be + * found + */ + @Override + public X509Certificate[] getCertificateChain(String alias) { + return delegate.getCertificateChain(alias); + } + + /** + * Get the matching aliases for authenticating the client side of a secure + * socket, given the public key type and the list of certificate issuer + * authorities recognized by the peer (if any). + * + * @param keyType The key algorithm type name + * @param issuers The list of acceptable CA issuer subject names, or null + * if it does not matter which issuers are used + * + * @return Array of the matching alias names, or null if there were no + * matches + */ + @Override + public String[] getClientAliases(String keyType, Principal[] issuers) { + return delegate.getClientAliases(keyType, issuers); + } + + /** + * Get the matching aliases for authenticating the server side of a secure + * socket, given the public key type and the list of certificate issuer + * authorities recognized by the peer (if any). + * + * @param keyType The key algorithm type name + * @param issuers The list of acceptable CA issuer subject names, or null + * if it does not matter which issuers are used + * + * @return Array of the matching alias names, or null if there were no + * matches + */ + @Override + public String[] getServerAliases(String keyType, Principal[] issuers) { + return delegate.getServerAliases(keyType, issuers); + } + + /** + * Returns the key associated with the given alias. + * + * @param alias The alias name + * + * @return The requested key, or null if the alias can't be found + */ + @Override + public PrivateKey getPrivateKey(String alias) { + return delegate.getPrivateKey(alias); + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/jsse/JSSESocketFactory.java b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/jsse/JSSESocketFactory.java new file mode 100644 index 0000000..14b1f11 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/jsse/JSSESocketFactory.java @@ -0,0 +1,848 @@ +/* + * 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 com.codefollower.douyu.http.ssl.jsse; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CRL; +import java.security.cert.CRLException; +import java.security.cert.CertPathParameters; +import java.security.cert.CertStore; +import java.security.cert.CertStoreParameters; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.CollectionCertStoreParameters; +import java.security.cert.PKIXBuilderParameters; +import java.security.cert.X509CertSelector; +import java.util.Collection; +import java.util.Locale; +import java.util.Vector; + +import javax.net.ssl.CertPathTrustManagerParameters; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.ManagerFactoryParameters; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSessionContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509KeyManager; + + +import com.codefollower.douyu.core.StringManager; +import com.codefollower.douyu.http.Constants; +import com.codefollower.douyu.http.ssl.SSLConfig; +import com.codefollower.douyu.http.ssl.SSLUtil; +import com.codefollower.douyu.http.ssl.ServerSocketFactory; +import com.codefollower.douyu.logging.InternalLogger; +import com.codefollower.douyu.logging.InternalLoggerFactory; + +/** + * SSL server socket factory. It requires a valid RSA key and + * JSSE.
+ * keytool -genkey -alias tomcat -keyalg RSA
+ * Use "changeit" as password (this is the default we use). + * + * @author Harish Prabandham + * @author Costin Manolache + * @author Stefan Freyr Stefansson + * @author EKR -- renamed to JSSESocketFactory + * @author Jan Luehe + * @author Bill Barker + */ +public class JSSESocketFactory implements ServerSocketFactory, SSLUtil { + + private static final InternalLogger log = InternalLoggerFactory.getInstance(JSSESocketFactory.class); + + private static final StringManager sm = + StringManager.getManager("org.douyu.http.ssl.jsse.res"); + + private static final boolean RFC_5746_SUPPORTED; + + // Defaults - made public where re-used + private static final String defaultProtocol = "TLS"; + private static final String defaultKeystoreType = "JKS"; + private static final String defaultKeystoreFile + = System.getProperty("user.home") + "/.keystore"; + private static final int defaultSessionCacheSize = 0; + private static final int defaultSessionTimeout = 86400; + private static final String ALLOW_ALL_SUPPORTED_CIPHERS = "ALL"; + public static final String DEFAULT_KEY_PASS = "changeit"; + + static { + boolean result = false; + SSLContext context; + try { + context = SSLContext.getInstance("TLS"); + context.init(null, null, null); + SSLServerSocketFactory ssf = context.getServerSocketFactory(); + String ciphers[] = ssf.getSupportedCipherSuites(); + for (String cipher : ciphers) { + if ("TLS_EMPTY_RENEGOTIATION_INFO_SCSV".equals(cipher)) { + result = true; + break; + } + } + } catch (NoSuchAlgorithmException e) { + // Assume no RFC 5746 support + } catch (KeyManagementException e) { + // Assume no RFC 5746 support + } + RFC_5746_SUPPORTED = result; + } + + + private SSLConfig endpoint; + + protected SSLServerSocketFactory sslProxy = null; + protected String[] enabledCiphers; + protected boolean allowUnsafeLegacyRenegotiation = false; + + /** + * Flag to state that we require client authentication. + */ + protected boolean requireClientAuth = false; + + /** + * Flag to state that we would like client authentication. + */ + protected boolean wantClientAuth = false; + + + public JSSESocketFactory (SSLConfig config) { + this.endpoint = config; + } + + @Override + public ServerSocket createSocket (int port) + throws IOException + { + init(); + ServerSocket socket = sslProxy.createServerSocket(port); + initServerSocket(socket); + return socket; + } + + @Override + public ServerSocket createSocket (int port, int backlog) + throws IOException + { + init(); + ServerSocket socket = sslProxy.createServerSocket(port, backlog); + initServerSocket(socket); + return socket; + } + + @Override + public ServerSocket createSocket (int port, int backlog, + InetAddress ifAddress) + throws IOException + { + init(); + ServerSocket socket = sslProxy.createServerSocket(port, backlog, + ifAddress); + initServerSocket(socket); + return socket; + } + + @Override + public Socket acceptSocket(ServerSocket socket) + throws IOException + { + SSLSocket asock = null; + try { + asock = (SSLSocket)socket.accept(); + } catch (SSLException e){ + throw new SocketException("SSL handshake error" + e.toString()); + } + return asock; + } + + @Override + public void handshake(Socket sock) throws IOException { + // We do getSession instead of startHandshake() so we can call this multiple times + SSLSession session = ((SSLSocket)sock).getSession(); + if (session.getCipherSuite().equals("SSL_NULL_WITH_NULL_NULL")) + throw new IOException("SSL handshake failed. Ciper suite in SSL Session is SSL_NULL_WITH_NULL_NULL"); + + if (!allowUnsafeLegacyRenegotiation && !RFC_5746_SUPPORTED) { + // Prevent further handshakes by removing all cipher suites + ((SSLSocket) sock).setEnabledCipherSuites(new String[0]); + } + } + + /* + * Determines the SSL cipher suites to be enabled. + * + * @param requestedCiphers Comma-separated list of requested ciphers + * @param supportedCiphers Array of supported ciphers + * + * @return Array of SSL cipher suites to be enabled, or null if none of the + * requested ciphers are supported + */ + protected String[] getEnabledCiphers(String requestedCiphers, + String[] supportedCiphers) { + + String[] result = null; + + if (ALLOW_ALL_SUPPORTED_CIPHERS.equals(requestedCiphers)) { + return supportedCiphers; + } + + if (requestedCiphers != null) { + Vector vec = null; + String cipher = requestedCiphers; + int index = requestedCiphers.indexOf(','); + if (index != -1) { + int fromIndex = 0; + while (index != -1) { + cipher = + requestedCiphers.substring(fromIndex, index).trim(); + if (cipher.length() > 0) { + /* + * Check to see if the requested cipher is among the + * supported ciphers, i.e., may be enabled + */ + for (int i=0; supportedCiphers != null + && i(); + } + vec.addElement(cipher); + break; + } + } + } + fromIndex = index+1; + index = requestedCiphers.indexOf(',', fromIndex); + } // while + cipher = requestedCiphers.substring(fromIndex); + } + + if (cipher != null) { + cipher = cipher.trim(); + if (cipher.length() > 0) { + /* + * Check to see if the requested cipher is among the + * supported ciphers, i.e., may be enabled + */ + for (int i=0; supportedCiphers != null + && i(); + } + vec.addElement(cipher); + break; + } + } + } + } + + if (vec != null) { + result = new String[vec.size()]; + vec.copyInto(result); + } + } else { + result = sslProxy.getDefaultCipherSuites(); + } + + return result; + } + + /* + * Gets the SSL server's keystore password. + */ + protected String getKeystorePassword() { + String keystorePass = endpoint.getKeystorePass(); + if (keystorePass == null) { + keystorePass = endpoint.getKeyPass(); + } + if (keystorePass == null) { + keystorePass = DEFAULT_KEY_PASS; + } + return keystorePass; + } + + /* + * Gets the SSL server's keystore. + */ + protected KeyStore getKeystore(String type, String provider, String pass) + throws IOException { + + String keystoreFile = endpoint.getKeystoreFile(); + if (keystoreFile == null) + keystoreFile = defaultKeystoreFile; + + return getStore(type, provider, keystoreFile, pass); + } + + /* + * Gets the SSL server's truststore. + */ + protected KeyStore getTrustStore(String keystoreType, + String keystoreProvider) throws IOException { + KeyStore trustStore = null; + + String truststoreFile = endpoint.getTruststoreFile(); + if(truststoreFile == null) { + truststoreFile = System.getProperty("javax.net.ssl.trustStore"); + } + if(log.isDebugEnabled()) { + log.debug("Truststore = " + truststoreFile); + } + + String truststorePassword = endpoint.getTruststorePass(); + if( truststorePassword == null) { + truststorePassword = + System.getProperty("javax.net.ssl.trustStorePassword"); + } + if(log.isDebugEnabled()) { + log.debug("TrustPass = " + truststorePassword); + } + + String truststoreType = endpoint.getTruststoreType(); + if( truststoreType == null) { + truststoreType = System.getProperty("javax.net.ssl.trustStoreType"); + } + if(truststoreType == null) { + truststoreType = keystoreType; + } + if(log.isDebugEnabled()) { + log.debug("trustType = " + truststoreType); + } + + String truststoreProvider = endpoint.getTruststoreProvider(); + if( truststoreProvider == null) { + truststoreProvider = + System.getProperty("javax.net.ssl.trustStoreProvider"); + } + if (truststoreProvider == null) { + truststoreProvider = keystoreProvider; + } + if(log.isDebugEnabled()) { + log.debug("trustProvider = " + truststoreProvider); + } + + if (truststoreFile != null){ + try { + trustStore = getStore(truststoreType, truststoreProvider, + truststoreFile, truststorePassword); + } catch (IOException ioe) { + Throwable cause = ioe.getCause(); + if (cause instanceof UnrecoverableKeyException) { + // Log a warning we had a password issue + log.warn(sm.getString("jsse.invalid_truststore_password"), + cause); + // Re-try + trustStore = getStore(truststoreType, truststoreProvider, + truststoreFile, null); + } else { + // Something else went wrong - re-throw + throw ioe; + } + } + } + + return trustStore; + } + + /* + * Gets the key- or truststore with the specified type, path, and password. + */ + private KeyStore getStore(String type, String provider, String path, + String pass) throws IOException { + + KeyStore ks = null; + InputStream istream = null; + try { + if (provider == null) { + ks = KeyStore.getInstance(type); + } else { + ks = KeyStore.getInstance(type, provider); + } + if(!("PKCS11".equalsIgnoreCase(type) || + "".equalsIgnoreCase(path))) { + File keyStoreFile = new File(path); + if (!keyStoreFile.isAbsolute()) { + keyStoreFile = new File(System.getProperty( + Constants.CATALINA_BASE_PROP), path); + } + istream = new FileInputStream(keyStoreFile); + } + + char[] storePass = null; + if (pass != null && !"".equals(pass)) { + storePass = pass.toCharArray(); + } + ks.load(istream, storePass); + } catch (FileNotFoundException fnfe) { + log.error(sm.getString("jsse.keystore_load_failed", type, path, + fnfe.getMessage()), fnfe); + throw fnfe; + } catch (IOException ioe) { + // May be expected when working with a trust store + // Re-throw. Caller will catch and log as required + throw ioe; + } catch(Exception ex) { + String msg = sm.getString("jsse.keystore_load_failed", type, path, + ex.getMessage()); + log.error(msg, ex); + throw new IOException(msg); + } finally { + if (istream != null) { + try { + istream.close(); + } catch (IOException ioe) { + // Do nothing + } + } + } + + return ks; + } + + /** + * Reads the keystore and initializes the SSL socket factory. + */ + void init() throws IOException { + try { + + String clientAuthStr = endpoint.getClientAuth(); + if("true".equalsIgnoreCase(clientAuthStr) || + "yes".equalsIgnoreCase(clientAuthStr)) { + requireClientAuth = true; + } else if("want".equalsIgnoreCase(clientAuthStr)) { + wantClientAuth = true; + } + + SSLContext context = createSSLContext(); + context.init(getKeyManagers(), getTrustManagers(), null); + + // Configure SSL session cache + SSLSessionContext sessionContext = + context.getServerSessionContext(); + if (sessionContext != null) { + configureSessionContext(sessionContext); + } + + // create proxy + sslProxy = context.getServerSocketFactory(); + + // Determine which cipher suites to enable + String requestedCiphers = endpoint.getCiphers(); + enabledCiphers = getEnabledCiphers(requestedCiphers, + sslProxy.getSupportedCipherSuites()); + + allowUnsafeLegacyRenegotiation = "true".equals( + endpoint.getAllowUnsafeLegacyRenegotiation()); + + // Check the SSL config is OK + checkConfig(); + + } catch(Exception e) { + if( e instanceof IOException ) + throw (IOException)e; + throw new IOException(e.getMessage(), e); + } + } + + @Override + public SSLContext createSSLContext() throws Exception { + + // SSL protocol variant (e.g., TLS, SSL v3, etc.) + String protocol = endpoint.getSslProtocol(); + if (protocol == null) { + protocol = defaultProtocol; + } + + SSLContext context = SSLContext.getInstance(protocol); + + return context; + } + + @Override + public KeyManager[] getKeyManagers() throws Exception { + String keystoreType = endpoint.getKeystoreType(); + if (keystoreType == null) { + keystoreType = defaultKeystoreType; + } + + String algorithm = endpoint.getAlgorithm(); + if (algorithm == null) { + algorithm = KeyManagerFactory.getDefaultAlgorithm(); + } + + return getKeyManagers(keystoreType, endpoint.getKeystoreProvider(), + algorithm, endpoint.getKeyAlias()); + } + + @Override + public TrustManager[] getTrustManagers() throws Exception { + String truststoreType = endpoint.getTruststoreType(); + if (truststoreType == null) { + truststoreType = System.getProperty("javax.net.ssl.trustStoreType"); + } + if (truststoreType == null) { + truststoreType = endpoint.getKeystoreType(); + } + if (truststoreType == null) { + truststoreType = defaultKeystoreType; + } + + String algorithm = endpoint.getTruststoreAlgorithm(); + if (algorithm == null) { + algorithm = TrustManagerFactory.getDefaultAlgorithm(); + } + + return getTrustManagers(truststoreType, endpoint.getKeystoreProvider(), + algorithm); + } + + @Override + public void configureSessionContext(SSLSessionContext sslSessionContext) { + int sessionCacheSize; + if (endpoint.getSessionCacheSize() != null) { + sessionCacheSize = Integer.parseInt( + endpoint.getSessionCacheSize()); + } else { + sessionCacheSize = defaultSessionCacheSize; + } + + int sessionTimeout; + if (endpoint.getSessionTimeout() != null) { + sessionTimeout = Integer.parseInt(endpoint.getSessionTimeout()); + } else { + sessionTimeout = defaultSessionTimeout; + } + + sslSessionContext.setSessionCacheSize(sessionCacheSize); + sslSessionContext.setSessionTimeout(sessionTimeout); + } + + /** + * Gets the initialized key managers. + */ + protected KeyManager[] getKeyManagers(String keystoreType, + String keystoreProvider, + String algorithm, + String keyAlias) + throws Exception { + + KeyManager[] kms = null; + + String keystorePass = getKeystorePassword(); + + KeyStore ks = getKeystore(keystoreType, keystoreProvider, keystorePass); + if (keyAlias != null && !ks.isKeyEntry(keyAlias)) { + throw new IOException( + sm.getString("jsse.alias_no_key_entry", keyAlias)); + } + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm); + String keyPass = endpoint.getKeyPass(); + if (keyPass == null) { + keyPass = keystorePass; + } + kmf.init(ks, keyPass.toCharArray()); + + kms = kmf.getKeyManagers(); + if (keyAlias != null) { + String alias = keyAlias; + if (JSSESocketFactory.defaultKeystoreType.equals(keystoreType)) { + alias = alias.toLowerCase(Locale.ENGLISH); + } + for(int i=0; i 0) { + ClassLoader classLoader = getClass().getClassLoader(); + Class clazz = classLoader.loadClass(className); + if(!(TrustManager.class.isAssignableFrom(clazz))){ + throw new InstantiationException(sm.getString( + "jsse.invalidTrustManagerClassName", className)); + } + Object trustManagerObject = clazz.newInstance(); + TrustManager trustManager = (TrustManager) trustManagerObject; + return new TrustManager[]{ trustManager }; + } + + TrustManager[] tms = null; + + KeyStore trustStore = getTrustStore(keystoreType, keystoreProvider); + if (trustStore != null || endpoint.getTrustManagerClassName() != null) { + if (crlf == null) { + TrustManagerFactory tmf = + TrustManagerFactory.getInstance(algorithm); + tmf.init(trustStore); + tms = tmf.getTrustManagers(); + } else { + TrustManagerFactory tmf = + TrustManagerFactory.getInstance(algorithm); + CertPathParameters params = + getParameters(algorithm, crlf, trustStore); + ManagerFactoryParameters mfp = + new CertPathTrustManagerParameters(params); + tmf.init(mfp); + tms = tmf.getTrustManagers(); + } + } + + return tms; + } + + /** + * Return the initialization parameters for the TrustManager. + * Currently, only the default PKIX is supported. + * + * @param algorithm The algorithm to get parameters for. + * @param crlf The path to the CRL file. + * @param trustStore The configured TrustStore. + * @return The parameters including the CRLs and TrustStore. + */ + protected CertPathParameters getParameters(String algorithm, + String crlf, + KeyStore trustStore) + throws Exception { + CertPathParameters params = null; + if("PKIX".equalsIgnoreCase(algorithm)) { + PKIXBuilderParameters xparams = + new PKIXBuilderParameters(trustStore, new X509CertSelector()); + Collection crls = getCRLs(crlf); + CertStoreParameters csp = new CollectionCertStoreParameters(crls); + CertStore store = CertStore.getInstance("Collection", csp); + xparams.addCertStore(store); + xparams.setRevocationEnabled(true); + String trustLength = endpoint.getTrustMaxCertLength(); + if(trustLength != null) { + try { + xparams.setMaxPathLength(Integer.parseInt(trustLength)); + } catch(Exception ex) { + log.warn("Bad maxCertLength: "+trustLength); + } + } + + params = xparams; + } else { + throw new CRLException("CRLs not supported for type: "+algorithm); + } + return params; + } + + + /** + * Load the collection of CRLs. + * + */ + protected Collection getCRLs(String crlf) + throws IOException, CRLException, CertificateException { + + File crlFile = new File(crlf); + if( !crlFile.isAbsolute() ) { + crlFile = new File( + System.getProperty(Constants.CATALINA_BASE_PROP), crlf); + } + Collection crls = null; + InputStream is = null; + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + is = new FileInputStream(crlFile); + crls = cf.generateCRLs(is); + } catch(IOException iex) { + throw iex; + } catch(CRLException crle) { + throw crle; + } catch(CertificateException ce) { + throw ce; + } finally { + if(is != null) { + try{ + is.close(); + } catch(Exception ex) { + // Ignore + } + } + } + return crls; + } + + /** + * Set the SSL protocol variants to be enabled. + * @param socket the SSLServerSocket. + * @param protocols the protocols to use. + */ + protected void setEnabledProtocols(SSLServerSocket socket, + String []protocols){ + if (protocols != null) { + socket.setEnabledProtocols(protocols); + } + } + + /** + * Determines the SSL protocol variants to be enabled. + * + * @param socket The socket to get supported list from. + * @param requestedProtocols Array of requested protocol names all of which + * must be non-null and non-zero length + * + * @return Array of SSL protocol variants to be enabled, or null if none of + * the requested protocol variants are supported + */ + protected String[] getEnabledProtocols(SSLServerSocket socket, + String[] requestedProtocols){ + String[] supportedProtocols = socket.getSupportedProtocols(); + + String[] enabledProtocols = null; + + if (requestedProtocols != null && requestedProtocols.length > 0) { + Vector vec = null; + for (String protocol : requestedProtocols) { + /* + * Check to see if the requested protocol is among the supported + * protocols, i.e., may be enabled + */ + for (int i=0; supportedProtocols != null && + i < supportedProtocols.length; i++) { + if (supportedProtocols[i].equals(protocol)) { + if (vec == null) { + vec = new Vector(); + } + vec.addElement(protocol); + break; + } + } + } + + if (vec != null) { + enabledProtocols = new String[vec.size()]; + vec.copyInto(enabledProtocols); + } + } + + return enabledProtocols; + } + + /** + * Configure Client authentication for this version of JSSE. The + * JSSE included in Java 1.4 supports the 'want' value. Prior + * versions of JSSE will treat 'want' as 'false'. + * @param socket the SSLServerSocket + */ + protected void configureClientAuth(SSLServerSocket socket){ + if (wantClientAuth){ + socket.setWantClientAuth(wantClientAuth); + } else { + socket.setNeedClientAuth(requireClientAuth); + } + } + + /** + * Configures the given SSL server socket with the requested cipher suites, + * protocol versions, and need for client authentication + */ + private void initServerSocket(ServerSocket ssocket) { + + SSLServerSocket socket = (SSLServerSocket) ssocket; + + if (enabledCiphers != null) { + socket.setEnabledCipherSuites(enabledCiphers); + } + + String[] requestedProtocols = endpoint.getSslEnabledProtocolsArray(); + setEnabledProtocols(socket, getEnabledProtocols(socket, + requestedProtocols)); + + // we don't know if client auth is needed - + // after parsing the request we may re-handshake + configureClientAuth(socket); + } + + /** + * Checks that the certificate is compatible with the enabled cipher suites. + * If we don't check now, the JIoEndpoint can enter a nasty logging loop. + * See bug 45528. + */ + private void checkConfig() throws IOException { + // Create an unbound server socket + ServerSocket socket = sslProxy.createServerSocket(); + initServerSocket(socket); + + try { + // Set the timeout to 1ms as all we care about is if it throws an + // SSLException on accept. + socket.setSoTimeout(1); + + socket.accept(); + // Will never get here - no client can connect to an unbound port + } catch (SSLException ssle) { + // SSL configuration is invalid. Possibly cert doesn't match ciphers + IOException ioe = new IOException(sm.getString( + "jsse.invalid_ssl_conf", ssle.getMessage())); + ioe.initCause(ssle); + throw ioe; + } catch (Exception e) { + /* + * Possible ways of getting here + * socket.accept() throws a SecurityException + * socket.setSoTimeout() throws a SocketException + * socket.accept() throws some other exception (after a JDK change) + * In these cases the test won't work so carry on - essentially + * the behaviour before this patch + * socket.accept() throws a SocketTimeoutException + * In this case all is well so carry on + */ + } finally { + // Should be open here but just in case + if (!socket.isClosed()) { + socket.close(); + } + } + + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/jsse/JSSESupport.java b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/jsse/JSSESupport.java new file mode 100644 index 0000000..84a1242 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/jsse/JSSESupport.java @@ -0,0 +1,272 @@ +/* + * 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 com.codefollower.douyu.http.ssl.jsse; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.SocketException; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.util.Map; +import java.util.WeakHashMap; + +import javax.net.ssl.HandshakeCompletedEvent; +import javax.net.ssl.HandshakeCompletedListener; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.security.cert.X509Certificate; + +import com.codefollower.douyu.logging.InternalLogger; +import com.codefollower.douyu.logging.InternalLoggerFactory; + +/** JSSESupport + + Concrete implementation class for JSSE + Support classes. + + This will only work with JDK 1.2 and up since it + depends on JDK 1.2's certificate support + + @author EKR + @author Craig R. McClanahan + @author Filip Hanik + Parts cribbed from JSSECertCompat + Parts cribbed from CertificatesValve +*/ + +class JSSESupport implements com.codefollower.douyu.http.ssl.SSLSupport, com.codefollower.douyu.http.ssl.SSLSessionManager { + + private static final InternalLogger log = InternalLoggerFactory.getInstance(JSSESupport.class); + + + private static final Map keySizeCache = + new WeakHashMap(); + + protected SSLSocket ssl; + protected SSLSession session; + + Listener listener = new Listener(); + + JSSESupport(SSLSocket sock){ + ssl=sock; + session = sock.getSession(); + sock.addHandshakeCompletedListener(listener); + } + + JSSESupport(SSLSession session) { + this.session = session; + } + + @Override + public String getCipherSuite() throws IOException { + // Look up the current SSLSession + if (session == null) + return null; + return session.getCipherSuite(); + } + + @Override + public Object[] getPeerCertificateChain() + throws IOException { + return getPeerCertificateChain(false); + } + + protected java.security.cert.X509Certificate [] getX509Certificates( + SSLSession session) { + Certificate [] certs=null; + try { + certs = session.getPeerCertificates(); + } catch( Throwable t ) { + log.debug("Error getting client certs",t); + return null; + } + if( certs==null ) return null; + + java.security.cert.X509Certificate [] x509Certs = + new java.security.cert.X509Certificate[certs.length]; + for(int i=0; i < certs.length; i++) { + if (certs[i] instanceof java.security.cert.X509Certificate ) { + // always currently true with the JSSE 1.1.x + x509Certs[i] = (java.security.cert.X509Certificate) certs[i]; + } else { + try { + byte [] buffer = certs[i].getEncoded(); + CertificateFactory cf = + CertificateFactory.getInstance("X.509"); + ByteArrayInputStream stream = + new ByteArrayInputStream(buffer); + x509Certs[i] = (java.security.cert.X509Certificate) + cf.generateCertificate(stream); + } catch(Exception ex) { + log.info("Error translating cert " + certs[i], ex); + return null; + } + } + if(log.isDebugEnabled()) + log.debug("Cert #" + i + " = " + x509Certs[i]); + } + if(x509Certs.length < 1) + return null; + return x509Certs; + } + + @Override + public Object[] getPeerCertificateChain(boolean force) + throws IOException { + // Look up the current SSLSession + if (session == null) + return null; + + // Convert JSSE's certificate format to the ones we need + X509Certificate [] jsseCerts = null; + try { + jsseCerts = session.getPeerCertificateChain(); + } catch(Exception bex) { + // ignore. + } + if (jsseCerts == null) + jsseCerts = new X509Certificate[0]; + if(jsseCerts.length <= 0 && force && ssl != null) { + session.invalidate(); + handShake(); + session = ssl.getSession(); + } + return getX509Certificates(session); + } + + protected void handShake() throws IOException { + if( ssl.getWantClientAuth() ) { + log.debug("No client cert sent for want"); + } else { + ssl.setNeedClientAuth(true); + } + + if (ssl.getEnabledCipherSuites().length == 0) { + // Handshake is never going to be successful. + // Assume this is because handshakes are disabled + log.warn("SSL server initiated renegotiation is disabled, closing connection"); + session.invalidate(); + ssl.close(); + return; + } + + InputStream in = ssl.getInputStream(); + int oldTimeout = ssl.getSoTimeout(); + ssl.setSoTimeout(1000); + byte[] b = new byte[0]; + listener.reset(); + ssl.startHandshake(); + int maxTries = 60; // 60 * 1000 = example 1 minute time out + for (int i = 0; i < maxTries; i++) { + if (log.isDebugEnabled()) + log.debug("Reading for try #" + i); + try { + in.read(b); + } catch(SSLException sslex) { + log.info("SSL Error getting client Certs",sslex); + throw sslex; + } catch (IOException e) { + // ignore - presumably the timeout + } + if (listener.completed) { + break; + } + } + ssl.setSoTimeout(oldTimeout); + if (listener.completed == false) { + throw new SocketException("SSL Cert handshake timeout"); + } + + } + + /** + * Copied from org.apache.catalina.valves.CertificateValve + */ + @Override + public Integer getKeySize() + throws IOException { + // Look up the current SSLSession + com.codefollower.douyu.http.ssl.SSLSupport.CipherData c_aux[]=ciphers; + if (session == null) + return null; + + Integer keySize = null; + synchronized(keySizeCache) { + keySize = keySizeCache.get(session); + } + + if (keySize == null) { + int size = 0; + String cipherSuite = session.getCipherSuite(); + for (int i = 0; i < c_aux.length; i++) { + if (cipherSuite.indexOf(c_aux[i].phrase) >= 0) { + size = c_aux[i].keySize; + break; + } + } + keySize = new Integer(size); + synchronized(keySizeCache) { + keySizeCache.put(session, keySize); + } + } + return keySize; + } + + @Override + public String getSessionId() + throws IOException { + // Look up the current SSLSession + if (session == null) + return null; + // Expose ssl_session (getId) + byte [] ssl_session = session.getId(); + if ( ssl_session == null) + return null; + StringBuilder buf=new StringBuilder(); + for(int x=0; x2) digit=digit.substring(digit.length()-2); + buf.append(digit); + } + return buf.toString(); + } + + + private static class Listener implements HandshakeCompletedListener { + volatile boolean completed = false; + @Override + public void handshakeCompleted(HandshakeCompletedEvent event) { + completed = true; + } + void reset() { + completed = false; + } + } + + /** + * Invalidate the session this support object is associated with. + */ + @Override + public void invalidateSession() { + session.invalidate(); + } +} + diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/jsse/NioX509KeyManager.java b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/jsse/NioX509KeyManager.java new file mode 100644 index 0000000..6498d49 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/ssl/jsse/NioX509KeyManager.java @@ -0,0 +1,92 @@ +/* + * 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 com.codefollower.douyu.http.ssl.jsse; + +import java.net.Socket; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.X509ExtendedKeyManager; +import javax.net.ssl.X509KeyManager; + +public class NioX509KeyManager extends X509ExtendedKeyManager { + + private X509KeyManager delegate; + private String serverKeyAlias; + + /** + * Constructor. + * + * @param mgr The X509KeyManager used as a delegate + * @param serverKeyAlias The alias name of the server's keypair and + * supporting certificate chain + */ + public NioX509KeyManager(X509KeyManager mgr, String serverKeyAlias) { + this.delegate = mgr; + this.serverKeyAlias = serverKeyAlias; + } + + @Override + public String chooseClientAlias(String[] keyType, Principal[] issuers, + Socket socket) { + return delegate.chooseClientAlias(keyType, issuers, socket); + } + + @Override + public String chooseServerAlias(String keyType, Principal[] issuers, + Socket socket) { + if (serverKeyAlias != null) { + return serverKeyAlias; + } + + return delegate.chooseServerAlias(keyType, issuers, socket); + } + + @Override + public X509Certificate[] getCertificateChain(String alias) { + return delegate.getCertificateChain(alias); + } + + @Override + public String[] getClientAliases(String keyType, Principal[] issuers) { + return delegate.getClientAliases(keyType, issuers); + } + + @Override + public PrivateKey getPrivateKey(String alias) { + return delegate.getPrivateKey(alias); + } + + @Override + public String[] getServerAliases(String keyType, Principal[] issuers) { + return delegate.getServerAliases(keyType, issuers); + } + + @Override + public String chooseEngineServerAlias(String keyType, Principal[] issuers, + SSLEngine engine) { + if (serverKeyAlias!=null) { + return serverKeyAlias; + } + + return super.chooseEngineServerAlias(keyType, issuers, engine); + } + +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/util/FastHttpDateFormat.java b/douyu-http/src/main/java/com/codefollower/douyu/http/util/FastHttpDateFormat.java new file mode 100644 index 0000000..5090208 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/util/FastHttpDateFormat.java @@ -0,0 +1,230 @@ +/* + * 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 com.codefollower.douyu.http.util; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Utility class to generate HTTP dates. + * + * @author Remy Maucherat + */ +public final class FastHttpDateFormat { + + + // -------------------------------------------------------------- Variables + + + private static final int CACHE_SIZE = + Integer.parseInt(System.getProperty("org.apache.tomcat.util.http.FastHttpDateFormat.CACHE_SIZE", "1000")); + + + /** + * HTTP date format. + */ + private static final SimpleDateFormat format = + new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); + + + /** + * The set of SimpleDateFormat formats to use in getDateHeader(). + */ + private static final SimpleDateFormat formats[] = { + new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US), + new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), + new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) + }; + + + private static final TimeZone gmtZone = TimeZone.getTimeZone("GMT"); + + + /** + * GMT timezone - all HTTP dates are on GMT + */ + static { + + format.setTimeZone(gmtZone); + + formats[0].setTimeZone(gmtZone); + formats[1].setTimeZone(gmtZone); + formats[2].setTimeZone(gmtZone); + + } + + + /** + * Instant on which the currentDate object was generated. + */ + private static volatile long currentDateGenerated = 0L; + + + /** + * Current formatted date. + */ + private static String currentDate = null; + + + /** + * Formatter cache. + */ + private static final ConcurrentHashMap formatCache = + new ConcurrentHashMap(CACHE_SIZE); + + + /** + * Parser cache. + */ + private static final ConcurrentHashMap parseCache = + new ConcurrentHashMap(CACHE_SIZE); + + + // --------------------------------------------------------- Public Methods + + + /** + * Get the current date in HTTP format. + */ + public static final String getCurrentDate() { + + long now = System.currentTimeMillis(); + if ((now - currentDateGenerated) > 1000) { + synchronized (format) { + if ((now - currentDateGenerated) > 1000) { + currentDate = format.format(new Date(now)); + currentDateGenerated = now; + } + } + } + return currentDate; + + } + + + /** + * Get the HTTP format of the specified date. + */ + public static final String formatDate + (long value, DateFormat threadLocalformat) { + + Long longValue = new Long(value); + String cachedDate = formatCache.get(longValue); + if (cachedDate != null) + return cachedDate; + + String newDate = null; + Date dateValue = new Date(value); + if (threadLocalformat != null) { + newDate = threadLocalformat.format(dateValue); + updateFormatCache(longValue, newDate); + } else { + synchronized (formatCache) { + synchronized (format) { + newDate = format.format(dateValue); + } + updateFormatCache(longValue, newDate); + } + } + return newDate; + + } + + + /** + * Try to parse the given date as a HTTP date. + */ + public static final long parseDate(String value, + DateFormat[] threadLocalformats) { + + Long cachedDate = parseCache.get(value); + if (cachedDate != null) + return cachedDate.longValue(); + + Long date = null; + if (threadLocalformats != null) { + date = internalParseDate(value, threadLocalformats); + updateParseCache(value, date); + } else { + synchronized (parseCache) { + date = internalParseDate(value, formats); + updateParseCache(value, date); + } + } + if (date == null) { + return (-1L); + } + + return date.longValue(); + } + + + /** + * Parse date with given formatters. + */ + private static final Long internalParseDate + (String value, DateFormat[] formats) { + Date date = null; + for (int i = 0; (date == null) && (i < formats.length); i++) { + try { + date = formats[i].parse(value); + } catch (ParseException e) { + // Ignore + } + } + if (date == null) { + return null; + } + return new Long(date.getTime()); + } + + + /** + * Update cache. + */ + private static void updateFormatCache(Long key, String value) { + if (value == null) { + return; + } + if (formatCache.size() > CACHE_SIZE) { + formatCache.clear(); + } + formatCache.put(key, value); + } + + + /** + * Update cache. + */ + private static void updateParseCache(String key, Long value) { + if (value == null) { + return; + } + if (parseCache.size() > CACHE_SIZE) { + parseCache.clear(); + } + parseCache.put(key, value); + } + + +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/util/HttpMessages.java b/douyu-http/src/main/java/com/codefollower/douyu/http/util/HttpMessages.java new file mode 100644 index 0000000..8dba269 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/util/HttpMessages.java @@ -0,0 +1,138 @@ +/* + * 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 com.codefollower.douyu.http.util; + +import com.codefollower.douyu.core.StringManager; + +/** + * Handle (internationalized) HTTP messages. + * + * @author James Duncan Davidson [duncan@eng.sun.com] + * @author James Todd [gonzo@eng.sun.com] + * @author Jason Hunter [jch@eng.sun.com] + * @author Harish Prabandham + * @author costin@eng.sun.com + */ +public class HttpMessages { + // XXX move message resources in this package + protected static final StringManager sm = + StringManager.getManager("org.douyu.ajp"); + + static String st_200=null; + static String st_302=null; + static String st_400=null; + static String st_404=null; + + /** Get the status string associated with a status code. + * No I18N - return the messages defined in the HTTP spec. + * ( the user isn't supposed to see them, this is the last + * thing to translate) + * + * Common messages are cached. + * + */ + public static String getMessage( int status ) { + // method from Response. + + // Does HTTP requires/allow international messages or + // are pre-defined? The user doesn't see them most of the time + switch( status ) { + case 200: + if( st_200==null ) st_200=sm.getString( "sc.200"); + return st_200; + case 302: + if( st_302==null ) st_302=sm.getString( "sc.302"); + return st_302; + case 400: + if( st_400==null ) st_400=sm.getString( "sc.400"); + return st_400; + case 404: + if( st_404==null ) st_404=sm.getString( "sc.404"); + return st_404; + } + return sm.getString("sc."+ status); + } + + /** + * Filter the specified message string for characters that are sensitive + * in HTML. This avoids potential attacks caused by including JavaScript + * codes in the request URL that is often reported in error messages. + * + * @param message The message string to be filtered + */ + public static String filter(String message) { + + if (message == null) + return (null); + + char content[] = new char[message.length()]; + message.getChars(0, message.length(), content, 0); + StringBuilder result = new StringBuilder(content.length + 50); + for (int i = 0; i < content.length; i++) { + switch (content[i]) { + case '<': + result.append("<"); + break; + case '>': + result.append(">"); + break; + case '&': + result.append("&"); + break; + case '"': + result.append("""); + break; + default: + result.append(content[i]); + } + } + return (result.toString()); + } + + /** + * Is the provided message safe to use in an HTTP header. Safe messages must + * meet the requirements of RFC2616 - i.e. must consist only of TEXT. + * + * @param msg The message to test + * @return true if the message is safe to use in an HTTP + * header else false + */ + public static boolean isSafeInHttpHeader(String msg) { + // Nulls are fine. It is up to the calling code to address any NPE + // concerns + if (msg == null) { + return true; + } + + // Reason-Phrase is defined as * + // TEXT is defined as any OCTET except CTLs, but including LWS + // OCTET is defined as an 8-bit sequence of data + // CTL is defined as octets 0-31 and 127 + // LWS, if we exclude CR LF pairs, is defined as SP or HT (32, 9) + final int len = msg.length(); + for (int i = 0; i < len; i++) { + char c = msg.charAt(i); + if (32 <= c && c <= 126 || 128 <= c && c <= 255 || c == 9) { + continue; + } + return false; + } + + return true; + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/websocket/DefaultWebSocketFrame.java b/douyu-http/src/main/java/com/codefollower/douyu/http/websocket/DefaultWebSocketFrame.java new file mode 100644 index 0000000..e76438d --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/websocket/DefaultWebSocketFrame.java @@ -0,0 +1,115 @@ +/* + * Copyright 2010 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http.websocket; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; +import com.codefollower.douyu.netty.util.CharsetUtil; + +/** + * The default {@link WebSocketFrame} implementation. + * + * @author The Netty Project + * @author Trustin Lee + * @version $Rev$, $Date$ + */ +public class DefaultWebSocketFrame implements WebSocketFrame { + + private int type; + private ChannelBuffer binaryData; + + /** + * Creates a new empty text frame. + */ + public DefaultWebSocketFrame() { + this(0, ChannelBuffers.EMPTY_BUFFER); + } + + /** + * Creates a new text frame from with the specified string. + */ + public DefaultWebSocketFrame(String textData) { + this(0, ChannelBuffers.copiedBuffer(textData, CharsetUtil.UTF_8)); + } + + /** + * Creates a new frame with the specified frame type and the specified data. + * + * @param type + * the type of the frame. {@code 0} is the only allowed type currently. + * @param binaryData + * the content of the frame. If (type & 0x80 == 0), + * it must be encoded in UTF-8. + * + * @throws IllegalArgumentException + * if If (type & 0x80 == 0) and the data is not encoded + * in UTF-8 + */ + public DefaultWebSocketFrame(int type, ChannelBuffer binaryData) { + setData(type, binaryData); + } + + @Override + public int getType() { + return type; + } + + @Override + public boolean isText() { + return (getType() & 0x80) == 0; + } + + @Override + public boolean isBinary() { + return !isText(); + } + + @Override + public ChannelBuffer getBinaryData() { + return binaryData; + } + + @Override + public String getTextData() { + return getBinaryData().toString(CharsetUtil.UTF_8); + } + + @Override + public void setData(int type, ChannelBuffer binaryData) { + if (binaryData == null) { + throw new NullPointerException("binaryData"); + } + + if ((type & 0x80) == 0) { + // If text, data should not contain 0xFF. + int delimPos = binaryData.indexOf( + binaryData.readerIndex(), binaryData.writerIndex(), (byte) 0xFF); + if (delimPos >= 0) { + throw new IllegalArgumentException( + "a text frame should not contain 0xFF."); + } + } + + this.type = type & 0xFF; + this.binaryData = binaryData; + } + + @Override + public String toString() { + return getClass().getSimpleName() + + "(type: " + getType() + ", " + "data: " + getBinaryData() + ')'; + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/websocket/WebSocketFrame.java b/douyu-http/src/main/java/com/codefollower/douyu/http/websocket/WebSocketFrame.java new file mode 100644 index 0000000..b4aadf6 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/websocket/WebSocketFrame.java @@ -0,0 +1,87 @@ +/* + * Copyright 2010 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http.websocket; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.buffer.ChannelBuffers; + +/** + * A Web Socket frame that represents either text or binary data. + * + * @author The Netty Project + * @author Trustin Lee + * @version $Rev$, $Date$ + */ +public interface WebSocketFrame { + + /** + * Closing handshake message (0xFF, 0x00) + */ + WebSocketFrame CLOSING_HANDSHAKE = new DefaultWebSocketFrame(0xFF, ChannelBuffers.EMPTY_BUFFER); + + /** + * Returns the type of this frame. + * 0x00-0x7F means a text frame encoded in UTF-8, and + * 0x80-0xFF means a binary frame. Currently, {@code 0} is the + * only allowed type according to the specification. + */ + int getType(); + + /** + * Returns {@code true} if and only if the content of this frame is a string + * encoded in UTF-8. + */ + boolean isText(); + + /** + * Returns {@code true} if and only if the content of this frame is an + * arbitrary binary data. + */ + boolean isBinary(); + + /** + * Returns the content of this frame as-is, with no UTF-8 decoding. + */ + ChannelBuffer getBinaryData(); + + /** + * Converts the content of this frame into a UTF-8 string and returns the + * converted string. + */ + String getTextData(); + + /** + * Sets the type and the content of this frame. + * + * @param type + * the type of the frame. {@code 0} is the only allowed type currently. + * @param binaryData + * the content of the frame. If (type & 0x80 == 0), + * it must be encoded in UTF-8. + * + * @throws IllegalArgumentException + * if If (type & 0x80 == 0) and the data is not encoded + * in UTF-8 + */ + void setData(int type, ChannelBuffer binaryData); + + /** + * Returns the string representation of this frame. Please note that this + * method is not identical to {@link #getTextData()}. + */ + @Override + String toString(); +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/websocket/WebSocketFrameDecoder.java b/douyu-http/src/main/java/com/codefollower/douyu/http/websocket/WebSocketFrameDecoder.java new file mode 100644 index 0000000..3f576c2 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/websocket/WebSocketFrameDecoder.java @@ -0,0 +1,132 @@ +/* + * Copyright 2010 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http.websocket; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.channel.Channel; +import com.codefollower.douyu.netty.channel.ChannelHandlerContext; +import com.codefollower.douyu.netty.handler.codec.frame.TooLongFrameException; +import com.codefollower.douyu.netty.handler.codec.replay.ReplayingDecoder; +import com.codefollower.douyu.netty.handler.codec.replay.VoidEnum; + +/** + * Decodes {@link ChannelBuffer}s into {@link WebSocketFrame}s. + *

+ * For the detailed instruction on adding add Web Socket support to your HTTP + * server, take a look into the WebSocketServer example located in the + * {@code org.jboss.netty.example.http.websocket} package. + * + * @author The Netty Project + * @author Mike Heath (mheath@apache.org) + * @author Trustin Lee + * @version $Rev$, $Date$ + * + * @apiviz.landmark + * @apiviz.uses org.jboss.netty.handler.codec.http.websocket.WebSocketFrame + */ +public class WebSocketFrameDecoder extends ReplayingDecoder { + + public static final int DEFAULT_MAX_FRAME_SIZE = 16384; + + private final int maxFrameSize; + private boolean receivedClosingHandshake; + + public WebSocketFrameDecoder() { + this(DEFAULT_MAX_FRAME_SIZE); + } + + /** + * Creates a new instance of {@code WebSocketFrameDecoder} with the specified {@code maxFrameSize}. If the client + * sends a frame size larger than {@code maxFrameSize}, the channel will be closed. + * + * @param maxFrameSize the maximum frame size to decode + */ + public WebSocketFrameDecoder(int maxFrameSize) { + this.maxFrameSize = maxFrameSize; + } + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, + ChannelBuffer buffer, VoidEnum state) throws Exception { + + // Discard all data received if closing handshake was received before. + if (receivedClosingHandshake) { + buffer.skipBytes(actualReadableBytes()); + return null; + } + + // Decode a frame otherwise. + byte type = buffer.readByte(); + if ((type & 0x80) == 0x80) { + // If the MSB on type is set, decode the frame length + return decodeBinaryFrame(type, buffer); + } else { + // Decode a 0xff terminated UTF-8 string + return decodeTextFrame(type, buffer); + } + } + + private WebSocketFrame decodeBinaryFrame(int type, ChannelBuffer buffer) throws TooLongFrameException { + long frameSize = 0; + int lengthFieldSize = 0; + byte b; + do { + b = buffer.readByte(); + frameSize <<= 7; + frameSize |= b & 0x7f; + if (frameSize > maxFrameSize) { + throw new TooLongFrameException(); + } + lengthFieldSize ++; + if (lengthFieldSize > 8) { + // Perhaps a malicious peer? + throw new TooLongFrameException(); + } + } while ((b & 0x80) == 0x80); + + if (type == 0xFF && frameSize == 0) { + receivedClosingHandshake = true; + } + + return new DefaultWebSocketFrame( + type, buffer.readBytes((int) frameSize)); + } + + private WebSocketFrame decodeTextFrame(int type, ChannelBuffer buffer) throws TooLongFrameException { + int ridx = buffer.readerIndex(); + int rbytes = actualReadableBytes(); + int delimPos = buffer.indexOf(ridx, ridx + rbytes, (byte) 0xFF); + if (delimPos == -1) { + // Frame delimiter (0xFF) not found + if (rbytes > maxFrameSize) { + // Frame length exceeded the maximum + throw new TooLongFrameException(); + } else { + // Wait until more data is received + return null; + } + } + + int frameSize = delimPos - ridx; + if (frameSize > maxFrameSize) { + throw new TooLongFrameException(); + } + + ChannelBuffer binaryData = buffer.readBytes(frameSize); + buffer.skipBytes(1); + return new DefaultWebSocketFrame(type, binaryData); + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/websocket/WebSocketFrameEncoder.java b/douyu-http/src/main/java/com/codefollower/douyu/http/websocket/WebSocketFrameEncoder.java new file mode 100644 index 0000000..5826551 --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/websocket/WebSocketFrameEncoder.java @@ -0,0 +1,100 @@ +/* + * Copyright 2010 Red Hat, Inc. + * + * Red Hat 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 com.codefollower.douyu.http.websocket; + +import com.codefollower.douyu.netty.buffer.ChannelBuffer; +import com.codefollower.douyu.netty.channel.Channel; +import com.codefollower.douyu.netty.channel.ChannelHandlerContext; +import com.codefollower.douyu.netty.channel.ChannelHandler.Sharable; +import com.codefollower.douyu.netty.handler.codec.oneone.OneToOneEncoder; + +/** + * Encodes a {@link WebSocketFrame} into a {@link ChannelBuffer}. + *

+ * For the detailed instruction on adding add Web Socket support to your HTTP + * server, take a look into the WebSocketServer example located in the + * {@code org.jboss.netty.example.http.websocket} package. + * + * @author The Netty Project + * @author Mike Heath (mheath@apache.org) + * @author Trustin Lee + * @version $Rev$, $Date$ + * + * @apiviz.landmark + * @apiviz.uses org.jboss.netty.handler.codec.http.websocket.WebSocketFrame + */ +@Sharable +public class WebSocketFrameEncoder extends OneToOneEncoder { + + @Override + protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { + if (msg instanceof WebSocketFrame) { + WebSocketFrame frame = (WebSocketFrame) msg; + int type = frame.getType(); + if (frame.isText()) { + // Text frame + ChannelBuffer data = frame.getBinaryData(); + ChannelBuffer encoded = + channel.getConfig().getBufferFactory().getBuffer( + data.order(), data.readableBytes() + 2); + encoded.writeByte((byte) type); + encoded.writeBytes(data, data.readerIndex(), data.readableBytes()); + encoded.writeByte((byte) 0xFF); + return encoded; + } else { + // Binary frame + ChannelBuffer data = frame.getBinaryData(); + int dataLen = data.readableBytes(); + ChannelBuffer encoded = + channel.getConfig().getBufferFactory().getBuffer( + data.order(), dataLen + 5); + + // Encode type. + encoded.writeByte((byte) type); + + // Encode length. + int b1 = dataLen >>> 28 & 0x7F; + int b2 = dataLen >>> 14 & 0x7F; + int b3 = dataLen >>> 7 & 0x7F; + int b4 = dataLen & 0x7F; + if (b1 == 0) { + if (b2 == 0) { + if (b3 == 0) { + encoded.writeByte(b4); + } else { + encoded.writeByte(b3 | 0x80); + encoded.writeByte(b4); + } + } else { + encoded.writeByte(b2 | 0x80); + encoded.writeByte(b3 | 0x80); + encoded.writeByte(b4); + } + } else { + encoded.writeByte(b1 | 0x80); + encoded.writeByte(b2 | 0x80); + encoded.writeByte(b3 | 0x80); + encoded.writeByte(b4); + } + + // Encode binary data. + encoded.writeBytes(data, data.readerIndex(), dataLen); + return encoded; + } + } + return msg; + } +} diff --git a/douyu-http/src/main/java/com/codefollower/douyu/http/websocket/package-info.java b/douyu-http/src/main/java/com/codefollower/douyu/http/websocket/package-info.java new file mode 100644 index 0000000..a1b09cd --- /dev/null +++ b/douyu-http/src/main/java/com/codefollower/douyu/http/websocket/package-info.java @@ -0,0 +1,29 @@ +/* + * Copyright 2010 Red Hat, Inc. + * + * Red Hat 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. + */ + +/** + * Encoder, decoder and their related message types for + * Web Socket data frames. + *

+ * For the detailed instruction on adding add Web Socket support to your HTTP + * server, take a look into the WebSocketServer example located in the + * {@code org.jboss.netty.example.http.websocket} package. + * * + * @apiviz.exclude OneToOne(Encoder|Decoder)$ + * @apiviz.exclude \.codec\.replay\. + * @apiviz.exclude \.Default + */ +package com.codefollower.douyu.http.websocket; diff --git a/douyu-http/src/main/resources/org/douyu/http/ssl/jsse/res/LocalStrings.properties b/douyu-http/src/main/resources/org/douyu/http/ssl/jsse/res/LocalStrings.properties new file mode 100644 index 0000000..5e8ce34 --- /dev/null +++ b/douyu-http/src/main/resources/org/douyu/http/ssl/jsse/res/LocalStrings.properties @@ -0,0 +1,20 @@ +# 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. + +jsse.alias_no_key_entry=Alias name {0} does not identify a key entry +jsse.keystore_load_failed=Failed to load keystore type {0} with path {1} due to {2} +jsse.invalid_ssl_conf=SSL configuration is invalid due to {0} +jsse.invalid_truststore_password=The provided trust store password could not be used to unlock and/or validate the trust store. Retrying to access the trust store with a null password which will skip validation. +jsse.invalidTrustManagerClassName=The trustManagerClassName provided [{0}] does not implement javax.net.ssl.TrustManager \ No newline at end of file diff --git a/douyu-http/src/main/resources/org/douyu/http/ssl/jsse/res/LocalStrings_es.properties b/douyu-http/src/main/resources/org/douyu/http/ssl/jsse/res/LocalStrings_es.properties new file mode 100644 index 0000000..6e7f813 --- /dev/null +++ b/douyu-http/src/main/resources/org/douyu/http/ssl/jsse/res/LocalStrings_es.properties @@ -0,0 +1,19 @@ +# 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. +jsse.alias_no_key_entry = El nombre de Alias {0} no identifica una entrada de clave +jsse.keystore_load_failed = No pude cargar almac\u00E9n de claves de tipo {0} con ruta {1} debido a {2} +jsse.invalid_ssl_conf = La configuraci\u00F3n SSL no es v\u00E1lida debido a {0} +jsse.invalid_truststore_password = La clave del almac\u00E9n de confianza suministrada no se pudo usar para desbloquear y/o validar el almac\u00E9n de confianza. Reintentando acceder el almac\u00E9n de confianza con una clave nula que se saltar\u00E1 la validaci\u00F3n. +jsse.invalidTrustManagerClassName = El trustManagerClassName suministrado [{0}] no implementa javax.net.ssl.TrustManager diff --git a/douyu-http/src/main/resources/org/douyu/http/ssl/jsse/res/LocalStrings_fr.properties b/douyu-http/src/main/resources/org/douyu/http/ssl/jsse/res/LocalStrings_fr.properties new file mode 100644 index 0000000..4e73340 --- /dev/null +++ b/douyu-http/src/main/resources/org/douyu/http/ssl/jsse/res/LocalStrings_fr.properties @@ -0,0 +1,16 @@ +# 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. + +jsse.alias_no_key_entry=Le nom alias {0} n''identifie pas une entr\u00e9e de clef diff --git a/douyu-http/src/main/resources/org/douyu/http/ssl/jsse/res/LocalStrings_ja.properties b/douyu-http/src/main/resources/org/douyu/http/ssl/jsse/res/LocalStrings_ja.properties new file mode 100644 index 0000000..adeb951 --- /dev/null +++ b/douyu-http/src/main/resources/org/douyu/http/ssl/jsse/res/LocalStrings_ja.properties @@ -0,0 +1,17 @@ +# 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. + +jsse.alias_no_key_entry=\u5225\u540d {0} \u306f\u30ad\u30fc\u30a8\u30f3\u30c8\u30ea\u3092\u767a\u898b\u3067\u304d\u307e\u305b\u3093 + diff --git a/douyu-http/src/main/resources/org/douyu/http/util/LocalStrings.properties b/douyu-http/src/main/resources/org/douyu/http/util/LocalStrings.properties new file mode 100644 index 0000000..33102df --- /dev/null +++ b/douyu-http/src/main/resources/org/douyu/http/util/LocalStrings.properties @@ -0,0 +1,62 @@ +# 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. + +# HttpMessages. The values in this file will be used in HTTP headers and as such +# may only contain TEXT as defined by RFC 2616 +sc.100=Continue +sc.101=Switching Protocols +sc.200=OK +sc.201=Created +sc.202=Accepted +sc.203=Non-Authoritative Information +sc.204=No Content +sc.205=Reset Content +sc.206=Partial Content +sc.207=Multi-Status +sc.300=Multiple Choices +sc.301=Moved Permanently +sc.302=Moved Temporarily +sc.303=See Other +sc.304=Not Modified +sc.305=Use Proxy +sc.307=Temporary Redirect +sc.400=Bad Request +sc.401=Unauthorized +sc.402=Payment Required +sc.403=Forbidden +sc.404=Not Found +sc.405=Method Not Allowed +sc.406=Not Acceptable +sc.407=Proxy Authentication Required +sc.408=Request Timeout +sc.409=Conflict +sc.410=Gone +sc.411=Length Required +sc.412=Precondition Failed +sc.413=Request Entity Too Large +sc.414=Request-URI Too Long +sc.415=Unsupported Media Type +sc.416=Requested Range Not Satisfiable +sc.417=Expectation Failed +sc.422=Unprocessable Entity +sc.423=Locked +sc.424=Failed Dependency +sc.500=Internal Server Error +sc.501=Not Implemented +sc.502=Bad Gateway +sc.503=Service Unavailable +sc.504=Gateway Timeout +sc.505=HTTP Version Not Supported +sc.507=Insufficient Storage diff --git a/douyu-http/src/main/resources/org/douyu/http/util/LocalStrings_es.properties b/douyu-http/src/main/resources/org/douyu/http/util/LocalStrings_es.properties new file mode 100644 index 0000000..9251884 --- /dev/null +++ b/douyu-http/src/main/resources/org/douyu/http/util/LocalStrings_es.properties @@ -0,0 +1,61 @@ +# 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. +# HttpMessages. The values in this file will be used in HTTP headers and as such +# may only contain TEXT as defined by RFC 2616 +sc.100 = Continuar +sc.101 = Cambiando Protocolos +sc.200 = OK +sc.201 = Creado +sc.202 = Aceptado +sc.203 = Informaci\u00F3n No-Autorizativa +sc.204 = Sin Contenido +sc.205 = Reponer Contenido +sc.206 = Contenido Parcial +sc.207 = Multi-Estado +sc.300 = M\u00FAltiples Elecciones +sc.301 = Movido permanentemente +sc.302 = Movido temporalmente +sc.303 = Mirar Otro +sc.304 = No Modificado +sc.305 = Usar Proxy +sc.307 = Redirecci\u00F3n Temporal +sc.400 = Petici\u00F3n incorrecta +sc.401 = No Autorizado +sc.402 = Pago requerido +sc.403 = Prohibido +sc.404 = No Encontrado +sc.405 = M\u00E9todo No Permitido +sc.406 = No Aceptable +sc.407 = Autentificaci\u00F3n Proxy Requerida +sc.408 = Request Caducada +sc.409 = Conflicto +sc.410 = Ido +sc.411 = Longitud Requerida +sc.412 = Precondici\u00F3n Fallada +sc.413 = Entidad de Request Demasiado Grande +sc.414 = Request-URI Demasiado Larga +sc.415 = Tipo de Medio No Soportado +sc.416 = El Rango Pedido No Ser Satisfecho +sc.417 = Expectativa Fallada +sc.422 = Entidad Improcesable +sc.423 = Bloqueado +sc.424 = Dependencia Fallida +sc.500 = Error Interno del Servidor +sc.501 = No Implementado +sc.502 = Pasarela Incorrecta +sc.503 = Servicio no Disponible +sc.504 = Pasarela Caducada +sc.505 = Versi\u00F3n de HTTP No Soportada +sc.507 = Almacenaje Insuficiente diff --git a/douyu-http/src/main/resources/org/douyu/http/util/LocalStrings_fr.properties b/douyu-http/src/main/resources/org/douyu/http/util/LocalStrings_fr.properties new file mode 100644 index 0000000..79e57ca --- /dev/null +++ b/douyu-http/src/main/resources/org/douyu/http/util/LocalStrings_fr.properties @@ -0,0 +1,61 @@ +# 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. + +# HttpMessages. The values in this file will be used in HTTP headers and as such +# may only contain TEXT as defined by RFC 2616 +sc.100=Continuer +sc.101=Changement de Protocols +sc.200=OK +sc.201=Cr\u00e9e +sc.202=Accept\u00e9 +sc.203=Information Sans-Autorit\u00e9 +sc.204=Pas de Contenu +sc.205=Remise \u00e0 Z\u00e9ro de Contenu +sc.206=Contenu Partiel +sc.207=Etat Multiple +sc.300=Choix Multiples +sc.301=D\u00e9plac\u00e9 de fa\u00e7on Permanente +sc.302=D\u00e9plac\u00e9 Temporairement +sc.303=Voir Autre +sc.304=Non Modifi\u00e9 +sc.305=Utilisation de Relais +sc.307=Redirection Temporaire +sc.400=Mauvaise Requ\u00eate +sc.401=Non-Autoris\u00e9 +sc.402=Paiement N\u00e9cessaire +sc.403=Interdit +sc.404=Introuvable +sc.405=M\u00e9thode Non Autoris\u00e9e +sc.406=Inacceptable +sc.407=Authentification de Relais N\u00e9cessaire +sc.408=D\u00e9passement de D\u00e9lais pour la Requ\u00eate +sc.409=Conflit +sc.410=Parti +sc.411=Taille Demand\u00e9e +sc.412=Echec de Pr\u00e9-condition +sc.413=Entit\u00e9 de Requ\u00eate Trop Grande +sc.414=URI de Requ\u00eate Trop Grande +sc.415=Type de Support Non Support\u00e9 +sc.416=Etendue de Requ\u00eate Irr\u00e9alisable +sc.417=Echec d'Attente +sc.422=Entit\u00e9 Ing\u00e9rable +sc.424=Echec de D\u00e9pendance +sc.500=Erreur Interne de Servlet +sc.501=Non Impl\u00e9ment\u00e9 +sc.502=Mauvaise Passerelle +sc.503=Service Indisponible +sc.504=D\u00e9passement de D\u00e9lais pour la Passerelle +sc.505=Version HTTP Non Support\u00e9e +sc.507=Stockage Insuffisant diff --git a/douyu-http/src/main/resources/org/douyu/http/util/LocalStrings_ja.properties b/douyu-http/src/main/resources/org/douyu/http/util/LocalStrings_ja.properties new file mode 100644 index 0000000..99234bc --- /dev/null +++ b/douyu-http/src/main/resources/org/douyu/http/util/LocalStrings_ja.properties @@ -0,0 +1,63 @@ +# 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. + +# HttpMessages. The values in this file will be used in HTTP headers and as such +# may only contain TEXT as defined by RFC 2616. Since Japanese language messages +# do not meet this requirement, English text is used. +sc.100=Continue +sc.101=Switching Protocols +sc.200=OK +sc.201=Created +sc.202=Accepted +sc.203=Non-Authoritative Information +sc.204=No Content +sc.205=Reset Content +sc.206=Partial Content +sc.207=Multi-Status +sc.300=Multiple Choices +sc.301=Moved Permanently +sc.302=Moved Temporarily +sc.303=See Other +sc.304=Not Modified +sc.305=Use Proxy +sc.307=Temporary Redirect +sc.400=Bad Request +sc.401=Unauthorized +sc.402=Payment Required +sc.403=Forbidden +sc.404=Not Found +sc.405=Method Not Allowed +sc.406=Not Acceptable +sc.407=Proxy Authentication Required +sc.408=Request Timeout +sc.409=Conflict +sc.410=Gone +sc.411=Length Required +sc.412=Precondition Failed +sc.413=Request Entity Too Large +sc.414=Request-URI Too Long +sc.415=Unsupported Media Type +sc.416=Requested Range Not Satisfiable +sc.417=Expectation Failed +sc.422=Unprocessable Entity +sc.423=Locked +sc.424=Failed Dependency +sc.500=Internal Server Error +sc.501=Not Implemented +sc.502=Bad Gateway +sc.503=Service Unavailable +sc.504=Gateway Timeout +sc.505=HTTP Version Not Supported +sc.507=Insufficient Storage diff --git a/douyu-javac/pom.xml b/douyu-javac/pom.xml new file mode 100644 index 0000000..d579dd1 --- /dev/null +++ b/douyu-javac/pom.xml @@ -0,0 +1,22 @@ + + 4.0.0 + + com.codefollower.douyu + douyu + 0.1.0 + + + douyu-javac + jar + ${douyu.version} + douyu javac + + + + com.codefollower.douyu + douyu-api + ${douyu.version} + + + diff --git a/douyu-javac/src/main/java/com/sun/source/tree/AnnotationTree.java b/douyu-javac/src/main/java/com/sun/source/tree/AnnotationTree.java new file mode 100644 index 0000000..5a9074b --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/AnnotationTree.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import java.util.List; + +/** + * A tree node for an annotation. + * + * For example: + *

+ *    {@code @}annotationType
+ *    {@code @}annotationType ( arguments )
+ * 
+ * + * @jls section 9.7 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface AnnotationTree extends ExpressionTree { + Tree getAnnotationType(); + List getArguments(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/ArrayAccessTree.java b/douyu-javac/src/main/java/com/sun/source/tree/ArrayAccessTree.java new file mode 100644 index 0000000..7e5fc2c --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/ArrayAccessTree.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for an array access expression. + * + * For example: + *
+ *   expression [ index ]
+ * 
+ * + * @jls section 15.13 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface ArrayAccessTree extends ExpressionTree { + ExpressionTree getExpression(); + ExpressionTree getIndex(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/ArrayTypeTree.java b/douyu-javac/src/main/java/com/sun/source/tree/ArrayTypeTree.java new file mode 100644 index 0000000..0c58a71 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/ArrayTypeTree.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for an array type. + * + * For example: + *
+ *   type []
+ * 
+ * + * @jls section 10.1 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface ArrayTypeTree extends Tree { + Tree getType(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/AssertTree.java b/douyu-javac/src/main/java/com/sun/source/tree/AssertTree.java new file mode 100644 index 0000000..fce758e --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/AssertTree.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for an 'assert' statement. + * + * For example: + *
+ *   assert condition ;
+ *
+ *   assert condition : detail ;
+ * 
+ * + * @jls section 14.10 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface AssertTree extends StatementTree { + ExpressionTree getCondition(); + ExpressionTree getDetail(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/AssignmentTree.java b/douyu-javac/src/main/java/com/sun/source/tree/AssignmentTree.java new file mode 100644 index 0000000..9c5480d --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/AssignmentTree.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for an assignment expression. + * + * For example: + *
+ *   variable = expression
+ * 
+ * + * @jls section 15.26.1 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface AssignmentTree extends ExpressionTree { + ExpressionTree getVariable(); + ExpressionTree getExpression(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/BinaryTree.java b/douyu-javac/src/main/java/com/sun/source/tree/BinaryTree.java new file mode 100644 index 0000000..d24497a --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/BinaryTree.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for a binary expression. + * Use {@link #getKind getKind} to determine the kind of operator. + * + * For example: + *
+ *   leftOperand operator rightOperand
+ * 
+ * + * @jls sections 15.17 to 15.24 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface BinaryTree extends ExpressionTree { + ExpressionTree getLeftOperand(); + ExpressionTree getRightOperand(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/BlockTree.java b/douyu-javac/src/main/java/com/sun/source/tree/BlockTree.java new file mode 100644 index 0000000..330309a --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/BlockTree.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import java.util.List; + +/** + * A tree node for a statement block. + * + * For example: + *
+ *   { }
+ *
+ *   { statements }
+ *
+ *   static { statements }
+ * 
+ * + * @jls section 14.2 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface BlockTree extends StatementTree { + boolean isStatic(); + List getStatements(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/BreakTree.java b/douyu-javac/src/main/java/com/sun/source/tree/BreakTree.java new file mode 100644 index 0000000..e287a98 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/BreakTree.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import javax.lang.model.element.Name; + +/** + * A tree node for a 'break' statement. + * + * For example: + *
+ *   break;
+ *
+ *   break label ;
+ * 
+ * + * @jls section 14.15 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface BreakTree extends StatementTree { + Name getLabel(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/CaseTree.java b/douyu-javac/src/main/java/com/sun/source/tree/CaseTree.java new file mode 100644 index 0000000..a273c2f --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/CaseTree.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import java.util.List; + +/** + * A tree node for a 'case' in a 'switch' statement. + * + * For example: + *
+ *   case expression :
+ *       statements
+ *
+ *   default :
+ *       statements
+ * 
+ * + * @jls section 14.11 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface CaseTree extends Tree { + /** + * @return null if and only if this Case is {@code default:} + */ + ExpressionTree getExpression(); + List getStatements(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/CatchTree.java b/douyu-javac/src/main/java/com/sun/source/tree/CatchTree.java new file mode 100644 index 0000000..9c34e86 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/CatchTree.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for a 'catch' block in a 'try' statement. + * + * For example: + *
+ *   catch ( parameter )
+ *       block
+ * 
+ * + * @jls section 14.20 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface CatchTree extends Tree { + VariableTree getParameter(); + BlockTree getBlock(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/ClassTree.java b/douyu-javac/src/main/java/com/sun/source/tree/ClassTree.java new file mode 100644 index 0000000..81c7c2f --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/ClassTree.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import java.util.List; +import javax.lang.model.element.Name; + +/** + * A tree node for a class, interface, enum, or annotation + * type declaration. + * + * For example: + *
+ *   modifiers class simpleName typeParameters
+ *       extends extendsClause
+ *       implements implementsClause
+ *   {
+ *       members
+ *   }
+ * 
+ * + * @jls sections 8.1, 8.9, 9.1, and 9.6 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface ClassTree extends StatementTree { + ModifiersTree getModifiers(); + Name getSimpleName(); + List getTypeParameters(); + Tree getExtendsClause(); + List getImplementsClause(); + List getMembers(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/CompilationUnitTree.java b/douyu-javac/src/main/java/com/sun/source/tree/CompilationUnitTree.java new file mode 100644 index 0000000..66b585c --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/CompilationUnitTree.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import java.util.List; +import javax.tools.JavaFileObject; +import com.sun.source.tree.LineMap; + +/** + * Represents the abstract syntax tree for compilation units (source + * files) and package declarations (package-info.java). + * + * @jls sections 7.3, and 7.4 + * + * @author Peter von der Ahé + * @since 1.6 + */ +public interface CompilationUnitTree extends Tree { + List getPackageAnnotations(); + ExpressionTree getPackageName(); + List getImports(); + List getTypeDecls(); + JavaFileObject getSourceFile(); + + /** + * Gets the line map for this compilation unit, if available. + * Returns null if the line map is not available. + * @return the line map for this compilation unit + */ + LineMap getLineMap(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/CompoundAssignmentTree.java b/douyu-javac/src/main/java/com/sun/source/tree/CompoundAssignmentTree.java new file mode 100644 index 0000000..86eb99c --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/CompoundAssignmentTree.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for compound assignment operator. + * Use {@link #getKind getKind} to determine the kind of operator. + * + * For example: + *
+ *   variable operator expression
+ * 
+ * + * @jls section 15.26.2 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface CompoundAssignmentTree extends ExpressionTree { + ExpressionTree getVariable(); + ExpressionTree getExpression(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/ConditionalExpressionTree.java b/douyu-javac/src/main/java/com/sun/source/tree/ConditionalExpressionTree.java new file mode 100644 index 0000000..4492611 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/ConditionalExpressionTree.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for the conditional operator ? :. + * + * For example: + *
+ *   condition ? trueExpression : falseExpression
+ * 
+ * + * @jls section 15.25 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface ConditionalExpressionTree extends ExpressionTree { + ExpressionTree getCondition(); + ExpressionTree getTrueExpression(); + ExpressionTree getFalseExpression(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/ContinueTree.java b/douyu-javac/src/main/java/com/sun/source/tree/ContinueTree.java new file mode 100644 index 0000000..98db354 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/ContinueTree.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import javax.lang.model.element.Name; + +/** + * A tree node for a 'continue' statement. + * + * For example: + *
+ *   continue;
+ *   continue label ;
+ * 
+ * + * @jls section 14.16 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface ContinueTree extends StatementTree { + Name getLabel(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/DoWhileLoopTree.java b/douyu-javac/src/main/java/com/sun/source/tree/DoWhileLoopTree.java new file mode 100644 index 0000000..4a6e20b --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/DoWhileLoopTree.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for a 'do' statement. + * + * For example: + *
+ *   do
+ *       statement
+ *   while ( expression );
+ * 
+ * + * @jls section 14.13 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface DoWhileLoopTree extends StatementTree { + ExpressionTree getCondition(); + StatementTree getStatement(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/EmptyStatementTree.java b/douyu-javac/src/main/java/com/sun/source/tree/EmptyStatementTree.java new file mode 100644 index 0000000..baf13c1 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/EmptyStatementTree.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for an empty (skip) statement. + * + * For example: + *
+ *    ;
+ * 
+ * + * @jls section 14.6 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface EmptyStatementTree extends StatementTree {} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/EnhancedForLoopTree.java b/douyu-javac/src/main/java/com/sun/source/tree/EnhancedForLoopTree.java new file mode 100644 index 0000000..f550bfa --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/EnhancedForLoopTree.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for an "enhanced" 'for' loop statement. + * + * For example: + *
+ *   for ( variable : expression )
+ *       statement
+ * 
+ * + * @jls section 14.14.2 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface EnhancedForLoopTree extends StatementTree { + VariableTree getVariable(); + ExpressionTree getExpression(); + StatementTree getStatement(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/ErroneousTree.java b/douyu-javac/src/main/java/com/sun/source/tree/ErroneousTree.java new file mode 100644 index 0000000..d379117 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/ErroneousTree.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import java.util.List; + +/** + * A tree node to stand in for a malformed expression. + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface ErroneousTree extends ExpressionTree { + List getErrorTrees(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/ExpressionStatementTree.java b/douyu-javac/src/main/java/com/sun/source/tree/ExpressionStatementTree.java new file mode 100644 index 0000000..f3661c1 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/ExpressionStatementTree.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for an expression statement. + * + * For example: + *
+ *   expression ;
+ * 
+ * + * @jls section 14.8 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface ExpressionStatementTree extends StatementTree { + ExpressionTree getExpression(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/ExpressionTree.java b/douyu-javac/src/main/java/com/sun/source/tree/ExpressionTree.java new file mode 100644 index 0000000..1ef4349 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/ExpressionTree.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node used as the base class for the different types of + * expressions. + * + * @jls chapter 15 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface ExpressionTree extends Tree {} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/ForLoopTree.java b/douyu-javac/src/main/java/com/sun/source/tree/ForLoopTree.java new file mode 100644 index 0000000..12e21b7 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/ForLoopTree.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import java.util.List; + +/** + * A tree node for a basic 'for' loop statement. + * + * For example: + *
+ *   for ( initializer ; condition ; update )
+ *       statement
+ * 
+ * + * @jls section 14.14.1 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface ForLoopTree extends StatementTree { + List getInitializer(); + ExpressionTree getCondition(); + List getUpdate(); + StatementTree getStatement(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/IdentifierTree.java b/douyu-javac/src/main/java/com/sun/source/tree/IdentifierTree.java new file mode 100644 index 0000000..1d2d8ad --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/IdentifierTree.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import javax.lang.model.element.Name; + +/** + * A tree node for an identifier expression. + * + * For example: + *
+ *   name
+ * 
+ * + * @jls section 6.5.6.1 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface IdentifierTree extends ExpressionTree { + Name getName(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/IfTree.java b/douyu-javac/src/main/java/com/sun/source/tree/IfTree.java new file mode 100644 index 0000000..f600946 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/IfTree.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for an 'if' statement. + * + * For example: + *
+ *   if ( condition )
+ *      thenStatement
+ *
+ *   if ( condition )
+ *       thenStatement
+ *   else
+ *       elseStatement
+ * 
+ * + * @jls section 14.9 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface IfTree extends StatementTree { + ExpressionTree getCondition(); + StatementTree getThenStatement(); + /** + * @return null if this if statement has no else branch. + */ + StatementTree getElseStatement(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/ImportTree.java b/douyu-javac/src/main/java/com/sun/source/tree/ImportTree.java new file mode 100644 index 0000000..1fd027d --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/ImportTree.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for an import statement. + * + * For example: + *
+ *   import qualifiedIdentifier ;
+ *
+ *   static import qualifiedIdentifier ;
+ * 
+ * + * @jls section 7.5 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface ImportTree extends Tree { + boolean isStatic(); + /** + * @return a qualified identifier ending in "*" if and only if + * this is an import-on-demand. + */ + Tree getQualifiedIdentifier(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/InstanceOfTree.java b/douyu-javac/src/main/java/com/sun/source/tree/InstanceOfTree.java new file mode 100644 index 0000000..20d95e7 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/InstanceOfTree.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for an 'instanceof' expression. + * + * For example: + *
+ *   expression instanceof type
+ * 
+ * + * @jls section 15.20.2 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface InstanceOfTree extends ExpressionTree { + ExpressionTree getExpression(); + Tree getType(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/LabeledStatementTree.java b/douyu-javac/src/main/java/com/sun/source/tree/LabeledStatementTree.java new file mode 100644 index 0000000..ba58dd4 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/LabeledStatementTree.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import javax.lang.model.element.Name; + +/** + * A tree node for a labeled statement. + * + * For example: + *
+ *   label : statement
+ * 
+ * + * @jls section 14.7 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface LabeledStatementTree extends StatementTree { + Name getLabel(); + StatementTree getStatement(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/LineMap.java b/douyu-javac/src/main/java/com/sun/source/tree/LineMap.java new file mode 100644 index 0000000..058d3bb --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/LineMap.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * Provides methods to convert between character positions and line numbers + * for a compilation unit. + * + * @since 1.6 + */ +public interface LineMap { + /** + * Find the start position of a line. + * + * @param line line number (beginning at 1) + * @return position of first character in line + * @throws IndexOutOfBoundsException + * if lineNumber < 1 + * if lineNumber > no. of lines + */ + long getStartPosition(long line); + + /** + * Find the position corresponding to a (line,column). + * + * @param line line number (beginning at 1) + * @param column tab-expanded column number (beginning 1) + * + * @return position of character + * @throws IndexOutOfBoundsException + * if {@code line < 1} + * if {@code line > no. of lines} + */ + long getPosition(long line, long column); + + /** + * Find the line containing a position; a line termination + * character is on the line it terminates. + * + * @param pos character offset of the position + * @return the line number of pos (first line is 1) + */ + long getLineNumber(long pos); + + /** + * Find the column for a character position. + * Tab characters preceding the position on the same line + * will be expanded when calculating the column number. + * + * @param pos character offset of the position + * @return the tab-expanded column number of pos (first column is 1) + */ + long getColumnNumber(long pos); + +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/LiteralTree.java b/douyu-javac/src/main/java/com/sun/source/tree/LiteralTree.java new file mode 100644 index 0000000..a8be528 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/LiteralTree.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for a literal expression. + * Use {@link #getKind getKind} to determine the kind of literal. + * + * For example: + *
+ *   value
+ * 
+ * + * @jls section 15.28 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface LiteralTree extends ExpressionTree { + Object getValue(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/MemberSelectTree.java b/douyu-javac/src/main/java/com/sun/source/tree/MemberSelectTree.java new file mode 100644 index 0000000..e5beec5 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/MemberSelectTree.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import javax.lang.model.element.Name; + +/** + * A tree node for a member access expression. + * + * For example: + *
+ *   expression . identifier
+ * 
+ * + * @jls sections 6.5, 15.11,and 15.12 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface MemberSelectTree extends ExpressionTree { + ExpressionTree getExpression(); + Name getIdentifier(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/MethodInvocationTree.java b/douyu-javac/src/main/java/com/sun/source/tree/MethodInvocationTree.java new file mode 100644 index 0000000..4728d99 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/MethodInvocationTree.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import java.util.List; + +/** + * A tree node for a method invocation expression. + * + * For example: + *
+ *   identifier ( arguments )
+ *
+ *   this . typeArguments identifier ( arguments )
+ * 
+ * + * @jls section 15.12 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface MethodInvocationTree extends ExpressionTree { + List getTypeArguments(); + ExpressionTree getMethodSelect(); + List getArguments(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/MethodTree.java b/douyu-javac/src/main/java/com/sun/source/tree/MethodTree.java new file mode 100644 index 0000000..8dfd0ba --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/MethodTree.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import java.util.List; +import javax.lang.model.element.Name; + +/** + * A tree node for a method or annotation type element declaration. + * + * For example: + *
+ *   modifiers typeParameters type name
+ *      ( parameters )
+ *      body
+ *
+ *   modifiers type name () default defaultValue
+ * 
+ * + * @jls sections 8.4, 8.6, 8.7, 9.4, and 9.6 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface MethodTree extends Tree { + ModifiersTree getModifiers(); + Name getName(); + Tree getReturnType(); + List getTypeParameters(); + List getParameters(); + List getThrows(); + BlockTree getBody(); + Tree getDefaultValue(); // for annotation types +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/ModifiersTree.java b/douyu-javac/src/main/java/com/sun/source/tree/ModifiersTree.java new file mode 100644 index 0000000..3189651 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/ModifiersTree.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import java.util.List; +import java.util.Set; +import javax.lang.model.element.Modifier; + +/** + * A tree node for the modifiers, including annotations, for a declaration. + * + * For example: + *
+ *   flags
+ *
+ *   flags annotations
+ * 
+ * + * @jls sections 8.1.1, 8.3.1, 8.4.3, 8.5.1, 8.8.3, 9.1.1, and 9.7 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface ModifiersTree extends Tree { + Set getFlags(); + List getAnnotations(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/NewArrayTree.java b/douyu-javac/src/main/java/com/sun/source/tree/NewArrayTree.java new file mode 100644 index 0000000..cdd5cd5 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/NewArrayTree.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import java.util.List; + +/** + * A tree node for an expression to create a new instance of an array. + * + * For example: + *
+ *   new type dimensions initializers
+ *
+ *   new type dimensions [ ] initializers
+ * 
+ * + * @jls section 15.10 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface NewArrayTree extends ExpressionTree { + Tree getType(); + List getDimensions(); + List getInitializers(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/NewClassTree.java b/douyu-javac/src/main/java/com/sun/source/tree/NewClassTree.java new file mode 100644 index 0000000..06d713b --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/NewClassTree.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import java.util.List; + +/** + * A tree node to declare a new instance of a class. + * + * For example: + *
+ *   new identifier ( )
+ *
+ *   new identifier ( arguments )
+ *
+ *   new typeArguments identifier ( arguments )
+ *       classBody
+ *
+ *   enclosingExpression.new identifier ( arguments )
+ * 
+ * + * @jls section 15.9 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface NewClassTree extends ExpressionTree { + ExpressionTree getEnclosingExpression(); + List getTypeArguments(); + ExpressionTree getIdentifier(); + List getArguments(); + ClassTree getClassBody(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/ParameterizedTypeTree.java b/douyu-javac/src/main/java/com/sun/source/tree/ParameterizedTypeTree.java new file mode 100644 index 0000000..aef328d --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/ParameterizedTypeTree.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import java.util.List; + +/** + * A tree node for a type expression involving type parameters. + * + * For example: + *
+ *   type < typeArguments >
+ * 
+ * + * @jls section 4.5.1 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface ParameterizedTypeTree extends Tree { + Tree getType(); + List getTypeArguments(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/ParenthesizedTree.java b/douyu-javac/src/main/java/com/sun/source/tree/ParenthesizedTree.java new file mode 100644 index 0000000..d68516f --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/ParenthesizedTree.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for a parenthesized expression. Note: parentheses + * not be preserved by the parser. + * + * For example: + *
+ *   ( expression )
+ * 
+ * + * @jls section 15.8.5 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface ParenthesizedTree extends ExpressionTree { + ExpressionTree getExpression(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/PrimitiveTypeTree.java b/douyu-javac/src/main/java/com/sun/source/tree/PrimitiveTypeTree.java new file mode 100644 index 0000000..f6cd566 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/PrimitiveTypeTree.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import javax.lang.model.type.TypeKind; + +/** + * A tree node for a primitive type. + * + * For example: + *
+ *   primitiveTypeKind
+ * 
+ * + * @jls section 4.2 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface PrimitiveTypeTree extends Tree { + TypeKind getPrimitiveTypeKind(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/ReturnTree.java b/douyu-javac/src/main/java/com/sun/source/tree/ReturnTree.java new file mode 100644 index 0000000..62a51cb --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/ReturnTree.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for a 'return' statement. + * + * For example: + *
+ *   return;
+ *   return expression;
+ * 
+ * + * @jls section 14.17 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface ReturnTree extends StatementTree { + ExpressionTree getExpression(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/Scope.java b/douyu-javac/src/main/java/com/sun/source/tree/Scope.java new file mode 100644 index 0000000..441f337 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/Scope.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import com.sun.source.tree.Tree; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; + +/** + * Interface for determining locally available program elements, such as + * local variables and imports. + * Upon creation, a Scope is associated with a given program position; + * for example, a {@linkplain Tree tree node}. This position may be used to + * infer an enclosing method and/or class. + * + *

A Scope does not itself contain the details of the elements corresponding + * to the parameters, methods and fields of the methods and classes containing + * its position. However, these elements can be determined from the enclosing + * elements. + * + *

Scopes may be contained in an enclosing scope. The outermost scope contains + * those elements available via "star import" declarations; the scope within that + * contains the top level elements of the compilation unit, including any named + * imports. + * + * @since 1.6 + */ +public interface Scope { + /** + * Returns the enclosing scope. + */ + public Scope getEnclosingScope(); + + /** + * Returns the innermost type element containing the position of this scope + */ + public TypeElement getEnclosingClass(); + + /** + * Returns the innermost executable element containing the position of this scope. + */ + public ExecutableElement getEnclosingMethod(); + + /** + * Returns the elements directly contained in this scope. + */ + public Iterable getLocalElements(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/StatementTree.java b/douyu-javac/src/main/java/com/sun/source/tree/StatementTree.java new file mode 100644 index 0000000..47dba7d --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/StatementTree.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node used as the base class for the different kinds of + * statements. + * + * @jls chapter 14 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface StatementTree extends Tree {} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/SwitchTree.java b/douyu-javac/src/main/java/com/sun/source/tree/SwitchTree.java new file mode 100644 index 0000000..c5a87c4 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/SwitchTree.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import java.util.List; + +/** + * A tree node for a 'switch' statement. + * + * For example: + *

+ *   switch ( expression ) {
+ *     cases
+ *   }
+ * 
+ * + * @jls section 14.11 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface SwitchTree extends StatementTree { + ExpressionTree getExpression(); + List getCases(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/SynchronizedTree.java b/douyu-javac/src/main/java/com/sun/source/tree/SynchronizedTree.java new file mode 100644 index 0000000..fd7cb06 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/SynchronizedTree.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for a 'synchronized' statement. + * + * For example: + *
+ *   synchronized ( expression )
+ *       block
+ * 
+ * + * @jls section 14.19 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface SynchronizedTree extends StatementTree { + ExpressionTree getExpression(); + BlockTree getBlock(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/ThrowTree.java b/douyu-javac/src/main/java/com/sun/source/tree/ThrowTree.java new file mode 100644 index 0000000..a91f63b --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/ThrowTree.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for a 'throw' statement. + * + * For example: + *
+ *   throw expression;
+ * 
+ * + * @jls section 14.18 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface ThrowTree extends StatementTree { + ExpressionTree getExpression(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/Tree.java b/douyu-javac/src/main/java/com/sun/source/tree/Tree.java new file mode 100644 index 0000000..5e26c18 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/Tree.java @@ -0,0 +1,606 @@ +/* + * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * Common interface for all nodes in an abstract syntax tree. + * + *

WARNING: This interface and its sub-interfaces are + * subject to change as the Java™ programming language evolves. + * These interfaces are implemented by the JDK Java compiler (javac) + * and should not be implemented either directly or indirectly by + * other applications. + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * + * @since 1.6 + */ +public interface Tree { + + /** + * Enumerates all kinds of trees. + */ + public enum Kind { + + /** + * Used for instances of {@link AnnotationTree}. + */ + ANNOTATION(AnnotationTree.class), + + /** + * Used for instances of {@link ArrayAccessTree}. + */ + ARRAY_ACCESS(ArrayAccessTree.class), + + /** + * Used for instances of {@link ArrayTypeTree}. + */ + ARRAY_TYPE(ArrayTypeTree.class), + + /** + * Used for instances of {@link AssertTree}. + */ + ASSERT(AssertTree.class), + + /** + * Used for instances of {@link AssignmentTree}. + */ + ASSIGNMENT(AssignmentTree.class), + + /** + * Used for instances of {@link BlockTree}. + */ + BLOCK(BlockTree.class), + + /** + * Used for instances of {@link BreakTree}. + */ + BREAK(BreakTree.class), + + /** + * Used for instances of {@link CaseTree}. + */ + CASE(CaseTree.class), + + /** + * Used for instances of {@link CatchTree}. + */ + CATCH(CatchTree.class), + + /** + * Used for instances of {@link ClassTree} representing classes. + */ + CLASS(ClassTree.class), + + /** + * Used for instances of {@link CompilationUnitTree}. + */ + COMPILATION_UNIT(CompilationUnitTree.class), + + /** + * Used for instances of {@link ConditionalExpressionTree}. + */ + CONDITIONAL_EXPRESSION(ConditionalExpressionTree.class), + + /** + * Used for instances of {@link ContinueTree}. + */ + CONTINUE(ContinueTree.class), + + /** + * Used for instances of {@link DoWhileLoopTree}. + */ + DO_WHILE_LOOP(DoWhileLoopTree.class), + + /** + * Used for instances of {@link EnhancedForLoopTree}. + */ + ENHANCED_FOR_LOOP(EnhancedForLoopTree.class), + + /** + * Used for instances of {@link ExpressionStatementTree}. + */ + EXPRESSION_STATEMENT(ExpressionStatementTree.class), + + /** + * Used for instances of {@link MemberSelectTree}. + */ + MEMBER_SELECT(MemberSelectTree.class), + + /** + * Used for instances of {@link ForLoopTree}. + */ + FOR_LOOP(ForLoopTree.class), + + /** + * Used for instances of {@link IdentifierTree}. + */ + IDENTIFIER(IdentifierTree.class), + + /** + * Used for instances of {@link IfTree}. + */ + IF(IfTree.class), + + /** + * Used for instances of {@link ImportTree}. + */ + IMPORT(ImportTree.class), + + /** + * Used for instances of {@link InstanceOfTree}. + */ + INSTANCE_OF(InstanceOfTree.class), + + /** + * Used for instances of {@link LabeledStatementTree}. + */ + LABELED_STATEMENT(LabeledStatementTree.class), + + /** + * Used for instances of {@link MethodTree}. + */ + METHOD(MethodTree.class), + + /** + * Used for instances of {@link MethodInvocationTree}. + */ + METHOD_INVOCATION(MethodInvocationTree.class), + + /** + * Used for instances of {@link ModifiersTree}. + */ + MODIFIERS(ModifiersTree.class), + + /** + * Used for instances of {@link NewArrayTree}. + */ + NEW_ARRAY(NewArrayTree.class), + + /** + * Used for instances of {@link NewClassTree}. + */ + NEW_CLASS(NewClassTree.class), + + /** + * Used for instances of {@link ParenthesizedTree}. + */ + PARENTHESIZED(ParenthesizedTree.class), + + /** + * Used for instances of {@link PrimitiveTypeTree}. + */ + PRIMITIVE_TYPE(PrimitiveTypeTree.class), + + /** + * Used for instances of {@link ReturnTree}. + */ + RETURN(ReturnTree.class), + + /** + * Used for instances of {@link EmptyStatementTree}. + */ + EMPTY_STATEMENT(EmptyStatementTree.class), + + /** + * Used for instances of {@link SwitchTree}. + */ + SWITCH(SwitchTree.class), + + /** + * Used for instances of {@link SynchronizedTree}. + */ + SYNCHRONIZED(SynchronizedTree.class), + + /** + * Used for instances of {@link ThrowTree}. + */ + THROW(ThrowTree.class), + + /** + * Used for instances of {@link TryTree}. + */ + TRY(TryTree.class), + + /** + * Used for instances of {@link ParameterizedTypeTree}. + */ + PARAMETERIZED_TYPE(ParameterizedTypeTree.class), + + /** + * Used for instances of {@link UnionTypeTree}. + */ + UNION_TYPE(UnionTypeTree.class), + + /** + * Used for instances of {@link TypeCastTree}. + */ + TYPE_CAST(TypeCastTree.class), + + /** + * Used for instances of {@link TypeParameterTree}. + */ + TYPE_PARAMETER(TypeParameterTree.class), + + /** + * Used for instances of {@link VariableTree}. + */ + VARIABLE(VariableTree.class), + + /** + * Used for instances of {@link WhileLoopTree}. + */ + WHILE_LOOP(WhileLoopTree.class), + + /** + * Used for instances of {@link UnaryTree} representing postfix + * increment operator {@code ++}. + */ + POSTFIX_INCREMENT(UnaryTree.class), + + /** + * Used for instances of {@link UnaryTree} representing postfix + * decrement operator {@code --}. + */ + POSTFIX_DECREMENT(UnaryTree.class), + + /** + * Used for instances of {@link UnaryTree} representing prefix + * increment operator {@code ++}. + */ + PREFIX_INCREMENT(UnaryTree.class), + + /** + * Used for instances of {@link UnaryTree} representing prefix + * decrement operator {@code --}. + */ + PREFIX_DECREMENT(UnaryTree.class), + + /** + * Used for instances of {@link UnaryTree} representing unary plus + * operator {@code +}. + */ + UNARY_PLUS(UnaryTree.class), + + /** + * Used for instances of {@link UnaryTree} representing unary minus + * operator {@code -}. + */ + UNARY_MINUS(UnaryTree.class), + + /** + * Used for instances of {@link UnaryTree} representing bitwise + * complement operator {@code ~}. + */ + BITWISE_COMPLEMENT(UnaryTree.class), + + /** + * Used for instances of {@link UnaryTree} representing logical + * complement operator {@code !}. + */ + LOGICAL_COMPLEMENT(UnaryTree.class), + + /** + * Used for instances of {@link BinaryTree} representing + * multiplication {@code *}. + */ + MULTIPLY(BinaryTree.class), + + /** + * Used for instances of {@link BinaryTree} representing + * division {@code /}. + */ + DIVIDE(BinaryTree.class), + + /** + * Used for instances of {@link BinaryTree} representing + * remainder {@code %}. + */ + REMAINDER(BinaryTree.class), + + /** + * Used for instances of {@link BinaryTree} representing + * addition or string concatenation {@code +}. + */ + PLUS(BinaryTree.class), + + /** + * Used for instances of {@link BinaryTree} representing + * subtraction {@code -}. + */ + MINUS(BinaryTree.class), + + /** + * Used for instances of {@link BinaryTree} representing + * left shift {@code <<}. + */ + LEFT_SHIFT(BinaryTree.class), + + /** + * Used for instances of {@link BinaryTree} representing + * right shift {@code >>}. + */ + RIGHT_SHIFT(BinaryTree.class), + + /** + * Used for instances of {@link BinaryTree} representing + * unsigned right shift {@code >>>}. + */ + UNSIGNED_RIGHT_SHIFT(BinaryTree.class), + + /** + * Used for instances of {@link BinaryTree} representing + * less-than {@code <}. + */ + LESS_THAN(BinaryTree.class), + + /** + * Used for instances of {@link BinaryTree} representing + * greater-than {@code >}. + */ + GREATER_THAN(BinaryTree.class), + + /** + * Used for instances of {@link BinaryTree} representing + * less-than-equal {@code <=}. + */ + LESS_THAN_EQUAL(BinaryTree.class), + + /** + * Used for instances of {@link BinaryTree} representing + * greater-than-equal {@code >=}. + */ + GREATER_THAN_EQUAL(BinaryTree.class), + + /** + * Used for instances of {@link BinaryTree} representing + * equal-to {@code ==}. + */ + EQUAL_TO(BinaryTree.class), + + /** + * Used for instances of {@link BinaryTree} representing + * not-equal-to {@code !=}. + */ + NOT_EQUAL_TO(BinaryTree.class), + + /** + * Used for instances of {@link BinaryTree} representing + * bitwise and logical "and" {@code &}. + */ + AND(BinaryTree.class), + + /** + * Used for instances of {@link BinaryTree} representing + * bitwise and logical "xor" {@code ^}. + */ + XOR(BinaryTree.class), + + /** + * Used for instances of {@link BinaryTree} representing + * bitwise and logical "or" {@code |}. + */ + OR(BinaryTree.class), + + /** + * Used for instances of {@link BinaryTree} representing + * conditional-and {@code &&}. + */ + CONDITIONAL_AND(BinaryTree.class), + + /** + * Used for instances of {@link BinaryTree} representing + * conditional-or {@code ||}. + */ + CONDITIONAL_OR(BinaryTree.class), + + /** + * Used for instances of {@link CompoundAssignmentTree} representing + * multiplication assignment {@code *=}. + */ + MULTIPLY_ASSIGNMENT(CompoundAssignmentTree.class), + + /** + * Used for instances of {@link CompoundAssignmentTree} representing + * division assignment {@code /=}. + */ + DIVIDE_ASSIGNMENT(CompoundAssignmentTree.class), + + /** + * Used for instances of {@link CompoundAssignmentTree} representing + * remainder assignment {@code %=}. + */ + REMAINDER_ASSIGNMENT(CompoundAssignmentTree.class), + + /** + * Used for instances of {@link CompoundAssignmentTree} representing + * addition or string concatenation assignment {@code +=}. + */ + PLUS_ASSIGNMENT(CompoundAssignmentTree.class), + + /** + * Used for instances of {@link CompoundAssignmentTree} representing + * subtraction assignment {@code -=}. + */ + MINUS_ASSIGNMENT(CompoundAssignmentTree.class), + + /** + * Used for instances of {@link CompoundAssignmentTree} representing + * left shift assignment {@code <<=}. + */ + LEFT_SHIFT_ASSIGNMENT(CompoundAssignmentTree.class), + + /** + * Used for instances of {@link CompoundAssignmentTree} representing + * right shift assignment {@code >>=}. + */ + RIGHT_SHIFT_ASSIGNMENT(CompoundAssignmentTree.class), + + /** + * Used for instances of {@link CompoundAssignmentTree} representing + * unsigned right shift assignment {@code >>>=}. + */ + UNSIGNED_RIGHT_SHIFT_ASSIGNMENT(CompoundAssignmentTree.class), + + /** + * Used for instances of {@link CompoundAssignmentTree} representing + * bitwise and logical "and" assignment {@code &=}. + */ + AND_ASSIGNMENT(CompoundAssignmentTree.class), + + /** + * Used for instances of {@link CompoundAssignmentTree} representing + * bitwise and logical "xor" assignment {@code ^=}. + */ + XOR_ASSIGNMENT(CompoundAssignmentTree.class), + + /** + * Used for instances of {@link CompoundAssignmentTree} representing + * bitwise and logical "or" assignment {@code |=}. + */ + OR_ASSIGNMENT(CompoundAssignmentTree.class), + + /** + * Used for instances of {@link LiteralTree} representing + * an integral literal expression of type {@code int}. + */ + INT_LITERAL(LiteralTree.class), + + /** + * Used for instances of {@link LiteralTree} representing + * an integral literal expression of type {@code long}. + */ + LONG_LITERAL(LiteralTree.class), + + /** + * Used for instances of {@link LiteralTree} representing + * a floating-point literal expression of type {@code float}. + */ + FLOAT_LITERAL(LiteralTree.class), + + /** + * Used for instances of {@link LiteralTree} representing + * a floating-point literal expression of type {@code double}. + */ + DOUBLE_LITERAL(LiteralTree.class), + + /** + * Used for instances of {@link LiteralTree} representing + * a boolean literal expression of type {@code boolean}. + */ + BOOLEAN_LITERAL(LiteralTree.class), + + /** + * Used for instances of {@link LiteralTree} representing + * a character literal expression of type {@code char}. + */ + CHAR_LITERAL(LiteralTree.class), + + /** + * Used for instances of {@link LiteralTree} representing + * a string literal expression of type {@link String}. + */ + STRING_LITERAL(LiteralTree.class), + + /** + * Used for instances of {@link LiteralTree} representing + * the use of {@code null}. + */ + NULL_LITERAL(LiteralTree.class), + + /** + * Used for instances of {@link WildcardTree} representing + * an unbounded wildcard type argument. + */ + UNBOUNDED_WILDCARD(WildcardTree.class), + + /** + * Used for instances of {@link WildcardTree} representing + * an extends bounded wildcard type argument. + */ + EXTENDS_WILDCARD(WildcardTree.class), + + /** + * Used for instances of {@link WildcardTree} representing + * a super bounded wildcard type argument. + */ + SUPER_WILDCARD(WildcardTree.class), + + /** + * Used for instances of {@link ErroneousTree}. + */ + ERRONEOUS(ErroneousTree.class), + + /** + * Used for instances of {@link ClassTree} representing interfaces. + */ + INTERFACE(ClassTree.class), + + /** + * Used for instances of {@link ClassTree} representing enums. + */ + ENUM(ClassTree.class), + + /** + * Used for instances of {@link ClassTree} representing annotation types. + */ + ANNOTATION_TYPE(ClassTree.class), + + /** + * An implementation-reserved node. This is the not the node + * you are looking for. + */ + OTHER(null); + + + Kind(Class intf) { + associatedInterface = intf; + } + + public Class asInterface() { + return associatedInterface; + } + + private final Class associatedInterface; + } + + /** + * Gets the kind of this tree. + * + * @return the kind of this tree. + */ + Kind getKind(); + + /** + * Accept method used to implement the visitor pattern. The + * visitor pattern is used to implement operations on trees. + * + * @param result type of this operation. + * @param type of additonal data. + */ + R accept(TreeVisitor visitor, D data); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/TreeVisitor.java b/douyu-javac/src/main/java/com/sun/source/tree/TreeVisitor.java new file mode 100644 index 0000000..ffaf0c3 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/TreeVisitor.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A visitor of trees, in the style of the visitor design pattern. + * Classes implementing this interface are used to operate + * on a tree when the kind of tree is unknown at compile time. + * When a visitor is passed to an tree's {@link Tree#accept + * accept} method, the visitXYZ method most applicable + * to that tree is invoked. + * + *

Classes implementing this interface may or may not throw a + * {@code NullPointerException} if the additional parameter {@code p} + * is {@code null}; see documentation of the implementing class for + * details. + * + *

WARNING: It is possible that methods will be added to + * this interface to accommodate new, currently unknown, language + * structures added to future versions of the Java™ programming + * language. Therefore, visitor classes directly implementing this + * interface may be source incompatible with future versions of the + * platform. + * + * @param the return type of this visitor's methods. Use {@link + * Void} for visitors that do not need to return results. + * @param

the type of the additional parameter to this visitor's + * methods. Use {@code Void} for visitors that do not need an + * additional parameter. + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * + * @since 1.6 + */ +public interface TreeVisitor { + R visitAnnotation(AnnotationTree node, P p); + R visitMethodInvocation(MethodInvocationTree node, P p); + R visitAssert(AssertTree node, P p); + R visitAssignment(AssignmentTree node, P p); + R visitCompoundAssignment(CompoundAssignmentTree node, P p); + R visitBinary(BinaryTree node, P p); + R visitBlock(BlockTree node, P p); + R visitBreak(BreakTree node, P p); + R visitCase(CaseTree node, P p); + R visitCatch(CatchTree node, P p); + R visitClass(ClassTree node, P p); + R visitConditionalExpression(ConditionalExpressionTree node, P p); + R visitContinue(ContinueTree node, P p); + R visitDoWhileLoop(DoWhileLoopTree node, P p); + R visitErroneous(ErroneousTree node, P p); + R visitExpressionStatement(ExpressionStatementTree node, P p); + R visitEnhancedForLoop(EnhancedForLoopTree node, P p); + R visitForLoop(ForLoopTree node, P p); + R visitIdentifier(IdentifierTree node, P p); + R visitIf(IfTree node, P p); + R visitImport(ImportTree node, P p); + R visitArrayAccess(ArrayAccessTree node, P p); + R visitLabeledStatement(LabeledStatementTree node, P p); + R visitLiteral(LiteralTree node, P p); + R visitMethod(MethodTree node, P p); + R visitModifiers(ModifiersTree node, P p); + R visitNewArray(NewArrayTree node, P p); + R visitNewClass(NewClassTree node, P p); + R visitParenthesized(ParenthesizedTree node, P p); + R visitReturn(ReturnTree node, P p); + R visitMemberSelect(MemberSelectTree node, P p); + R visitEmptyStatement(EmptyStatementTree node, P p); + R visitSwitch(SwitchTree node, P p); + R visitSynchronized(SynchronizedTree node, P p); + R visitThrow(ThrowTree node, P p); + R visitCompilationUnit(CompilationUnitTree node, P p); + R visitTry(TryTree node, P p); + R visitParameterizedType(ParameterizedTypeTree node, P p); + R visitUnionType(UnionTypeTree node, P p); + R visitArrayType(ArrayTypeTree node, P p); + R visitTypeCast(TypeCastTree node, P p); + R visitPrimitiveType(PrimitiveTypeTree node, P p); + R visitTypeParameter(TypeParameterTree node, P p); + R visitInstanceOf(InstanceOfTree node, P p); + R visitUnary(UnaryTree node, P p); + R visitVariable(VariableTree node, P p); + R visitWhileLoop(WhileLoopTree node, P p); + R visitWildcard(WildcardTree node, P p); + R visitOther(Tree node, P p); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/TryTree.java b/douyu-javac/src/main/java/com/sun/source/tree/TryTree.java new file mode 100644 index 0000000..f2510bd --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/TryTree.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import java.util.List; + +/** + * A tree node for a 'try' statement. + * + * For example: + *

+ *   try
+ *       block
+ *   catches
+ *   finally
+ *       finallyBlock
+ * 
+ * + * @jls section 14.20 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface TryTree extends StatementTree { + BlockTree getBlock(); + List getCatches(); + BlockTree getFinallyBlock(); + List getResources(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/TypeCastTree.java b/douyu-javac/src/main/java/com/sun/source/tree/TypeCastTree.java new file mode 100644 index 0000000..43dc619 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/TypeCastTree.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for a type cast expression. + * + * For example: + *
+ *   ( type ) expression
+ * 
+ * + * @jls section 15.16 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface TypeCastTree extends ExpressionTree { + Tree getType(); + ExpressionTree getExpression(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/TypeParameterTree.java b/douyu-javac/src/main/java/com/sun/source/tree/TypeParameterTree.java new file mode 100644 index 0000000..6a60338 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/TypeParameterTree.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import java.util.List; +import javax.lang.model.element.Name; + +/** + * A tree node for a type parameter. + * + * For example: + *
+ *   name
+ *
+ *   name extends bounds
+ * 
+ * + * @jls section 4.4 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface TypeParameterTree extends Tree { + Name getName(); + List getBounds(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/UnaryTree.java b/douyu-javac/src/main/java/com/sun/source/tree/UnaryTree.java new file mode 100644 index 0000000..7b4d9e8 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/UnaryTree.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for postfix and unary expressions. + * Use {@link #getKind getKind} to determine the kind of operator. + * + * For example: + *
+ *   operator expression
+ *
+ *   expression operator
+ * 
+ * + * @jls sections 15.14 and 15.15 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface UnaryTree extends ExpressionTree { + ExpressionTree getExpression(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/UnionTypeTree.java b/douyu-javac/src/main/java/com/sun/source/tree/UnionTypeTree.java new file mode 100644 index 0000000..75e3867 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/UnionTypeTree.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2010, 2011 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import java.util.List; + +/** + * A tree node for a union type expression in a multicatch var declaration. + * + * @author Maurizio Cimadamore + * + * @since 1.7 + */ +public interface UnionTypeTree extends Tree { + List getTypeAlternatives(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/VariableTree.java b/douyu-javac/src/main/java/com/sun/source/tree/VariableTree.java new file mode 100644 index 0000000..ea73f23 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/VariableTree.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +import javax.lang.model.element.Name; + +/** + * A tree node for a variable declaration. + * + * For example: + *
+ *   modifiers type name initializer ;
+ * 
+ * + * @jls sections 8.3 and 14.4 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface VariableTree extends StatementTree { + ModifiersTree getModifiers(); + Name getName(); + Tree getType(); + ExpressionTree getInitializer(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/WhileLoopTree.java b/douyu-javac/src/main/java/com/sun/source/tree/WhileLoopTree.java new file mode 100644 index 0000000..fd4a832 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/WhileLoopTree.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for a 'while' loop statement. + * + * For example: + *
+ *   while ( condition )
+ *     statement
+ * 
+ * + * + * @jls section 14.12 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface WhileLoopTree extends StatementTree { + ExpressionTree getCondition(); + StatementTree getStatement(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/WildcardTree.java b/douyu-javac/src/main/java/com/sun/source/tree/WildcardTree.java new file mode 100644 index 0000000..d3f62fd --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/WildcardTree.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.tree; + +/** + * A tree node for a wildcard type argument. + * Use {@link #getKind getKind} to determine the kind of bound. + * + * For example: + *
+ *   ?
+ *
+ *   ? extends bound
+ *
+ *   ? super bound
+ * 
+ * + * @jls section 4.5.1 + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface WildcardTree extends Tree { + Tree getBound(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/tree/package-info.java b/douyu-javac/src/main/java/com/sun/source/tree/package-info.java new file mode 100644 index 0000000..7a20464 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/tree/package-info.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * Provides interfaces to represent source code as abstract syntax + * trees (AST). + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +package com.sun.source.tree; diff --git a/douyu-javac/src/main/java/com/sun/source/util/JavacTask.java b/douyu-javac/src/main/java/com/sun/source/util/JavacTask.java new file mode 100644 index 0000000..2f2038f --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/util/JavacTask.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.util; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.Tree; +import java.io.IOException; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import javax.tools.JavaCompiler.CompilationTask; +import javax.tools.JavaFileObject; + +/** + * Provides access to functionality specific to the JDK Java Compiler, javac. + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public abstract class JavacTask implements CompilationTask { + + /** + * Parse the specified files returning a list of abstract syntax trees. + * + * @return a list of abstract syntax trees + * @throws IOException if an unhandled I/O error occurred in the compiler. + */ + public abstract Iterable parse() + throws IOException; + + /** + * Complete all analysis. + * + * @return a list of elements that were analyzed + * @throws IOException if an unhandled I/O error occurred in the compiler. + */ + public abstract Iterable analyze() throws IOException; + + /** + * Generate code. + * + * @return a list of files that were generated + * @throws IOException if an unhandled I/O error occurred in the compiler. + */ + public abstract Iterable generate() throws IOException; + + /** + * The specified listener will receive events describing the progress of + * this compilation task. + */ + public abstract void setTaskListener(TaskListener taskListener); + + /** + * Get a type mirror of the tree node determined by the specified path. + */ + public abstract TypeMirror getTypeMirror(Iterable path); + /** + * Get a utility object for dealing with program elements. + */ + public abstract Elements getElements(); + + /** + * Get a utility object for dealing with type mirrors. + */ + public abstract Types getTypes(); +} diff --git a/douyu-javac/src/main/java/com/sun/source/util/SimpleTreeVisitor.java b/douyu-javac/src/main/java/com/sun/source/util/SimpleTreeVisitor.java new file mode 100644 index 0000000..fc406b4 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/util/SimpleTreeVisitor.java @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.util; + +import com.sun.source.tree.*; + +/** + * A simple visitor for tree nodes. + * + * @author Peter von der Ahé + * @since 1.6 + */ +public class SimpleTreeVisitor implements TreeVisitor { + protected final R DEFAULT_VALUE; + + protected SimpleTreeVisitor() { + DEFAULT_VALUE = null; + } + + protected SimpleTreeVisitor(R defaultValue) { + DEFAULT_VALUE = defaultValue; + } + + protected R defaultAction(Tree node, P p) { + return DEFAULT_VALUE; + } + + public final R visit(Tree node, P p) { + return (node == null) ? null : node.accept(this, p); + } + + public final R visit(Iterable nodes, P p) { + R r = null; + if (nodes != null) + for (Tree node : nodes) + r = visit(node, p); + return r; + } + + public R visitCompilationUnit(CompilationUnitTree node, P p) { + return defaultAction(node, p); + } + + public R visitImport(ImportTree node, P p) { + return defaultAction(node, p); + } + + public R visitClass(ClassTree node, P p) { + return defaultAction(node, p); + } + + public R visitMethod(MethodTree node, P p) { + return defaultAction(node, p); + } + + public R visitVariable(VariableTree node, P p) { + return defaultAction(node, p); + } + + public R visitEmptyStatement(EmptyStatementTree node, P p) { + return defaultAction(node, p); + } + + public R visitBlock(BlockTree node, P p) { + return defaultAction(node, p); + } + + public R visitDoWhileLoop(DoWhileLoopTree node, P p) { + return defaultAction(node, p); + } + + public R visitWhileLoop(WhileLoopTree node, P p) { + return defaultAction(node, p); + } + + public R visitForLoop(ForLoopTree node, P p) { + return defaultAction(node, p); + } + + public R visitEnhancedForLoop(EnhancedForLoopTree node, P p) { + return defaultAction(node, p); + } + + public R visitLabeledStatement(LabeledStatementTree node, P p) { + return defaultAction(node, p); + } + + public R visitSwitch(SwitchTree node, P p) { + return defaultAction(node, p); + } + + public R visitCase(CaseTree node, P p) { + return defaultAction(node, p); + } + + public R visitSynchronized(SynchronizedTree node, P p) { + return defaultAction(node, p); + } + + public R visitTry(TryTree node, P p) { + return defaultAction(node, p); + } + + public R visitCatch(CatchTree node, P p) { + return defaultAction(node, p); + } + + public R visitConditionalExpression(ConditionalExpressionTree node, P p) { + return defaultAction(node, p); + } + + public R visitIf(IfTree node, P p) { + return defaultAction(node, p); + } + + public R visitExpressionStatement(ExpressionStatementTree node, P p) { + return defaultAction(node, p); + } + + public R visitBreak(BreakTree node, P p) { + return defaultAction(node, p); + } + + public R visitContinue(ContinueTree node, P p) { + return defaultAction(node, p); + } + + public R visitReturn(ReturnTree node, P p) { + return defaultAction(node, p); + } + + public R visitThrow(ThrowTree node, P p) { + return defaultAction(node, p); + } + + public R visitAssert(AssertTree node, P p) { + return defaultAction(node, p); + } + + public R visitMethodInvocation(MethodInvocationTree node, P p) { + return defaultAction(node, p); + } + + public R visitNewClass(NewClassTree node, P p) { + return defaultAction(node, p); + } + + public R visitNewArray(NewArrayTree node, P p) { + return defaultAction(node, p); + } + + public R visitParenthesized(ParenthesizedTree node, P p) { + return defaultAction(node, p); + } + + public R visitAssignment(AssignmentTree node, P p) { + return defaultAction(node, p); + } + + public R visitCompoundAssignment(CompoundAssignmentTree node, P p) { + return defaultAction(node, p); + } + + public R visitUnary(UnaryTree node, P p) { + return defaultAction(node, p); + } + + public R visitBinary(BinaryTree node, P p) { + return defaultAction(node, p); + } + + public R visitTypeCast(TypeCastTree node, P p) { + return defaultAction(node, p); + } + + public R visitInstanceOf(InstanceOfTree node, P p) { + return defaultAction(node, p); + } + + public R visitArrayAccess(ArrayAccessTree node, P p) { + return defaultAction(node, p); + } + + public R visitMemberSelect(MemberSelectTree node, P p) { + return defaultAction(node, p); + } + + public R visitIdentifier(IdentifierTree node, P p) { + return defaultAction(node, p); + } + + public R visitLiteral(LiteralTree node, P p) { + return defaultAction(node, p); + } + + public R visitPrimitiveType(PrimitiveTypeTree node, P p) { + return defaultAction(node, p); + } + + public R visitArrayType(ArrayTypeTree node, P p) { + return defaultAction(node, p); + } + + public R visitParameterizedType(ParameterizedTypeTree node, P p) { + return defaultAction(node, p); + } + + public R visitUnionType(UnionTypeTree node, P p) { + return defaultAction(node, p); + } + + public R visitTypeParameter(TypeParameterTree node, P p) { + return defaultAction(node, p); + } + + public R visitWildcard(WildcardTree node, P p) { + return defaultAction(node, p); + } + + public R visitModifiers(ModifiersTree node, P p) { + return defaultAction(node, p); + } + + public R visitAnnotation(AnnotationTree node, P p) { + return defaultAction(node, p); + } + + public R visitErroneous(ErroneousTree node, P p) { + return defaultAction(node, p); + } + + public R visitOther(Tree node, P p) { + return defaultAction(node, p); + } +} diff --git a/douyu-javac/src/main/java/com/sun/source/util/SourcePositions.java b/douyu-javac/src/main/java/com/sun/source/util/SourcePositions.java new file mode 100644 index 0000000..b3a9fea --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/util/SourcePositions.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.util; + +import com.sun.source.tree.*; + +/** + * Provides methods to obtain the position of a Tree within a CompilationUnit. + * A position is defined as a simple character offset from the start of a + * CompilationUnit where the first character is at offset 0. + * + * @author Peter von der Ahé + * @since 1.6 + */ +public interface SourcePositions { + + /** + * Gets the starting position of tree within file. If tree is not found within + * file, or if the starting position is not available, + * return {@link javax.tools.Diagnostic#NOPOS}. + * The returned position must be at the start of the yield of this tree, that + * is for any sub-tree of this tree, the following must hold: + * + *

+ * {@code tree.getStartPosition() <= subtree.getStartPosition()} or
+ * {@code tree.getStartPosition() == NOPOS} or
+ * {@code subtree.getStartPosition() == NOPOS} + *

+ * + * @param file CompilationUnit in which to find tree. + * @param tree tree for which a position is sought. + * @return the start position of tree. + */ + long getStartPosition(CompilationUnitTree file, Tree tree); + + /** + * Gets the ending position of tree within file. If tree is not found within + * file, or if the starting position is not available, + * return {@link javax.tools.Diagnostic#NOPOS}. + * The returned position must be at the end of the yield of this tree, + * that is for any sub-tree of this tree, the following must hold: + * + *

+ * {@code tree.getEndPosition() >= subtree.getEndPosition()} or
+ * {@code tree.getEndPosition() == NOPOS} or
+ * {@code subtree.getEndPosition() == NOPOS} + *

+ * + * In addition, the following must hold: + * + *

+ * {@code tree.getStartPosition() <= tree.getEndPosition()} or
+ * {@code tree.getStartPosition() == NOPOS} or
+ * {@code tree.getEndPosition() == NOPOS} + *

+ * + * @param file CompilationUnit in which to find tree. + * @param tree tree for which a position is sought. + * @return the end position of tree. + */ + long getEndPosition(CompilationUnitTree file, Tree tree); + +} diff --git a/douyu-javac/src/main/java/com/sun/source/util/TaskEvent.java b/douyu-javac/src/main/java/com/sun/source/util/TaskEvent.java new file mode 100644 index 0000000..7f45149 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/util/TaskEvent.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.util; + +import com.sun.source.tree.CompilationUnitTree; +import javax.lang.model.element.TypeElement; +import javax.tools.JavaFileObject; + +/** + * Provides details about work that has been done by the JDK Java Compiler, javac. + * + * @author Jonathan Gibbons + * @since 1.6 + */ +public final class TaskEvent +{ + /** + * Kind of task event. + * @since 1.6 + */ + public enum Kind { + /** + * For events related to the parsing of a file. + */ + PARSE, + /** + * For events relating to elements being entered. + **/ + ENTER, + /** + * For events relating to elements being analyzed for errors. + **/ + ANALYZE, + /** + * For events relating to class files being generated. + **/ + GENERATE, + /** + * For events relating to overall annotaion processing. + **/ + ANNOTATION_PROCESSING, + /** + * For events relating to an individual annotation processing round. + **/ + ANNOTATION_PROCESSING_ROUND + }; + + public TaskEvent(Kind kind) { + this(kind, null, null, null); + } + + public TaskEvent(Kind kind, JavaFileObject sourceFile) { + this(kind, sourceFile, null, null); + } + + public TaskEvent(Kind kind, CompilationUnitTree unit) { + this(kind, unit.getSourceFile(), unit, null); + } + + public TaskEvent(Kind kind, CompilationUnitTree unit, TypeElement clazz) { + this(kind, unit.getSourceFile(), unit, clazz); + } + + private TaskEvent(Kind kind, JavaFileObject file, CompilationUnitTree unit, TypeElement clazz) { + this.kind = kind; + this.file = file; + this.unit = unit; + this.clazz = clazz; + } + + public Kind getKind() { + return kind; + } + + public JavaFileObject getSourceFile() { + return file; + } + + public CompilationUnitTree getCompilationUnit() { + return unit; + } + + public TypeElement getTypeElement() { + return clazz; + } + + public String toString() { + return "TaskEvent[" + + kind + "," + + file + "," + // the compilation unit is identified by the file + + clazz + "]"; + } + + private Kind kind; + private JavaFileObject file; + private CompilationUnitTree unit; + private TypeElement clazz; +} diff --git a/douyu-javac/src/main/java/com/sun/source/util/TaskListener.java b/douyu-javac/src/main/java/com/sun/source/util/TaskListener.java new file mode 100644 index 0000000..602ccbf --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/util/TaskListener.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.util; + + +/** + * Provides a listener to monitor the activity of the JDK Java Compiler, javac. + * + * @author Jonathan Gibbons + * @since 1.6 + */ +public interface TaskListener +{ + public void started(TaskEvent e); + + public void finished(TaskEvent e); +} diff --git a/douyu-javac/src/main/java/com/sun/source/util/TreePath.java b/douyu-javac/src/main/java/com/sun/source/util/TreePath.java new file mode 100644 index 0000000..103f624 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/util/TreePath.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.util; + +import com.sun.source.tree.*; +import java.util.Iterator; + +/** + * A path of tree nodes, typically used to represent the sequence of ancestor + * nodes of a tree node up to the top level CompilationUnitTree node. + * + * @author Jonathan Gibbons + * @since 1.6 + */ +public class TreePath implements Iterable { + /** + * Gets a tree path for a tree node within a compilation unit. + * @return null if the node is not found + */ + public static TreePath getPath(CompilationUnitTree unit, Tree target) { + return getPath(new TreePath(unit), target); + } + + /** + * Gets a tree path for a tree node within a subtree identified by a TreePath object. + * @return null if the node is not found + */ + public static TreePath getPath(TreePath path, Tree target) { + path.getClass(); + target.getClass(); + + class Result extends Error { + static final long serialVersionUID = -5942088234594905625L; + TreePath path; + Result(TreePath path) { + this.path = path; + } + } + class PathFinder extends TreePathScanner { + public TreePath scan(Tree tree, Tree target) { + if (tree == target) + throw new Result(new TreePath(getCurrentPath(), target)); + return super.scan(tree, target); + } + } + + try { + new PathFinder().scan(path, target); + } catch (Result result) { + return result.path; + } + return null; + } + + /** + * Creates a TreePath for a root node. + */ + public TreePath(CompilationUnitTree t) { + this(null, t); + } + + /** + * Creates a TreePath for a child node. + */ + public TreePath(TreePath p, Tree t) { + if (t.getKind() == Tree.Kind.COMPILATION_UNIT) { + compilationUnit = (CompilationUnitTree) t; + parent = null; + } + else { + compilationUnit = p.compilationUnit; + parent = p; + } + leaf = t; + } + /** + * Get the compilation unit associated with this path. + */ + public CompilationUnitTree getCompilationUnit() { + return compilationUnit; + } + + /** + * Get the leaf node for this path. + */ + public Tree getLeaf() { + return leaf; + } + + /** + * Get the path for the enclosing node, or null if there is no enclosing node. + */ + public TreePath getParentPath() { + return parent; + } + + public Iterator iterator() { + return new Iterator() { + public boolean hasNext() { + return next != null; + } + + public Tree next() { + Tree t = next.leaf; + next = next.parent; + return t; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + private TreePath next = TreePath.this; + }; + } + + private CompilationUnitTree compilationUnit; + private Tree leaf; + private TreePath parent; +} diff --git a/douyu-javac/src/main/java/com/sun/source/util/TreePathScanner.java b/douyu-javac/src/main/java/com/sun/source/util/TreePathScanner.java new file mode 100644 index 0000000..4df7d24 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/util/TreePathScanner.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.util; + +import com.sun.source.tree.*; + +/** + * A TreeVisitor that visits all the child tree nodes, and provides + * support for maintaining a path for the parent nodes. + * To visit nodes of a particular type, just override the + * corresponding visitorXYZ method. + * Inside your method, call super.visitXYZ to visit descendant + * nodes. + * + * @author Jonathan Gibbons + * @since 1.6 + */ +public class TreePathScanner extends TreeScanner { + + /** + * Scan a tree from a position identified by a TreePath. + */ + public R scan(TreePath path, P p) { + this.path = path; + try { + return path.getLeaf().accept(this, p); + } finally { + this.path = null; + } + } + + /** + * Scan a single node. + * The current path is updated for the duration of the scan. + */ + @Override + public R scan(Tree tree, P p) { + if (tree == null) + return null; + + TreePath prev = path; + path = new TreePath(path, tree); + try { + return tree.accept(this, p); + } finally { + path = prev; + } + } + + /** + * Get the current path for the node, as built up by the currently + * active set of scan calls. + */ + public TreePath getCurrentPath() { + return path; + } + + private TreePath path; +} diff --git a/douyu-javac/src/main/java/com/sun/source/util/TreeScanner.java b/douyu-javac/src/main/java/com/sun/source/util/TreeScanner.java new file mode 100644 index 0000000..e87189d --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/util/TreeScanner.java @@ -0,0 +1,388 @@ +/* + * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.util; + +import com.sun.source.tree.*; + +/** + * A TreeVisitor that visits all the child tree nodes. + * To visit nodes of a particular type, just override the + * corresponding visitXYZ method. + * Inside your method, call super.visitXYZ to visit descendant + * nodes. + * + *

The default implementation of the visitXYZ methods will determine + * a result as follows: + *

    + *
  • If the node being visited has no children, the result will be null. + *
  • If the node being visited has one child, the result will be the + * result of calling {@code scan} on that child. The child may be a simple node + * or itself a list of nodes. + *
  • If the node being visited has more than one child, the result will + * be determined by calling {@code scan} each child in turn, and then combining the + * result of each scan after the first with the cumulative result + * so far, as determined by the {@link #reduce} method. Each child may be either + * a simple node of a list of nodes. The default behavior of the {@code reduce} + * method is such that the result of the visitXYZ method will be the result of + * the last child scanned. + *
+ * + *

Here is an example to count the number of identifier nodes in a tree: + *

+ *   class CountIdentifiers extends TreeScanner {
+ *      {@literal @}Override
+ *      public Integer visitIdentifier(IdentifierTree node, Void p) {
+ *          return 1;
+ *      }
+ *      {@literal @}Override
+ *      public Integer reduce(Integer r1, Integer r2) {
+ *          return (r1 == null ? 0 : r1) + (r2 == null ? 0 : r2);
+ *      }
+ *   }
+ * 
+ * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +public class TreeScanner implements TreeVisitor { + + /** Scan a single node. + */ + public R scan(Tree node, P p) { + return (node == null) ? null : node.accept(this, p); + } + + private R scanAndReduce(Tree node, P p, R r) { + return reduce(scan(node, p), r); + } + + /** Scan a list of nodes. + */ + public R scan(Iterable nodes, P p) { + R r = null; + if (nodes != null) { + boolean first = true; + for (Tree node : nodes) { + r = (first ? scan(node, p) : scanAndReduce(node, p, r)); + first = false; + } + } + return r; + } + + private R scanAndReduce(Iterable nodes, P p, R r) { + return reduce(scan(nodes, p), r); + } + + /** + * Reduces two results into a combined result. + * The default implementation is to return the first parameter. + * The general contract of the method is that it may take any action whatsoever. + */ + public R reduce(R r1, R r2) { + return r1; + } + + +/* *************************************************************************** + * Visitor methods + ****************************************************************************/ + + public R visitCompilationUnit(CompilationUnitTree node, P p) { + R r = scan(node.getPackageAnnotations(), p); + r = scanAndReduce(node.getPackageName(), p, r); + r = scanAndReduce(node.getImports(), p, r); + r = scanAndReduce(node.getTypeDecls(), p, r); + return r; + } + + public R visitImport(ImportTree node, P p) { + return scan(node.getQualifiedIdentifier(), p); + } + + public R visitClass(ClassTree node, P p) { + R r = scan(node.getModifiers(), p); + r = scanAndReduce(node.getTypeParameters(), p, r); + r = scanAndReduce(node.getExtendsClause(), p, r); + r = scanAndReduce(node.getImplementsClause(), p, r); + r = scanAndReduce(node.getMembers(), p, r); + return r; + } + + public R visitMethod(MethodTree node, P p) { + R r = scan(node.getModifiers(), p); + r = scanAndReduce(node.getReturnType(), p, r); + r = scanAndReduce(node.getTypeParameters(), p, r); + r = scanAndReduce(node.getParameters(), p, r); + r = scanAndReduce(node.getThrows(), p, r); + r = scanAndReduce(node.getBody(), p, r); + r = scanAndReduce(node.getDefaultValue(), p, r); + return r; + } + + public R visitVariable(VariableTree node, P p) { + R r = scan(node.getModifiers(), p); + r = scanAndReduce(node.getType(), p, r); + r = scanAndReduce(node.getInitializer(), p, r); + return r; + } + + public R visitEmptyStatement(EmptyStatementTree node, P p) { + return null; + } + + public R visitBlock(BlockTree node, P p) { + return scan(node.getStatements(), p); + } + + public R visitDoWhileLoop(DoWhileLoopTree node, P p) { + R r = scan(node.getStatement(), p); + r = scanAndReduce(node.getCondition(), p, r); + return r; + } + + public R visitWhileLoop(WhileLoopTree node, P p) { + R r = scan(node.getCondition(), p); + r = scanAndReduce(node.getStatement(), p, r); + return r; + } + + public R visitForLoop(ForLoopTree node, P p) { + R r = scan(node.getInitializer(), p); + r = scanAndReduce(node.getCondition(), p, r); + r = scanAndReduce(node.getUpdate(), p, r); + r = scanAndReduce(node.getStatement(), p, r); + return r; + } + + public R visitEnhancedForLoop(EnhancedForLoopTree node, P p) { + R r = scan(node.getVariable(), p); + r = scanAndReduce(node.getExpression(), p, r); + r = scanAndReduce(node.getStatement(), p, r); + return r; + } + + public R visitLabeledStatement(LabeledStatementTree node, P p) { + return scan(node.getStatement(), p); + } + + public R visitSwitch(SwitchTree node, P p) { + R r = scan(node.getExpression(), p); + r = scanAndReduce(node.getCases(), p, r); + return r; + } + + public R visitCase(CaseTree node, P p) { + R r = scan(node.getExpression(), p); + r = scanAndReduce(node.getStatements(), p, r); + return r; + } + + public R visitSynchronized(SynchronizedTree node, P p) { + R r = scan(node.getExpression(), p); + r = scanAndReduce(node.getBlock(), p, r); + return r; + } + + public R visitTry(TryTree node, P p) { + R r = scan(node.getResources(), p); + r = scanAndReduce(node.getBlock(), p, r); + r = scanAndReduce(node.getCatches(), p, r); + r = scanAndReduce(node.getFinallyBlock(), p, r); + return r; + } + + public R visitCatch(CatchTree node, P p) { + R r = scan(node.getParameter(), p); + r = scanAndReduce(node.getBlock(), p, r); + return r; + } + + public R visitConditionalExpression(ConditionalExpressionTree node, P p) { + R r = scan(node.getCondition(), p); + r = scanAndReduce(node.getTrueExpression(), p, r); + r = scanAndReduce(node.getFalseExpression(), p, r); + return r; + } + + public R visitIf(IfTree node, P p) { + R r = scan(node.getCondition(), p); + r = scanAndReduce(node.getThenStatement(), p, r); + r = scanAndReduce(node.getElseStatement(), p, r); + return r; + } + + public R visitExpressionStatement(ExpressionStatementTree node, P p) { + return scan(node.getExpression(), p); + } + + public R visitBreak(BreakTree node, P p) { + return null; + } + + public R visitContinue(ContinueTree node, P p) { + return null; + } + + public R visitReturn(ReturnTree node, P p) { + return scan(node.getExpression(), p); + } + + public R visitThrow(ThrowTree node, P p) { + return scan(node.getExpression(), p); + } + + public R visitAssert(AssertTree node, P p) { + R r = scan(node.getCondition(), p); + r = scanAndReduce(node.getDetail(), p, r); + return r; + } + + public R visitMethodInvocation(MethodInvocationTree node, P p) { + R r = scan(node.getTypeArguments(), p); + r = scanAndReduce(node.getMethodSelect(), p, r); + r = scanAndReduce(node.getArguments(), p, r); + return r; + } + + public R visitNewClass(NewClassTree node, P p) { + R r = scan(node.getEnclosingExpression(), p); + r = scanAndReduce(node.getIdentifier(), p, r); + r = scanAndReduce(node.getTypeArguments(), p, r); + r = scanAndReduce(node.getArguments(), p, r); + r = scanAndReduce(node.getClassBody(), p, r); + return r; + } + + public R visitNewArray(NewArrayTree node, P p) { + R r = scan(node.getType(), p); + r = scanAndReduce(node.getDimensions(), p, r); + r = scanAndReduce(node.getInitializers(), p, r); + return r; + } + + public R visitParenthesized(ParenthesizedTree node, P p) { + return scan(node.getExpression(), p); + } + + public R visitAssignment(AssignmentTree node, P p) { + R r = scan(node.getVariable(), p); + r = scanAndReduce(node.getExpression(), p, r); + return r; + } + + public R visitCompoundAssignment(CompoundAssignmentTree node, P p) { + R r = scan(node.getVariable(), p); + r = scanAndReduce(node.getExpression(), p, r); + return r; + } + + public R visitUnary(UnaryTree node, P p) { + return scan(node.getExpression(), p); + } + + public R visitBinary(BinaryTree node, P p) { + R r = scan(node.getLeftOperand(), p); + r = scanAndReduce(node.getRightOperand(), p, r); + return r; + } + + public R visitTypeCast(TypeCastTree node, P p) { + R r = scan(node.getType(), p); + r = scanAndReduce(node.getExpression(), p, r); + return r; + } + + public R visitInstanceOf(InstanceOfTree node, P p) { + R r = scan(node.getExpression(), p); + r = scanAndReduce(node.getType(), p, r); + return r; + } + + public R visitArrayAccess(ArrayAccessTree node, P p) { + R r = scan(node.getExpression(), p); + r = scanAndReduce(node.getIndex(), p, r); + return r; + } + + public R visitMemberSelect(MemberSelectTree node, P p) { + return scan(node.getExpression(), p); + } + + public R visitIdentifier(IdentifierTree node, P p) { + return null; + } + + public R visitLiteral(LiteralTree node, P p) { + return null; + } + + public R visitPrimitiveType(PrimitiveTypeTree node, P p) { + return null; + } + + public R visitArrayType(ArrayTypeTree node, P p) { + return scan(node.getType(), p); + } + + public R visitParameterizedType(ParameterizedTypeTree node, P p) { + R r = scan(node.getType(), p); + r = scanAndReduce(node.getTypeArguments(), p, r); + return r; + } + + public R visitUnionType(UnionTypeTree node, P p) { + return scan(node.getTypeAlternatives(), p); + } + + public R visitTypeParameter(TypeParameterTree node, P p) { + R r = scan(node.getBounds(), p); + return r; + } + + public R visitWildcard(WildcardTree node, P p) { + return scan(node.getBound(), p); + } + + public R visitModifiers(ModifiersTree node, P p) { + return scan(node.getAnnotations(), p); + } + + public R visitAnnotation(AnnotationTree node, P p) { + R r = scan(node.getAnnotationType(), p); + r = scanAndReduce(node.getArguments(), p, r); + return r; + } + + public R visitOther(Tree node, P p) { + return null; + } + + public R visitErroneous(ErroneousTree node, P p) { + return null; + } +} diff --git a/douyu-javac/src/main/java/com/sun/source/util/Trees.java b/douyu-javac/src/main/java/com/sun/source/util/Trees.java new file mode 100644 index 0000000..a787061 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/util/Trees.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.util; + +import java.lang.reflect.Method; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ErrorType; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; +import javax.tools.JavaCompiler.CompilationTask; + +import com.sun.source.tree.CatchTree; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.Scope; +import com.sun.source.tree.Tree; + +/** + * Bridges JSR 199, JSR 269, and the Tree API. + * + * @author Peter von der Ahé + */ +public abstract class Trees { + /** + * Gets a Trees object for a given CompilationTask. + * @param task the compilation task for which to get the Trees object + * @throws IllegalArgumentException if the task does not support the Trees API. + */ + public static Trees instance(CompilationTask task) { + if (!task.getClass().getName().equals("com.sun.tools.javac.api.JavacTaskImpl")) + throw new IllegalArgumentException(); + return getJavacTrees(CompilationTask.class, task); + } + + /** + * Gets a Trees object for a given ProcessingEnvironment. + * @param env the processing environment for which to get the Trees object + * @throws IllegalArgumentException if the env does not support the Trees API. + */ + public static Trees instance(ProcessingEnvironment env) { + if (!env.getClass().getName().equals("com.sun.tools.javac.processing.JavacProcessingEnvironment")) + throw new IllegalArgumentException(); + return getJavacTrees(ProcessingEnvironment.class, env); + } + + private static Trees getJavacTrees(Class argType, Object arg) { + try { + ClassLoader cl = arg.getClass().getClassLoader(); + Class c = Class.forName("com.sun.tools.javac.api.JavacTrees", false, cl); + argType = Class.forName(argType.getName(), false, cl); + Method m = c.getMethod("instance", new Class[] { argType }); + return (Trees) m.invoke(null, new Object[] { arg }); + } catch (Throwable e) { + throw new AssertionError(e); + } + } + + /** + * Gets a utility object for obtaining source positions. + */ + public abstract SourcePositions getSourcePositions(); + + /** + * Gets the Tree node for a given Element. + * Returns null if the node can not be found. + */ + public abstract Tree getTree(Element element); + + /** + * Gets the ClassTree node for a given TypeElement. + * Returns null if the node can not be found. + */ + public abstract ClassTree getTree(TypeElement element); + + /** + * Gets the MethodTree node for a given ExecutableElement. + * Returns null if the node can not be found. + */ + public abstract MethodTree getTree(ExecutableElement method); + + /** + * Gets the Tree node for an AnnotationMirror on a given Element. + * Returns null if the node can not be found. + */ + public abstract Tree getTree(Element e, AnnotationMirror a); + + /** + * Gets the Tree node for an AnnotationValue for an AnnotationMirror on a given Element. + * Returns null if the node can not be found. + */ + public abstract Tree getTree(Element e, AnnotationMirror a, AnnotationValue v); + + /** + * Gets the path to tree node within the specified compilation unit. + */ + public abstract TreePath getPath(CompilationUnitTree unit, Tree node); + + /** + * Gets the TreePath node for a given Element. + * Returns null if the node can not be found. + */ + public abstract TreePath getPath(Element e); + + /** + * Gets the TreePath node for an AnnotationMirror on a given Element. + * Returns null if the node can not be found. + */ + public abstract TreePath getPath(Element e, AnnotationMirror a); + + /** + * Gets the TreePath node for an AnnotationValue for an AnnotationMirror on a given Element. + * Returns null if the node can not be found. + */ + public abstract TreePath getPath(Element e, AnnotationMirror a, AnnotationValue v); + + /** + * Gets the Element for the Tree node identified by a given TreePath. + * Returns null if the element is not available. + * @throws IllegalArgumentException is the TreePath does not identify + * a Tree node that might have an associated Element. + */ + public abstract Element getElement(TreePath path); + + /** + * Gets the TypeMirror for the Tree node identified by a given TreePath. + * Returns null if the TypeMirror is not available. + * @throws IllegalArgumentException is the TreePath does not identify + * a Tree node that might have an associated TypeMirror. + */ + public abstract TypeMirror getTypeMirror(TreePath path); + + /** + * Gets the Scope for the Tree node identified by a given TreePath. + * Returns null if the Scope is not available. + */ + public abstract Scope getScope(TreePath path); + + /** + * Gets the doc comment, if any, for the Tree node identified by a given TreePath. + * Returns null if no doc comment was found. + */ + public abstract String getDocComment(TreePath path); + + /** + * Checks whether a given type is accessible in a given scope. + * @param scope the scope to be checked + * @param type the type to be checked + * @return true if {@code type} is accessible + */ + public abstract boolean isAccessible(Scope scope, TypeElement type); + + /** + * Checks whether the given element is accessible as a member of the given + * type in a given scope. + * @param scope the scope to be checked + * @param member the member to be checked + * @param type the type for which to check if the member is accessible + * @return true if {@code member} is accessible in {@code type} + */ + public abstract boolean isAccessible(Scope scope, Element member, DeclaredType type); + + /** + * Gets the original type from the ErrorType object. + * @param errorType The errorType for which we want to get the original type. + * @return javax.lang.model.type.TypeMirror corresponding to the original type, replaced by the ErrorType. + */ + public abstract TypeMirror getOriginalType(ErrorType errorType); + + /** + * Prints a message of the specified kind at the location of the + * tree within the provided compilation unit + * + * @param kind the kind of message + * @param msg the message, or an empty string if none + * @param t the tree to use as a position hint + * @param root the compilation unit that contains tree + */ + public abstract void printMessage(Diagnostic.Kind kind, CharSequence msg, + com.sun.source.tree.Tree t, + com.sun.source.tree.CompilationUnitTree root); + + /** + * Gets the lub of an exception parameter declared in a catch clause. + * @param tree the tree for the catch clause + * @return The lub of the exception parameter + */ + public abstract TypeMirror getLub(CatchTree tree); +} diff --git a/douyu-javac/src/main/java/com/sun/source/util/package-info.java b/douyu-javac/src/main/java/com/sun/source/util/package-info.java new file mode 100644 index 0000000..852dbc2 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/source/util/package-info.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * Provides utilities for operations on abstract syntax trees (AST). + * + * @author Peter von der Ahé + * @author Jonathan Gibbons + * @since 1.6 + */ +package com.sun.source.util; diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/Launcher.java b/douyu-javac/src/main/java/com/sun/tools/javac/Launcher.java new file mode 100644 index 0000000..8c17804 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/Launcher.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac; + +import java.io.File; +import java.util.prefs.Preferences; +import javax.swing.JFileChooser; +import javax.tools.JavaCompiler; +import javax.tools.ToolProvider; + + +/** + * Unsupported entry point for starting javac from an IDE. + * + *

Note: this class is not available in the JDK. It is not + * compiled by default and will not be in tools.jar. It is designed + * to be useful when editing the compiler sources in an IDE (as part + * of a project). Simply ensure that this class is added to + * the project and make it the main class of the project.

+ * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own + * risk. This code and its internal interfaces are subject to change + * or deletion without notice.

+ * + * @author Peter von der Ahé + * @since 1.6 + */ +class Launcher { + public static void main(String... args) { + JavaCompiler javac = ToolProvider.getSystemJavaCompiler(); + JFileChooser fileChooser; + Preferences prefs = Preferences.userNodeForPackage(Launcher.class); + if (args.length > 0) + fileChooser = new JFileChooser(args[0]); + else { + String fileName = prefs.get("recent.file", null); + fileChooser = new JFileChooser(); + if (fileName != null) { + fileChooser = new JFileChooser(); + fileChooser.setSelectedFile(new File(fileName)); + } + } + if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { + String fileName = fileChooser.getSelectedFile().getPath(); + prefs.put("recent.file", fileName); + javac.run(System.in, null, null, "-d", "/tmp", fileName); + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/Main.java b/douyu-javac/src/main/java/com/sun/tools/javac/Main.java new file mode 100644 index 0000000..e569c1b --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/Main.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac; + +import java.io.PrintWriter; +import java.lang.reflect.*; + + +/** + * The programmatic interface for the Java Programming Language + * compiler, javac. + * + *

Except for the two methods + * {@link #compile(java.lang.String[])} + * {@link #compile(java.lang.String[],java.io.PrintWriter)}, + * nothing described in this source file is part of any supported + * API. If you write code that depends on this, you do so at your own + * risk. This code and its internal interfaces are subject to change + * or deletion without notice. + */ +public class Main { + + /** Unsupported command line interface. + * @param args The command line parameters. + */ + public static void main(String[] args) throws Exception { + if (args.length > 0 && args[0].equals("-Xjdb")) { + String[] newargs = new String[args.length + 2]; + Class c = Class.forName("com.sun.tools.example.debug.tty.TTY"); + Method method = c.getDeclaredMethod ("main", new Class[] {args.getClass()}); + method.setAccessible(true); + System.arraycopy(args, 1, newargs, 3, args.length - 1); + newargs[0] = "-connect"; + newargs[1] = "com.sun.jdi.CommandLineLaunch:options=-esa -ea:com.sun.tools..."; + newargs[2] = "com.sun.tools.javac.Main"; + method.invoke(null, new Object[] { newargs }); + } else { + System.exit(compile(args)); + } + } + + /** Programmatic interface to the Java Programming Language + * compiler, javac. + * + * @param args The command line arguments that would normally be + * passed to the javac program as described in the man page. + * @return an integer equivalent to the exit value from invoking + * javac, see the man page for details. + */ + public static int compile(String[] args) { + com.sun.tools.javac.main.Main compiler = + new com.sun.tools.javac.main.Main("javac"); + return compiler.compile(args); + } + + + + /** Programmatic interface to the Java Programming Language + * compiler, javac. + * + * @param args The command line arguments that would normally be + * passed to the javac program as described in the man page. + * @param out PrintWriter to which the compiler's diagnostic + * output is directed. + * @return an integer equivalent to the exit value from invoking + * javac, see the man page for details. + */ + public static int compile(String[] args, PrintWriter out) { + com.sun.tools.javac.main.Main compiler = + new com.sun.tools.javac.main.Main("javac", out); + return compiler.compile(args); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/Server.java b/douyu-javac/src/main/java/com/sun/tools/javac/Server.java new file mode 100644 index 0000000..fcb2127 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/Server.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac; + +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.logging.Logger; +import javax.tools.*; + +/** + * Java Compiler Server. Can be used to speed up a set of (small) + * compilation tasks by caching jar files between compilations. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own + * risk. This code and its internal interfaces are subject to change + * or deletion without notice.

+ * + * @author Peter von der Ahé + * @since 1.6 + */ +class Server implements Runnable { + private final BufferedReader in; + private final OutputStream out; + private final boolean isSocket; + private static final JavaCompiler tool = ToolProvider.getSystemJavaCompiler(); + private static Logger logger = Logger.getLogger("com.sun.tools.javac"); + static class CwdFileManager extends ForwardingJavaFileManager { + String cwd; + CwdFileManager(JavaFileManager fileManager) { + super(fileManager); + } + String getAbsoluteName(String name) { + if (new File(name).isAbsolute()) { + return name; + } else { + return new File(cwd,name).getPath(); + } + } +// public JavaFileObject getFileForInput(String name) +// throws IOException +// { +// return super.getFileForInput(getAbsoluteName(name)); +// } + } + // static CwdFileManager fm = new CwdFileManager(tool.getStandardFileManager()); + static StandardJavaFileManager fm = tool.getStandardFileManager(null, null, null); + static { + // Use the same file manager for all compilations. This will + // cache jar files in the standard file manager. Use + // tool.getStandardFileManager().close() to release. + // FIXME tool.setFileManager(fm); + logger.setLevel(java.util.logging.Level.SEVERE); + } + private Server(BufferedReader in, OutputStream out, boolean isSocket) { + this.in = in; + this.out = out; + this.isSocket = isSocket; + } + private Server(BufferedReader in, OutputStream out) { + this(in, out, false); + } + private Server(Socket socket) throws IOException, UnsupportedEncodingException { + this(new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8")), + socket.getOutputStream(), + true); + } + public void run() { + List args = new ArrayList(); + int res = -1; + try { + String line = null; + try { + line = in.readLine(); + } catch (IOException e) { + System.err.println(e.getLocalizedMessage()); + System.exit(0); + line = null; + } + // fm.cwd=null; + String cwd = null; + while (line != null) { + if (line.startsWith("PWD:")) { + cwd = line.substring(4); + } else if (line.equals("END")) { + break; + } else if (!"-XDstdout".equals(line)) { + args.add(line); + } + try { + line = in.readLine(); + } catch (IOException e) { + System.err.println(e.getLocalizedMessage()); + System.exit(0); + line = null; + } + } + Iterable path = cwd == null ? null : Arrays.asList(new File(cwd)); + // try { in.close(); } catch (IOException e) {} + long msec = System.currentTimeMillis(); + try { + synchronized (tool) { + for (StandardLocation location : StandardLocation.values()) + fm.setLocation(location, path); + res = compile(out, fm, args); + // FIXME res = tool.run((InputStream)null, null, out, args.toArray(new String[args.size()])); + } + } catch (Throwable ex) { + logger.log(java.util.logging.Level.SEVERE, args.toString(), ex); + PrintWriter p = new PrintWriter(out, true); + ex.printStackTrace(p); + p.flush(); + } + if (res >= 3) { + logger.severe(String.format("problem: %s", args)); + } else { + logger.info(String.format("success: %s", args)); + } + // res = compile(args.toArray(new String[args.size()]), out); + msec -= System.currentTimeMillis(); + logger.info(String.format("Real time: %sms", -msec)); + } finally { + if (!isSocket) { + try { in.close(); } catch (IOException e) {} + } + try { + out.write(String.format("EXIT: %s%n", res).getBytes()); + } catch (IOException ex) { + logger.log(java.util.logging.Level.SEVERE, args.toString(), ex); + } + try { + out.flush(); + out.close(); + } catch (IOException ex) { + logger.log(java.util.logging.Level.SEVERE, args.toString(), ex); + } + logger.info(String.format("EXIT: %s", res)); + } + } + public static void main(String... args) throws FileNotFoundException { + if (args.length == 2) { + for (;;) { + throw new UnsupportedOperationException("TODO"); +// BufferedReader in = new BufferedReader(new FileReader(args[0])); +// PrintWriter out = new PrintWriter(args[1]); +// new Server(in, out).run(); +// System.out.flush(); +// System.err.flush(); + } + } else { + ExecutorService pool = Executors.newCachedThreadPool(); + try + { + ServerSocket socket = new ServerSocket(0xcafe, -1, null); + for (;;) { + pool.execute(new Server(socket.accept())); + } + } + catch (IOException e) { + System.err.format("Error: %s%n", e.getLocalizedMessage()); + pool.shutdown(); + } + } + } + + private int compile(OutputStream out, StandardJavaFileManager fm, List args) { + // FIXME parse args and use getTask + // System.err.println("Running " + args); + return tool.run(null, null, out, args.toArray(new String[args.size()])); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/api/ClientCodeWrapper.java b/douyu-javac/src/main/java/com/sun/tools/javac/api/ClientCodeWrapper.java new file mode 100644 index 0000000..88a279c --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/api/ClientCodeWrapper.java @@ -0,0 +1,593 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +package com.sun.tools.javac.api; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.element.NestingKind; +import javax.tools.Diagnostic; +import javax.tools.FileObject; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileManager.Location; +import javax.tools.JavaFileObject; + +import com.sun.source.util.TaskEvent; +import com.sun.source.util.TaskListener; +import com.sun.tools.javac.util.ClientCodeException; +import com.sun.tools.javac.util.Context; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import javax.lang.model.element.Modifier; +import javax.tools.DiagnosticListener; +import javax.tools.JavaFileObject.Kind; + +/** + * Wrap objects to enable unchecked exceptions to be caught and handled. + * + * For each method, exceptions are handled as follows: + *
    + *
  • Checked exceptions are left alone and propogate upwards in the + * obvious way, since they are an expected aspect of the method's + * specification. + *
  • Unchecked exceptions which have already been caught and wrapped in + * ClientCodeException are left alone to continue propogating upwards. + *
  • All other unchecked exceptions (i.e. subtypes of RuntimeException + * and Error) and caught, and rethrown as a ClientCodeException with + * its cause set to the original exception. + *
+ * + * The intent is that ClientCodeException can be caught at an appropriate point + * in the program and can be distinguished from any unanticipated unchecked + * exceptions arising in the main body of the code (i.e. bugs.) When the + * ClientCodeException has been caught, either a suitable message can be + * generated, or if appropriate, the original cause can be rethrown. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class ClientCodeWrapper { + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface Trusted { } + + public static ClientCodeWrapper instance(Context context) { + ClientCodeWrapper instance = context.get(ClientCodeWrapper.class); + if (instance == null) + instance = new ClientCodeWrapper(context); + return instance; + } + + /** + * A map to cache the results of whether or not a specific classes can + * be "trusted", and thus does not need to be wrapped. + */ + Map, Boolean> trustedClasses; + + protected ClientCodeWrapper(Context context) { + trustedClasses = new HashMap, Boolean>(); + } + + public JavaFileManager wrap(JavaFileManager fm) { + if (isTrusted(fm)) + return fm; + return new WrappedJavaFileManager(fm); + } + + public FileObject wrap(FileObject fo) { + if (isTrusted(fo)) + return fo; + return new WrappedFileObject(fo); + } + + FileObject unwrap(FileObject fo) { + if (fo instanceof WrappedFileObject) + return ((WrappedFileObject) fo).clientFileObject; + else + return fo; + } + + public JavaFileObject wrap(JavaFileObject fo) { + if (isTrusted(fo)) + return fo; + return new WrappedJavaFileObject(fo); + } + + public Iterable wrapJavaFileObjects(Iterable list) { + List wrapped = new ArrayList(); + for (JavaFileObject fo : list) + wrapped.add(wrap(fo)); + return Collections.unmodifiableList(wrapped); + } + + JavaFileObject unwrap(JavaFileObject fo) { + if (fo instanceof WrappedJavaFileObject) + return ((JavaFileObject) ((WrappedJavaFileObject) fo).clientFileObject); + else + return fo; + } + + DiagnosticListener wrap(DiagnosticListener dl) { + if (isTrusted(dl)) + return dl; + return new WrappedDiagnosticListener(dl); + } + + TaskListener wrap(TaskListener tl) { + if (isTrusted(tl)) + return tl; + return new WrappedTaskListener(tl); + } + + protected boolean isTrusted(Object o) { + Class c = o.getClass(); + Boolean trusted = trustedClasses.get(c); + if (trusted == null) { + trusted = c.getName().startsWith("com.sun.tools.javac.") + || c.isAnnotationPresent(Trusted.class); + trustedClasses.put(c, trusted); + } + return trusted; + } + + // + + // FIXME: all these classes should be converted to use multi-catch when + // that is available in the bootstrap compiler. + + protected class WrappedJavaFileManager implements JavaFileManager { + protected JavaFileManager clientJavaFileManager; + WrappedJavaFileManager(JavaFileManager clientJavaFileManager) { + clientJavaFileManager.getClass(); // null check + this.clientJavaFileManager = clientJavaFileManager; + } + + @Override + public ClassLoader getClassLoader(Location location) { + try { + return clientJavaFileManager.getClassLoader(location); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public Iterable list(Location location, String packageName, Set kinds, boolean recurse) throws IOException { + try { + return wrapJavaFileObjects(clientJavaFileManager.list(location, packageName, kinds, recurse)); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public String inferBinaryName(Location location, JavaFileObject file) { + try { + return clientJavaFileManager.inferBinaryName(location, unwrap(file)); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public boolean isSameFile(FileObject a, FileObject b) { + try { + return clientJavaFileManager.isSameFile(unwrap(a), unwrap(b)); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public boolean handleOption(String current, Iterator remaining) { + try { + return clientJavaFileManager.handleOption(current, remaining); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public boolean hasLocation(Location location) { + try { + return clientJavaFileManager.hasLocation(location); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException { + try { + return wrap(clientJavaFileManager.getJavaFileForInput(location, className, kind)); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException { + try { + return wrap(clientJavaFileManager.getJavaFileForOutput(location, className, kind, unwrap(sibling))); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException { + try { + return wrap(clientJavaFileManager.getFileForInput(location, packageName, relativeName)); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException { + try { + return wrap(clientJavaFileManager.getFileForOutput(location, packageName, relativeName, unwrap(sibling))); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public void flush() throws IOException { + try { + clientJavaFileManager.flush(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public void close() throws IOException { + try { + clientJavaFileManager.close(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public int isSupportedOption(String option) { + try { + return clientJavaFileManager.isSupportedOption(option); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + } + + protected class WrappedFileObject implements FileObject { + protected FileObject clientFileObject; + WrappedFileObject(FileObject clientFileObject) { + clientFileObject.getClass(); // null check + this.clientFileObject = clientFileObject; + } + + @Override + public URI toUri() { + try { + return clientFileObject.toUri(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public String getName() { + try { + return clientFileObject.getName(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public InputStream openInputStream() throws IOException { + try { + return clientFileObject.openInputStream(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public OutputStream openOutputStream() throws IOException { + try { + return clientFileObject.openOutputStream(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public Reader openReader(boolean ignoreEncodingErrors) throws IOException { + try { + return clientFileObject.openReader(ignoreEncodingErrors); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + try { + return clientFileObject.getCharContent(ignoreEncodingErrors); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public Writer openWriter() throws IOException { + try { + return clientFileObject.openWriter(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public long getLastModified() { + try { + return clientFileObject.getLastModified(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public boolean delete() { + try { + return clientFileObject.delete(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + } + + protected class WrappedJavaFileObject extends WrappedFileObject implements JavaFileObject { + WrappedJavaFileObject(JavaFileObject clientJavaFileObject) { + super(clientJavaFileObject); + } + + @Override + public Kind getKind() { + try { + return ((JavaFileObject)clientFileObject).getKind(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public boolean isNameCompatible(String simpleName, Kind kind) { + try { + return ((JavaFileObject)clientFileObject).isNameCompatible(simpleName, kind); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public NestingKind getNestingKind() { + try { + return ((JavaFileObject)clientFileObject).getNestingKind(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public Modifier getAccessLevel() { + try { + return ((JavaFileObject)clientFileObject).getAccessLevel(); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + } + + protected class WrappedDiagnosticListener implements DiagnosticListener { + protected DiagnosticListener clientDiagnosticListener; + WrappedDiagnosticListener(DiagnosticListener clientDiagnosticListener) { + clientDiagnosticListener.getClass(); // null check + this.clientDiagnosticListener = clientDiagnosticListener; + } + + @Override + public void report(Diagnostic diagnostic) { + try { + clientDiagnosticListener.report(diagnostic); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + } + + protected class WrappedTaskListener implements TaskListener { + protected TaskListener clientTaskListener; + WrappedTaskListener(TaskListener clientTaskListener) { + clientTaskListener.getClass(); // null check + this.clientTaskListener = clientTaskListener; + } + + @Override + public void started(TaskEvent ev) { + try { + clientTaskListener.started(ev); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + + @Override + public void finished(TaskEvent ev) { + try { + clientTaskListener.finished(ev); + } catch (ClientCodeException e) { + throw e; + } catch (RuntimeException e) { + throw new ClientCodeException(e); + } catch (Error e) { + throw new ClientCodeException(e); + } + } + } + + // +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/api/DiagnosticFormatter.java b/douyu-javac/src/main/java/com/sun/tools/javac/api/DiagnosticFormatter.java new file mode 100644 index 0000000..18aafee --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/api/DiagnosticFormatter.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2008, 2009, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.sun.tools.javac.api; + +import java.util.Locale; +import java.util.Set; +import javax.tools.Diagnostic; +import com.sun.tools.javac.api.DiagnosticFormatter.*; + +/** + * Provides simple functionalities for javac diagnostic formatting. + * @param type of diagnostic handled by this formatter + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public interface DiagnosticFormatter> { + + /** + * Whether the source code output for this diagnostic is to be displayed. + * + * @param diag diagnostic to be formatted + * @return true if the source line this diagnostic refers to is to be displayed + */ + boolean displaySource(D diag); + + /** + * Format the contents of a diagnostics. + * + * @param diag the diagnostic to be formatted + * @param l locale object to be used for i18n + * @return a string representing the diagnostic + */ + public String format(D diag, Locale l); + + /** + * Controls the way in which a diagnostic message is displayed. + * + * @param diag diagnostic to be formatted + * @param l locale object to be used for i18n + * @return string representation of the diagnostic message + */ + public String formatMessage(D diag,Locale l); + + /** + * Controls the way in which a diagnostic kind is displayed. + * + * @param diag diagnostic to be formatted + * @param l locale object to be used for i18n + * @return string representation of the diagnostic prefix + */ + public String formatKind(D diag, Locale l); + + /** + * Controls the way in which a diagnostic source is displayed. + * + * @param diag diagnostic to be formatted + * @param l locale object to be used for i18n + * @param fullname whether the source fullname should be printed + * @return string representation of the diagnostic source + */ + public String formatSource(D diag, boolean fullname, Locale l); + + /** + * Controls the way in which a diagnostic position is displayed. + * + * @param diag diagnostic to be formatted + * @param pk enum constant representing the position kind + * @param l locale object to be used for i18n + * @return string representation of the diagnostic position + */ + public String formatPosition(D diag, PositionKind pk, Locale l); + //where + /** + * This enum defines a set of constants for all the kinds of position + * that a diagnostic can be asked for. All positions are intended to be + * relative to a given diagnostic source. + */ + public enum PositionKind { + /** + * Start position + */ + START, + /** + * End position + */ + END, + /** + * Line number + */ + LINE, + /** + * Column number + */ + COLUMN, + /** + * Offset position + */ + OFFSET + } + + /** + * Get a list of all the enabled verbosity options. + * @return verbosity options + */ + public Configuration getConfiguration(); + //where + + /** + * This interface provides functionalities for tuning the output of a + * diagnostic formatter in multiple ways. + */ + interface Configuration { + /** + * Configure the set of diagnostic parts that should be displayed + * by the formatter. + * @param options options to set + */ + public void setVisible(Set visibleParts); + + /** + * Retrieve the set of diagnostic parts that should be displayed + * by the formatter. + * @return verbosity options + */ + public Set getVisible(); + + //where + /** + * A given diagnostic message can be divided into sub-parts each of which + * might/might not be displayed by the formatter, according to the + * current configuration settings. + */ + public enum DiagnosticPart { + /** + * Short description of the diagnostic - usually one line long. + */ + SUMMARY, + /** + * Longer description that provides additional details w.r.t. the ones + * in the diagnostic's description. + */ + DETAILS, + /** + * Source line the diagnostic refers to (if applicable). + */ + SOURCE, + /** + * Subdiagnostics attached to a given multiline diagnostic. + */ + SUBDIAGNOSTICS, + /** + * JLS paragraph this diagnostic might refer to (if applicable). + */ + JLS; + } + + /** + * Set a limit for multiline diagnostics. + * Note: Setting a limit has no effect if multiline diagnostics are either + * fully enabled or disabled. + * + * @param limit the kind of limit to be set + * @param value the limit value + */ + public void setMultilineLimit(MultilineLimit limit, int value); + + /** + * Get a multiline diagnostic limit. + * + * @param limit the kind of limit to be retrieved + * @return limit value or -1 if no limit is set + */ + public int getMultilineLimit(MultilineLimit limit); + //where + /** + * A multiline limit control the verbosity of multiline diagnostics + * either by setting a maximum depth of nested multidiagnostics, + * or by limiting the amount of subdiagnostics attached to a given + * diagnostic (or both). + */ + public enum MultilineLimit { + /** + * Controls the maximum depth of nested multiline diagnostics. + */ + DEPTH, + /** + * Controls the maximum amount of subdiagnostics that are part of a + * given multiline diagnostic. + */ + LENGTH; + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/api/Formattable.java b/douyu-javac/src/main/java/com/sun/tools/javac/api/Formattable.java new file mode 100644 index 0000000..bb2bd92 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/api/Formattable.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.api; + +import java.util.Locale; + +/** + * This interface must be implemented by any javac class that has non-trivial + * formatting needs (e.g. where toString() does not apply because of localization). + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Maurizio Cimadamore + */ +public interface Formattable { + + /** + * Used to obtain a localized String representing the object accordingly + * to a given locale + * + * @param locale locale in which the object's representation is to be rendered + * @param messages messages object used for localization + * @return a locale-dependent string representing the object + */ + public String toString(Locale locale, Messages messages); + /** + * Retrieve a pretty name of this object's kind + * @return a string representing the object's kind + */ + String getKind(); + + static class LocalizedString implements Formattable { + String key; + + public LocalizedString(String key) { + this.key = key; + } + + public String toString(java.util.Locale l, Messages messages) { + return messages.getLocalizedString(l, key); + } + public String getKind() { + return "LocalizedString"; + } + + public String toString() { + return key; + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/api/JavacScope.java b/douyu-javac/src/main/java/com/sun/tools/javac/api/JavacScope.java new file mode 100644 index 0000000..5aa4c0c --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/api/JavacScope.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2006, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.api; + + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; + +import com.sun.tools.javac.comp.AttrContext; +import com.sun.tools.javac.comp.Env; + + + +/** + * Provides an implementation of Scope. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own + * risk. This code and its internal interfaces are subject to change + * or deletion without notice.

+ * + * @author Jonathan Gibbons; + */ +public class JavacScope implements com.sun.source.tree.Scope { + protected final Env env; + + /** Creates a new instance of JavacScope */ + JavacScope(Env env) { + env.getClass(); // null-check + this.env = env; + } + + public JavacScope getEnclosingScope() { + if (env.outer != null && env.outer != env) + return new JavacScope(env.outer); + else { + // synthesize an outermost "star-import" scope + return new JavacScope(env) { + public boolean isStarImportScope() { + return true; + } + public JavacScope getEnclosingScope() { + return null; + } + public Iterable getLocalElements() { + return env.toplevel.starImportScope.getElements(); + } + }; + } + } + + public TypeElement getEnclosingClass() { + // hide the dummy class that javac uses to enclose the top level declarations + return (env.outer == null || env.outer == env ? null : env.enclClass.sym); + } + + public ExecutableElement getEnclosingMethod() { + return (env.enclMethod == null ? null : env.enclMethod.sym); + } + + public Iterable getLocalElements() { + return env.info.getLocalElements(); + } + + public Env getEnv() { + return env; + } + + public boolean isStarImportScope() { + return false; + } + + public boolean equals(Object other) { + if (other instanceof JavacScope) { + JavacScope s = (JavacScope) other; + return (env.equals(s.env) + && isStarImportScope() == s.isStarImportScope()); + } else + return false; + } + + public int hashCode() { + return env.hashCode() + (isStarImportScope() ? 1 : 0); + } + + public String toString() { + return "JavacScope[env=" + env + ",starImport=" + isStarImportScope() + "]"; + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/api/JavacTaskImpl.java b/douyu-javac/src/main/java/com/sun/tools/javac/api/JavacTaskImpl.java new file mode 100644 index 0000000..4a61f5a --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/api/JavacTaskImpl.java @@ -0,0 +1,518 @@ +/* + * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.api; + +import java.io.File; +import java.io.IOException; +import java.nio.CharBuffer; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.annotation.processing.Processor; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import javax.tools.*; + +import com.sun.source.tree.*; +import com.sun.source.util.*; +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.comp.*; +import com.sun.tools.javac.file.JavacFileManager; +import com.sun.tools.javac.main.*; +import com.sun.tools.javac.model.*; +import com.sun.tools.javac.parser.Parser; +import com.sun.tools.javac.parser.ParserFactory; +import com.sun.tools.javac.tree.*; +import com.sun.tools.javac.tree.JCTree.*; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.main.JavaCompiler; + +/** + * Provides access to functionality specific to the JDK Java Compiler, javac. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own + * risk. This code and its internal interfaces are subject to change + * or deletion without notice.

+ * + * @author Peter von der Ahé + * @author Jonathan Gibbons + */ +public class JavacTaskImpl extends JavacTask { + private ClientCodeWrapper ccw; + private Main compilerMain; + private JavaCompiler compiler; + private Locale locale; + private String[] args; + private Context context; + private List fileObjects; + private Map notYetEntered; + private ListBuffer> genList; + private TaskListener taskListener; + private AtomicBoolean used = new AtomicBoolean(); + private Iterable processors; + + private Integer result = null; + + JavacTaskImpl(Main compilerMain, + String[] args, + Context context, + List fileObjects) { + this.ccw = ClientCodeWrapper.instance(context); + this.compilerMain = compilerMain; + this.args = args; + this.context = context; + this.fileObjects = fileObjects; + setLocale(Locale.getDefault()); + // null checks + compilerMain.getClass(); + args.getClass(); + fileObjects.getClass(); + } + + JavacTaskImpl(Main compilerMain, + Iterable flags, + Context context, + Iterable classes, + Iterable fileObjects) { + this(compilerMain, toArray(flags, classes), context, toList(fileObjects)); + } + + static private String[] toArray(Iterable flags, Iterable classes) { + ListBuffer result = new ListBuffer(); + if (flags != null) + for (String flag : flags) + result.append(flag); + if (classes != null) + for (String cls : classes) + result.append(cls); + return result.toArray(new String[result.length()]); + } + + static private List toList(Iterable fileObjects) { + if (fileObjects == null) + return List.nil(); + ListBuffer result = new ListBuffer(); + for (JavaFileObject fo : fileObjects) + result.append(fo); + return result.toList(); + } + + public Boolean call() { + if (!used.getAndSet(true)) { + initContext(); + notYetEntered = new HashMap(); + compilerMain.setAPIMode(true); + result = compilerMain.compile(args, context, fileObjects, processors); + cleanup(); + return result == 0; + } else { + throw new IllegalStateException("multiple calls to method 'call'"); + } + } + + public void setProcessors(Iterable processors) { + processors.getClass(); // null check + // not mt-safe + if (used.get()) + throw new IllegalStateException(); + this.processors = processors; + } + + public void setLocale(Locale locale) { + if (used.get()) + throw new IllegalStateException(); + this.locale = locale; + } + + private void prepareCompiler() throws IOException { + if (used.getAndSet(true)) { + if (compiler == null) + throw new IllegalStateException(); + } else { + initContext(); + compilerMain.setOptions(Options.instance(context)); + compilerMain.filenames = new ListBuffer(); + List filenames = compilerMain.processArgs(CommandLine.parse(args)); + if (!filenames.isEmpty()) + throw new IllegalArgumentException("Malformed arguments " + filenames.toString(" ")); + compiler = JavaCompiler.instance(context); + compiler.keepComments = true; + compiler.genEndPos = true; + // NOTE: this value will be updated after annotation processing + compiler.initProcessAnnotations(processors); + notYetEntered = new HashMap(); + for (JavaFileObject file: fileObjects) + notYetEntered.put(file, null); + genList = new ListBuffer>(); + // endContext will be called when all classes have been generated + // TODO: should handle the case after each phase if errors have occurred + args = null; + } + } + + private void initContext() { + context.put(JavacTaskImpl.class, this); + if (context.get(TaskListener.class) != null) + context.put(TaskListener.class, (TaskListener)null); + if (taskListener != null) + context.put(TaskListener.class, ccw.wrap(taskListener)); + //initialize compiler's default locale + context.put(Locale.class, locale); + } + + void cleanup() { + if (compiler != null) + compiler.close(); + compiler = null; + compilerMain = null; + args = null; + context = null; + fileObjects = null; + notYetEntered = null; + } + + /** + * Construct a JavaFileObject from the given file. + * + *

TODO: this method is useless here

+ * + * @param file a file + * @return a JavaFileObject from the standard file manager. + */ + public JavaFileObject asJavaFileObject(File file) { + JavacFileManager fm = (JavacFileManager)context.get(JavaFileManager.class); + return fm.getRegularFile(file); + } + + public void setTaskListener(TaskListener taskListener) { + this.taskListener = taskListener; + } + + /** + * Parse the specified files returning a list of abstract syntax trees. + * + * @throws java.io.IOException TODO + * @return a list of abstract syntax trees + */ + public Iterable parse() throws IOException { + try { + prepareCompiler(); + List units = compiler.parseFiles(fileObjects); + for (JCCompilationUnit unit: units) { + JavaFileObject file = unit.getSourceFile(); + if (notYetEntered.containsKey(file)) + notYetEntered.put(file, unit); + } + return units; + } + finally { + parsed = true; + if (compiler != null && compiler.log != null) + compiler.log.flush(); + } + } + + private boolean parsed = false; + + /** + * Translate all the abstract syntax trees to elements. + * + * @throws IOException TODO + * @return a list of elements corresponding to the top level + * classes in the abstract syntax trees + */ + public Iterable enter() throws IOException { + return enter(null); + } + + /** + * Translate the given abstract syntax trees to elements. + * + * @param trees a list of abstract syntax trees. + * @throws java.io.IOException TODO + * @return a list of elements corresponding to the top level + * classes in the abstract syntax trees + */ + public Iterable enter(Iterable trees) + throws IOException + { + prepareCompiler(); + + ListBuffer roots = null; + + if (trees == null) { + // If there are still files which were specified to be compiled + // (i.e. in fileObjects) but which have not yet been entered, + // then we make sure they have been parsed and add them to the + // list to be entered. + if (notYetEntered.size() > 0) { + if (!parsed) + parse(); // TODO would be nice to specify files needed to be parsed + for (JavaFileObject file: fileObjects) { + JCCompilationUnit unit = notYetEntered.remove(file); + if (unit != null) { + if (roots == null) + roots = new ListBuffer(); + roots.append(unit); + } + } + notYetEntered.clear(); + } + } + else { + for (CompilationUnitTree cu : trees) { + if (cu instanceof JCCompilationUnit) { + if (roots == null) + roots = new ListBuffer(); + roots.append((JCCompilationUnit)cu); + notYetEntered.remove(cu.getSourceFile()); + } + else + throw new IllegalArgumentException(cu.toString()); + } + } + + if (roots == null) + return List.nil(); + + try { + List units = compiler.enterTrees(roots.toList()); + + if (notYetEntered.isEmpty()) + compiler = compiler.processAnnotations(units); + + ListBuffer elements = new ListBuffer(); + for (JCCompilationUnit unit : units) { + for (JCTree node : unit.defs) { + if (node.getTag() == JCTree.CLASSDEF) { + JCClassDecl cdef = (JCClassDecl) node; + if (cdef.sym != null) // maybe null if errors in anno processing + elements.append(cdef.sym); + } + } + } + return elements.toList(); + } + finally { + compiler.log.flush(); + } + } + + /** + * Complete all analysis. + * @throws IOException TODO + */ + @Override + public Iterable analyze() throws IOException { + return analyze(null); + } + + /** + * Complete all analysis on the given classes. + * This can be used to ensure that all compile time errors are reported. + * The classes must have previously been returned from {@link #enter}. + * If null is specified, all outstanding classes will be analyzed. + * + * @param classes a list of class elements + */ + // This implementation requires that we open up privileges on JavaCompiler. + // An alternative implementation would be to move this code to JavaCompiler and + // wrap it here + public Iterable analyze(Iterable classes) throws IOException { + enter(null); // ensure all classes have been entered + + final ListBuffer results = new ListBuffer(); + try { + if (classes == null) { + handleFlowResults(compiler.flow(compiler.attribute(compiler.todo)), results); + } else { + Filter f = new Filter() { + public void process(Env env) { + handleFlowResults(compiler.flow(compiler.attribute(env)), results); + } + }; + f.run(compiler.todo, classes); + } + } finally { + compiler.log.flush(); + } + return results; + } + // where + private void handleFlowResults(Queue> queue, ListBuffer elems) { + for (Env env: queue) { + switch (env.tree.getTag()) { + case JCTree.CLASSDEF: + JCClassDecl cdef = (JCClassDecl) env.tree; + if (cdef.sym != null) + elems.append(cdef.sym); + break; + case JCTree.TOPLEVEL: + JCCompilationUnit unit = (JCCompilationUnit) env.tree; + if (unit.packge != null) + elems.append(unit.packge); + break; + } + } + genList.addAll(queue); + } + + + /** + * Generate code. + * @throws IOException TODO + */ + @Override + public Iterable generate() throws IOException { + return generate(null); + } + + /** + * Generate code corresponding to the given classes. + * The classes must have previously been returned from {@link #enter}. + * If there are classes outstanding to be analyzed, that will be done before + * any classes are generated. + * If null is specified, code will be generated for all outstanding classes. + * + * @param classes a list of class elements + */ + public Iterable generate(Iterable classes) throws IOException { + final ListBuffer results = new ListBuffer(); + try { + analyze(null); // ensure all classes have been parsed, entered, and analyzed + + if (classes == null) { + compiler.generate(compiler.desugar(genList), results); + genList.clear(); + } + else { + Filter f = new Filter() { + public void process(Env env) { + compiler.generate(compiler.desugar(ListBuffer.of(env)), results); + } + }; + f.run(genList, classes); + } + if (genList.isEmpty()) { + compiler.reportDeferredDiagnostics(); + cleanup(); + } + } + finally { + if (compiler != null) + compiler.log.flush(); + } + return results; + } + + public TypeMirror getTypeMirror(Iterable path) { + // TODO: Should complete attribution if necessary + Tree last = null; + for (Tree node : path) + last = node; + return ((JCTree)last).type; + } + + public JavacElements getElements() { + if (context == null) + throw new IllegalStateException(); + return JavacElements.instance(context); + } + + public JavacTypes getTypes() { + if (context == null) + throw new IllegalStateException(); + return JavacTypes.instance(context); + } + + public Iterable pathFor(CompilationUnitTree unit, Tree node) { + return TreeInfo.pathFor((JCTree) node, (JCTree.JCCompilationUnit) unit).reverse(); + } + + abstract class Filter { + void run(Queue> list, Iterable classes) { + Set set = new HashSet(); + for (TypeElement item: classes) + set.add(item); + + ListBuffer> defer = ListBuffer.>lb(); + while (list.peek() != null) { + Env env = list.remove(); + ClassSymbol csym = env.enclClass.sym; + if (csym != null && set.contains(csym.outermostClass())) + process(env); + else + defer = defer.append(env); + } + + list.addAll(defer); + } + + abstract void process(Env env); + } + + /** + * For internal use only. This method will be + * removed without warning. + */ + public Context getContext() { + return context; + } + + /** + * For internal use only. This method will be + * removed without warning. + */ + public void updateContext(Context newContext) { + context = newContext; + } + + /** + * For internal use only. This method will be + * removed without warning. + */ + public Type parseType(String expr, TypeElement scope) { + if (expr == null || expr.equals("")) + throw new IllegalArgumentException(); + compiler = JavaCompiler.instance(context); + JavaFileObject prev = compiler.log.useSource(null); + ParserFactory parserFactory = ParserFactory.instance(context); + Attr attr = Attr.instance(context); + try { + CharBuffer buf = CharBuffer.wrap((expr+"\u0000").toCharArray(), 0, expr.length()); + Parser parser = parserFactory.newParser(buf, false, false, false); + JCTree tree = parser.parseType(); + return attr.attribType(tree, (Symbol.TypeSymbol)scope); + } finally { + compiler.log.useSource(prev); + } + } + +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/api/JavacTool.java b/douyu-javac/src/main/java/com/sun/tools/javac/api/JavacTool.java new file mode 100644 index 0000000..8b015d2 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/api/JavacTool.java @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.api; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import javax.lang.model.SourceVersion; +import javax.tools.*; + +import com.sun.source.util.JavacTask; +import com.sun.tools.javac.file.JavacFileManager; +import com.sun.tools.javac.main.JavacOption.OptionKind; +import com.sun.tools.javac.main.JavacOption; +import com.sun.tools.javac.main.Main; +import com.sun.tools.javac.main.RecognizedOptions.GrumpyHelper; +import com.sun.tools.javac.main.RecognizedOptions; +import com.sun.tools.javac.util.ClientCodeException; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Log; +import com.sun.tools.javac.util.Options; +import com.sun.tools.javac.util.Pair; + +/** + * TODO: describe com.sun.tools.javac.api.Tool + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own + * risk. This code and its internal interfaces are subject to change + * or deletion without notice.

+ * + * @author Peter von der Ah\u00e9 + */ +public final class JavacTool implements JavaCompiler { + private final List> options + = new ArrayList>(); + private final Context dummyContext = new Context(); + + private final PrintWriter silent = new PrintWriter(new OutputStream(){ + public void write(int b) {} + }); + + private final Main sharedCompiler = new Main("javac", silent); + { + sharedCompiler.setOptions(Options.instance(dummyContext)); + } + + /** + * Constructor used by service provider mechanism. The correct way to + * obtain an instance of this class is using create or the service provider + * mechanism. + * @see javax.tools.JavaCompilerTool + * @see javax.tools.ToolProvider + * @see #create + */ + @Deprecated + public JavacTool() {} + + /** + * Static factory method for creating new instances of this tool. + * @return new instance of this tool + */ + public static JavacTool create() { + return new JavacTool(); + } + + private String argsToString(Object... args) { + String newArgs = null; + if (args.length > 0) { + StringBuilder sb = new StringBuilder(); + String separator = ""; + for (Object arg : args) { + sb.append(separator).append(arg.toString()); + separator = File.pathSeparator; + } + newArgs = sb.toString(); + } + return newArgs; + } + + private void setOption1(String name, OptionKind kind, Object... args) { + String arg = argsToString(args); + JavacOption option = sharedCompiler.getOption(name); + if (option == null || !match(kind, option.getKind())) + throw new IllegalArgumentException(name); + if ((args.length != 0) != option.hasArg()) + throw new IllegalArgumentException(name); + if (option.hasArg()) { + if (option.process(null, name, arg)) // FIXME + throw new IllegalArgumentException(name); + } else { + if (option.process(null, name)) // FIXME + throw new IllegalArgumentException(name); + } + options.add(new Pair(name,arg)); + } + + public void setOption(String name, Object... args) { + setOption1(name, OptionKind.NORMAL, args); + } + + public void setExtendedOption(String name, Object... args) { + setOption1(name, OptionKind.EXTENDED, args); + } + + private static boolean match(OptionKind clientKind, OptionKind optionKind) { + return (clientKind == (optionKind == OptionKind.HIDDEN ? OptionKind.EXTENDED : optionKind)); + } + + public JavacFileManager getStandardFileManager( + DiagnosticListener diagnosticListener, + Locale locale, + Charset charset) { + Context context = new Context(); + context.put(Locale.class, locale); + if (diagnosticListener != null) + context.put(DiagnosticListener.class, diagnosticListener); + PrintWriter pw = (charset == null) + ? new PrintWriter(System.err, true) + : new PrintWriter(new OutputStreamWriter(System.err, charset), true); + context.put(Log.outKey, pw); + return new JavacFileManager(context, true, charset); + } + + public JavacTask getTask(Writer out, + JavaFileManager fileManager, + DiagnosticListener diagnosticListener, + Iterable options, + Iterable classes, + Iterable compilationUnits) + { + try { + Context context = new Context(); + ClientCodeWrapper ccw = ClientCodeWrapper.instance(context); + + final String kindMsg = "All compilation units must be of SOURCE kind"; + if (options != null) + for (String option : options) + option.getClass(); // null check + if (classes != null) { + for (String cls : classes) + if (!SourceVersion.isName(cls)) // implicit null check + throw new IllegalArgumentException("Not a valid class name: " + cls); + } + if (compilationUnits != null) { + compilationUnits = ccw.wrapJavaFileObjects(compilationUnits); // implicit null check + for (JavaFileObject cu : compilationUnits) { + if (cu.getKind() != JavaFileObject.Kind.SOURCE) + throw new IllegalArgumentException(kindMsg); + } + } + + if (diagnosticListener != null) + context.put(DiagnosticListener.class, ccw.wrap(diagnosticListener)); + + if (out == null) + context.put(Log.outKey, new PrintWriter(System.err, true)); + else + context.put(Log.outKey, new PrintWriter(out, true)); + + if (fileManager == null) + fileManager = getStandardFileManager(diagnosticListener, null, null); + fileManager = ccw.wrap(fileManager); + context.put(JavaFileManager.class, fileManager); + processOptions(context, fileManager, options); + Main compiler = new Main("javacTask", context.get(Log.outKey)); + return new JavacTaskImpl(compiler, options, context, classes, compilationUnits); + } catch (ClientCodeException ex) { + throw new RuntimeException(ex.getCause()); + } + } + + private static void processOptions(Context context, + JavaFileManager fileManager, + Iterable options) + { + if (options == null) + return; + + Options optionTable = Options.instance(context); + + JavacOption[] recognizedOptions = + RecognizedOptions.getJavacToolOptions(new GrumpyHelper()); + Iterator flags = options.iterator(); + while (flags.hasNext()) { + String flag = flags.next(); + int j; + for (j=0; j getSourceVersions() { + return Collections.unmodifiableSet(EnumSet.range(SourceVersion.RELEASE_3, + SourceVersion.latest())); + } + + public int isSupportedOption(String option) { + JavacOption[] recognizedOptions = + RecognizedOptions.getJavacToolOptions(new GrumpyHelper()); + for (JavacOption o : recognizedOptions) { + if (o.matches(option)) + return o.hasArg() ? 1 : 0; + } + return -1; + } + +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/api/JavacTrees.java b/douyu-javac/src/main/java/com/sun/tools/javac/api/JavacTrees.java new file mode 100644 index 0000000..b684d29 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/api/JavacTrees.java @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.api; + +import java.io.IOException; +import java.util.Map; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; + +import com.sun.source.tree.CatchTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.Scope; +import com.sun.source.tree.Tree; +import com.sun.source.util.SourcePositions; +import com.sun.source.util.TreePath; +import com.sun.source.util.Trees; +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.code.Symbol.TypeSymbol; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Type.UnionClassType; +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.comp.Resolve; +import com.sun.tools.javac.model.JavacElements; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import com.sun.tools.javac.tree.JCTree.*; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.TreeCopier; +import com.sun.tools.javac.tree.TreeInfo; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.JCDiagnostic; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Log; +import com.sun.tools.javac.util.Pair; + +/** + * Provides an implementation of Trees. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own + * risk. This code and its internal interfaces are subject to change + * or deletion without notice.

+ * + * @author Peter von der Ahé + */ +public class JavacTrees extends Trees { + + // in a world of a single context per compilation, these would all be final + private Resolve resolve; + private Enter enter; + private Log log; + private MemberEnter memberEnter; + private Attr attr; + private TreeMaker treeMaker; + private JavacElements elements; + private JavacTaskImpl javacTaskImpl; + + public static JavacTrees instance(JavaCompiler.CompilationTask task) { + if (!(task instanceof JavacTaskImpl)) + throw new IllegalArgumentException(); + return instance(((JavacTaskImpl)task).getContext()); + } + + public static JavacTrees instance(ProcessingEnvironment env) { + if (!(env instanceof JavacProcessingEnvironment)) + throw new IllegalArgumentException(); + return instance(((JavacProcessingEnvironment)env).getContext()); + } + + public static JavacTrees instance(Context context) { + JavacTrees instance = context.get(JavacTrees.class); + if (instance == null) + instance = new JavacTrees(context); + return instance; + } + + private JavacTrees(Context context) { + context.put(JavacTrees.class, this); + init(context); + } + + public void updateContext(Context context) { + init(context); + } + + private void init(Context context) { + attr = Attr.instance(context); + enter = Enter.instance(context); + elements = JavacElements.instance(context); + log = Log.instance(context); + resolve = Resolve.instance(context); + treeMaker = TreeMaker.instance(context); + memberEnter = MemberEnter.instance(context); + javacTaskImpl = context.get(JavacTaskImpl.class); + } + + public SourcePositions getSourcePositions() { + return new SourcePositions() { + public long getStartPosition(CompilationUnitTree file, Tree tree) { + return TreeInfo.getStartPos((JCTree) tree); + } + + public long getEndPosition(CompilationUnitTree file, Tree tree) { + Map endPositions = ((JCCompilationUnit) file).endPositions; + return TreeInfo.getEndPos((JCTree) tree, endPositions); + } + }; + } + + public JCClassDecl getTree(TypeElement element) { + return (JCClassDecl) getTree((Element) element); + } + + public JCMethodDecl getTree(ExecutableElement method) { + return (JCMethodDecl) getTree((Element) method); + } + + public JCTree getTree(Element element) { + Symbol symbol = (Symbol) element; + TypeSymbol enclosing = symbol.enclClass(); + Env env = enter.getEnv(enclosing); + if (env == null) + return null; + JCClassDecl classNode = env.enclClass; + if (classNode != null) { + if (TreeInfo.symbolFor(classNode) == element) + return classNode; + for (JCTree node : classNode.getMembers()) + if (TreeInfo.symbolFor(node) == element) + return node; + } + return null; + } + + public JCTree getTree(Element e, AnnotationMirror a) { + return getTree(e, a, null); + } + + public JCTree getTree(Element e, AnnotationMirror a, AnnotationValue v) { + Pair treeTopLevel = elements.getTreeAndTopLevel(e, a, v); + if (treeTopLevel == null) + return null; + return treeTopLevel.fst; + } + + public TreePath getPath(CompilationUnitTree unit, Tree node) { + return TreePath.getPath(unit, node); + } + + public TreePath getPath(Element e) { + return getPath(e, null, null); + } + + public TreePath getPath(Element e, AnnotationMirror a) { + return getPath(e, a, null); + } + + public TreePath getPath(Element e, AnnotationMirror a, AnnotationValue v) { + final Pair treeTopLevel = elements.getTreeAndTopLevel(e, a, v); + if (treeTopLevel == null) + return null; + return TreePath.getPath(treeTopLevel.snd, treeTopLevel.fst); + } + + public Element getElement(TreePath path) { + JCTree tree = (JCTree) path.getLeaf(); + Symbol sym = TreeInfo.symbolFor(tree); + if (sym == null && TreeInfo.isDeclaration(tree)) { + for (TreePath p = path; p != null; p = p.getParentPath()) { + JCTree t = (JCTree) p.getLeaf(); + if (t.getTag() == JCTree.CLASSDEF) { + JCClassDecl ct = (JCClassDecl) t; + if (ct.sym != null) { + if ((ct.sym.flags_field & Flags.UNATTRIBUTED) != 0) { + attr.attribClass(ct.pos(), ct.sym); + sym = TreeInfo.symbolFor(tree); + } + break; + } + } + } + } + return sym; + } + + public TypeMirror getTypeMirror(TreePath path) { + Tree t = path.getLeaf(); + return ((JCTree)t).type; + } + + public JavacScope getScope(TreePath path) { + return new JavacScope(getAttrContext(path)); + } + + public String getDocComment(TreePath path) { + CompilationUnitTree t = path.getCompilationUnit(); + if (t instanceof JCTree.JCCompilationUnit) { + JCCompilationUnit cu = (JCCompilationUnit) t; + if (cu.docComments != null) { + return cu.docComments.get(path.getLeaf()); + } + } + return null; + } + + public boolean isAccessible(Scope scope, TypeElement type) { + if (scope instanceof JavacScope && type instanceof ClassSymbol) { + Env env = ((JavacScope) scope).env; + return resolve.isAccessible(env, (ClassSymbol)type, true); + } else + return false; + } + + public boolean isAccessible(Scope scope, Element member, DeclaredType type) { + if (scope instanceof JavacScope + && member instanceof Symbol + && type instanceof com.sun.tools.javac.code.Type) { + Env env = ((JavacScope) scope).env; + return resolve.isAccessible(env, (com.sun.tools.javac.code.Type)type, (Symbol)member, true); + } else + return false; + } + + private Env getAttrContext(TreePath path) { + if (!(path.getLeaf() instanceof JCTree)) // implicit null-check + throw new IllegalArgumentException(); + + // if we're being invoked via from a JSR199 client, we need to make sure + // all the classes have been entered; if we're being invoked from JSR269, + // then the classes will already have been entered. + if (javacTaskImpl != null) { + try { + javacTaskImpl.enter(null); + } catch (IOException e) { + throw new Error("unexpected error while entering symbols: " + e); + } + } + + + JCCompilationUnit unit = (JCCompilationUnit) path.getCompilationUnit(); + Copier copier = new Copier(treeMaker.forToplevel(unit)); + + Env env = null; + JCMethodDecl method = null; + JCVariableDecl field = null; + + List l = List.nil(); + TreePath p = path; + while (p != null) { + l = l.prepend(p.getLeaf()); + p = p.getParentPath(); + } + + for ( ; l.nonEmpty(); l = l.tail) { + Tree tree = l.head; + switch (tree.getKind()) { + case COMPILATION_UNIT: +// System.err.println("COMP: " + ((JCCompilationUnit)tree).sourcefile); + env = enter.getTopLevelEnv((JCCompilationUnit)tree); + break; + case ANNOTATION_TYPE: + case CLASS: + case ENUM: + case INTERFACE: +// System.err.println("CLASS: " + ((JCClassDecl)tree).sym.getSimpleName()); + env = enter.getClassEnv(((JCClassDecl)tree).sym); + break; + case METHOD: +// System.err.println("METHOD: " + ((JCMethodDecl)tree).sym.getSimpleName()); + method = (JCMethodDecl)tree; + break; + case VARIABLE: +// System.err.println("FIELD: " + ((JCVariableDecl)tree).sym.getSimpleName()); + field = (JCVariableDecl)tree; + break; + case BLOCK: { +// System.err.println("BLOCK: "); + if (method != null) + env = memberEnter.getMethodEnv(method, env); + JCTree body = copier.copy((JCTree)tree, (JCTree) path.getLeaf()); + env = attribStatToTree(body, env, copier.leafCopy); + return env; + } + default: +// System.err.println("DEFAULT: " + tree.getKind()); + if (field != null && field.getInitializer() == tree) { + env = memberEnter.getInitEnv(field, env); + JCExpression expr = copier.copy((JCExpression)tree, (JCTree) path.getLeaf()); + env = attribExprToTree(expr, env, copier.leafCopy); + return env; + } + } + } + return field != null ? memberEnter.getInitEnv(field, env) : env; + } + + private Env attribStatToTree(JCTree stat, Envenv, JCTree tree) { + JavaFileObject prev = log.useSource(env.toplevel.sourcefile); + try { + return attr.attribStatToTree(stat, env, tree); + } finally { + log.useSource(prev); + } + } + + private Env attribExprToTree(JCExpression expr, Envenv, JCTree tree) { + JavaFileObject prev = log.useSource(env.toplevel.sourcefile); + try { + return attr.attribExprToTree(expr, env, tree); + } finally { + log.useSource(prev); + } + } + + /** + * Makes a copy of a tree, noting the value resulting from copying a particular leaf. + **/ + static class Copier extends TreeCopier { + JCTree leafCopy = null; + + Copier(TreeMaker M) { + super(M); + } + + @Override + public T copy(T t, JCTree leaf) { + T t2 = super.copy(t, leaf); + if (t == leaf) + leafCopy = t2; + return t2; + } + } + + /** + * Gets the original type from the ErrorType object. + * @param errorType The errorType for which we want to get the original type. + * @returns TypeMirror corresponding to the original type, replaced by the ErrorType. + * noType (type.tag == NONE) is returned if there is no original type. + */ + public TypeMirror getOriginalType(javax.lang.model.type.ErrorType errorType) { + if (errorType instanceof com.sun.tools.javac.code.Type.ErrorType) { + return ((com.sun.tools.javac.code.Type.ErrorType)errorType).getOriginalType(); + } + + return com.sun.tools.javac.code.Type.noType; + } + + /** + * Prints a message of the specified kind at the location of the + * tree within the provided compilation unit + * + * @param kind the kind of message + * @param msg the message, or an empty string if none + * @param t the tree to use as a position hint + * @param root the compilation unit that contains tree + */ + public void printMessage(Diagnostic.Kind kind, CharSequence msg, + com.sun.source.tree.Tree t, + com.sun.source.tree.CompilationUnitTree root) { + JavaFileObject oldSource = null; + JavaFileObject newSource = null; + JCDiagnostic.DiagnosticPosition pos = null; + + newSource = root.getSourceFile(); + if (newSource != null) { + oldSource = log.useSource(newSource); + pos = ((JCTree) t).pos(); + } + + try { + switch (kind) { + case ERROR: + boolean prev = log.multipleErrors; + try { + log.error(pos, "proc.messager", msg.toString()); + } finally { + log.multipleErrors = prev; + } + break; + + case WARNING: + log.warning(pos, "proc.messager", msg.toString()); + break; + + case MANDATORY_WARNING: + log.mandatoryWarning(pos, "proc.messager", msg.toString()); + break; + + default: + log.note(pos, "proc.messager", msg.toString()); + } + } finally { + if (oldSource != null) + log.useSource(oldSource); + } + } + + @Override + public TypeMirror getLub(CatchTree tree) { + JCCatch ct = (JCCatch) tree; + JCVariableDecl v = ct.param; + if (v.type != null && v.type.getKind() == TypeKind.UNION) { + UnionClassType ut = (UnionClassType) v.type; + return ut.getLub(); + } else { + return v.type; + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/api/Messages.java b/douyu-javac/src/main/java/com/sun/tools/javac/api/Messages.java new file mode 100644 index 0000000..c1caa09 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/api/Messages.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2008, 2009, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.api; + +import java.util.Locale; +import java.util.MissingResourceException; + +/** + * This interface defines the minimum requirements in order to provide support + * for localized formatted strings. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Maurizio Cimadamore + */ +public interface Messages { + + /** + * Add a new resource bundle to the list that is searched for localized messages. + * @param bundleName the name to identify the resource bundle of localized messages. + * @throws MissingResourceException if the given resource is not found + */ + void add(String bundleName) throws MissingResourceException; + + /** + * Get a localized formatted string. + * @param l locale in which the text is to be localized + * @param key locale-independent message key + * @param args misc message arguments + * @return a localized formatted string + */ + String getLocalizedString(Locale l, String key, Object... args); +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/api/WrappingJavaFileManager.java b/douyu-javac/src/main/java/com/sun/tools/javac/api/WrappingJavaFileManager.java new file mode 100644 index 0000000..41d9874 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/api/WrappingJavaFileManager.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2006, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.api; + +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.tools.JavaFileObject.Kind; +import javax.tools.*; + +/** + * Wraps all calls to a given file manager. Subclasses of this class + * might override some of these methods and might also provide + * additional fields and methods. + * + *

This class might be moved to {@link javax.tools} in a future + * release. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own + * risk. This code and its internal interfaces are subject to change + * or deletion without notice.

+ * + * @param the type of file manager wrapped to by this object + * + * @author Peter von der Ahé + * @since 1.6 + */ +public class WrappingJavaFileManager extends ForwardingJavaFileManager { + + /** + * Creates a new instance of WrappingJavaFileManager. + * @param fileManager file manager to be wrapped + */ + protected WrappingJavaFileManager(M fileManager) { + super(fileManager); + } + + /** + * This implementation returns the given file object. Subclasses + * may override this behavior. + * + * @param fileObject a file object + */ + protected FileObject wrap(FileObject fileObject) { + return fileObject; + } + + /** + * This implementation forwards to {@link #wrap(FileObject)}. + * Subclasses may override this behavior. + * + * @param fileObject a file object + * @throws ClassCastException if the file object returned from the + * forwarded call is not a subtype of {@linkplain JavaFileObject} + */ + protected JavaFileObject wrap(JavaFileObject fileObject) { + return (JavaFileObject)wrap((FileObject)fileObject); + } + + /** + * This implementation returns the given file object. Subclasses + * may override this behavior. + * + * @param fileObject a file object + */ + protected FileObject unwrap(FileObject fileObject) { + return fileObject; + } + + /** + * This implementation forwards to {@link #unwrap(FileObject)}. + * Subclasses may override this behavior. + * + * @param fileObject a file object + * @throws ClassCastException if the file object returned from the + * forwarded call is not a subtype of {@linkplain JavaFileObject} + */ + protected JavaFileObject unwrap(JavaFileObject fileObject) { + return (JavaFileObject)unwrap((FileObject)fileObject); + } + + /** + * This implementation maps the given list of file objects by + * calling wrap on each. Subclasses may override this behavior. + * + * @param fileObjects a list of file objects + * @return the mapping + */ + protected Iterable wrap(Iterable fileObjects) { + List mapped = new ArrayList(); + for (JavaFileObject fileObject : fileObjects) + mapped.add(wrap(fileObject)); + return Collections.unmodifiableList(mapped); + } + + /** + * This implementation returns the given URI. Subclasses may + * override this behavior. + * + * @param uri a URI + */ + protected URI unwrap(URI uri) { + return uri; + } + + /** + * @throws IllegalStateException {@inheritDoc} + */ + public Iterable list(Location location, + String packageName, + Set kinds, + boolean recurse) + throws IOException + { + return wrap(super.list(location, packageName, kinds, recurse)); + } + + /** + * @throws IllegalStateException {@inheritDoc} + */ + public String inferBinaryName(Location location, JavaFileObject file) { + return super.inferBinaryName(location, unwrap(file)); + } + + /** + * @throws IllegalArgumentException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} + * @throws IllegalStateException {@inheritDoc} + */ + public JavaFileObject getJavaFileForInput(Location location, + String className, + Kind kind) + throws IOException + { + return wrap(super.getJavaFileForInput(location, className, kind)); + } + + /** + * @throws IllegalArgumentException {@inheritDoc} + * @throws UnsupportedOperationException {@inheritDoc} + * @throws IllegalStateException {@inheritDoc} + */ + public JavaFileObject getJavaFileForOutput(Location location, + String className, + Kind kind, + FileObject sibling) + throws IOException + { + return wrap(super.getJavaFileForOutput(location, className, kind, unwrap(sibling))); + } + + /** + * @throws IllegalArgumentException {@inheritDoc} + * @throws IllegalStateException {@inheritDoc} + */ + public FileObject getFileForInput(Location location, + String packageName, + String relativeName) + throws IOException + { + return wrap(super.getFileForInput(location, packageName, relativeName)); + } + + /** + * @throws IllegalArgumentException {@inheritDoc} + * @throws IllegalStateException {@inheritDoc} + */ + public FileObject getFileForOutput(Location location, + String packageName, + String relativeName, + FileObject sibling) + throws IOException + { + return wrap(super.getFileForOutput(location, + packageName, + relativeName, + unwrap(sibling))); + } + +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/code/Attribute.java b/douyu-javac/src/main/java/com/sun/tools/javac/code/Attribute.java new file mode 100644 index 0000000..37bb6ee --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/code/Attribute.java @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.code; + +import java.util.LinkedHashMap; +import java.util.Map; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.AnnotationValueVisitor; +import javax.lang.model.type.DeclaredType; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.util.*; + +import static com.sun.tools.javac.code.TypeTags.*; + +/** An annotation value. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public abstract class Attribute implements AnnotationValue { + + /** The type of the annotation element. */ + public Type type; + + public Attribute(Type type) { + this.type = type; + } + + public abstract void accept(Visitor v); + + public Object getValue() { + throw new UnsupportedOperationException(); + } + + public R accept(AnnotationValueVisitor v, P p) { + throw new UnsupportedOperationException(); + } + + + /** The value for an annotation element of primitive type or String. */ + public static class Constant extends Attribute { + public final Object value; + public void accept(Visitor v) { v.visitConstant(this); } + public Constant(Type type, Object value) { + super(type); + this.value = value; + } + public String toString() { + return Constants.format(value, type); + } + public Object getValue() { + return Constants.decode(value, type); + } + public R accept(AnnotationValueVisitor v, P p) { + if (value instanceof String) + return v.visitString((String) value, p); + if (value instanceof Integer) { + int i = (Integer) value; + switch (type.tag) { + case BOOLEAN: return v.visitBoolean(i != 0, p); + case CHAR: return v.visitChar((char) i, p); + case BYTE: return v.visitByte((byte) i, p); + case SHORT: return v.visitShort((short) i, p); + case INT: return v.visitInt(i, p); + } + } + switch (type.tag) { + case LONG: return v.visitLong((Long) value, p); + case FLOAT: return v.visitFloat((Float) value, p); + case DOUBLE: return v.visitDouble((Double) value, p); + } + throw new AssertionError("Bad annotation element value: " + value); + } + } + + /** The value for an annotation element of type java.lang.Class, + * represented as a ClassSymbol. + */ + public static class Class extends Attribute { + public final Type type; + public void accept(Visitor v) { v.visitClass(this); } + public Class(Types types, Type type) { + super(makeClassType(types, type)); + this.type = type; + } + static Type makeClassType(Types types, Type type) { + Type arg = type.isPrimitive() + ? types.boxedClass(type).type + : types.erasure(type); + return new Type.ClassType(types.syms.classType.getEnclosingType(), + List.of(arg), + types.syms.classType.tsym); + } + public String toString() { + return type + ".class"; + } + public Type getValue() { + return type; + } + public R accept(AnnotationValueVisitor v, P p) { + return v.visitType(type, p); + } + } + + /** A compound annotation element value, the type of which is an + * attribute interface. + */ + public static class Compound extends Attribute implements AnnotationMirror { + /** The attributes values, as pairs. Each pair contains a + * reference to the accessing method in the attribute interface + * and the value to be returned when that method is called to + * access this attribute. + */ + public final List> values; + public Compound(Type type, + List> values) { + super(type); + this.values = values; + } + public void accept(Visitor v) { v.visitCompound(this); } + + /** + * Returns a string representation of this annotation. + * String is of one of the forms: + * @com.example.foo(name1=val1, name2=val2) + * @com.example.foo(val) + * @com.example.foo + * Omit parens for marker annotations, and omit "value=" when allowed. + */ + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("@"); + buf.append(type); + int len = values.length(); + if (len > 0) { + buf.append('('); + boolean first = true; + for (Pair value : values) { + if (!first) buf.append(", "); + first = false; + + Name name = value.fst.name; + if (len > 1 || name != name.table.names.value) { + buf.append(name); + buf.append('='); + } + buf.append(value.snd); + } + buf.append(')'); + } + return buf.toString(); + } + + public Attribute member(Name member) { + for (Pair pair : values) + if (pair.fst.name == member) return pair.snd; + return null; + } + + public Attribute.Compound getValue() { + return this; + } + + public R accept(AnnotationValueVisitor v, P p) { + return v.visitAnnotation(this, p); + } + + public DeclaredType getAnnotationType() { + return (DeclaredType) type; + } + + public Map getElementValues() { + Map valmap = + new LinkedHashMap(); + for (Pair value : values) + valmap.put(value.fst, value.snd); + return valmap; + } + } + + /** The value for an annotation element of an array type. + */ + public static class Array extends Attribute { + public final Attribute[] values; + public Array(Type type, Attribute[] values) { + super(type); + this.values = values; + } + public void accept(Visitor v) { v.visitArray(this); } + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append('{'); + boolean first = true; + for (Attribute value : values) { + if (!first) + buf.append(", "); + first = false; + buf.append(value); + } + buf.append('}'); + return buf.toString(); + } + public List getValue() { + return List.from(values); + } + public R accept(AnnotationValueVisitor v, P p) { + return v.visitArray(getValue(), p); + } + } + + /** The value for an annotation element of an enum type. + */ + public static class Enum extends Attribute { + public VarSymbol value; + public Enum(Type type, VarSymbol value) { + super(type); + this.value = Assert.checkNonNull(value); + } + public void accept(Visitor v) { v.visitEnum(this); } + public String toString() { + return value.enclClass() + "." + value; // qualified name + } + public VarSymbol getValue() { + return value; + } + public R accept(AnnotationValueVisitor v, P p) { + return v.visitEnumConstant(value, p); + } + } + + public static class Error extends Attribute { + public Error(Type type) { + super(type); + } + public void accept(Visitor v) { v.visitError(this); } + public String toString() { + return ""; + } + public String getValue() { + return toString(); + } + public R accept(AnnotationValueVisitor v, P p) { + return v.visitString(toString(), p); + } + } + + /** A visitor type for dynamic dispatch on the kind of attribute value. */ + public static interface Visitor { + void visitConstant(Attribute.Constant value); + void visitClass(Attribute.Class clazz); + void visitCompound(Attribute.Compound compound); + void visitArray(Attribute.Array array); + void visitEnum(Attribute.Enum e); + void visitError(Attribute.Error e); + } + + /** A mirror of java.lang.annotation.RetentionPolicy. */ + public static enum RetentionPolicy { + SOURCE, + CLASS, + RUNTIME + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/code/BoundKind.java b/douyu-javac/src/main/java/com/sun/tools/javac/code/BoundKind.java new file mode 100644 index 0000000..d2c4e67 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/code/BoundKind.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2003, 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.code; + +/** + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public enum BoundKind { + EXTENDS("? extends "), + SUPER("? super "), + UNBOUND("?"); + + private final String name; + + BoundKind(String name) { + this.name = name; + } + + public String toString() { return name; } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/code/DeferredLintHandler.java b/douyu-javac/src/main/java/com/sun/tools/javac/code/DeferredLintHandler.java new file mode 100644 index 0000000..3d8f00e --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/code/DeferredLintHandler.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.code; + +import java.util.HashMap; +import java.util.Map; + +import com.sun.tools.javac.util.Assert; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; + +/** + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class DeferredLintHandler { + protected static final Context.Key deferredLintHandlerKey = + new Context.Key(); + + public static DeferredLintHandler instance(Context context) { + DeferredLintHandler instance = context.get(deferredLintHandlerKey); + if (instance == null) + instance = new DeferredLintHandler(context); + return instance; + } + + protected DeferredLintHandler(Context context) { + context.put(deferredLintHandlerKey, this); + } + + private DeferredLintHandler() {} + + public interface LintLogger { + void report(); + } + + private DiagnosticPosition currentPos; + private Map> loggersQueue = new HashMap>(); + + public void report(LintLogger logger) { + ListBuffer loggers = loggersQueue.get(currentPos); + Assert.checkNonNull(loggers); + loggers.append(logger); + } + + public void flush(DiagnosticPosition pos) { + ListBuffer loggers = loggersQueue.get(pos); + if (loggers != null) { + for (LintLogger lintLogger : loggers) { + lintLogger.report(); + } + loggersQueue.remove(pos); + } + } + + public DeferredLintHandler setPos(DiagnosticPosition currentPos) { + this.currentPos = currentPos; + loggersQueue.put(currentPos, ListBuffer.lb()); + return this; + } + + public static final DeferredLintHandler immediateHandler = new DeferredLintHandler() { + @Override + public void report(LintLogger logger) { + logger.report(); + } + }; +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/code/Flags.java b/douyu-javac/src/main/java/com/sun/tools/javac/code/Flags.java new file mode 100644 index 0000000..5c93ba7 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/code/Flags.java @@ -0,0 +1,358 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.code; + +import java.util.EnumSet; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import javax.lang.model.element.Modifier; + +/** Access flags and other modifiers for Java classes and members. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Flags { + + private Flags() {} // uninstantiable + + public static String toString(long flags) { + StringBuilder buf = new StringBuilder(); + String sep = ""; + for (Flag s : asFlagSet(flags)) { + buf.append(sep); + buf.append(s); + sep = " "; + } + return buf.toString(); + } + + public static EnumSet asFlagSet(long mask) { + EnumSet flags = EnumSet.noneOf(Flag.class); + if ((mask&PUBLIC) != 0) flags.add(Flag.PUBLIC); + if ((mask&PRIVATE) != 0) flags.add(Flag.PRIVATE); + if ((mask&PROTECTED) != 0) flags.add(Flag.PROTECTED); + if ((mask&STATIC) != 0) flags.add(Flag.STATIC); + if ((mask&FINAL) != 0) flags.add(Flag.FINAL); + if ((mask&SYNCHRONIZED) != 0) flags.add(Flag.SYNCHRONIZED); + if ((mask&VOLATILE) != 0) flags.add(Flag.VOLATILE); + if ((mask&TRANSIENT) != 0) flags.add(Flag.TRANSIENT); + if ((mask&NATIVE) != 0) flags.add(Flag.NATIVE); + if ((mask&INTERFACE) != 0) flags.add(Flag.INTERFACE); + if ((mask&ABSTRACT) != 0) flags.add(Flag.ABSTRACT); + if ((mask&STRICTFP) != 0) flags.add(Flag.STRICTFP); + if ((mask&BRIDGE) != 0) flags.add(Flag.BRIDGE); + if ((mask&SYNTHETIC) != 0) flags.add(Flag.SYNTHETIC); + if ((mask&DEPRECATED) != 0) flags.add(Flag.DEPRECATED); + if ((mask&HASINIT) != 0) flags.add(Flag.HASINIT); + if ((mask&ENUM) != 0) flags.add(Flag.ENUM); + if ((mask&IPROXY) != 0) flags.add(Flag.IPROXY); + if ((mask&NOOUTERTHIS) != 0) flags.add(Flag.NOOUTERTHIS); + if ((mask&EXISTS) != 0) flags.add(Flag.EXISTS); + if ((mask&COMPOUND) != 0) flags.add(Flag.COMPOUND); + if ((mask&CLASS_SEEN) != 0) flags.add(Flag.CLASS_SEEN); + if ((mask&SOURCE_SEEN) != 0) flags.add(Flag.SOURCE_SEEN); + if ((mask&LOCKED) != 0) flags.add(Flag.LOCKED); + if ((mask&UNATTRIBUTED) != 0) flags.add(Flag.UNATTRIBUTED); + if ((mask&ANONCONSTR) != 0) flags.add(Flag.ANONCONSTR); + if ((mask&ACYCLIC) != 0) flags.add(Flag.ACYCLIC); + if ((mask&PARAMETER) != 0) flags.add(Flag.PARAMETER); + if ((mask&VARARGS) != 0) flags.add(Flag.VARARGS); + return flags; + } + + /* Standard Java flags. + */ + public static final int PUBLIC = 1<<0; + public static final int PRIVATE = 1<<1; + public static final int PROTECTED = 1<<2; + public static final int STATIC = 1<<3; + public static final int FINAL = 1<<4; + public static final int SYNCHRONIZED = 1<<5; + public static final int VOLATILE = 1<<6; + public static final int TRANSIENT = 1<<7; + public static final int NATIVE = 1<<8; + public static final int INTERFACE = 1<<9; + public static final int ABSTRACT = 1<<10; + public static final int STRICTFP = 1<<11; + + /* Flag that marks a symbol synthetic, added in classfile v49.0. */ + public static final int SYNTHETIC = 1<<12; + + /** Flag that marks attribute interfaces, added in classfile v49.0. */ + public static final int ANNOTATION = 1<<13; + + /** An enumeration type or an enumeration constant, added in + * classfile v49.0. */ + public static final int ENUM = 1<<14; + + public static final int StandardFlags = 0x0fff; + public static final int ModifierFlags = StandardFlags & ~INTERFACE; + + // Because the following access flags are overloaded with other + // bit positions, we translate them when reading and writing class + // files into unique bits positions: ACC_SYNTHETIC <-> SYNTHETIC, + // for example. + public static final int ACC_SUPER = 0x0020; + public static final int ACC_BRIDGE = 0x0040; + public static final int ACC_VARARGS = 0x0080; + + /***************************************** + * Internal compiler flags (no bits in the lower 16). + *****************************************/ + + /** Flag is set if symbol is deprecated. + */ + public static final int DEPRECATED = 1<<17; + + /** Flag is set for a variable symbol if the variable's definition + * has an initializer part. + */ + public static final int HASINIT = 1<<18; + + /** Flag is set for compiler-generated anonymous method symbols + * that `own' an initializer block. + */ + public static final int BLOCK = 1<<20; + + /** Flag is set for compiler-generated abstract methods that implement + * an interface method (Miranda methods). + */ + public static final int IPROXY = 1<<21; + + /** Flag is set for nested classes that do not access instance members + * or `this' of an outer class and therefore don't need to be passed + * a this$n reference. This flag is currently set only for anonymous + * classes in superclass constructor calls and only for pre 1.4 targets. + * todo: use this flag for optimizing away this$n parameters in + * other cases. + */ + public static final int NOOUTERTHIS = 1<<22; + + /** Flag is set for package symbols if a package has a member or + * directory and therefore exists. + */ + public static final int EXISTS = 1<<23; + + /** Flag is set for compiler-generated compound classes + * representing multiple variable bounds + */ + public static final int COMPOUND = 1<<24; + + /** Flag is set for class symbols if a class file was found for this class. + */ + public static final int CLASS_SEEN = 1<<25; + + /** Flag is set for class symbols if a source file was found for this + * class. + */ + public static final int SOURCE_SEEN = 1<<26; + + /* State flags (are reset during compilation). + */ + + /** Flag for class symbols is set and later re-set as a lock in + * Enter to detect cycles in the superclass/superinterface + * relations. Similarly for constructor call cycle detection in + * Attr. + */ + public static final int LOCKED = 1<<27; + + /** Flag for class symbols is set and later re-set to indicate that a class + * has been entered but has not yet been attributed. + */ + public static final int UNATTRIBUTED = 1<<28; + + /** Flag for synthesized default constructors of anonymous classes. + */ + public static final int ANONCONSTR = 1<<29; + + /** Flag for class symbols to indicate it has been checked and found + * acyclic. + */ + public static final int ACYCLIC = 1<<30; + + /** Flag that marks bridge methods. + */ + public static final long BRIDGE = 1L<<31; + + /** Flag that marks formal parameters. + */ + public static final long PARAMETER = 1L<<33; + + /** Flag that marks varargs methods. + */ + public static final long VARARGS = 1L<<34; + + /** Flag for annotation type symbols to indicate it has been + * checked and found acyclic. + */ + public static final long ACYCLIC_ANN = 1L<<35; + + /** Flag that marks a generated default constructor. + */ + public static final long GENERATEDCONSTR = 1L<<36; + + /** Flag that marks a hypothetical method that need not really be + * generated in the binary, but is present in the symbol table to + * simplify checking for erasure clashes. + */ + public static final long HYPOTHETICAL = 1L<<37; + + /** + * Flag that marks an internal proprietary class. + */ + public static final long PROPRIETARY = 1L<<38; + + /** + * Flag that marks a a multi-catch parameter + */ + public static final long UNION = 1L<<39; + + /** + * Flag that marks a signature-polymorphic invoke method. + * (These occur inside java.lang.invoke.MethodHandle.) + */ + public static final long POLYMORPHIC_SIGNATURE = 1L<<40; + + /** + * Flag that marks a special kind of bridge methods (the ones that + * come from restricted supertype bounds) + */ + public static final long OVERRIDE_BRIDGE = 1L<<41; + + /** + * Flag that marks an 'effectively final' local variable + */ + public static final long EFFECTIVELY_FINAL = 1L<<42; + + /** + * Flag that marks non-override equivalent methods with the same signature + */ + public static final long CLASH = 1L<<43; + + /** Modifier masks. + */ + public static final int + AccessFlags = PUBLIC | PROTECTED | PRIVATE, + LocalClassFlags = FINAL | ABSTRACT | STRICTFP | ENUM | SYNTHETIC, + MemberClassFlags = LocalClassFlags | INTERFACE | AccessFlags, + ClassFlags = LocalClassFlags | INTERFACE | PUBLIC | ANNOTATION, + InterfaceVarFlags = FINAL | STATIC | PUBLIC, + VarFlags = AccessFlags | FINAL | STATIC | + VOLATILE | TRANSIENT | ENUM, + ConstructorFlags = AccessFlags, + InterfaceMethodFlags = ABSTRACT | PUBLIC, + MethodFlags = AccessFlags | ABSTRACT | STATIC | NATIVE | + SYNCHRONIZED | FINAL | STRICTFP; + public static final long + LocalVarFlags = FINAL | PARAMETER; + + public static Set asModifierSet(long flags) { + Set modifiers = modifierSets.get(flags); + if (modifiers == null) { + modifiers = java.util.EnumSet.noneOf(Modifier.class); + if (0 != (flags & PUBLIC)) modifiers.add(Modifier.PUBLIC); + if (0 != (flags & PROTECTED)) modifiers.add(Modifier.PROTECTED); + if (0 != (flags & PRIVATE)) modifiers.add(Modifier.PRIVATE); + if (0 != (flags & ABSTRACT)) modifiers.add(Modifier.ABSTRACT); + if (0 != (flags & STATIC)) modifiers.add(Modifier.STATIC); + if (0 != (flags & FINAL)) modifiers.add(Modifier.FINAL); + if (0 != (flags & TRANSIENT)) modifiers.add(Modifier.TRANSIENT); + if (0 != (flags & VOLATILE)) modifiers.add(Modifier.VOLATILE); + if (0 != (flags & SYNCHRONIZED)) + modifiers.add(Modifier.SYNCHRONIZED); + if (0 != (flags & NATIVE)) modifiers.add(Modifier.NATIVE); + if (0 != (flags & STRICTFP)) modifiers.add(Modifier.STRICTFP); + modifiers = Collections.unmodifiableSet(modifiers); + modifierSets.put(flags, modifiers); + } + return modifiers; + } + + // Cache of modifier sets. + private static Map> modifierSets = + new java.util.concurrent.ConcurrentHashMap>(64); + + public static boolean isStatic(Symbol symbol) { + return (symbol.flags() & STATIC) != 0; + } + + public static boolean isEnum(Symbol symbol) { + return (symbol.flags() & ENUM) != 0; + } + + public static boolean isConstant(Symbol.VarSymbol symbol) { + return symbol.getConstValue() != null; + } + + public enum Flag { + + PUBLIC("public"), + PRIVATE("private"), + PROTECTED("protected"), + STATIC("static"), + FINAL("final"), + SYNCHRONIZED("synchronized"), + VOLATILE("volatile"), + TRANSIENT("transient"), + NATIVE("native"), + INTERFACE("interface"), + ABSTRACT("abstract"), + STRICTFP("strictfp"), + BRIDGE("bridge"), + SYNTHETIC("synthetic"), + DEPRECATED("deprecated"), + HASINIT("hasinit"), + ENUM("enum"), + IPROXY("iproxy"), + NOOUTERTHIS("noouterthis"), + EXISTS("exists"), + COMPOUND("compound"), + CLASS_SEEN("class_seen"), + SOURCE_SEEN("source_seen"), + LOCKED("locked"), + UNATTRIBUTED("unattributed"), + ANONCONSTR("anonconstr"), + ACYCLIC("acyclic"), + PARAMETER("parameter"), + VARARGS("varargs"), + PACKAGE("package"); + + String name; + + Flag(String name) { + this.name = name; + } + + public String toString() { + return name; + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/code/Kinds.java b/douyu-javac/src/main/java/com/sun/tools/javac/code/Kinds.java new file mode 100644 index 0000000..7156513 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/code/Kinds.java @@ -0,0 +1,230 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.code; + +import java.util.EnumSet; +import java.util.Locale; + +import com.sun.tools.javac.api.Formattable; +import com.sun.tools.javac.api.Messages; + +import static com.sun.tools.javac.code.TypeTags.*; +import static com.sun.tools.javac.code.Flags.*; + +/** Internal symbol kinds, which distinguish between elements of + * different subclasses of Symbol. Symbol kinds are organized so they can be + * or'ed to sets. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Kinds { + + private Kinds() {} // uninstantiable + + /** The empty set of kinds. + */ + public final static int NIL = 0; + + /** The kind of package symbols. + */ + public final static int PCK = 1 << 0; + + /** The kind of type symbols (classes, interfaces and type variables). + */ + public final static int TYP = 1 << 1; + + /** The kind of variable symbols. + */ + public final static int VAR = 1 << 2; + + /** The kind of values (variables or non-variable expressions), includes VAR. + */ + public final static int VAL = (1 << 3) | VAR; + + /** The kind of methods. + */ + public final static int MTH = 1 << 4; + + /** The error kind, which includes all other kinds. + */ + public final static int ERR = (1 << 5) - 1; + + /** The set of all kinds. + */ + public final static int AllKinds = ERR; + + /** Kinds for erroneous symbols that complement the above + */ + public static final int ERRONEOUS = 1 << 6; + public static final int AMBIGUOUS = ERRONEOUS+1; // ambiguous reference + public static final int HIDDEN = ERRONEOUS+2; // hidden method or field + public static final int STATICERR = ERRONEOUS+3; // nonstatic member from static context + public static final int ABSENT_VAR = ERRONEOUS+4; // missing variable + public static final int WRONG_MTHS = ERRONEOUS+5; // methods with wrong arguments + public static final int WRONG_MTH = ERRONEOUS+6; // one method with wrong arguments + public static final int ABSENT_MTH = ERRONEOUS+7; // missing method + public static final int ABSENT_TYP = ERRONEOUS+8; // missing type + + public enum KindName implements Formattable { + ANNOTATION("kindname.annotation"), + CONSTRUCTOR("kindname.constructor"), + INTERFACE("kindname.interface"), + ENUM("kindname.enum"), + STATIC("kindname.static"), + TYPEVAR("kindname.type.variable"), + BOUND("kindname.type.variable.bound"), + VAR("kindname.variable"), + VAL("kindname.value"), + METHOD("kindname.method"), + CLASS("kindname.class"), + PACKAGE("kindname.package"); + + private String name; + + KindName(String name) { + this.name = name; + } + + public String toString() { + return name; + } + + public String getKind() { + return "Kindname"; + } + + public String toString(Locale locale, Messages messages) { + String s = toString(); + return messages.getLocalizedString(locale, "compiler.misc." + s); + } + } + + /** A KindName representing a given symbol kind + */ + public static KindName kindName(int kind) { + switch (kind) { + case PCK: return KindName.PACKAGE; + case TYP: return KindName.CLASS; + case VAR: return KindName.VAR; + case VAL: return KindName.VAL; + case MTH: return KindName.METHOD; + default : throw new AssertionError("Unexpected kind: "+kind); + } + } + + /** A KindName representing a given symbol + */ + public static KindName kindName(Symbol sym) { + switch (sym.getKind()) { + case PACKAGE: + return KindName.PACKAGE; + + case ENUM: + return KindName.ENUM; + + case ANNOTATION_TYPE: + case CLASS: + return KindName.CLASS; + + case INTERFACE: + return KindName.INTERFACE; + + case TYPE_PARAMETER: + return KindName.TYPEVAR; + + case ENUM_CONSTANT: + case FIELD: + case PARAMETER: + case LOCAL_VARIABLE: + case EXCEPTION_PARAMETER: + case RESOURCE_VARIABLE: + return KindName.VAR; + + case CONSTRUCTOR: + return KindName.CONSTRUCTOR; + + case METHOD: + case STATIC_INIT: + case INSTANCE_INIT: + return KindName.METHOD; + + default: + if (sym.kind == VAL) + // I don't think this can happen but it can't harm + // playing it safe --ahe + return KindName.VAL; + else + throw new AssertionError("Unexpected kind: "+sym.getKind()); + } + } + + /** A set of KindName(s) representing a set of symbol's kinds. + */ + public static EnumSet kindNames(int kind) { + EnumSet kinds = EnumSet.noneOf(KindName.class); + if ((kind & VAL) != 0) + kinds.add(((kind & VAL) == VAR) ? KindName.VAR : KindName.VAL); + if ((kind & MTH) != 0) kinds.add(KindName.METHOD); + if ((kind & TYP) != 0) kinds.add(KindName.CLASS); + if ((kind & PCK) != 0) kinds.add(KindName.PACKAGE); + return kinds; + } + + /** A KindName representing the kind of a given class/interface type. + */ + public static KindName typeKindName(Type t) { + if (t.tag == TYPEVAR || + t.tag == CLASS && (t.tsym.flags() & COMPOUND) != 0) + return KindName.BOUND; + else if (t.tag == PACKAGE) + return KindName.PACKAGE; + else if ((t.tsym.flags_field & ANNOTATION) != 0) + return KindName.ANNOTATION; + else if ((t.tsym.flags_field & INTERFACE) != 0) + return KindName.INTERFACE; + else + return KindName.CLASS; + } + + /** A KindName representing the kind of a a missing symbol, given an + * error kind. + * */ + public static KindName absentKind(int kind) { + switch (kind) { + case ABSENT_VAR: + return KindName.VAR; + case WRONG_MTHS: case WRONG_MTH: case ABSENT_MTH: + return KindName.METHOD; + case ABSENT_TYP: + return KindName.CLASS; + default: + throw new AssertionError("Unexpected kind: "+kind); + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/code/Lint.java b/douyu-javac/src/main/java/com/sun/tools/javac/code/Lint.java new file mode 100644 index 0000000..46f7bdd --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/code/Lint.java @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.code; + +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Options; +import com.sun.tools.javac.util.Pair; +import static com.sun.tools.javac.code.Flags.*; + + +/** + * A class for handling -Xlint suboptions and @SuppresssWarnings. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Lint +{ + /** The context key for the root Lint object. */ + protected static final Context.Key lintKey = new Context.Key(); + + /** Get the root Lint instance. */ + public static Lint instance(Context context) { + Lint instance = context.get(lintKey); + if (instance == null) + instance = new Lint(context); + return instance; + } + + /** + * Returns the result of combining the values in this object with + * the given annotation. + */ + public Lint augment(Attribute.Compound attr) { + return augmentor.augment(this, attr); + } + + + /** + * Returns the result of combining the values in this object with + * the given annotations. + */ + public Lint augment(List attrs) { + return augmentor.augment(this, attrs); + } + + /** + * Returns the result of combining the values in this object with + * the given annotations and flags. + */ + public Lint augment(List attrs, long flags) { + Lint l = augmentor.augment(this, attrs); + if ((flags & DEPRECATED) != 0) { + if (l == this) + l = new Lint(this); + l.values.remove(LintCategory.DEPRECATION); + l.suppressedValues.add(LintCategory.DEPRECATION); + } + return l; + } + + + private final AugmentVisitor augmentor; + + private final EnumSet values; + private final EnumSet suppressedValues; + + private static Map map = new HashMap(); + + + protected Lint(Context context) { + // initialize values according to the lint options + Options options = Options.instance(context); + values = EnumSet.noneOf(LintCategory.class); + for (Map.Entry e: map.entrySet()) { + if (options.lint(e.getKey())) + values.add(e.getValue()); + } + + suppressedValues = EnumSet.noneOf(LintCategory.class); + + context.put(lintKey, this); + augmentor = new AugmentVisitor(context); + } + + protected Lint(Lint other) { + this.augmentor = other.augmentor; + this.values = other.values.clone(); + this.suppressedValues = other.suppressedValues.clone(); + } + + @Override + public String toString() { + return "Lint:[values" + values + " suppressedValues" + suppressedValues + "]"; + } + + /** + * Categories of warnings that can be generated by the compiler. + */ + public enum LintCategory { + /** + * Warn about use of unnecessary casts. + */ + CAST("cast"), + + /** + * Warn about issues related to classfile contents + */ + CLASSFILE("classfile"), + + /** + * Warn about use of deprecated items. + */ + DEPRECATION("deprecation"), + + /** + * Warn about items which are documented with an {@code @deprecated} JavaDoc + * comment, but which do not have {@code @Deprecated} annotation. + */ + DEP_ANN("dep-ann"), + + /** + * Warn about division by constant integer 0. + */ + DIVZERO("divzero"), + + /** + * Warn about empty statement after if. + */ + EMPTY("empty"), + + /** + * Warn about falling through from one case of a switch statement to the next. + */ + FALLTHROUGH("fallthrough"), + + /** + * Warn about finally clauses that do not terminate normally. + */ + FINALLY("finally"), + + /** + * Warn about issues relating to use of command line options + */ + OPTIONS("options"), + + /** + * Warn about issues regarding method overrides. + */ + OVERRIDES("overrides"), + + /** + * Warn about invalid path elements on the command line. + * Such warnings cannot be suppressed with the SuppressWarnings + * annotation. + */ + PATH("path"), + + /** + * Warn about issues regarding annotation processing. + */ + PROCESSING("processing"), + + /** + * Warn about unchecked operations on raw types. + */ + RAW("rawtypes"), + + /** + * Warn about Serializable classes that do not provide a serial version ID. + */ + SERIAL("serial"), + + /** + * Warn about issues relating to use of statics + */ + STATIC("static"), + + /** + * Warn about proprietary API that may be removed in a future release. + */ + SUNAPI("sunapi", true), + + /** + * Warn about issues relating to use of try blocks (i.e. try-with-resources) + */ + TRY("try"), + + /** + * Warn about unchecked operations on raw types. + */ + UNCHECKED("unchecked"), + + /** + * Warn about potentially unsafe vararg methods + */ + VARARGS("varargs"); + + LintCategory(String option) { + this(option, false); + } + + LintCategory(String option, boolean hidden) { + this.option = option; + this.hidden = hidden; + map.put(option, this); + } + + static LintCategory get(String option) { + return map.get(option); + } + + public final String option; + public final boolean hidden; + }; + + /** + * Checks if a warning category is enabled. A warning category may be enabled + * on the command line, or by default, and can be temporarily disabled with + * the SuppressWarnings annotation. + */ + public boolean isEnabled(LintCategory lc) { + return values.contains(lc); + } + + /** + * Checks is a warning category has been specifically suppressed, by means + * of the SuppressWarnings annotation, or, in the case of the deprecated + * category, whether it has been implicitly suppressed by virtue of the + * current entity being itself deprecated. + */ + public boolean isSuppressed(LintCategory lc) { + return suppressedValues.contains(lc); + } + + protected static class AugmentVisitor implements Attribute.Visitor { + private final Context context; + private Symtab syms; + private Lint parent; + private Lint lint; + + AugmentVisitor(Context context) { + // to break an ugly sequence of initialization dependencies, + // we defer the initialization of syms until it is needed + this.context = context; + } + + Lint augment(Lint parent, Attribute.Compound attr) { + initSyms(); + this.parent = parent; + lint = null; + attr.accept(this); + return (lint == null ? parent : lint); + } + + Lint augment(Lint parent, List attrs) { + initSyms(); + this.parent = parent; + lint = null; + for (Attribute.Compound a: attrs) { + a.accept(this); + } + return (lint == null ? parent : lint); + } + + private void initSyms() { + if (syms == null) + syms = Symtab.instance(context); + } + + private void suppress(LintCategory lc) { + if (lint == null) + lint = new Lint(parent); + lint.suppressedValues.add(lc); + lint.values.remove(lc); + } + + public void visitConstant(Attribute.Constant value) { + if (value.type.tsym == syms.stringType.tsym) { + LintCategory lc = LintCategory.get((String) (value.value)); + if (lc != null) + suppress(lc); + } + } + + public void visitClass(Attribute.Class clazz) { + } + + // If we find a @SuppressWarnings annotation, then we continue + // walking the tree, in order to suppress the individual warnings + // specified in the @SuppressWarnings annotation. + public void visitCompound(Attribute.Compound compound) { + if (compound.type.tsym == syms.suppressWarningsType.tsym) { + for (List> v = compound.values; + v.nonEmpty(); v = v.tail) { + Pair value = v.head; + if (value.fst.name.toString().equals("value")) + value.snd.accept(this); + } + + } + } + + public void visitArray(Attribute.Array array) { + for (Attribute value : array.values) + value.accept(this); + } + + public void visitEnum(Attribute.Enum e) { + } + + public void visitError(Attribute.Error e) { + } + }; +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/code/Printer.java b/douyu-javac/src/main/java/com/sun/tools/javac/code/Printer.java new file mode 100644 index 0000000..7f63aa4 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/code/Printer.java @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.code; + +import java.util.Locale; + +import com.sun.tools.javac.api.Messages; +import com.sun.tools.javac.code.Type.*; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; + +import static com.sun.tools.javac.code.TypeTags.*; +import static com.sun.tools.javac.code.BoundKind.*; +import static com.sun.tools.javac.code.Flags.*; + +/** + * A combined type/symbol visitor for generating non-trivial localized string + * representation of types and symbols. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public abstract class Printer implements Type.Visitor, Symbol.Visitor { + + List seenCaptured = List.nil(); + static final int PRIME = 997; // largest prime less than 1000 + + /** + * This method should be overriden in order to provide proper i18n support. + * + * @param locale the locale in which the string is to be rendered + * @param key the key corresponding to the message to be displayed + * @param args a list of optional arguments + * @return localized string representation + */ + protected abstract String localize(Locale locale, String key, Object... args); + + /** + * Maps a captured type into an unique identifier. + * + * @param t the captured type for which an id is to be retrieved + * @param locale locale settings + * @return unique id representing this captured type + */ + protected abstract String capturedVarId(CapturedType t, Locale locale); + + /** + * Create a printer with default i18n support provided by Messages. By default, + * captured types ids are generated using hashcode. + * + * @param messages Messages class to be used for i18n + * @return printer visitor instance + */ + public static Printer createStandardPrinter(final Messages messages) { + return new Printer() { + @Override + protected String localize(Locale locale, String key, Object... args) { + return messages.getLocalizedString(locale, key, args); + } + + @Override + protected String capturedVarId(CapturedType t, Locale locale) { + return (t.hashCode() & 0xFFFFFFFFL) % PRIME + ""; + }}; + } + + /** + * Get a localized string representation for all the types in the input list. + * + * @param ts types to be displayed + * @param locale the locale in which the string is to be rendered + * @return localized string representation + */ + public String visitTypes(List ts, Locale locale) { + ListBuffer sbuf = ListBuffer.lb(); + for (Type t : ts) { + sbuf.append(visit(t, locale)); + } + return sbuf.toList().toString(); + } + + /** + * * Get a localized string representation for all the symbols in the input list. + * + * @param ts symbols to be displayed + * @param locale the locale in which the string is to be rendered + * @return localized string representation + */ + public String visitSymbols(List ts, Locale locale) { + ListBuffer sbuf = ListBuffer.lb(); + for (Symbol t : ts) { + sbuf.append(visit(t, locale)); + } + return sbuf.toList().toString(); + } + + /** + * Get a localized string represenation for a given type. + * + * @param ts type to be displayed + * @param locale the locale in which the string is to be rendered + * @return localized string representation + */ + public String visit(Type t, Locale locale) { + return t.accept(this, locale); + } + + /** + * Get a localized string represenation for a given symbol. + * + * @param ts symbol to be displayed + * @param locale the locale in which the string is to be rendered + * @return localized string representation + */ + public String visit(Symbol s, Locale locale) { + return s.accept(this, locale); + } + + @Override + public String visitCapturedType(CapturedType t, Locale locale) { + if (seenCaptured.contains(t)) + return localize(locale, "compiler.misc.type.captureof.1", + capturedVarId(t, locale)); + else { + try { + seenCaptured = seenCaptured.prepend(t); + return localize(locale, "compiler.misc.type.captureof", + capturedVarId(t, locale), + visit(t.wildcard, locale)); + } + finally { + seenCaptured = seenCaptured.tail; + } + } + } + + @Override + public String visitForAll(ForAll t, Locale locale) { + return "<" + visitTypes(t.tvars, locale) + ">" + visit(t.qtype, locale); + } + + @Override + public String visitUndetVar(UndetVar t, Locale locale) { + if (t.inst != null) { + return visit(t.inst, locale); + } else { + return visit(t.qtype, locale) + "?"; + } + } + + @Override + public String visitArrayType(ArrayType t, Locale locale) { + return visit(t.elemtype, locale) + "[]"; + } + + @Override + public String visitClassType(ClassType t, Locale locale) { + StringBuffer buf = new StringBuffer(); + if (t.getEnclosingType().tag == CLASS && t.tsym.owner.kind == Kinds.TYP) { + buf.append(visit(t.getEnclosingType(), locale)); + buf.append("."); + buf.append(className(t, false, locale)); + } else { + buf.append(className(t, true, locale)); + } + if (t.getTypeArguments().nonEmpty()) { + buf.append('<'); + buf.append(visitTypes(t.getTypeArguments(), locale)); + buf.append(">"); + } + return buf.toString(); + } + + @Override + public String visitMethodType(MethodType t, Locale locale) { + return "(" + printMethodArgs(t.argtypes, false, locale) + ")" + visit(t.restype, locale); + } + + @Override + public String visitPackageType(PackageType t, Locale locale) { + return t.tsym.getQualifiedName().toString(); + } + + @Override + public String visitWildcardType(WildcardType t, Locale locale) { + StringBuffer s = new StringBuffer(); + s.append(t.kind); + if (t.kind != UNBOUND) { + s.append(visit(t.type, locale)); + } + return s.toString(); + } + + @Override + public String visitErrorType(ErrorType t, Locale locale) { + return visitType(t, locale); + } + + @Override + public String visitTypeVar(TypeVar t, Locale locale) { + return visitType(t, locale); + } + + public String visitType(Type t, Locale locale) { + String s = (t.tsym == null || t.tsym.name == null) + ? localize(locale, "compiler.misc.type.none") + : t.tsym.name.toString(); + return s; + } + + /** + * Converts a class name into a (possibly localized) string. Anonymous + * inner classes gets converted into a localized string. + * + * @param t the type of the class whose name is to be rendered + * @param longform if set, the class' fullname is displayed - if unset the + * short name is chosen (w/o package) + * @param locale the locale in which the string is to be rendered + * @return localized string representation + */ + protected String className(ClassType t, boolean longform, Locale locale) { + Symbol sym = t.tsym; + if (sym.name.length() == 0 && (sym.flags() & COMPOUND) != 0) { + StringBuffer s = new StringBuffer(visit(t.supertype_field, locale)); + for (List is = t.interfaces_field; is.nonEmpty(); is = is.tail) { + s.append("&"); + s.append(visit(is.head, locale)); + } + return s.toString(); + } else if (sym.name.length() == 0) { + String s; + ClassType norm = (ClassType) t.tsym.type; + if (norm == null) { + s = localize(locale, "compiler.misc.anonymous.class", (Object) null); + } else if (norm.interfaces_field.nonEmpty()) { + s = localize(locale, "compiler.misc.anonymous.class", + visit(norm.interfaces_field.head, locale)); + } else { + s = localize(locale, "compiler.misc.anonymous.class", + visit(norm.supertype_field, locale)); + } + return s; + } else if (longform) { + return sym.getQualifiedName().toString(); + } else { + return sym.name.toString(); + } + } + + /** + * Converts a set of method argument types into their corresponding + * localized string representation. + * + * @param args arguments to be rendered + * @param varArgs if true, the last method argument is regarded as a vararg + * @param locale the locale in which the string is to be rendered + * @return localized string representation + */ + protected String printMethodArgs(List args, boolean varArgs, Locale locale) { + if (!varArgs) { + return visitTypes(args, locale); + } else { + StringBuffer buf = new StringBuffer(); + while (args.tail.nonEmpty()) { + buf.append(visit(args.head, locale)); + args = args.tail; + buf.append(','); + } + if (args.head.tag == ARRAY) { + buf.append(visit(((ArrayType) args.head).elemtype, locale)); + buf.append("..."); + } else { + buf.append(visit(args.head, locale)); + } + return buf.toString(); + } + } + + @Override + public String visitClassSymbol(ClassSymbol sym, Locale locale) { + return sym.name.isEmpty() + ? localize(locale, "compiler.misc.anonymous.class", sym.flatname) + : sym.fullname.toString(); + } + + @Override + public String visitMethodSymbol(MethodSymbol s, Locale locale) { + if ((s.flags() & BLOCK) != 0) { + return s.owner.name.toString(); + } else { + String ms = (s.name == s.name.table.names.init) + ? s.owner.name.toString() + : s.name.toString(); + if (s.type != null) { + if (s.type.tag == FORALL) { + ms = "<" + visitTypes(s.type.getTypeArguments(), locale) + ">" + ms; + } + ms += "(" + printMethodArgs( + s.type.getParameterTypes(), + (s.flags() & VARARGS) != 0, + locale) + ")"; + } + return ms; + } + } + + @Override + public String visitOperatorSymbol(OperatorSymbol s, Locale locale) { + return visitMethodSymbol(s, locale); + } + + @Override + public String visitPackageSymbol(PackageSymbol s, Locale locale) { + return s.isUnnamed() + ? localize(locale, "compiler.misc.unnamed.package") + : s.fullname.toString(); + } + + @Override + public String visitTypeSymbol(TypeSymbol s, Locale locale) { + return visitSymbol(s, locale); + } + + @Override + public String visitVarSymbol(VarSymbol s, Locale locale) { + return visitSymbol(s, locale); + } + + @Override + public String visitSymbol(Symbol s, Locale locale) { + return s.name.toString(); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/code/Scope.java b/douyu-javac/src/main/java/com/sun/tools/javac/code/Scope.java new file mode 100644 index 0000000..db96649 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/code/Scope.java @@ -0,0 +1,753 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.code; + +import com.sun.tools.javac.util.*; +import java.util.Iterator; + +/** A scope represents an area of visibility in a Java program. The + * Scope class is a container for symbols which provides + * efficient access to symbols given their names. Scopes are implemented + * as hash tables with "open addressing" and "double hashing". + * Scopes can be nested; the next field of a scope points + * to its next outer scope. Nested scopes can share their hash tables. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Scope { + + /** The number of scopes that share this scope's hash table. + */ + private int shared; + + /** Next enclosing scope (with whom this scope may share a hashtable) + */ + public Scope next; + + /** The scope's owner. + */ + public Symbol owner; + + /** A hash table for the scope's entries. + */ + Entry[] table; + + /** Mask for hash codes, always equal to (table.length - 1). + */ + int hashMask; + + /** A linear list that also contains all entries in + * reverse order of appearance (i.e later entries are pushed on top). + */ + public Entry elems; + + /** The number of elements in this scope. + * This includes deleted elements, whose value is the sentinel. + */ + int nelems = 0; + + /** A list of scopes to be notified if items are to be removed from this scope. + */ + List listeners = List.nil(); + + /** Use as a "not-found" result for lookup. + * Also used to mark deleted entries in the table. + */ + private static final Entry sentinel = new Entry(null, null, null, null); + + /** The hash table's initial size. + */ + private static final int INITIAL_SIZE = 0x10; + + /** A value for the empty scope. + */ + public static final Scope emptyScope = new Scope(null, null, new Entry[]{}); + + /** Construct a new scope, within scope next, with given owner, using + * given table. The table's length must be an exponent of 2. + */ + private Scope(Scope next, Symbol owner, Entry[] table) { + this.next = next; + Assert.check(emptyScope == null || owner != null); + this.owner = owner; + this.table = table; + this.hashMask = table.length - 1; + } + + /** Convenience constructor used for dup and dupUnshared. */ + private Scope(Scope next, Symbol owner, Entry[] table, int nelems) { + this(next, owner, table); + this.nelems = nelems; + } + + /** Construct a new scope, within scope next, with given owner, + * using a fresh table of length INITIAL_SIZE. + */ + public Scope(Symbol owner) { + this(null, owner, new Entry[INITIAL_SIZE]); + } + + /** Construct a fresh scope within this scope, with same owner, + * which shares its table with the outer scope. Used in connection with + * method leave if scope access is stack-like in order to avoid allocation + * of fresh tables. + */ + public Scope dup() { + return dup(this.owner); + } + + /** Construct a fresh scope within this scope, with new owner, + * which shares its table with the outer scope. Used in connection with + * method leave if scope access is stack-like in order to avoid allocation + * of fresh tables. + */ + public Scope dup(Symbol newOwner) { + Scope result = new Scope(this, newOwner, this.table, this.nelems); + shared++; + // System.out.println("====> duping scope " + this.hashCode() + " owned by " + newOwner + " to " + result.hashCode()); + // new Error().printStackTrace(System.out); + return result; + } + + /** Construct a fresh scope within this scope, with same owner, + * with a new hash table, whose contents initially are those of + * the table of its outer scope. + */ + public Scope dupUnshared() { + return new Scope(this, this.owner, this.table.clone(), this.nelems); + } + + /** Remove all entries of this scope from its table, if shared + * with next. + */ + public Scope leave() { + Assert.check(shared == 0); + if (table != next.table) return next; + while (elems != null) { + int hash = getIndex(elems.sym.name); + Entry e = table[hash]; + Assert.check(e == elems, elems.sym); + table[hash] = elems.shadowed; + elems = elems.sibling; + } + Assert.check(next.shared > 0); + next.shared--; + next.nelems = nelems; + // System.out.println("====> leaving scope " + this.hashCode() + " owned by " + this.owner + " to " + next.hashCode()); + // new Error().printStackTrace(System.out); + return next; + } + + /** Double size of hash table. + */ + private void dble() { + Assert.check(shared == 0); + Entry[] oldtable = table; + Entry[] newtable = new Entry[oldtable.length * 2]; + for (Scope s = this; s != null; s = s.next) { + if (s.table == oldtable) { + Assert.check(s == this || s.shared != 0); + s.table = newtable; + s.hashMask = newtable.length - 1; + } + } + int n = 0; + for (int i = oldtable.length; --i >= 0; ) { + Entry e = oldtable[i]; + if (e != null && e != sentinel) { + table[getIndex(e.sym.name)] = e; + n++; + } + } + // We don't need to update nelems for shared inherited scopes, + // since that gets handled by leave(). + nelems = n; + } + + /** Enter symbol sym in this scope. + */ + public void enter(Symbol sym) { + Assert.check(shared == 0); + enter(sym, this); + } + + public void enter(Symbol sym, Scope s) { + enter(sym, s, s); + } + + /** + * Enter symbol sym in this scope, but mark that it comes from + * given scope `s' accessed through `origin'. The last two + * arguments are only used in import scopes. + */ + public void enter(Symbol sym, Scope s, Scope origin) { + Assert.check(shared == 0); + if (nelems * 3 >= hashMask * 2) + dble(); + int hash = getIndex(sym.name); + Entry old = table[hash]; + if (old == null) { + old = sentinel; + nelems++; + } + Entry e = makeEntry(sym, old, elems, s, origin); + table[hash] = e; + elems = e; + + //notify listeners + for (List l = listeners; l.nonEmpty(); l = l.tail) { + l.head.symbolAdded(sym, this); + } + } + + Entry makeEntry(Symbol sym, Entry shadowed, Entry sibling, Scope scope, Scope origin) { + return new Entry(sym, shadowed, sibling, scope); + } + + + public interface ScopeListener { + public void symbolAdded(Symbol sym, Scope s); + public void symbolRemoved(Symbol sym, Scope s); + } + + public void addScopeListener(ScopeListener sl) { + listeners = listeners.prepend(sl); + } + + /** Remove symbol from this scope. Used when an inner class + * attribute tells us that the class isn't a package member. + */ + public void remove(Symbol sym) { + Assert.check(shared == 0); + Entry e = lookup(sym.name); + if (e.scope == null) return; + + // remove e from table and shadowed list; + int i = getIndex(sym.name); + Entry te = table[i]; + if (te == e) + table[i] = e.shadowed; + else while (true) { + if (te.shadowed == e) { + te.shadowed = e.shadowed; + break; + } + te = te.shadowed; + } + + // remove e from elems and sibling list + te = elems; + if (te == e) + elems = e.sibling; + else while (true) { + if (te.sibling == e) { + te.sibling = e.sibling; + break; + } + te = te.sibling; + } + + //notify listeners + for (List l = listeners; l.nonEmpty(); l = l.tail) { + l.head.symbolRemoved(sym, this); + } + } + + /** Enter symbol sym in this scope if not already there. + */ + public void enterIfAbsent(Symbol sym) { + Assert.check(shared == 0); + Entry e = lookup(sym.name); + while (e.scope == this && e.sym.kind != sym.kind) e = e.next(); + if (e.scope != this) enter(sym); + } + + /** Given a class, is there already a class with same fully + * qualified name in this (import) scope? + */ + public boolean includes(Symbol c) { + for (Scope.Entry e = lookup(c.name); + e.scope == this; + e = e.next()) { + if (e.sym == c) return true; + } + return false; + } + + static final Filter noFilter = new Filter() { + public boolean accepts(Symbol s) { + return true; + } + }; + + /** Return the entry associated with given name, starting in + * this scope and proceeding outwards. If no entry was found, + * return the sentinel, which is characterized by having a null in + * both its scope and sym fields, whereas both fields are non-null + * for regular entries. + */ + public Entry lookup(Name name) { + return lookup(name, noFilter); + } + public Entry lookup(Name name, Filter sf) { + Entry e = table[getIndex(name)]; + if (e == null || e == sentinel) + return sentinel; + while (e.scope != null && (e.sym.name != name || !sf.accepts(e.sym))) + e = e.shadowed; + return e; + } + + /*void dump (java.io.PrintStream out) { + out.println(this); + for (int l=0; l < table.length; l++) { + Entry le = table[l]; + out.print("#"+l+": "); + if (le==sentinel) out.println("sentinel"); + else if(le == null) out.println("null"); + else out.println(""+le+" s:"+le.sym); + } + }*/ + + /** Look for slot in the table. + * We use open addressing with double hashing. + */ + int getIndex (Name name) { + int h = name.hashCode(); + int i = h & hashMask; + // The expression below is always odd, so it is guaranteed + // to be mutually prime with table.length, a power of 2. + int x = hashMask - ((h + (h >> 16)) << 1); + int d = -1; // Index of a deleted item. + for (;;) { + Entry e = table[i]; + if (e == null) + return d >= 0 ? d : i; + if (e == sentinel) { + // We have to keep searching even if we see a deleted item. + // However, remember the index in case we fail to find the name. + if (d < 0) + d = i; + } else if (e.sym.name == name) + return i; + i = (i + x) & hashMask; + } + } + + public Iterable getElements() { + return getElements(noFilter); + } + + public Iterable getElements(final Filter sf) { + return new Iterable() { + public Iterator iterator() { + return new Iterator() { + private Scope currScope = Scope.this; + private Scope.Entry currEntry = elems; + { + update(); + } + + public boolean hasNext() { + return currEntry != null; + } + + public Symbol next() { + Symbol sym = (currEntry == null ? null : currEntry.sym); + if (currEntry != null) { + currEntry = currEntry.sibling; + } + update(); + return sym; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + private void update() { + skipToNextMatchingEntry(); + while (currEntry == null && currScope.next != null) { + currScope = currScope.next; + currEntry = currScope.elems; + skipToNextMatchingEntry(); + } + } + + void skipToNextMatchingEntry() { + while (currEntry != null && !sf.accepts(currEntry.sym)) { + currEntry = currEntry.sibling; + } + } + }; + } + }; + } + + public Iterable getElementsByName(Name name) { + return getElementsByName(name, noFilter); + } + + public Iterable getElementsByName(final Name name, final Filter sf) { + return new Iterable() { + public Iterator iterator() { + return new Iterator() { + Scope.Entry currentEntry = lookup(name, sf); + + public boolean hasNext() { + return currentEntry.scope != null; + } + public Symbol next() { + Scope.Entry prevEntry = currentEntry; + currentEntry = currentEntry.next(sf); + return prevEntry.sym; + } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("Scope["); + for (Scope s = this; s != null ; s = s.next) { + if (s != this) result.append(" | "); + for (Entry e = s.elems; e != null; e = e.sibling) { + if (e != s.elems) result.append(", "); + result.append(e.sym); + } + } + result.append("]"); + return result.toString(); + } + + /** A class for scope entries. + */ + public static class Entry { + + /** The referenced symbol. + * sym == null iff this == sentinel + */ + public Symbol sym; + + /** An entry with the same hash code, or sentinel. + */ + private Entry shadowed; + + /** Next entry in same scope. + */ + public Entry sibling; + + /** The entry's scope. + * scope == null iff this == sentinel + * for an entry in an import scope, this is the scope + * where the entry came from (i.e. was imported from). + */ + public Scope scope; + + public Entry(Symbol sym, Entry shadowed, Entry sibling, Scope scope) { + this.sym = sym; + this.shadowed = shadowed; + this.sibling = sibling; + this.scope = scope; + } + + /** Return next entry with the same name as this entry, proceeding + * outwards if not found in this scope. + */ + public Entry next() { + return shadowed; + } + + public Entry next(Filter sf) { + if (shadowed.sym == null || sf.accepts(shadowed.sym)) return shadowed; + else return shadowed.next(sf); + } + + public Scope getOrigin() { + // The origin is only recorded for import scopes. For all + // other scope entries, the "enclosing" type is available + // from other sources. See Attr.visitSelect and + // Attr.visitIdent. Rather than throwing an assertion + // error, we return scope which will be the same as origin + // in many cases. + return scope; + } + } + + public static class ImportScope extends Scope { + + public ImportScope(Symbol owner) { + super(owner); + } + + @Override + Entry makeEntry(Symbol sym, Entry shadowed, Entry sibling, Scope scope, Scope origin) { + return new ImportEntry(sym, shadowed, sibling, scope, origin); + } + + static class ImportEntry extends Entry { + private Scope origin; + + ImportEntry(Symbol sym, Entry shadowed, Entry sibling, Scope scope, Scope origin) { + super(sym, shadowed, sibling, scope); + this.origin = origin; + } + + @Override + public Scope getOrigin() { return origin; } + } + } + + public static class StarImportScope extends ImportScope implements ScopeListener { + + public StarImportScope(Symbol owner) { + super(owner); + } + + public void importAll (Scope fromScope) { + for (Scope.Entry e = fromScope.elems; e != null; e = e.sibling) { + if (e.sym.kind == Kinds.TYP && !includes(e.sym)) + enter(e.sym, fromScope); + } + // Register to be notified when imported items are removed + fromScope.addScopeListener(this); + } + + public void symbolRemoved(Symbol sym, Scope s) { + remove(sym); + } + public void symbolAdded(Symbol sym, Scope s) { } + } + + /** An empty scope, into which you can't place anything. Used for + * the scope for a variable initializer. + */ + public static class DelegatedScope extends Scope { + Scope delegatee; + public static final Entry[] emptyTable = new Entry[0]; + + public DelegatedScope(Scope outer) { + super(outer, outer.owner, emptyTable); + delegatee = outer; + } + public Scope dup() { + return new DelegatedScope(next); + } + public Scope dupUnshared() { + return new DelegatedScope(next); + } + public Scope leave() { + return next; + } + public void enter(Symbol sym) { + // only anonymous classes could be put here + } + public void enter(Symbol sym, Scope s) { + // only anonymous classes could be put here + } + public void remove(Symbol sym) { + throw new AssertionError(sym); + } + public Entry lookup(Name name) { + return delegatee.lookup(name); + } + } + + /** A class scope adds capabilities to keep track of changes in related + * class scopes - this allows client to realize whether a class scope + * has changed, either directly (because a new member has been added/removed + * to this scope) or indirectly (i.e. because a new member has been + * added/removed into a supertype scope) + */ + public static class CompoundScope extends Scope implements ScopeListener { + + public static final Entry[] emptyTable = new Entry[0]; + + private List subScopes = List.nil(); + private int mark = 0; + + public CompoundScope(Symbol owner) { + super(null, owner, emptyTable); + } + + public void addSubScope(Scope that) { + if (that != null) { + subScopes = subScopes.prepend(that); + that.addScopeListener(this); + mark++; + for (ScopeListener sl : listeners) { + sl.symbolAdded(null, this); //propagate upwards in case of nested CompoundScopes + } + } + } + + public void symbolAdded(Symbol sym, Scope s) { + mark++; + for (ScopeListener sl : listeners) { + sl.symbolAdded(sym, s); + } + } + + public void symbolRemoved(Symbol sym, Scope s) { + mark++; + for (ScopeListener sl : listeners) { + sl.symbolRemoved(sym, s); + } + } + + public int getMark() { + return mark; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("CompoundScope{"); + String sep = ""; + for (Scope s : subScopes) { + buf.append(sep); + buf.append(s); + sep = ","; + } + buf.append("}"); + return buf.toString(); + } + + @Override + public Iterable getElements(final Filter sf) { + return new Iterable() { + public Iterator iterator() { + return new CompoundScopeIterator(subScopes) { + Iterator nextIterator(Scope s) { + return s.getElements(sf).iterator(); + } + }; + } + }; + } + + @Override + public Iterable getElementsByName(final Name name, final Filter sf) { + return new Iterable() { + public Iterator iterator() { + return new CompoundScopeIterator(subScopes) { + Iterator nextIterator(Scope s) { + return s.getElementsByName(name, sf).iterator(); + } + }; + } + }; + } + + abstract class CompoundScopeIterator implements Iterator { + + private Iterator currentIterator; + private List scopesToScan; + + public CompoundScopeIterator(List scopesToScan) { + this.scopesToScan = scopesToScan; + update(); + } + + abstract Iterator nextIterator(Scope s); + + public boolean hasNext() { + return currentIterator != null; + } + + public Symbol next() { + Symbol sym = currentIterator.next(); + if (!currentIterator.hasNext()) { + update(); + } + return sym; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + private void update() { + while (scopesToScan.nonEmpty()) { + currentIterator = nextIterator(scopesToScan.head); + scopesToScan = scopesToScan.tail; + if (currentIterator.hasNext()) return; + } + currentIterator = null; + } + } + + @Override + public Entry lookup(Name name, Filter sf) { + throw new UnsupportedOperationException(); + } + + @Override + public Scope dup(Symbol newOwner) { + throw new UnsupportedOperationException(); + } + + @Override + public void enter(Symbol sym, Scope s, Scope origin) { + throw new UnsupportedOperationException(); + } + + @Override + public void remove(Symbol sym) { + throw new UnsupportedOperationException(); + } + } + + /** An error scope, for which the owner should be an error symbol. */ + public static class ErrorScope extends Scope { + ErrorScope(Scope next, Symbol errSymbol, Entry[] table) { + super(next, /*owner=*/errSymbol, table); + } + public ErrorScope(Symbol errSymbol) { + super(errSymbol); + } + public Scope dup() { + return new ErrorScope(this, owner, table); + } + public Scope dupUnshared() { + return new ErrorScope(this, owner, table.clone()); + } + public Entry lookup(Name name) { + Entry e = super.lookup(name); + if (e.scope == null) + return new Entry(owner, null, null, null); + else + return e; + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/code/Source.java b/douyu-javac/src/main/java/com/sun/tools/javac/code/Source.java new file mode 100644 index 0000000..a11cde9 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/code/Source.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.code; + +import java.util.*; +import javax.lang.model.SourceVersion; +import static javax.lang.model.SourceVersion.*; + +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.jvm.Target; + +import static com.sun.tools.javac.main.OptionName.*; + +/** The source language version accepted. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public enum Source { + /** 1.0 had no inner classes, and so could not pass the JCK. */ + // public static final Source JDK1_0 = new Source("1.0"); + + /** 1.1 did not have strictfp, and so could not pass the JCK. */ + // public static final Source JDK1_1 = new Source("1.1"); + + /** 1.2 introduced strictfp. */ + JDK1_2("1.2"), + + /** 1.3 is the same language as 1.2. */ + JDK1_3("1.3"), + + /** 1.4 introduced assert. */ + JDK1_4("1.4"), + + /** 1.5 introduced generics, attributes, foreach, boxing, static import, + * covariant return, enums, varargs, et al. */ + JDK1_5("1.5"), + + /** 1.6 reports encoding problems as errors instead of warnings. */ + JDK1_6("1.6"), + + /** 1.7 covers the to be determined language features that will be added in JDK 7. */ + JDK1_7("1.7"); + + private static final Context.Key sourceKey + = new Context.Key(); + + public static Source instance(Context context) { + Source instance = context.get(sourceKey); + if (instance == null) { + Options options = Options.instance(context); + String sourceString = options.get(SOURCE); + if (sourceString != null) instance = lookup(sourceString); + if (instance == null) instance = DEFAULT; + context.put(sourceKey, instance); + } + return instance; + } + + public final String name; + + private static Map tab = new HashMap(); + static { + for (Source s : values()) { + tab.put(s.name, s); + } + tab.put("5", JDK1_5); // Make 5 an alias for 1.5 + tab.put("6", JDK1_6); // Make 6 an alias for 1.6 + tab.put("7", JDK1_7); // Make 7 an alias for 1.7 + } + + private Source(String name) { + this.name = name; + } + + public static final Source DEFAULT = JDK1_7; + + public static Source lookup(String name) { + return tab.get(name); + } + + public Target requiredTarget() { + if (this.compareTo(JDK1_7) >= 0) return Target.JDK1_7; + if (this.compareTo(JDK1_6) >= 0) return Target.JDK1_6; + if (this.compareTo(JDK1_5) >= 0) return Target.JDK1_5; + if (this.compareTo(JDK1_4) >= 0) return Target.JDK1_4; + return Target.JDK1_1; + } + + /** Allow encoding errors, giving only warnings. */ + public boolean allowEncodingErrors() { + return compareTo(JDK1_6) < 0; + } + public boolean allowAsserts() { + return compareTo(JDK1_4) >= 0; + } + public boolean allowCovariantReturns() { + return compareTo(JDK1_5) >= 0; + } + public boolean allowGenerics() { + return compareTo(JDK1_5) >= 0; + } + public boolean allowDiamond() { + return compareTo(JDK1_7) >= 0; + } + public boolean allowMulticatch() { + return compareTo(JDK1_7) >= 0; + } + public boolean allowImprovedRethrowAnalysis() { + return compareTo(JDK1_7) >= 0; + } + public boolean allowImprovedCatchAnalysis() { + return compareTo(JDK1_7) >= 0; + } + public boolean allowEnums() { + return compareTo(JDK1_5) >= 0; + } + public boolean allowForeach() { + return compareTo(JDK1_5) >= 0; + } + public boolean allowStaticImport() { + return compareTo(JDK1_5) >= 0; + } + public boolean allowBoxing() { + return compareTo(JDK1_5) >= 0; + } + public boolean allowVarargs() { + return compareTo(JDK1_5) >= 0; + } + public boolean allowAnnotations() { + return compareTo(JDK1_5) >= 0; + } + // hex floating-point literals supported? + public boolean allowHexFloats() { + return compareTo(JDK1_5) >= 0; + } + public boolean allowAnonOuterThis() { + return compareTo(JDK1_5) >= 0; + } + public boolean addBridges() { + return compareTo(JDK1_5) >= 0; + } + public boolean enforceMandatoryWarnings() { + return compareTo(JDK1_5) >= 0; + } + public boolean allowTryWithResources() { + return compareTo(JDK1_7) >= 0; + } + public boolean allowTypeAnnotations() { + return compareTo(JDK1_7) >= 0; + } + public boolean allowBinaryLiterals() { + return compareTo(JDK1_7) >= 0; + } + public boolean allowUnderscoresInLiterals() { + return compareTo(JDK1_7) >= 0; + } + public boolean allowStringsInSwitch() { + return compareTo(JDK1_7) >= 0; + } + public boolean allowSimplifiedVarargs() { + return compareTo(JDK1_7) >= 0; + } + public boolean allowObjectToPrimitiveCast() { + return compareTo(JDK1_7) >= 0; + } + +// public static SourceVersion toSourceVersion(Source source) { +// switch(source) { +// case JDK1_2: +// return RELEASE_2; +// case JDK1_3: +// return RELEASE_3; +// case JDK1_4: +// return RELEASE_4; +// case JDK1_5: +// return RELEASE_5; +// case JDK1_6: +// return RELEASE_6; +// case JDK1_7: +// return RELEASE_6; +// default: +// return null; +// } +// } + + //我加上的,原有代码无法在1.6上运行 + public static SourceVersion toSourceVersion(Source source) { + switch(source) { + case JDK1_2: + return RELEASE_2; + case JDK1_3: + return RELEASE_3; + case JDK1_4: + return RELEASE_4; + case JDK1_5: + return RELEASE_5; + case JDK1_6: + return RELEASE_6; + case JDK1_7: + if (SourceVersion.latest().ordinal() > SourceVersion.RELEASE_6.ordinal()) { + return RELEASE_7; + } else { + return RELEASE_6; + } + default: + return null; + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/code/Symbol.java b/douyu-javac/src/main/java/com/sun/tools/javac/code/Symbol.java new file mode 100644 index 0000000..2ee1af2 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/code/Symbol.java @@ -0,0 +1,1470 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.code; + +import java.util.Set; +import java.util.concurrent.Callable; + +import javax.lang.model.SourceVersion; +import javax.lang.model.element.*; +import javax.tools.JavaFileObject; + +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.Name; +import com.sun.tools.javac.code.Type.*; +import com.sun.tools.javac.comp.Attr; +import com.sun.tools.javac.comp.AttrContext; +import com.sun.tools.javac.comp.Env; +import com.sun.tools.javac.jvm.*; +import com.sun.tools.javac.model.*; +import com.sun.tools.javac.tree.JCTree; + +import static com.sun.tools.javac.code.Flags.*; +import static com.sun.tools.javac.code.Kinds.*; +import static com.sun.tools.javac.code.TypeTags.*; + +/** Root class for Java symbols. It contains subclasses + * for specific sorts of symbols, such as variables, methods and operators, + * types, packages. Each subclass is represented as a static inner class + * inside Symbol. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public abstract class Symbol implements Element { + // public Throwable debug = new Throwable(); + + /** The kind of this symbol. + * @see Kinds + */ + public int kind; + + /** The flags of this symbol. + */ + public long flags_field; + + /** An accessor method for the flags of this symbol. + * Flags of class symbols should be accessed through the accessor + * method to make sure that the class symbol is loaded. + */ + public long flags() { return flags_field; } + + /** The attributes of this symbol. + */ + public List attributes_field; + + /** An accessor method for the attributes of this symbol. + * Attributes of class symbols should be accessed through the accessor + * method to make sure that the class symbol is loaded. + */ + public List getAnnotationMirrors() { + return Assert.checkNonNull(attributes_field); + } + + /** Fetch a particular annotation from a symbol. */ + public Attribute.Compound attribute(Symbol anno) { + for (Attribute.Compound a : getAnnotationMirrors()) + if (a.type.tsym == anno) return a; + return null; + } + + /** The name of this symbol in Utf8 representation. + */ + public Name name; + + /** The type of this symbol. + */ + public Type type; + + /** The owner of this symbol. + */ + public Symbol owner; + + /** The completer of this symbol. + */ + public Completer completer; + + /** A cache for the type erasure of this symbol. + */ + public Type erasure_field; + + /** Construct a symbol with given kind, flags, name, type and owner. + */ + public Symbol(int kind, long flags, Name name, Type type, Symbol owner) { + this.kind = kind; + this.flags_field = flags; + this.type = type; + this.owner = owner; + this.completer = null; + this.erasure_field = null; + this.attributes_field = List.nil(); + this.name = name; + } + + /** Clone this symbol with new owner. + * Legal only for fields and methods. + */ + public Symbol clone(Symbol newOwner) { + throw new AssertionError(); + } + + public R accept(Symbol.Visitor v, P p) { + return v.visitSymbol(this, p); + } + + /** The Java source which this symbol represents. + * A description of this symbol; overrides Object. + */ + public String toString() { + return name.toString(); + } + + /** A Java source description of the location of this symbol; used for + * error reporting. + * + * @return null if the symbol is a package or a toplevel class defined in + * the default package; otherwise, the owner symbol is returned + */ + public Symbol location() { + if (owner.name == null || (owner.name.isEmpty() && owner.kind != PCK && owner.kind != TYP)) { + return null; + } + return owner; + } + + public Symbol location(Type site, Types types) { + if (owner.name == null || owner.name.isEmpty()) { + return location(); + } + if (owner.type.tag == CLASS) { + Type ownertype = types.asOuterSuper(site, owner); + if (ownertype != null) return ownertype.tsym; + } + return owner; + } + + /** The symbol's erased type. + */ + public Type erasure(Types types) { + if (erasure_field == null) + erasure_field = types.erasure(type); + return erasure_field; + } + + /** The external type of a symbol. This is the symbol's erased type + * except for constructors of inner classes which get the enclosing + * instance class added as first argument. + */ + public Type externalType(Types types) { + Type t = erasure(types); + if (name == name.table.names.init && owner.hasOuterInstance()) { + Type outerThisType = types.erasure(owner.type.getEnclosingType()); + return new MethodType(t.getParameterTypes().prepend(outerThisType), + t.getReturnType(), + t.getThrownTypes(), + t.tsym); + } else { + return t; + } + } + + public boolean isStatic() { + return + (flags() & STATIC) != 0 || + (owner.flags() & INTERFACE) != 0 && kind != MTH; + } + + public boolean isInterface() { + return (flags() & INTERFACE) != 0; + } + + /** Recognize if this symbol was marked @PolymorphicSignature in the source. */ + public boolean isPolymorphicSignatureGeneric() { + return (flags() & (POLYMORPHIC_SIGNATURE | HYPOTHETICAL)) == POLYMORPHIC_SIGNATURE; + } + + /** Recognize if this symbol was split from a @PolymorphicSignature symbol in the source. */ + public boolean isPolymorphicSignatureInstance() { + return (flags() & (POLYMORPHIC_SIGNATURE | HYPOTHETICAL)) == (POLYMORPHIC_SIGNATURE | HYPOTHETICAL); + } + + /** Is this symbol declared (directly or indirectly) local + * to a method or variable initializer? + * Also includes fields of inner classes which are in + * turn local to a method or variable initializer. + */ + public boolean isLocal() { + return + (owner.kind & (VAR | MTH)) != 0 || + (owner.kind == TYP && owner.isLocal()); + } + + /** Has this symbol an empty name? This includes anonymous + * inner classses. + */ + public boolean isAnonymous() { + return name.isEmpty(); + } + + /** Is this symbol a constructor? + */ + public boolean isConstructor() { + return name == name.table.names.init; + } + + /** The fully qualified name of this symbol. + * This is the same as the symbol's name except for class symbols, + * which are handled separately. + */ + public Name getQualifiedName() { + return name; + } + + /** The fully qualified name of this symbol after converting to flat + * representation. This is the same as the symbol's name except for + * class symbols, which are handled separately. + */ + public Name flatName() { + return getQualifiedName(); + } + + /** If this is a class or package, its members, otherwise null. + */ + public Scope members() { + return null; + } + + /** A class is an inner class if it it has an enclosing instance class. + */ + public boolean isInner() { + return type.getEnclosingType().tag == CLASS; + } + + /** An inner class has an outer instance if it is not an interface + * it has an enclosing instance class which might be referenced from the class. + * Nested classes can see instance members of their enclosing class. + * Their constructors carry an additional this$n parameter, inserted + * implicitly by the compiler. + * + * @see #isInner + */ + public boolean hasOuterInstance() { + return + type.getEnclosingType().tag == CLASS && (flags() & (INTERFACE | NOOUTERTHIS)) == 0; + } + + /** The closest enclosing class of this symbol's declaration. + */ + public ClassSymbol enclClass() { + Symbol c = this; + while (c != null && + ((c.kind & TYP) == 0 || c.type.tag != CLASS)) { + c = c.owner; + } + return (ClassSymbol)c; + } + + /** The outermost class which indirectly owns this symbol. + */ + public ClassSymbol outermostClass() { + Symbol sym = this; + Symbol prev = null; + while (sym.kind != PCK) { + prev = sym; + sym = sym.owner; + } + return (ClassSymbol) prev; + } + + /** The package which indirectly owns this symbol. + */ + public PackageSymbol packge() { + Symbol sym = this; + while (sym.kind != PCK) { + sym = sym.owner; + } + return (PackageSymbol) sym; + } + + /** Is this symbol a subclass of `base'? Only defined for ClassSymbols. + */ + public boolean isSubClass(Symbol base, Types types) { + throw new AssertionError("isSubClass " + this); + } + + /** Fully check membership: hierarchy, protection, and hiding. + * Does not exclude methods not inherited due to overriding. + */ + public boolean isMemberOf(TypeSymbol clazz, Types types) { + return + owner == clazz || + clazz.isSubClass(owner, types) && + isInheritedIn(clazz, types) && + !hiddenIn((ClassSymbol)clazz, types); + } + + /** Is this symbol the same as or enclosed by the given class? */ + public boolean isEnclosedBy(ClassSymbol clazz) { + for (Symbol sym = this; sym.kind != PCK; sym = sym.owner) + if (sym == clazz) return true; + return false; + } + + /** Check for hiding. Note that this doesn't handle multiple + * (interface) inheritance. */ + private boolean hiddenIn(ClassSymbol clazz, Types types) { + if (kind == MTH && (flags() & STATIC) == 0) return false; + while (true) { + if (owner == clazz) return false; + Scope.Entry e = clazz.members().lookup(name); + while (e.scope != null) { + if (e.sym == this) return false; + if (e.sym.kind == kind && + (kind != MTH || + (e.sym.flags() & STATIC) != 0 && + types.isSubSignature(e.sym.type, type))) + return true; + e = e.next(); + } + Type superType = types.supertype(clazz.type); + if (superType.tag != TypeTags.CLASS) return false; + clazz = (ClassSymbol)superType.tsym; + } + } + + /** Is this symbol inherited into a given class? + * PRE: If symbol's owner is a interface, + * it is already assumed that the interface is a superinterface + * of given class. + * @param clazz The class for which we want to establish membership. + * This must be a subclass of the member's owner. + */ + public boolean isInheritedIn(Symbol clazz, Types types) { + switch ((int)(flags_field & Flags.AccessFlags)) { + default: // error recovery + case PUBLIC: + return true; + case PRIVATE: + return this.owner == clazz; + case PROTECTED: + // we model interfaces as extending Object + return (clazz.flags() & INTERFACE) == 0; + case 0: + PackageSymbol thisPackage = this.packge(); + for (Symbol sup = clazz; + sup != null && sup != this.owner; + sup = types.supertype(sup.type).tsym) { + while (sup.type.tag == TYPEVAR) + sup = sup.type.getUpperBound().tsym; + if (sup.type.isErroneous()) + return true; // error recovery + if ((sup.flags() & COMPOUND) != 0) + continue; + if (sup.packge() != thisPackage) + return false; + } + return (clazz.flags() & INTERFACE) == 0; + } + } + + /** The (variable or method) symbol seen as a member of given + * class type`site' (this might change the symbol's type). + * This is used exclusively for producing diagnostics. + */ + public Symbol asMemberOf(Type site, Types types) { + throw new AssertionError(); + } + + /** Does this method symbol override `other' symbol, when both are seen as + * members of class `origin'? It is assumed that _other is a member + * of origin. + * + * It is assumed that both symbols have the same name. The static + * modifier is ignored for this test. + * + * See JLS 8.4.6.1 (without transitivity) and 8.4.6.4 + */ + public boolean overrides(Symbol _other, TypeSymbol origin, Types types, boolean checkResult) { + return false; + } + + /** Complete the elaboration of this symbol's definition. + */ + public void complete() throws CompletionFailure { + if (completer != null) { + Completer c = completer; + completer = null; + c.complete(this); + } + } + + /** True if the symbol represents an entity that exists. + */ + public boolean exists() { + return true; + } + + public Type asType() { + return type; + } + + public Symbol getEnclosingElement() { + return owner; + } + + public ElementKind getKind() { + return ElementKind.OTHER; // most unkind + } + + public Set getModifiers() { + return Flags.asModifierSet(flags()); + } + + public Name getSimpleName() { + return name; + } + + /** + * @deprecated this method should never be used by javac internally. + */ + @Deprecated + public A getAnnotation(Class annoType) { + return JavacElements.getAnnotation(this, annoType); + } + + // TODO: getEnclosedElements should return a javac List, fix in FilteredMemberList + public java.util.List getEnclosedElements() { + return List.nil(); + } + + public List getTypeParameters() { + ListBuffer l = ListBuffer.lb(); + for (Type t : type.getTypeArguments()) { + l.append(t.tsym); + } + return l.toList(); + } + + public static class DelegatedSymbol extends Symbol { + protected Symbol other; + public DelegatedSymbol(Symbol other) { + super(other.kind, other.flags_field, other.name, other.type, other.owner); + this.other = other; + } + public String toString() { return other.toString(); } + public Symbol location() { return other.location(); } + public Symbol location(Type site, Types types) { return other.location(site, types); } + public Type erasure(Types types) { return other.erasure(types); } + public Type externalType(Types types) { return other.externalType(types); } + public boolean isLocal() { return other.isLocal(); } + public boolean isConstructor() { return other.isConstructor(); } + public Name getQualifiedName() { return other.getQualifiedName(); } + public Name flatName() { return other.flatName(); } + public Scope members() { return other.members(); } + public boolean isInner() { return other.isInner(); } + public boolean hasOuterInstance() { return other.hasOuterInstance(); } + public ClassSymbol enclClass() { return other.enclClass(); } + public ClassSymbol outermostClass() { return other.outermostClass(); } + public PackageSymbol packge() { return other.packge(); } + public boolean isSubClass(Symbol base, Types types) { return other.isSubClass(base, types); } + public boolean isMemberOf(TypeSymbol clazz, Types types) { return other.isMemberOf(clazz, types); } + public boolean isEnclosedBy(ClassSymbol clazz) { return other.isEnclosedBy(clazz); } + public boolean isInheritedIn(Symbol clazz, Types types) { return other.isInheritedIn(clazz, types); } + public Symbol asMemberOf(Type site, Types types) { return other.asMemberOf(site, types); } + public void complete() throws CompletionFailure { other.complete(); } + + public R accept(ElementVisitor v, P p) { + return other.accept(v, p); + } + + public R accept(Symbol.Visitor v, P p) { + return v.visitSymbol(other, p); + } + } + + /** A class for type symbols. Type variables are represented by instances + * of this class, classes and packages by instances of subclasses. + */ + public static class TypeSymbol + extends Symbol implements TypeParameterElement { + // Implements TypeParameterElement because type parameters don't + // have their own TypeSymbol subclass. + // TODO: type parameters should have their own TypeSymbol subclass + + public TypeSymbol(long flags, Name name, Type type, Symbol owner) { + super(TYP, flags, name, type, owner); + } + + /** form a fully qualified name from a name and an owner + */ + static public Name formFullName(Name name, Symbol owner) { + if (owner == null) return name; + if (((owner.kind != ERR)) && + ((owner.kind & (VAR | MTH)) != 0 + || (owner.kind == TYP && owner.type.tag == TYPEVAR) + )) return name; + Name prefix = owner.getQualifiedName(); + if (prefix == null || prefix == prefix.table.names.empty) + return name; + else return prefix.append('.', name); + } + + /** form a fully qualified name from a name and an owner, after + * converting to flat representation + */ + static public Name formFlatName(Name name, Symbol owner) { + if (owner == null || + (owner.kind & (VAR | MTH)) != 0 + || (owner.kind == TYP && owner.type.tag == TYPEVAR) + ) return name; + char sep = owner.kind == TYP ? '$' : '.'; + Name prefix = owner.flatName(); + if (prefix == null || prefix == prefix.table.names.empty) + return name; + else return prefix.append(sep, name); + } + + /** + * A total ordering between type symbols that refines the + * class inheritance graph. + * + * Typevariables always precede other kinds of symbols. + */ + public final boolean precedes(TypeSymbol that, Types types) { + if (this == that) + return false; + if (this.type.tag == that.type.tag) { + if (this.type.tag == CLASS) { + return + types.rank(that.type) < types.rank(this.type) || + types.rank(that.type) == types.rank(this.type) && + that.getQualifiedName().compareTo(this.getQualifiedName()) < 0; + } else if (this.type.tag == TYPEVAR) { + return types.isSubtype(this.type, that.type); + } + } + return this.type.tag == TYPEVAR; + } + + // For type params; overridden in subclasses. + public ElementKind getKind() { + return ElementKind.TYPE_PARAMETER; + } + + public java.util.List getEnclosedElements() { + List list = List.nil(); + if (kind == TYP && type.tag == TYPEVAR) { + return list; + } + for (Scope.Entry e = members().elems; e != null; e = e.sibling) { + if (e.sym != null && (e.sym.flags() & SYNTHETIC) == 0 && e.sym.owner == this) + list = list.prepend(e.sym); + } + return list; + } + + // For type params. + // Perhaps not needed if getEnclosingElement can be spec'ed + // to do the same thing. + // TODO: getGenericElement() might not be needed + public Symbol getGenericElement() { + return owner; + } + + public R accept(ElementVisitor v, P p) { + Assert.check(type.tag == TYPEVAR); // else override will be invoked + return v.visitTypeParameter(this, p); + } + + public R accept(Symbol.Visitor v, P p) { + return v.visitTypeSymbol(this, p); + } + + public List getBounds() { + TypeVar t = (TypeVar)type; + Type bound = t.getUpperBound(); + if (!bound.isCompound()) + return List.of(bound); + ClassType ct = (ClassType)bound; + if (!ct.tsym.erasure_field.isInterface()) { + return ct.interfaces_field.prepend(ct.supertype_field); + } else { + // No superclass was given in bounds. + // In this case, supertype is Object, erasure is first interface. + return ct.interfaces_field; + } + } + } + + /** A class for package symbols + */ + public static class PackageSymbol extends TypeSymbol + implements PackageElement { + + public Scope members_field; + public Name fullname; + public ClassSymbol package_info; // see bug 6443073 + + public PackageSymbol(Name name, Type type, Symbol owner) { + super(0, name, type, owner); + this.kind = PCK; + this.members_field = null; + this.fullname = formFullName(name, owner); + } + + public PackageSymbol(Name name, Symbol owner) { + this(name, null, owner); + this.type = new PackageType(this); + } + + public String toString() { + return fullname.toString(); + } + + public Name getQualifiedName() { + return fullname; + } + + public boolean isUnnamed() { + return name.isEmpty() && owner != null; + } + + public Scope members() { + if (completer != null) complete(); + return members_field; + } + + public long flags() { + if (completer != null) complete(); + return flags_field; + } + + public List getAnnotationMirrors() { + if (completer != null) complete(); + if (package_info != null && package_info.completer != null) { + package_info.complete(); + if (attributes_field.isEmpty()) + attributes_field = package_info.attributes_field; + } + return Assert.checkNonNull(attributes_field); + } + + /** A package "exists" if a type or package that exists has + * been seen within it. + */ + public boolean exists() { + return (flags_field & EXISTS) != 0; + } + + public ElementKind getKind() { + return ElementKind.PACKAGE; + } + + public Symbol getEnclosingElement() { + return null; + } + + public R accept(ElementVisitor v, P p) { + return v.visitPackage(this, p); + } + + public R accept(Symbol.Visitor v, P p) { + return v.visitPackageSymbol(this, p); + } + } + + /** A class for class symbols + */ + public static class ClassSymbol extends TypeSymbol implements TypeElement { + + /** a scope for all class members; variables, methods and inner classes + * type parameters are not part of this scope + */ + public Scope members_field; + + /** the fully qualified name of the class, i.e. pck.outer.inner. + * null for anonymous classes + */ + public Name fullname; + + /** the fully qualified name of the class after converting to flat + * representation, i.e. pck.outer$inner, + * set externally for local and anonymous classes + */ + public Name flatname; + + /** the sourcefile where the class came from + */ + public JavaFileObject sourcefile; + + /** the classfile from where to load this class + * this will have extension .class or .java + */ + public JavaFileObject classfile; + + /** the constant pool of the class + */ + public Pool pool; + + /** members closure cache (set by Types.membersClosure) + */ + Scope.CompoundScope membersClosure; + + public ClassSymbol(long flags, Name name, Type type, Symbol owner) { + super(flags, name, type, owner); + this.members_field = null; + this.fullname = formFullName(name, owner); + this.flatname = formFlatName(name, owner); + this.sourcefile = null; + this.classfile = null; + this.pool = null; + } + + public ClassSymbol(long flags, Name name, Symbol owner) { + this( + flags, + name, + new ClassType(Type.noType, null, null), + owner); + this.type.tsym = this; + } + + /** The Java source which this symbol represents. + */ + public String toString() { + return className(); + } + + public long flags() { + if (completer != null) complete(); + return flags_field; + } + + public Scope members() { + if (completer != null) complete(); + return members_field; + } + + public List getAnnotationMirrors() { + if (completer != null) complete(); + return Assert.checkNonNull(attributes_field); + } + + public Type erasure(Types types) { + if (erasure_field == null) + erasure_field = new ClassType(types.erasure(type.getEnclosingType()), + List.nil(), this); + return erasure_field; + } + + public String className() { + if (name.isEmpty()) + return + Log.getLocalizedString("anonymous.class", flatname); + else + return fullname.toString(); + } + + public Name getQualifiedName() { + return fullname; + } + + public Name flatName() { + return flatname; + } + + public boolean isSubClass(Symbol base, Types types) { + if (this == base) { + return true; + } else if ((base.flags() & INTERFACE) != 0) { + for (Type t = type; t.tag == CLASS; t = types.supertype(t)) + for (List is = types.interfaces(t); + is.nonEmpty(); + is = is.tail) + if (is.head.tsym.isSubClass(base, types)) return true; + } else { + for (Type t = type; t.tag == CLASS; t = types.supertype(t)) + if (t.tsym == base) return true; + } + return false; + } + + /** Complete the elaboration of this symbol's definition. + */ + public void complete() throws CompletionFailure { + try { + super.complete(); + } catch (CompletionFailure ex) { + // quiet error recovery + flags_field |= (PUBLIC|STATIC); + this.type = new ErrorType(this, Type.noType); + throw ex; + } + } + + public List getInterfaces() { + complete(); + if (type instanceof ClassType) { + ClassType t = (ClassType)type; + if (t.interfaces_field == null) // FIXME: shouldn't be null + t.interfaces_field = List.nil(); + if (t.all_interfaces_field != null) + return Type.getModelTypes(t.all_interfaces_field); + return t.interfaces_field; + } else { + return List.nil(); + } + } + + public Type getSuperclass() { + complete(); + if (type instanceof ClassType) { + ClassType t = (ClassType)type; + if (t.supertype_field == null) // FIXME: shouldn't be null + t.supertype_field = Type.noType; + // An interface has no superclass; its supertype is Object. + return t.isInterface() + ? Type.noType + : t.supertype_field.getModelType(); + } else { + return Type.noType; + } + } + + public ElementKind getKind() { + long flags = flags(); + if ((flags & ANNOTATION) != 0) + return ElementKind.ANNOTATION_TYPE; + else if ((flags & INTERFACE) != 0) + return ElementKind.INTERFACE; + else if ((flags & ENUM) != 0) + return ElementKind.ENUM; + else + return ElementKind.CLASS; + } + + public NestingKind getNestingKind() { + complete(); + if (owner.kind == PCK) + return NestingKind.TOP_LEVEL; + else if (name.isEmpty()) + return NestingKind.ANONYMOUS; + else if (owner.kind == MTH) + return NestingKind.LOCAL; + else + return NestingKind.MEMBER; + } + + /** + * @deprecated this method should never be used by javac internally. + */ + @Override @Deprecated + public A getAnnotation(Class annoType) { + return JavacElements.getAnnotation(this, annoType); + } + + public R accept(ElementVisitor v, P p) { + return v.visitType(this, p); + } + + public R accept(Symbol.Visitor v, P p) { + return v.visitClassSymbol(this, p); + } + } + + + /** A class for variable symbols + */ + public static class VarSymbol extends Symbol implements VariableElement { + + /** The variable's declaration position. + */ + public int pos = Position.NOPOS; + + /** The variable's address. Used for different purposes during + * flow analysis, translation and code generation. + * Flow analysis: + * If this is a blank final or local variable, its sequence number. + * Translation: + * If this is a private field, its access number. + * Code generation: + * If this is a local variable, its logical slot number. + */ + public int adr = -1; + + /** Construct a variable symbol, given its flags, name, type and owner. + */ + public VarSymbol(long flags, Name name, Type type, Symbol owner) { + super(VAR, flags, name, type, owner); + } + + /** Clone this symbol with new owner. + */ + public VarSymbol clone(Symbol newOwner) { + VarSymbol v = new VarSymbol(flags_field, name, type, newOwner); + v.pos = pos; + v.adr = adr; + v.data = data; +// System.out.println("clone " + v + " in " + newOwner);//DEBUG + return v; + } + + public String toString() { + return name.toString(); + } + + public Symbol asMemberOf(Type site, Types types) { + return new VarSymbol(flags_field, name, types.memberType(site, this), owner); + } + + public ElementKind getKind() { + long flags = flags(); + if ((flags & PARAMETER) != 0) { + if (isExceptionParameter()) + return ElementKind.EXCEPTION_PARAMETER; + else + return ElementKind.PARAMETER; + } else if ((flags & ENUM) != 0) { + return ElementKind.ENUM_CONSTANT; + } else if (owner.kind == TYP || owner.kind == ERR) { + return ElementKind.FIELD; + } else if (isResourceVariable()) { + return ElementKind.RESOURCE_VARIABLE; + } else { + return ElementKind.LOCAL_VARIABLE; + } + } + + public R accept(ElementVisitor v, P p) { + return v.visitVariable(this, p); + } + + public Object getConstantValue() { // Mirror API + return Constants.decode(getConstValue(), type); + } + + public void setLazyConstValue(final Env env, + final Attr attr, + final JCTree.JCExpression initializer) + { + setData(new Callable() { + public Object call() { + return attr.attribLazyConstantValue(env, initializer, type); + } + }); + } + + /** + * The variable's constant value, if this is a constant. + * Before the constant value is evaluated, it points to an + * initalizer environment. If this is not a constant, it can + * be used for other stuff. + */ + private Object data; + + public boolean isExceptionParameter() { + return data == ElementKind.EXCEPTION_PARAMETER; + } + +// public boolean isResourceVariable() { +// return data == ElementKind.RESOURCE_VARIABLE; +// } + + //我加上的,原有代码无法在1.6上运行 + public boolean isResourceVariable() { + if (SourceVersion.latest().ordinal() > SourceVersion.RELEASE_6.ordinal()) + return data == ElementKind.RESOURCE_VARIABLE; + + return false; + } + +// public Object getConstValue() { +// // TODO: Consider if getConstValue and getConstantValue can be collapsed +// if (data == ElementKind.EXCEPTION_PARAMETER || +// data == ElementKind.RESOURCE_VARIABLE) { +// return null; +// } else if (data instanceof Callable) { +// // In this case, this is a final variable, with an as +// // yet unevaluated initializer. +// Callable eval = (Callable)data; +// data = null; // to make sure we don't evaluate this twice. +// try { +// data = eval.call(); +// } catch (Exception ex) { +// throw new AssertionError(ex); +// } +// } +// return data; +// } + + //我加上的,原有代码无法在1.6上运行 + public Object getConstValue() { + // TODO: Consider if getConstValue and getConstantValue can be collapsed + if (SourceVersion.latest().ordinal() > SourceVersion.RELEASE_6.ordinal()) { + if (data == ElementKind.EXCEPTION_PARAMETER || + data == ElementKind.RESOURCE_VARIABLE) { + return null; + } + } + + if (data == ElementKind.EXCEPTION_PARAMETER) { + return null; + } else if (data instanceof Callable) { + // In this case, this is a final variable, with an as + // yet unevaluated initializer. + Callable eval = (Callable)data; + data = null; // to make sure we don't evaluate this twice. + try { + data = eval.call(); + } catch (Exception ex) { + throw new AssertionError(ex); + } + } + return data; + } + + public void setData(Object data) { + Assert.check(!(data instanceof Env), this); + this.data = data; + } + + public R accept(Symbol.Visitor v, P p) { + return v.visitVarSymbol(this, p); + } + } + + /** A class for method symbols. + */ + public static class MethodSymbol extends Symbol implements ExecutableElement { + + /** The code of the method. */ + public Code code = null; + + /** The parameters of the method. */ + public List params = null; + + /** The names of the parameters */ + public List savedParameterNames; + + /** For an attribute field accessor, its default value if any. + * The value is null if none appeared in the method + * declaration. + */ + public Attribute defaultValue = null; + + /** Construct a method symbol, given its flags, name, type and owner. + */ + public MethodSymbol(long flags, Name name, Type type, Symbol owner) { + super(MTH, flags, name, type, owner); + if (owner.type.tag == TYPEVAR) Assert.error(owner + "." + name); + } + + /** Clone this symbol with new owner. + */ + public MethodSymbol clone(Symbol newOwner) { + MethodSymbol m = new MethodSymbol(flags_field, name, type, newOwner); + m.code = code; + return m; + } + + /** The Java source which this symbol represents. + */ + public String toString() { + if ((flags() & BLOCK) != 0) { + return owner.name.toString(); + } else { + String s = (name == name.table.names.init) + ? owner.name.toString() + : name.toString(); + if (type != null) { + if (type.tag == FORALL) + s = "<" + ((ForAll)type).getTypeArguments() + ">" + s; + s += "(" + type.argtypes((flags() & VARARGS) != 0) + ")"; + } + return s; + } + } + + /** find a symbol that this (proxy method) symbol implements. + * @param c The class whose members are searched for + * implementations + */ + public Symbol implemented(TypeSymbol c, Types types) { + Symbol impl = null; + for (List is = types.interfaces(c.type); + impl == null && is.nonEmpty(); + is = is.tail) { + TypeSymbol i = is.head.tsym; + impl = implementedIn(i, types); + if (impl == null) + impl = implemented(i, types); + } + return impl; + } + + public Symbol implementedIn(TypeSymbol c, Types types) { + Symbol impl = null; + for (Scope.Entry e = c.members().lookup(name); + impl == null && e.scope != null; + e = e.next()) { + if (this.overrides(e.sym, (TypeSymbol)owner, types, true) && + // FIXME: I suspect the following requires a + // subst() for a parametric return type. + types.isSameType(type.getReturnType(), + types.memberType(owner.type, e.sym).getReturnType())) { + impl = e.sym; + } + } + return impl; + } + + /** Will the erasure of this method be considered by the VM to + * override the erasure of the other when seen from class `origin'? + */ + public boolean binaryOverrides(Symbol _other, TypeSymbol origin, Types types) { + if (isConstructor() || _other.kind != MTH) return false; + + if (this == _other) return true; + MethodSymbol other = (MethodSymbol)_other; + + // check for a direct implementation + if (other.isOverridableIn((TypeSymbol)owner) && + types.asSuper(owner.type, other.owner) != null && + types.isSameType(erasure(types), other.erasure(types))) + return true; + + // check for an inherited implementation + return + (flags() & ABSTRACT) == 0 && + other.isOverridableIn(origin) && + this.isMemberOf(origin, types) && + types.isSameType(erasure(types), other.erasure(types)); + } + + /** The implementation of this (abstract) symbol in class origin, + * from the VM's point of view, null if method does not have an + * implementation in class. + * @param origin The class of which the implementation is a member. + */ + public MethodSymbol binaryImplementation(ClassSymbol origin, Types types) { + for (TypeSymbol c = origin; c != null; c = types.supertype(c.type).tsym) { + for (Scope.Entry e = c.members().lookup(name); + e.scope != null; + e = e.next()) { + if (e.sym.kind == MTH && + ((MethodSymbol)e.sym).binaryOverrides(this, origin, types)) + return (MethodSymbol)e.sym; + } + } + return null; + } + + /** Does this symbol override `other' symbol, when both are seen as + * members of class `origin'? It is assumed that _other is a member + * of origin. + * + * It is assumed that both symbols have the same name. The static + * modifier is ignored for this test. + * + * See JLS 8.4.6.1 (without transitivity) and 8.4.6.4 + */ + public boolean overrides(Symbol _other, TypeSymbol origin, Types types, boolean checkResult) { + if (isConstructor() || _other.kind != MTH) return false; + + if (this == _other) return true; + MethodSymbol other = (MethodSymbol)_other; + + // check for a direct implementation + if (other.isOverridableIn((TypeSymbol)owner) && + types.asSuper(owner.type, other.owner) != null) { + Type mt = types.memberType(owner.type, this); + Type ot = types.memberType(owner.type, other); + if (types.isSubSignature(mt, ot)) { + if (!checkResult) + return true; + if (types.returnTypeSubstitutable(mt, ot)) + return true; + } + } + + // check for an inherited implementation + if ((flags() & ABSTRACT) != 0 || + (other.flags() & ABSTRACT) == 0 || + !other.isOverridableIn(origin) || + !this.isMemberOf(origin, types)) + return false; + + // assert types.asSuper(origin.type, other.owner) != null; + Type mt = types.memberType(origin.type, this); + Type ot = types.memberType(origin.type, other); + return + types.isSubSignature(mt, ot) && + (!checkResult || types.resultSubtype(mt, ot, Warner.noWarnings)); + } + + private boolean isOverridableIn(TypeSymbol origin) { + // JLS 8.4.6.1 + switch ((int)(flags_field & Flags.AccessFlags)) { + case Flags.PRIVATE: + return false; + case Flags.PUBLIC: + return true; + case Flags.PROTECTED: + return (origin.flags() & INTERFACE) == 0; + case 0: + // for package private: can only override in the same + // package + return + this.packge() == origin.packge() && + (origin.flags() & INTERFACE) == 0; + default: + return false; + } + } + + /** The implementation of this (abstract) symbol in class origin; + * null if none exists. Synthetic methods are not considered + * as possible implementations. + */ + public MethodSymbol implementation(TypeSymbol origin, Types types, boolean checkResult) { + return implementation(origin, types, checkResult, implementation_filter); + } + // where + private static final Filter implementation_filter = new Filter() { + public boolean accepts(Symbol s) { + return s.kind == Kinds.MTH && + (s.flags() & SYNTHETIC) == 0; + } + }; + + public MethodSymbol implementation(TypeSymbol origin, Types types, boolean checkResult, Filter implFilter) { + MethodSymbol res = types.implementation(this, origin, checkResult, implFilter); + if (res != null) + return res; + // if origin is derived from a raw type, we might have missed + // an implementation because we do not know enough about instantiations. + // in this case continue with the supertype as origin. + if (types.isDerivedRaw(origin.type)) + return implementation(types.supertype(origin.type).tsym, types, checkResult); + else + return null; + } + + public List params() { + owner.complete(); + if (params == null) { + // If ClassReader.saveParameterNames has been set true, then + // savedParameterNames will be set to a list of names that + // matches the types in type.getParameterTypes(). If any names + // were not found in the class file, those names in the list will + // be set to the empty name. + // If ClassReader.saveParameterNames has been set false, then + // savedParameterNames will be null. + List paramNames = savedParameterNames; + savedParameterNames = null; + // discard the provided names if the list of names is the wrong size. + if (paramNames == null || paramNames.size() != type.getParameterTypes().size()) + paramNames = List.nil(); + ListBuffer buf = new ListBuffer(); + List remaining = paramNames; + // assert: remaining and paramNames are both empty or both + // have same cardinality as type.getParameterTypes() + int i = 0; + for (Type t : type.getParameterTypes()) { + Name paramName; + if (remaining.isEmpty()) { + // no names for any parameters available + paramName = createArgName(i, paramNames); + } else { + paramName = remaining.head; + remaining = remaining.tail; + if (paramName.isEmpty()) { + // no name for this specific parameter + paramName = createArgName(i, paramNames); + } + } + buf.append(new VarSymbol(PARAMETER, paramName, t, this)); + i++; + } + params = buf.toList(); + } + return params; + } + + // Create a name for the argument at position 'index' that is not in + // the exclude list. In normal use, either no names will have been + // provided, in which case the exclude list is empty, or all the names + // will have been provided, in which case this method will not be called. + private Name createArgName(int index, List exclude) { + String prefix = "arg"; + while (true) { + Name argName = name.table.fromString(prefix + index); + if (!exclude.contains(argName)) + return argName; + prefix += "$"; + } + } + + public Symbol asMemberOf(Type site, Types types) { + return new MethodSymbol(flags_field, name, types.memberType(site, this), owner); + } + + public ElementKind getKind() { + if (name == name.table.names.init) + return ElementKind.CONSTRUCTOR; + else if (name == name.table.names.clinit) + return ElementKind.STATIC_INIT; + else + return ElementKind.METHOD; + } + + public Attribute getDefaultValue() { + return defaultValue; + } + + public List getParameters() { + return params(); + } + + public boolean isVarArgs() { + return (flags() & VARARGS) != 0; + } + + public R accept(ElementVisitor v, P p) { + return v.visitExecutable(this, p); + } + + public R accept(Symbol.Visitor v, P p) { + return v.visitMethodSymbol(this, p); + } + + public Type getReturnType() { + return asType().getReturnType(); + } + + public List getThrownTypes() { + return asType().getThrownTypes(); + } + } + + /** A class for predefined operators. + */ + public static class OperatorSymbol extends MethodSymbol { + + public int opcode; + + public OperatorSymbol(Name name, Type type, int opcode, Symbol owner) { + super(PUBLIC | STATIC, name, type, owner); + this.opcode = opcode; + } + + public R accept(Symbol.Visitor v, P p) { + return v.visitOperatorSymbol(this, p); + } + } + + /** Symbol completer interface. + */ + public static interface Completer { + void complete(Symbol sym) throws CompletionFailure; + } + + public static class CompletionFailure extends RuntimeException { + private static final long serialVersionUID = 0; + public Symbol sym; + + /** A diagnostic object describing the failure + */ + public JCDiagnostic diag; + + /** A localized string describing the failure. + * @deprecated Use {@code getDetail()} or {@code getMessage()} + */ + @Deprecated + public String errmsg; + + public CompletionFailure(Symbol sym, String errmsg) { + this.sym = sym; + this.errmsg = errmsg; +// this.printStackTrace();//DEBUG + } + + public CompletionFailure(Symbol sym, JCDiagnostic diag) { + this.sym = sym; + this.diag = diag; +// this.printStackTrace();//DEBUG + } + + public JCDiagnostic getDiagnostic() { + return diag; + } + + @Override + public String getMessage() { + if (diag != null) + return diag.getMessage(null); + else + return errmsg; + } + + public Object getDetailValue() { + return (diag != null ? diag : errmsg); + } + + @Override + public CompletionFailure initCause(Throwable cause) { + super.initCause(cause); + return this; + } + + } + + /** + * A visitor for symbols. A visitor is used to implement operations + * (or relations) on symbols. Most common operations on types are + * binary relations and this interface is designed for binary + * relations, that is, operations on the form + * Symbol × P → R. + * + * + * @param the return type of the operation implemented by this + * visitor; use Void if no return type is needed. + * @param

the type of the second argument (the first being the + * symbol itself) of the operation implemented by this visitor; use + * Void if a second argument is not needed. + */ + public interface Visitor { + R visitClassSymbol(ClassSymbol s, P arg); + R visitMethodSymbol(MethodSymbol s, P arg); + R visitPackageSymbol(PackageSymbol s, P arg); + R visitOperatorSymbol(OperatorSymbol s, P arg); + R visitVarSymbol(VarSymbol s, P arg); + R visitTypeSymbol(TypeSymbol s, P arg); + R visitSymbol(Symbol s, P arg); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/code/Symtab.java b/douyu-javac/src/main/java/com/sun/tools/javac/code/Symtab.java new file mode 100644 index 0000000..ebb1e9a --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/code/Symtab.java @@ -0,0 +1,687 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.code; + +import java.util.*; +import javax.lang.model.type.TypeVisitor; +import javax.lang.model.element.ElementVisitor; + +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.code.Type.*; +import com.sun.tools.javac.jvm.*; + +import static com.sun.tools.javac.jvm.ByteCodes.*; +import static com.sun.tools.javac.code.Flags.*; + +/** A class that defines all predefined constants and operators + * as well as special classes such as java.lang.Object, which need + * to be known to the compiler. All symbols are held in instance + * fields. This makes it possible to work in multiple concurrent + * projects, which might use different class files for library classes. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Symtab { + /** The context key for the symbol table. */ + protected static final Context.Key symtabKey = + new Context.Key(); + + /** Get the symbol table instance. */ + public static Symtab instance(Context context) { + Symtab instance = context.get(symtabKey); + if (instance == null) + instance = new Symtab(context); + return instance; + } + + /** Builtin types. + */ + public final Type byteType = new Type(TypeTags.BYTE, null); + public final Type charType = new Type(TypeTags.CHAR, null); + public final Type shortType = new Type(TypeTags.SHORT, null); + public final Type intType = new Type(TypeTags.INT, null); + public final Type longType = new Type(TypeTags.LONG, null); + public final Type floatType = new Type(TypeTags.FLOAT, null); + public final Type doubleType = new Type(TypeTags.DOUBLE, null); + public final Type booleanType = new Type(TypeTags.BOOLEAN, null); + public final Type botType = new BottomType(); + public final JCNoType voidType = new JCNoType(TypeTags.VOID); + + private final Names names; + private final ClassReader reader; + private final Target target; + + /** A symbol for the root package. + */ + public final PackageSymbol rootPackage; + + /** A symbol for the unnamed package. + */ + public final PackageSymbol unnamedPackage; + + /** A symbol that stands for a missing symbol. + */ + public final TypeSymbol noSymbol; + + /** The error symbol. + */ + public final ClassSymbol errSymbol; + + /** The unknown symbol. + */ + public final ClassSymbol unknownSymbol; + + /** A value for the errType, with a originalType of noType */ + public final Type errType; + + /** A value for the unknown type. */ + public final Type unknownType; + + /** The builtin type of all arrays. */ + public final ClassSymbol arrayClass; + public final MethodSymbol arrayCloneMethod; + + /** VGJ: The (singleton) type of all bound types. */ + public final ClassSymbol boundClass; + + /** The builtin type of all methods. */ + public final ClassSymbol methodClass; + + /** Predefined types. + */ + public final Type objectType; + public final Type classType; + public final Type classLoaderType; + public final Type stringType; + public final Type stringBufferType; + public final Type stringBuilderType; + public final Type cloneableType; + public final Type serializableType; + public final Type methodHandleType; + public final Type polymorphicSignatureType; + public final Type throwableType; + public final Type errorType; + public final Type interruptedExceptionType; + public final Type illegalArgumentExceptionType; + public final Type exceptionType; + public final Type runtimeExceptionType; + public final Type classNotFoundExceptionType; + public final Type noClassDefFoundErrorType; + public final Type noSuchFieldErrorType; + public final Type assertionErrorType; + public final Type cloneNotSupportedExceptionType; + public final Type annotationType; + public final TypeSymbol enumSym; + public final Type listType; + public final Type collectionsType; + public final Type comparableType; + public final Type arraysType; + public final Type iterableType; + public final Type iteratorType; + public final Type annotationTargetType; + public final Type overrideType; + public final Type retentionType; + public final Type deprecatedType; + public final Type suppressWarningsType; + public final Type inheritedType; + public final Type proprietaryType; + public final Type systemType; + public final Type autoCloseableType; + public final Type trustMeType; + + //M + //---------------------------------- + public final Type viewManagerType; + + /** The symbol representing the length field of an array. + */ + public final VarSymbol lengthVar; + + /** The null check operator. */ + public final OperatorSymbol nullcheck; + + /** The symbol representing the final finalize method on enums */ + public final MethodSymbol enumFinalFinalize; + + /** The symbol representing the close method on TWR AutoCloseable type */ + public final MethodSymbol autoCloseableClose; + + /** The predefined type that belongs to a tag. + */ + public final Type[] typeOfTag = new Type[TypeTags.TypeTagCount]; + + /** The name of the class that belongs to a basix type tag. + */ + public final Name[] boxedName = new Name[TypeTags.TypeTagCount]; + + /** A hashtable containing the encountered top-level and member classes, + * indexed by flat names. The table does not contain local classes. + * It should be updated from the outside to reflect classes defined + * by compiled source files. + */ + public final Map classes = new HashMap(); + + /** A hashtable containing the encountered packages. + * the table should be updated from outside to reflect packages defined + * by compiled source files. + */ + public final Map packages = new HashMap(); + + public void initType(Type type, ClassSymbol c) { + type.tsym = c; + typeOfTag[type.tag] = type; + } + + public void initType(Type type, String name) { + initType( + type, + new ClassSymbol( + PUBLIC, names.fromString(name), type, rootPackage)); + } + + public void initType(Type type, String name, String bname) { + initType(type, name); + boxedName[type.tag] = names.fromString("java.lang." + bname); + } + + /** The class symbol that owns all predefined symbols. + */ + public final ClassSymbol predefClass; + + /** Enter a constant into symbol table. + * @param name The constant's name. + * @param type The constant's type. + */ + private VarSymbol enterConstant(String name, Type type) { + VarSymbol c = new VarSymbol( + PUBLIC | STATIC | FINAL, + names.fromString(name), + type, + predefClass); + c.setData(type.constValue()); + predefClass.members().enter(c); + return c; + } + + /** Enter a binary operation into symbol table. + * @param name The name of the operator. + * @param left The type of the left operand. + * @param right The type of the left operand. + * @param res The operation's result type. + * @param opcode The operation's bytecode instruction. + */ + private void enterBinop(String name, + Type left, Type right, Type res, + int opcode) { + predefClass.members().enter( + new OperatorSymbol( + names.fromString(name), + new MethodType(List.of(left, right), res, + List.nil(), methodClass), + opcode, + predefClass)); + } + + /** Enter a binary operation, as above but with two opcodes, + * which get encoded as (opcode1 << ByteCodeTags.preShift) + opcode2. + * @param opcode1 First opcode. + * @param opcode2 Second opcode. + */ + private void enterBinop(String name, + Type left, Type right, Type res, + int opcode1, int opcode2) { + enterBinop( + name, left, right, res, (opcode1 << ByteCodes.preShift) | opcode2); + } + + /** Enter a unary operation into symbol table. + * @param name The name of the operator. + * @param arg The type of the operand. + * @param res The operation's result type. + * @param opcode The operation's bytecode instruction. + */ + private OperatorSymbol enterUnop(String name, + Type arg, + Type res, + int opcode) { + OperatorSymbol sym = + new OperatorSymbol(names.fromString(name), + new MethodType(List.of(arg), + res, + List.nil(), + methodClass), + opcode, + predefClass); + predefClass.members().enter(sym); + return sym; + } + + /** Enter a class into symbol table. + * @param The name of the class. + */ + private Type enterClass(String s) { + return reader.enterClass(names.fromString(s)).type; + } + + public void synthesizeEmptyInterfaceIfMissing(final Type type) { + final Completer completer = type.tsym.completer; + if (completer != null) { + type.tsym.completer = new Completer() { + public void complete(Symbol sym) throws CompletionFailure { + try { + completer.complete(sym); + } catch (CompletionFailure e) { + sym.flags_field |= (PUBLIC | INTERFACE); + ((ClassType) sym.type).supertype_field = objectType; + } + } + }; + } + } + + public void synthesizeBoxTypeIfMissing(final Type type) { + ClassSymbol sym = reader.enterClass(boxedName[type.tag]); + final Completer completer = sym.completer; + if (completer != null) { + sym.completer = new Completer() { + public void complete(Symbol sym) throws CompletionFailure { + try { + completer.complete(sym); + } catch (CompletionFailure e) { + sym.flags_field |= PUBLIC; + ((ClassType) sym.type).supertype_field = objectType; + Name n = target.boxWithConstructors() ? names.init : names.valueOf; + MethodSymbol boxMethod = + new MethodSymbol(PUBLIC | STATIC, + n, + new MethodType(List.of(type), sym.type, + List.nil(), methodClass), + sym); + sym.members().enter(boxMethod); + MethodSymbol unboxMethod = + new MethodSymbol(PUBLIC, + type.tsym.name.append(names.Value), // x.intValue() + new MethodType(List.nil(), type, + List.nil(), methodClass), + sym); + sym.members().enter(unboxMethod); + } + } + }; + } + + } + + /** Constructor; enters all predefined identifiers and operators + * into symbol table. + */ + protected Symtab(Context context) throws CompletionFailure { + context.put(symtabKey, this); + + names = Names.instance(context); + target = Target.instance(context); + + // Create the unknown type + unknownType = new Type(TypeTags.UNKNOWN, null) { + @Override + public R accept(TypeVisitor v, P p) { + return v.visitUnknown(this, p); + } + }; + + // create the basic builtin symbols + rootPackage = new PackageSymbol(names.empty, null); + final JavacMessages messages = JavacMessages.instance(context); + unnamedPackage = new PackageSymbol(names.empty, rootPackage) { + public String toString() { + return messages.getLocalizedString("compiler.misc.unnamed.package"); + } + }; + noSymbol = new TypeSymbol(0, names.empty, Type.noType, rootPackage) { + public R accept(ElementVisitor v, P p) { + return v.visitUnknown(this, p); + } + }; + noSymbol.kind = Kinds.NIL; + + // create the error symbols + errSymbol = new ClassSymbol(PUBLIC|STATIC|ACYCLIC, names.any, null, rootPackage); + errType = new ErrorType(errSymbol, Type.noType); + + unknownSymbol = new ClassSymbol(PUBLIC|STATIC|ACYCLIC, names.fromString(""), null, rootPackage); + unknownSymbol.members_field = new Scope.ErrorScope(unknownSymbol); + unknownSymbol.type = unknownType; + + // initialize builtin types + initType(byteType, "byte", "Byte"); + initType(shortType, "short", "Short"); + initType(charType, "char", "Character"); + initType(intType, "int", "Integer"); + initType(longType, "long", "Long"); + initType(floatType, "float", "Float"); + initType(doubleType, "double", "Double"); + initType(booleanType, "boolean", "Boolean"); + initType(voidType, "void", "Void"); + initType(botType, ""); + initType(errType, errSymbol); + initType(unknownType, unknownSymbol); + + // the builtin class of all arrays + arrayClass = new ClassSymbol(PUBLIC|ACYCLIC, names.Array, noSymbol); + + // VGJ + boundClass = new ClassSymbol(PUBLIC|ACYCLIC, names.Bound, noSymbol); + boundClass.members_field = new Scope.ErrorScope(boundClass); + + // the builtin class of all methods + methodClass = new ClassSymbol(PUBLIC|ACYCLIC, names.Method, noSymbol); + methodClass.members_field = new Scope.ErrorScope(boundClass); + + // Create class to hold all predefined constants and operations. + predefClass = new ClassSymbol(PUBLIC|ACYCLIC, names.empty, rootPackage); + Scope scope = new Scope(predefClass); + predefClass.members_field = scope; + + // Enter symbols for basic types. + scope.enter(byteType.tsym); + scope.enter(shortType.tsym); + scope.enter(charType.tsym); + scope.enter(intType.tsym); + scope.enter(longType.tsym); + scope.enter(floatType.tsym); + scope.enter(doubleType.tsym); + scope.enter(booleanType.tsym); + scope.enter(errType.tsym); + + // Enter symbol for the errSymbol + scope.enter(errSymbol); + + classes.put(predefClass.fullname, predefClass); + + reader = ClassReader.instance(context); + reader.init(this); + + //M + //-------------------------------- + viewManagerType = enterClass("douyu.mvc.ViewManager"); + + // Enter predefined classes. + objectType = enterClass("java.lang.Object"); + classType = enterClass("java.lang.Class"); + stringType = enterClass("java.lang.String"); + stringBufferType = enterClass("java.lang.StringBuffer"); + stringBuilderType = enterClass("java.lang.StringBuilder"); + cloneableType = enterClass("java.lang.Cloneable"); + throwableType = enterClass("java.lang.Throwable"); + serializableType = enterClass("java.io.Serializable"); + methodHandleType = enterClass("java.lang.invoke.MethodHandle"); + polymorphicSignatureType = enterClass("java.lang.invoke.MethodHandle$PolymorphicSignature"); + errorType = enterClass("java.lang.Error"); + illegalArgumentExceptionType = enterClass("java.lang.IllegalArgumentException"); + interruptedExceptionType = enterClass("java.lang.InterruptedException"); + exceptionType = enterClass("java.lang.Exception"); + runtimeExceptionType = enterClass("java.lang.RuntimeException"); + classNotFoundExceptionType = enterClass("java.lang.ClassNotFoundException"); + noClassDefFoundErrorType = enterClass("java.lang.NoClassDefFoundError"); + noSuchFieldErrorType = enterClass("java.lang.NoSuchFieldError"); + assertionErrorType = enterClass("java.lang.AssertionError"); + cloneNotSupportedExceptionType = enterClass("java.lang.CloneNotSupportedException"); + annotationType = enterClass("java.lang.annotation.Annotation"); + classLoaderType = enterClass("java.lang.ClassLoader"); + enumSym = reader.enterClass(names.java_lang_Enum); + enumFinalFinalize = + new MethodSymbol(PROTECTED|FINAL|HYPOTHETICAL, + names.finalize, + new MethodType(List.nil(), voidType, + List.nil(), methodClass), + enumSym); + listType = enterClass("java.util.List"); + collectionsType = enterClass("java.util.Collections"); + comparableType = enterClass("java.lang.Comparable"); + arraysType = enterClass("java.util.Arrays"); + iterableType = target.hasIterable() + ? enterClass("java.lang.Iterable") + : enterClass("java.util.Collection"); + iteratorType = enterClass("java.util.Iterator"); + annotationTargetType = enterClass("java.lang.annotation.Target"); + overrideType = enterClass("java.lang.Override"); + retentionType = enterClass("java.lang.annotation.Retention"); + deprecatedType = enterClass("java.lang.Deprecated"); + suppressWarningsType = enterClass("java.lang.SuppressWarnings"); + inheritedType = enterClass("java.lang.annotation.Inherited"); + systemType = enterClass("java.lang.System"); + autoCloseableType = enterClass("java.lang.AutoCloseable"); + autoCloseableClose = new MethodSymbol(PUBLIC, + names.close, + new MethodType(List.nil(), voidType, + List.of(exceptionType), methodClass), + autoCloseableType.tsym); + trustMeType = enterClass("java.lang.SafeVarargs"); + + synthesizeEmptyInterfaceIfMissing(autoCloseableType); + synthesizeEmptyInterfaceIfMissing(cloneableType); + synthesizeEmptyInterfaceIfMissing(serializableType); + synthesizeEmptyInterfaceIfMissing(polymorphicSignatureType); + synthesizeBoxTypeIfMissing(doubleType); + synthesizeBoxTypeIfMissing(floatType); + synthesizeBoxTypeIfMissing(voidType); + + // Enter a synthetic class that is used to mark internal + // proprietary classes in ct.sym. This class does not have a + // class file. + ClassType proprietaryType = (ClassType)enterClass("sun.Proprietary+Annotation"); + this.proprietaryType = proprietaryType; + ClassSymbol proprietarySymbol = (ClassSymbol)proprietaryType.tsym; + proprietarySymbol.completer = null; + proprietarySymbol.flags_field = PUBLIC|ACYCLIC|ANNOTATION|INTERFACE; + proprietarySymbol.erasure_field = proprietaryType; + proprietarySymbol.members_field = new Scope(proprietarySymbol); + proprietaryType.typarams_field = List.nil(); + proprietaryType.allparams_field = List.nil(); + proprietaryType.supertype_field = annotationType; + proprietaryType.interfaces_field = List.nil(); + + // Enter a class for arrays. + // The class implements java.lang.Cloneable and java.io.Serializable. + // It has a final length field and a clone method. + ClassType arrayClassType = (ClassType)arrayClass.type; + arrayClassType.supertype_field = objectType; + arrayClassType.interfaces_field = List.of(cloneableType, serializableType); + arrayClass.members_field = new Scope(arrayClass); + lengthVar = new VarSymbol( + PUBLIC | FINAL, + names.length, + intType, + arrayClass); + arrayClass.members().enter(lengthVar); + arrayCloneMethod = new MethodSymbol( + PUBLIC, + names.clone, + new MethodType(List.nil(), objectType, + List.nil(), methodClass), + arrayClass); + arrayClass.members().enter(arrayCloneMethod); + + // Enter operators. + enterUnop("+", doubleType, doubleType, nop); + enterUnop("+", floatType, floatType, nop); + enterUnop("+", longType, longType, nop); + enterUnop("+", intType, intType, nop); + + enterUnop("-", doubleType, doubleType, dneg); + enterUnop("-", floatType, floatType, fneg); + enterUnop("-", longType, longType, lneg); + enterUnop("-", intType, intType, ineg); + + enterUnop("~", longType, longType, lxor); + enterUnop("~", intType, intType, ixor); + + enterUnop("++", doubleType, doubleType, dadd); + enterUnop("++", floatType, floatType, fadd); + enterUnop("++", longType, longType, ladd); + enterUnop("++", intType, intType, iadd); + enterUnop("++", charType, charType, iadd); + enterUnop("++", shortType, shortType, iadd); + enterUnop("++", byteType, byteType, iadd); + + enterUnop("--", doubleType, doubleType, dsub); + enterUnop("--", floatType, floatType, fsub); + enterUnop("--", longType, longType, lsub); + enterUnop("--", intType, intType, isub); + enterUnop("--", charType, charType, isub); + enterUnop("--", shortType, shortType, isub); + enterUnop("--", byteType, byteType, isub); + + enterUnop("!", booleanType, booleanType, bool_not); + nullcheck = enterUnop("<*nullchk*>", objectType, objectType, nullchk); + + // string concatenation + enterBinop("+", stringType, objectType, stringType, string_add); + enterBinop("+", objectType, stringType, stringType, string_add); + enterBinop("+", stringType, stringType, stringType, string_add); + enterBinop("+", stringType, intType, stringType, string_add); + enterBinop("+", stringType, longType, stringType, string_add); + enterBinop("+", stringType, floatType, stringType, string_add); + enterBinop("+", stringType, doubleType, stringType, string_add); + enterBinop("+", stringType, booleanType, stringType, string_add); + enterBinop("+", stringType, botType, stringType, string_add); + enterBinop("+", intType, stringType, stringType, string_add); + enterBinop("+", longType, stringType, stringType, string_add); + enterBinop("+", floatType, stringType, stringType, string_add); + enterBinop("+", doubleType, stringType, stringType, string_add); + enterBinop("+", booleanType, stringType, stringType, string_add); + enterBinop("+", botType, stringType, stringType, string_add); + + // these errors would otherwise be matched as string concatenation + enterBinop("+", botType, botType, botType, error); + enterBinop("+", botType, intType, botType, error); + enterBinop("+", botType, longType, botType, error); + enterBinop("+", botType, floatType, botType, error); + enterBinop("+", botType, doubleType, botType, error); + enterBinop("+", botType, booleanType, botType, error); + enterBinop("+", botType, objectType, botType, error); + enterBinop("+", intType, botType, botType, error); + enterBinop("+", longType, botType, botType, error); + enterBinop("+", floatType, botType, botType, error); + enterBinop("+", doubleType, botType, botType, error); + enterBinop("+", booleanType, botType, botType, error); + enterBinop("+", objectType, botType, botType, error); + + enterBinop("+", doubleType, doubleType, doubleType, dadd); + enterBinop("+", floatType, floatType, floatType, fadd); + enterBinop("+", longType, longType, longType, ladd); + enterBinop("+", intType, intType, intType, iadd); + + enterBinop("-", doubleType, doubleType, doubleType, dsub); + enterBinop("-", floatType, floatType, floatType, fsub); + enterBinop("-", longType, longType, longType, lsub); + enterBinop("-", intType, intType, intType, isub); + + enterBinop("*", doubleType, doubleType, doubleType, dmul); + enterBinop("*", floatType, floatType, floatType, fmul); + enterBinop("*", longType, longType, longType, lmul); + enterBinop("*", intType, intType, intType, imul); + + enterBinop("/", doubleType, doubleType, doubleType, ddiv); + enterBinop("/", floatType, floatType, floatType, fdiv); + enterBinop("/", longType, longType, longType, ldiv); + enterBinop("/", intType, intType, intType, idiv); + + enterBinop("%", doubleType, doubleType, doubleType, dmod); + enterBinop("%", floatType, floatType, floatType, fmod); + enterBinop("%", longType, longType, longType, lmod); + enterBinop("%", intType, intType, intType, imod); + + enterBinop("&", booleanType, booleanType, booleanType, iand); + enterBinop("&", longType, longType, longType, land); + enterBinop("&", intType, intType, intType, iand); + + enterBinop("|", booleanType, booleanType, booleanType, ior); + enterBinop("|", longType, longType, longType, lor); + enterBinop("|", intType, intType, intType, ior); + + enterBinop("^", booleanType, booleanType, booleanType, ixor); + enterBinop("^", longType, longType, longType, lxor); + enterBinop("^", intType, intType, intType, ixor); + + enterBinop("<<", longType, longType, longType, lshll); + enterBinop("<<", intType, longType, intType, ishll); + enterBinop("<<", longType, intType, longType, lshl); + enterBinop("<<", intType, intType, intType, ishl); + + enterBinop(">>", longType, longType, longType, lshrl); + enterBinop(">>", intType, longType, intType, ishrl); + enterBinop(">>", longType, intType, longType, lshr); + enterBinop(">>", intType, intType, intType, ishr); + + enterBinop(">>>", longType, longType, longType, lushrl); + enterBinop(">>>", intType, longType, intType, iushrl); + enterBinop(">>>", longType, intType, longType, lushr); + enterBinop(">>>", intType, intType, intType, iushr); + + enterBinop("<", doubleType, doubleType, booleanType, dcmpg, iflt); + enterBinop("<", floatType, floatType, booleanType, fcmpg, iflt); + enterBinop("<", longType, longType, booleanType, lcmp, iflt); + enterBinop("<", intType, intType, booleanType, if_icmplt); + + enterBinop(">", doubleType, doubleType, booleanType, dcmpl, ifgt); + enterBinop(">", floatType, floatType, booleanType, fcmpl, ifgt); + enterBinop(">", longType, longType, booleanType, lcmp, ifgt); + enterBinop(">", intType, intType, booleanType, if_icmpgt); + + enterBinop("<=", doubleType, doubleType, booleanType, dcmpg, ifle); + enterBinop("<=", floatType, floatType, booleanType, fcmpg, ifle); + enterBinop("<=", longType, longType, booleanType, lcmp, ifle); + enterBinop("<=", intType, intType, booleanType, if_icmple); + + enterBinop(">=", doubleType, doubleType, booleanType, dcmpl, ifge); + enterBinop(">=", floatType, floatType, booleanType, fcmpl, ifge); + enterBinop(">=", longType, longType, booleanType, lcmp, ifge); + enterBinop(">=", intType, intType, booleanType, if_icmpge); + + enterBinop("==", objectType, objectType, booleanType, if_acmpeq); + enterBinop("==", booleanType, booleanType, booleanType, if_icmpeq); + enterBinop("==", doubleType, doubleType, booleanType, dcmpl, ifeq); + enterBinop("==", floatType, floatType, booleanType, fcmpl, ifeq); + enterBinop("==", longType, longType, booleanType, lcmp, ifeq); + enterBinop("==", intType, intType, booleanType, if_icmpeq); + + enterBinop("!=", objectType, objectType, booleanType, if_acmpne); + enterBinop("!=", booleanType, booleanType, booleanType, if_icmpne); + enterBinop("!=", doubleType, doubleType, booleanType, dcmpl, ifne); + enterBinop("!=", floatType, floatType, booleanType, fcmpl, ifne); + enterBinop("!=", longType, longType, booleanType, lcmp, ifne); + enterBinop("!=", intType, intType, booleanType, if_icmpne); + + enterBinop("&&", booleanType, booleanType, booleanType, bool_and); + enterBinop("||", booleanType, booleanType, booleanType, bool_or); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/code/TargetType.java b/douyu-javac/src/main/java/com/sun/tools/javac/code/TargetType.java new file mode 100644 index 0000000..6c24287 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/code/TargetType.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2008, 2009, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.code; + +import static com.sun.tools.javac.code.TargetType.TargetAttribute.*; + +import java.util.EnumSet; +import java.util.Set; + +/** + * Describes the type of program element an extended annotation (or extended + * compound attribute) targets. + * + * By comparison, a Tree.Kind has enum values for all elements in the AST, and + * it does not provide enough resolution for type arguments (i.e., whether an + * annotation targets a type argument in a local variable, method return type, + * or a typecast). + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public enum TargetType { + + // + // Some target types are commented out, because Java doesn't permit such + // targets. They are included here to confirm that their omission is + // intentional omission not an accidental omission. + // + + /** For annotations on typecasts. */ + TYPECAST(0x00, IsLocal), + + /** For annotations on a type argument or nested array of a typecast. */ + TYPECAST_GENERIC_OR_ARRAY(0x01, HasLocation, IsLocal), + + /** For annotations on type tests. */ + INSTANCEOF(0x02, IsLocal), + + /** For annotations on a type argument or nested array of a type test. */ + INSTANCEOF_GENERIC_OR_ARRAY(0x03, HasLocation, IsLocal), + + /** For annotations on object creation expressions. */ + NEW(0x04, IsLocal), + + /** + * For annotations on a type argument or nested array of an object creation + * expression. + */ + NEW_GENERIC_OR_ARRAY(0x05, HasLocation, IsLocal), + + + /** For annotations on the method receiver. */ + METHOD_RECEIVER(0x06), + + // invalid location + //@Deprecated METHOD_RECEIVER_GENERIC_OR_ARRAY(0x07, HasLocation), + + /** For annotations on local variables. */ + LOCAL_VARIABLE(0x08, IsLocal), + + /** For annotations on a type argument or nested array of a local. */ + LOCAL_VARIABLE_GENERIC_OR_ARRAY(0x09, HasLocation, IsLocal), + + // handled by regular annotations + //@Deprecated METHOD_RETURN(0x0A), + + /** + * For annotations on a type argument or nested array of a method return + * type. + */ + METHOD_RETURN_GENERIC_OR_ARRAY(0x0B, HasLocation), + + // handled by regular annotations + //@Deprecated METHOD_PARAMETER(0x0C), + + /** For annotations on a type argument or nested array of a method parameter. */ + METHOD_PARAMETER_GENERIC_OR_ARRAY(0x0D, HasLocation), + + // handled by regular annotations + //@Deprecated FIELD(0x0E), + + /** For annotations on a type argument or nested array of a field. */ + FIELD_GENERIC_OR_ARRAY(0x0F, HasLocation), + + /** For annotations on a bound of a type parameter of a class. */ + CLASS_TYPE_PARAMETER_BOUND(0x10, HasBound, HasParameter), + + /** + * For annotations on a type argument or nested array of a bound of a type + * parameter of a class. + */ + CLASS_TYPE_PARAMETER_BOUND_GENERIC_OR_ARRAY(0x11, HasBound, HasLocation, HasParameter), + + /** For annotations on a bound of a type parameter of a method. */ + METHOD_TYPE_PARAMETER_BOUND(0x12, HasBound, HasParameter), + + /** + * For annotations on a type argument or nested array of a bound of a type + * parameter of a method. + */ + METHOD_TYPE_PARAMETER_BOUND_GENERIC_OR_ARRAY(0x13, HasBound, HasLocation, HasParameter), + + /** For annotations on the type of an "extends" or "implements" clause. */ + CLASS_EXTENDS(0x14), + + /** For annotations on the inner type of an "extends" or "implements" clause. */ + CLASS_EXTENDS_GENERIC_OR_ARRAY(0x15, HasLocation), + + /** For annotations on a throws clause in a method declaration. */ + THROWS(0x16), + + // invalid location + //@Deprecated THROWS_GENERIC_OR_ARRAY(0x17, HasLocation), + + /** For annotations in type arguments of object creation expressions. */ + NEW_TYPE_ARGUMENT(0x18, IsLocal), + NEW_TYPE_ARGUMENT_GENERIC_OR_ARRAY(0x19, HasLocation, IsLocal), + + METHOD_TYPE_ARGUMENT(0x1A, IsLocal), + METHOD_TYPE_ARGUMENT_GENERIC_OR_ARRAY(0x1B, HasLocation, IsLocal), + + WILDCARD_BOUND(0x1C, HasBound), + WILDCARD_BOUND_GENERIC_OR_ARRAY(0x1D, HasBound, HasLocation), + + CLASS_LITERAL(0x1E, IsLocal), + CLASS_LITERAL_GENERIC_OR_ARRAY(0x1F, HasLocation, IsLocal), + + METHOD_TYPE_PARAMETER(0x20, HasParameter), + + // invalid location + //@Deprecated METHOD_TYPE_PARAMETER_GENERIC_OR_ARRAY(0x21, HasLocation, HasParameter), + + CLASS_TYPE_PARAMETER(0x22, HasParameter), + + // invalid location + //@Deprecated CLASS_TYPE_PARAMETER_GENERIC_OR_ARRAY(0x23, HasLocation, HasParameter), + + /** For annotations with an unknown target. */ + UNKNOWN(-1); + + static final int MAXIMUM_TARGET_TYPE_VALUE = 0x22; + + private final int targetTypeValue; + private Set flags; + + TargetType(int targetTypeValue, TargetAttribute... attributes) { + if (targetTypeValue < Byte.MIN_VALUE + || targetTypeValue > Byte.MAX_VALUE) + throw new AssertionError("attribute type value needs to be a byte: " + targetTypeValue); + this.targetTypeValue = (byte)targetTypeValue; + flags = EnumSet.noneOf(TargetAttribute.class); + for (TargetAttribute attr : attributes) + flags.add(attr); + } + + /** + * Returns whether or not this TargetType represents an annotation whose + * target is an inner type of a generic or array type. + * + * @return true if this TargetType represents an annotation on an inner + * type, false otherwise + */ + public boolean hasLocation() { + return flags.contains(HasLocation); + } + + public TargetType getGenericComplement() { + if (hasLocation()) + return this; + else + return fromTargetTypeValue(targetTypeValue() + 1); + } + + /** + * Returns whether or not this TargetType represents an annotation whose + * target has a parameter index. + * + * @return true if this TargetType has a parameter index, + * false otherwise + */ + public boolean hasParameter() { + return flags.contains(HasParameter); + } + + /** + * Returns whether or not this TargetType represents an annotation whose + * target is a type parameter bound. + * + * @return true if this TargetType represents an type parameter bound + * annotation, false otherwise + */ + public boolean hasBound() { + return flags.contains(HasBound); + } + + /** + * Returns whether or not this TargetType represents an annotation whose + * target is exclusively a tree in a method body + * + * Note: wildcard bound targets could target a local tree and a class + * member declaration signature tree + */ + public boolean isLocal() { + return flags.contains(IsLocal); + } + + public int targetTypeValue() { + return this.targetTypeValue; + } + + private static TargetType[] targets = null; + + private static TargetType[] buildTargets() { + TargetType[] targets = new TargetType[MAXIMUM_TARGET_TYPE_VALUE + 1]; + TargetType[] alltargets = values(); + for (TargetType target : alltargets) { + if (target.targetTypeValue >= 0) + targets[target.targetTypeValue] = target; + } + for (int i = 0; i <= MAXIMUM_TARGET_TYPE_VALUE; ++i) { + if (targets[i] == null) + targets[i] = UNKNOWN; + } + return targets; + } + + public static boolean isValidTargetTypeValue(int tag) { + if (targets == null) + targets = buildTargets(); + + if (((byte)tag) == ((byte)UNKNOWN.targetTypeValue)) + return true; + + return (tag >= 0 && tag < targets.length); + } + + public static TargetType fromTargetTypeValue(int tag) { + if (targets == null) + targets = buildTargets(); + + if (((byte)tag) == ((byte)UNKNOWN.targetTypeValue)) + return UNKNOWN; + + if (tag < 0 || tag >= targets.length) + throw new IllegalArgumentException("Unknown TargetType: " + tag); + return targets[tag]; + } + + static enum TargetAttribute { + HasLocation, HasParameter, HasBound, IsLocal; + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/code/Type.java b/douyu-javac/src/main/java/com/sun/tools/javac/code/Type.java new file mode 100644 index 0000000..448834c --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/code/Type.java @@ -0,0 +1,1376 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.code; + +import java.util.Collections; + +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.code.Symbol.*; + +import javax.lang.model.type.*; + +import static com.sun.tools.javac.code.Flags.*; +import static com.sun.tools.javac.code.Kinds.*; +import static com.sun.tools.javac.code.BoundKind.*; +import static com.sun.tools.javac.code.TypeTags.*; + +/** This class represents Java types. The class itself defines the behavior of + * the following types: + *

+ *  base types (tags: BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE, BOOLEAN),
+ *  type `void' (tag: VOID),
+ *  the bottom type (tag: BOT),
+ *  the missing type (tag: NONE).
+ *  
+ *

The behavior of the following types is defined in subclasses, which are + * all static inner classes of this class: + *

+ *  class types (tag: CLASS, class: ClassType),
+ *  array types (tag: ARRAY, class: ArrayType),
+ *  method types (tag: METHOD, class: MethodType),
+ *  package types (tag: PACKAGE, class: PackageType),
+ *  type variables (tag: TYPEVAR, class: TypeVar),
+ *  type arguments (tag: WILDCARD, class: WildcardType),
+ *  polymorphic types (tag: FORALL, class: ForAll),
+ *  the error type (tag: ERROR, class: ErrorType).
+ *  
+ * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @see TypeTags + */ +public class Type implements PrimitiveType { + + /** Constant type: no type at all. */ + public static final JCNoType noType = new JCNoType(NONE); + + /** If this switch is turned on, the names of type variables + * and anonymous classes are printed with hashcodes appended. + */ + public static boolean moreInfo = false; + + /** The tag of this type. + * + * @see TypeTags + */ + public int tag; + + /** The defining class / interface / package / type variable + */ + public TypeSymbol tsym; + + /** + * The constant value of this type, null if this type does not + * have a constant value attribute. Only primitive types and + * strings (ClassType) can have a constant value attribute. + * @return the constant value attribute of this type + */ + public Object constValue() { + return null; + } + + /** + * Get the representation of this type used for modelling purposes. + * By default, this is itself. For ErrorType, a different value + * may be provided, + */ + public Type getModelType() { + return this; + } + + public static List getModelTypes(List ts) { + ListBuffer lb = new ListBuffer(); + for (Type t: ts) + lb.append(t.getModelType()); + return lb.toList(); + } + + public R accept(Type.Visitor v, S s) { return v.visitType(this, s); } + + /** Define a type given its tag and type symbol + */ + public Type(int tag, TypeSymbol tsym) { + this.tag = tag; + this.tsym = tsym; + } + + /** An abstract class for mappings from types to types + */ + public static abstract class Mapping { + private String name; + public Mapping(String name) { + this.name = name; + } + public abstract Type apply(Type t); + public String toString() { + return name; + } + } + + /** map a type function over all immediate descendants of this type + */ + public Type map(Mapping f) { + return this; + } + + /** map a type function over a list of types + */ + public static List map(List ts, Mapping f) { + if (ts.nonEmpty()) { + List tail1 = map(ts.tail, f); + Type t = f.apply(ts.head); + if (tail1 != ts.tail || t != ts.head) + return tail1.prepend(t); + } + return ts; + } + + /** Define a constant type, of the same kind as this type + * and with given constant value + */ + public Type constType(Object constValue) { + final Object value = constValue; + Assert.check(tag <= BOOLEAN); + return new Type(tag, tsym) { + @Override + public Object constValue() { + return value; + } + @Override + public Type baseType() { + return tsym.type; + } + }; + } + + /** + * If this is a constant type, return its underlying type. + * Otherwise, return the type itself. + */ + public Type baseType() { + return this; + } + + /** Return the base types of a list of types. + */ + public static List baseTypes(List ts) { + if (ts.nonEmpty()) { + Type t = ts.head.baseType(); + List baseTypes = baseTypes(ts.tail); + if (t != ts.head || baseTypes != ts.tail) + return baseTypes.prepend(t); + } + return ts; + } + + /** The Java source which this type represents. + */ + public String toString() { + String s = (tsym == null || tsym.name == null) + ? "" + : tsym.name.toString(); + if (moreInfo && tag == TYPEVAR) s = s + hashCode(); + return s; + } + + /** + * The Java source which this type list represents. A List is + * represented as a comma-spearated listing of the elements in + * that list. + */ + public static String toString(List ts) { + if (ts.isEmpty()) { + return ""; + } else { + StringBuilder buf = new StringBuilder(); + buf.append(ts.head.toString()); + for (List l = ts.tail; l.nonEmpty(); l = l.tail) + buf.append(",").append(l.head.toString()); + return buf.toString(); + } + } + + /** + * The constant value of this type, converted to String + */ + public String stringValue() { + Object cv = Assert.checkNonNull(constValue()); + if (tag == BOOLEAN) + return ((Integer) cv).intValue() == 0 ? "false" : "true"; + else if (tag == CHAR) + return String.valueOf((char) ((Integer) cv).intValue()); + else + return cv.toString(); + } + + /** + * This method is analogous to isSameType, but weaker, since we + * never complete classes. Where isSameType would complete a + * class, equals assumes that the two types are different. + */ + public boolean equals(Object t) { + return super.equals(t); + } + + public int hashCode() { + return super.hashCode(); + } + + /** Is this a constant type whose value is false? + */ + public boolean isFalse() { + return + tag == BOOLEAN && + constValue() != null && + ((Integer)constValue()).intValue() == 0; + } + + /** Is this a constant type whose value is true? + */ + public boolean isTrue() { + return + tag == BOOLEAN && + constValue() != null && + ((Integer)constValue()).intValue() != 0; + } + + public String argtypes(boolean varargs) { + List args = getParameterTypes(); + if (!varargs) return args.toString(); + StringBuilder buf = new StringBuilder(); + while (args.tail.nonEmpty()) { + buf.append(args.head); + args = args.tail; + buf.append(','); + } + if (args.head.tag == ARRAY) { + buf.append(((ArrayType)args.head).elemtype); + buf.append("..."); + } else { + buf.append(args.head); + } + return buf.toString(); + } + + /** Access methods. + */ + public List getTypeArguments() { return List.nil(); } + public Type getEnclosingType() { return null; } + public List getParameterTypes() { return List.nil(); } + public Type getReturnType() { return null; } + public List getThrownTypes() { return List.nil(); } + public Type getUpperBound() { return null; } + public Type getLowerBound() { return null; } + + /** Navigation methods, these will work for classes, type variables, + * foralls, but will return null for arrays and methods. + */ + + /** Return all parameters of this type and all its outer types in order + * outer (first) to inner (last). + */ + public List allparams() { return List.nil(); } + + /** Does this type contain "error" elements? + */ + public boolean isErroneous() { + return false; + } + + public static boolean isErroneous(List ts) { + for (List l = ts; l.nonEmpty(); l = l.tail) + if (l.head.isErroneous()) return true; + return false; + } + + /** Is this type parameterized? + * A class type is parameterized if it has some parameters. + * An array type is parameterized if its element type is parameterized. + * All other types are not parameterized. + */ + public boolean isParameterized() { + return false; + } + + /** Is this type a raw type? + * A class type is a raw type if it misses some of its parameters. + * An array type is a raw type if its element type is raw. + * All other types are not raw. + * Type validation will ensure that the only raw types + * in a program are types that miss all their type variables. + */ + public boolean isRaw() { + return false; + } + + public boolean isCompound() { + return tsym.completer == null + // Compound types can't have a completer. Calling + // flags() will complete the symbol causing the + // compiler to load classes unnecessarily. This led + // to regression 6180021. + && (tsym.flags() & COMPOUND) != 0; + } + + public boolean isInterface() { + return (tsym.flags() & INTERFACE) != 0; + } + + public boolean isFinal() { + return (tsym.flags() & FINAL) != 0; + } + + public boolean isPrimitive() { + return tag < VOID; + } + + /** + * Does this type contain occurrences of type t? + */ + public boolean contains(Type t) { + return t == this; + } + + public static boolean contains(List ts, Type t) { + for (List l = ts; + l.tail != null /*inlined: l.nonEmpty()*/; + l = l.tail) + if (l.head.contains(t)) return true; + return false; + } + + /** Does this type contain an occurrence of some type in 'ts'? + */ + public boolean containsAny(List ts) { + for (Type t : ts) + if (this.contains(t)) return true; + return false; + } + + public static boolean containsAny(List ts1, List ts2) { + for (Type t : ts1) + if (t.containsAny(ts2)) return true; + return false; + } + + public static List filter(List ts, Filter tf) { + ListBuffer buf = ListBuffer.lb(); + for (Type t : ts) { + if (tf.accepts(t)) { + buf.append(t); + } + } + return buf.toList(); + } + + public boolean isSuperBound() { return false; } + public boolean isExtendsBound() { return false; } + public boolean isUnbound() { return false; } + public Type withTypeVar(Type t) { return this; } + + /** The underlying method type of this type. + */ + public MethodType asMethodType() { throw new AssertionError(); } + + /** Complete loading all classes in this type. + */ + public void complete() {} + + public TypeSymbol asElement() { + return tsym; + } + + public TypeKind getKind() { + switch (tag) { + case BYTE: return TypeKind.BYTE; + case CHAR: return TypeKind.CHAR; + case SHORT: return TypeKind.SHORT; + case INT: return TypeKind.INT; + case LONG: return TypeKind.LONG; + case FLOAT: return TypeKind.FLOAT; + case DOUBLE: return TypeKind.DOUBLE; + case BOOLEAN: return TypeKind.BOOLEAN; + case VOID: return TypeKind.VOID; + case BOT: return TypeKind.NULL; + case NONE: return TypeKind.NONE; + default: return TypeKind.OTHER; + } + } + + public R accept(TypeVisitor v, P p) { + if (isPrimitive()) + return v.visitPrimitive(this, p); + else + throw new AssertionError(); + } + + public static class WildcardType extends Type + implements javax.lang.model.type.WildcardType { + + public Type type; + public BoundKind kind; + public TypeVar bound; + + @Override + public R accept(Type.Visitor v, S s) { + return v.visitWildcardType(this, s); + } + + public WildcardType(Type type, BoundKind kind, TypeSymbol tsym) { + super(WILDCARD, tsym); + this.type = Assert.checkNonNull(type); + this.kind = kind; + } + public WildcardType(WildcardType t, TypeVar bound) { + this(t.type, t.kind, t.tsym, bound); + } + + public WildcardType(Type type, BoundKind kind, TypeSymbol tsym, TypeVar bound) { + this(type, kind, tsym); + this.bound = bound; + } + + public boolean contains(Type t) { + return kind != UNBOUND && type.contains(t); + } + + public boolean isSuperBound() { + return kind == SUPER || + kind == UNBOUND; + } + public boolean isExtendsBound() { + return kind == EXTENDS || + kind == UNBOUND; + } + public boolean isUnbound() { + return kind == UNBOUND; + } + + public Type withTypeVar(Type t) { + //-System.err.println(this+".withTypeVar("+t+");");//DEBUG + if (bound == t) + return this; + bound = (TypeVar)t; + return this; + } + + boolean isPrintingBound = false; + public String toString() { + StringBuilder s = new StringBuilder(); + s.append(kind.toString()); + if (kind != UNBOUND) + s.append(type); + if (moreInfo && bound != null && !isPrintingBound) + try { + isPrintingBound = true; + s.append("{:").append(bound.bound).append(":}"); + } finally { + isPrintingBound = false; + } + return s.toString(); + } + + public Type map(Mapping f) { + //- System.err.println(" (" + this + ").map(" + f + ")");//DEBUG + Type t = type; + if (t != null) + t = f.apply(t); + if (t == type) + return this; + else + return new WildcardType(t, kind, tsym, bound); + } + + public Type getExtendsBound() { + if (kind == EXTENDS) + return type; + else + return null; + } + + public Type getSuperBound() { + if (kind == SUPER) + return type; + else + return null; + } + + public TypeKind getKind() { + return TypeKind.WILDCARD; + } + + public R accept(TypeVisitor v, P p) { + return v.visitWildcard(this, p); + } + } + + public static class ClassType extends Type implements DeclaredType { + + /** The enclosing type of this type. If this is the type of an inner + * class, outer_field refers to the type of its enclosing + * instance class, in all other cases it referes to noType. + */ + private Type outer_field; + + /** The type parameters of this type (to be set once class is loaded). + */ + public List typarams_field; + + /** A cache variable for the type parameters of this type, + * appended to all parameters of its enclosing class. + * @see #allparams + */ + public List allparams_field; + + /** The supertype of this class (to be set once class is loaded). + */ + public Type supertype_field; + + /** The interfaces of this class (to be set once class is loaded). + */ + public List interfaces_field; + + /** All the interfaces of this class, including missing ones. + */ + public List all_interfaces_field; + + public ClassType(Type outer, List typarams, TypeSymbol tsym) { + super(CLASS, tsym); + this.outer_field = outer; + this.typarams_field = typarams; + this.allparams_field = null; + this.supertype_field = null; + this.interfaces_field = null; + /* + // this can happen during error recovery + assert + outer.isParameterized() ? + typarams.length() == tsym.type.typarams().length() : + outer.isRaw() ? + typarams.length() == 0 : + true; + */ + } + + @Override + public R accept(Type.Visitor v, S s) { + return v.visitClassType(this, s); + } + + public Type constType(Object constValue) { + final Object value = constValue; + return new ClassType(getEnclosingType(), typarams_field, tsym) { + @Override + public Object constValue() { + return value; + } + @Override + public Type baseType() { + return tsym.type; + } + }; + } + + /** The Java source which this type represents. + */ + public String toString() { + StringBuilder buf = new StringBuilder(); + if (getEnclosingType().tag == CLASS && tsym.owner.kind == TYP) { + buf.append(getEnclosingType().toString()); + buf.append("."); + buf.append(className(tsym, false)); + } else { + buf.append(className(tsym, true)); + } + if (getTypeArguments().nonEmpty()) { + buf.append('<'); + buf.append(getTypeArguments().toString()); + buf.append(">"); + } + return buf.toString(); + } +//where + private String className(Symbol sym, boolean longform) { + if (sym.name.isEmpty() && (sym.flags() & COMPOUND) != 0) { + StringBuilder s = new StringBuilder(supertype_field.toString()); + for (List is=interfaces_field; is.nonEmpty(); is = is.tail) { + s.append("&"); + s.append(is.head.toString()); + } + return s.toString(); + } else if (sym.name.isEmpty()) { + String s; + ClassType norm = (ClassType) tsym.type; + if (norm == null) { + s = Log.getLocalizedString("anonymous.class", (Object)null); + } else if (norm.interfaces_field != null && norm.interfaces_field.nonEmpty()) { + s = Log.getLocalizedString("anonymous.class", + norm.interfaces_field.head); + } else { + s = Log.getLocalizedString("anonymous.class", + norm.supertype_field); + } + if (moreInfo) + s += String.valueOf(sym.hashCode()); + return s; + } else if (longform) { + return sym.getQualifiedName().toString(); + } else { + return sym.name.toString(); + } + } + + public List getTypeArguments() { + if (typarams_field == null) { + complete(); + if (typarams_field == null) + typarams_field = List.nil(); + } + return typarams_field; + } + + public boolean hasErasedSupertypes() { + return isRaw(); + } + + public Type getEnclosingType() { + return outer_field; + } + + public void setEnclosingType(Type outer) { + outer_field = outer; + } + + public List allparams() { + if (allparams_field == null) { + allparams_field = getTypeArguments().prependList(getEnclosingType().allparams()); + } + return allparams_field; + } + + public boolean isErroneous() { + return + getEnclosingType().isErroneous() || + isErroneous(getTypeArguments()) || + this != tsym.type && tsym.type.isErroneous(); + } + + public boolean isParameterized() { + return allparams().tail != null; + // optimization, was: allparams().nonEmpty(); + } + + /** A cache for the rank. */ + int rank_field = -1; + + /** A class type is raw if it misses some + * of its type parameter sections. + * After validation, this is equivalent to: + * allparams.isEmpty() && tsym.type.allparams.nonEmpty(); + */ + public boolean isRaw() { + return + this != tsym.type && // necessary, but not sufficient condition + tsym.type.allparams().nonEmpty() && + allparams().isEmpty(); + } + + public Type map(Mapping f) { + Type outer = getEnclosingType(); + Type outer1 = f.apply(outer); + List typarams = getTypeArguments(); + List typarams1 = map(typarams, f); + if (outer1 == outer && typarams1 == typarams) return this; + else return new ClassType(outer1, typarams1, tsym); + } + + public boolean contains(Type elem) { + return + elem == this + || (isParameterized() + && (getEnclosingType().contains(elem) || contains(getTypeArguments(), elem))) + || (isCompound() + && (supertype_field.contains(elem) || contains(interfaces_field, elem))); + } + + public void complete() { + if (tsym.completer != null) tsym.complete(); + } + + public TypeKind getKind() { + return TypeKind.DECLARED; + } + + public R accept(TypeVisitor v, P p) { + return v.visitDeclared(this, p); + } + } + + public static class ErasedClassType extends ClassType { + public ErasedClassType(Type outer, TypeSymbol tsym) { + super(outer, List.nil(), tsym); + } + + @Override + public boolean hasErasedSupertypes() { + return true; + } + } + + // a clone of a ClassType that knows about the alternatives of a union type. + public static class UnionClassType extends ClassType implements UnionType { + final List alternatives_field; + + public UnionClassType(ClassType ct, List alternatives) { + super(ct.outer_field, ct.typarams_field, ct.tsym); + allparams_field = ct.allparams_field; + supertype_field = ct.supertype_field; + interfaces_field = ct.interfaces_field; + all_interfaces_field = ct.interfaces_field; + alternatives_field = alternatives; + } + + public Type getLub() { + return tsym.type; + } + + public java.util.List getAlternatives() { + return Collections.unmodifiableList(alternatives_field); + } + + @Override + public TypeKind getKind() { + return TypeKind.UNION; + } + + @Override + public R accept(TypeVisitor v, P p) { + return v.visitUnion(this, p); + } + } + + public static class ArrayType extends Type + implements javax.lang.model.type.ArrayType { + + public Type elemtype; + + public ArrayType(Type elemtype, TypeSymbol arrayClass) { + super(ARRAY, arrayClass); + this.elemtype = elemtype; + } + + @Override + public R accept(Type.Visitor v, S s) { + return v.visitArrayType(this, s); + } + + public String toString() { + return elemtype + "[]"; + } + + public boolean equals(Object obj) { + return + this == obj || + (obj instanceof ArrayType && + this.elemtype.equals(((ArrayType)obj).elemtype)); + } + + public int hashCode() { + return (ARRAY << 5) + elemtype.hashCode(); + } + + public boolean isVarargs() { + return false; + } + + public List allparams() { return elemtype.allparams(); } + + public boolean isErroneous() { + return elemtype.isErroneous(); + } + + public boolean isParameterized() { + return elemtype.isParameterized(); + } + + public boolean isRaw() { + return elemtype.isRaw(); + } + + public ArrayType makeVarargs() { + return new ArrayType(elemtype, tsym) { + @Override + public boolean isVarargs() { + return true; + } + }; + } + + public Type map(Mapping f) { + Type elemtype1 = f.apply(elemtype); + if (elemtype1 == elemtype) return this; + else return new ArrayType(elemtype1, tsym); + } + + public boolean contains(Type elem) { + return elem == this || elemtype.contains(elem); + } + + public void complete() { + elemtype.complete(); + } + + public Type getComponentType() { + return elemtype; + } + + public TypeKind getKind() { + return TypeKind.ARRAY; + } + + public R accept(TypeVisitor v, P p) { + return v.visitArray(this, p); + } + } + + public static class MethodType extends Type implements ExecutableType { + + public List argtypes; + public Type restype; + public List thrown; + + public MethodType(List argtypes, + Type restype, + List thrown, + TypeSymbol methodClass) { + super(METHOD, methodClass); + this.argtypes = argtypes; + this.restype = restype; + this.thrown = thrown; + } + + @Override + public R accept(Type.Visitor v, S s) { + return v.visitMethodType(this, s); + } + + /** The Java source which this type represents. + * + * XXX 06/09/99 iris This isn't correct Java syntax, but it probably + * should be. + */ + public String toString() { + return "(" + argtypes + ")" + restype; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof MethodType)) + return false; + MethodType m = (MethodType)obj; + List args1 = argtypes; + List args2 = m.argtypes; + while (!args1.isEmpty() && !args2.isEmpty()) { + if (!args1.head.equals(args2.head)) + return false; + args1 = args1.tail; + args2 = args2.tail; + } + if (!args1.isEmpty() || !args2.isEmpty()) + return false; + return restype.equals(m.restype); + } + + public int hashCode() { + int h = METHOD; + for (List thisargs = this.argtypes; + thisargs.tail != null; /*inlined: thisargs.nonEmpty()*/ + thisargs = thisargs.tail) + h = (h << 5) + thisargs.head.hashCode(); + return (h << 5) + this.restype.hashCode(); + } + + public List getParameterTypes() { return argtypes; } + public Type getReturnType() { return restype; } + public List getThrownTypes() { return thrown; } + + public boolean isErroneous() { + return + isErroneous(argtypes) || + restype != null && restype.isErroneous(); + } + + public Type map(Mapping f) { + List argtypes1 = map(argtypes, f); + Type restype1 = f.apply(restype); + List thrown1 = map(thrown, f); + if (argtypes1 == argtypes && + restype1 == restype && + thrown1 == thrown) return this; + else return new MethodType(argtypes1, restype1, thrown1, tsym); + } + + public boolean contains(Type elem) { + return elem == this || contains(argtypes, elem) || restype.contains(elem); + } + + public MethodType asMethodType() { return this; } + + public void complete() { + for (List l = argtypes; l.nonEmpty(); l = l.tail) + l.head.complete(); + restype.complete(); + for (List l = thrown; l.nonEmpty(); l = l.tail) + l.head.complete(); + } + + public List getTypeVariables() { + return List.nil(); + } + + public TypeSymbol asElement() { + return null; + } + + public TypeKind getKind() { + return TypeKind.EXECUTABLE; + } + + public R accept(TypeVisitor v, P p) { + return v.visitExecutable(this, p); + } + } + + public static class PackageType extends Type implements NoType { + + PackageType(TypeSymbol tsym) { + super(PACKAGE, tsym); + } + + @Override + public R accept(Type.Visitor v, S s) { + return v.visitPackageType(this, s); + } + + public String toString() { + return tsym.getQualifiedName().toString(); + } + + public TypeKind getKind() { + return TypeKind.PACKAGE; + } + + public R accept(TypeVisitor v, P p) { + return v.visitNoType(this, p); + } + } + + public static class TypeVar extends Type implements TypeVariable { + + /** The upper bound of this type variable; set from outside. + * Must be nonempty once it is set. + * For a bound, `bound' is the bound type itself. + * Multiple bounds are expressed as a single class type which has the + * individual bounds as superclass, respectively interfaces. + * The class type then has as `tsym' a compiler generated class `c', + * which has a flag COMPOUND and whose owner is the type variable + * itself. Furthermore, the erasure_field of the class + * points to the first class or interface bound. + */ + public Type bound = null; + + /** The lower bound of this type variable. + * TypeVars don't normally have a lower bound, so it is normally set + * to syms.botType. + * Subtypes, such as CapturedType, may provide a different value. + */ + public Type lower; + + public TypeVar(Name name, Symbol owner, Type lower) { + super(TYPEVAR, null); + tsym = new TypeSymbol(0, name, this, owner); + this.lower = lower; + } + + public TypeVar(TypeSymbol tsym, Type bound, Type lower) { + super(TYPEVAR, tsym); + this.bound = bound; + this.lower = lower; + } + + @Override + public R accept(Type.Visitor v, S s) { + return v.visitTypeVar(this, s); + } + + @Override + public Type getUpperBound() { return bound; } + + int rank_field = -1; + + @Override + public Type getLowerBound() { + return lower; + } + + public TypeKind getKind() { + return TypeKind.TYPEVAR; + } + + public boolean isCaptured() { + return false; + } + + public R accept(TypeVisitor v, P p) { + return v.visitTypeVariable(this, p); + } + } + + /** A captured type variable comes from wildcards which can have + * both upper and lower bound. CapturedType extends TypeVar with + * a lower bound. + */ + public static class CapturedType extends TypeVar { + + public WildcardType wildcard; + + public CapturedType(Name name, + Symbol owner, + Type upper, + Type lower, + WildcardType wildcard) { + super(name, owner, lower); + this.lower = Assert.checkNonNull(lower); + this.bound = upper; + this.wildcard = wildcard; + } + + @Override + public R accept(Type.Visitor v, S s) { + return v.visitCapturedType(this, s); + } + + @Override + public boolean isCaptured() { + return true; + } + + @Override + public String toString() { + return "capture#" + + (hashCode() & 0xFFFFFFFFL) % Printer.PRIME + + " of " + + wildcard; + } + } + + public static abstract class DelegatedType extends Type { + public Type qtype; + public DelegatedType(int tag, Type qtype) { + super(tag, qtype.tsym); + this.qtype = qtype; + } + public String toString() { return qtype.toString(); } + public List getTypeArguments() { return qtype.getTypeArguments(); } + public Type getEnclosingType() { return qtype.getEnclosingType(); } + public List getParameterTypes() { return qtype.getParameterTypes(); } + public Type getReturnType() { return qtype.getReturnType(); } + public List getThrownTypes() { return qtype.getThrownTypes(); } + public List allparams() { return qtype.allparams(); } + public Type getUpperBound() { return qtype.getUpperBound(); } + public boolean isErroneous() { return qtype.isErroneous(); } + } + + public static class ForAll extends DelegatedType implements ExecutableType { + public List tvars; + + public ForAll(List tvars, Type qtype) { + super(FORALL, qtype); + this.tvars = tvars; + } + + @Override + public R accept(Type.Visitor v, S s) { + return v.visitForAll(this, s); + } + + public String toString() { + return "<" + tvars + ">" + qtype; + } + + public List getTypeArguments() { return tvars; } + + public boolean isErroneous() { + return qtype.isErroneous(); + } + + /** + * Replaces this ForAll's typevars with a set of concrete Java types + * and returns the instantiated generic type. Subclasses should override + * in order to check that the list of types is a valid instantiation + * of the ForAll's typevars. + * + * @param actuals list of actual types + * @param types types instance + * @return qtype where all occurrences of tvars are replaced + * by types in actuals + */ + public Type inst(List actuals, Types types) { + return types.subst(qtype, tvars, actuals); + } + + /** + * Kind of type-constraint derived during type inference + */ + public enum ConstraintKind { + /** + * upper bound constraint (a type variable must be instantiated + * with a type T, where T is a subtype of all the types specified by + * its EXTENDS constraints). + */ + EXTENDS, + /** + * lower bound constraint (a type variable must be instantiated + * with a type T, where T is a supertype of all the types specified by + * its SUPER constraints). + */ + SUPER, + /** + * equality constraint (a type variable must be instantiated to the type + * specified by its EQUAL constraint. + */ + EQUAL; + } + + /** + * Get the type-constraints of a given kind for a given type-variable of + * this ForAll type. Subclasses should override in order to return more + * accurate sets of constraints. + * + * @param tv the type-variable for which the constraint is to be retrieved + * @param ck the constraint kind to be retrieved + * @return the list of types specified by the selected constraint + */ + public List getConstraints(TypeVar tv, ConstraintKind ck) { + return List.nil(); + } + + public Type map(Mapping f) { + return f.apply(qtype); + } + + public boolean contains(Type elem) { + return qtype.contains(elem); + } + + public MethodType asMethodType() { + return qtype.asMethodType(); + } + + public void complete() { + for (List l = tvars; l.nonEmpty(); l = l.tail) { + ((TypeVar)l.head).bound.complete(); + } + qtype.complete(); + } + + public List getTypeVariables() { + return List.convert(TypeVar.class, getTypeArguments()); + } + + public TypeKind getKind() { + return TypeKind.EXECUTABLE; + } + + public R accept(TypeVisitor v, P p) { + return v.visitExecutable(this, p); + } + } + + /** A class for instantiatable variables, for use during type + * inference. + */ + public static class UndetVar extends DelegatedType { + public List lobounds = List.nil(); + public List hibounds = List.nil(); + public Type inst = null; + + @Override + public R accept(Type.Visitor v, S s) { + return v.visitUndetVar(this, s); + } + + public UndetVar(Type origin) { + super(UNDETVAR, origin); + } + + public String toString() { + if (inst != null) return inst.toString(); + else return qtype + "?"; + } + + public Type baseType() { + if (inst != null) return inst.baseType(); + else return this; + } + } + + /** Represents VOID or NONE. + */ + static class JCNoType extends Type implements NoType { + public JCNoType(int tag) { + super(tag, null); + } + + @Override + public TypeKind getKind() { + switch (tag) { + case VOID: return TypeKind.VOID; + case NONE: return TypeKind.NONE; + default: + throw new AssertionError("Unexpected tag: " + tag); + } + } + + @Override + public R accept(TypeVisitor v, P p) { + return v.visitNoType(this, p); + } + } + + static class BottomType extends Type implements NullType { + public BottomType() { + super(TypeTags.BOT, null); + } + + @Override + public TypeKind getKind() { + return TypeKind.NULL; + } + + @Override + public R accept(TypeVisitor v, P p) { + return v.visitNull(this, p); + } + + @Override + public Type constType(Object value) { + return this; + } + + @Override + public String stringValue() { + return "null"; + } + } + + public static class ErrorType extends ClassType + implements javax.lang.model.type.ErrorType { + + private Type originalType = null; + + public ErrorType(Type originalType, TypeSymbol tsym) { + super(noType, List.nil(), null); + tag = ERROR; + this.tsym = tsym; + this.originalType = (originalType == null ? noType : originalType); + } + + public ErrorType(ClassSymbol c, Type originalType) { + this(originalType, c); + c.type = this; + c.kind = ERR; + c.members_field = new Scope.ErrorScope(c); + } + + public ErrorType(Name name, TypeSymbol container, Type originalType) { + this(new ClassSymbol(PUBLIC|STATIC|ACYCLIC, name, null, container), originalType); + } + + @Override + public R accept(Type.Visitor v, S s) { + return v.visitErrorType(this, s); + } + + public Type constType(Object constValue) { return this; } + public Type getEnclosingType() { return this; } + public Type getReturnType() { return this; } + public Type asSub(Symbol sym) { return this; } + public Type map(Mapping f) { return this; } + + public boolean isGenType(Type t) { return true; } + public boolean isErroneous() { return true; } + public boolean isCompound() { return false; } + public boolean isInterface() { return false; } + + public List allparams() { return List.nil(); } + public List getTypeArguments() { return List.nil(); } + + public TypeKind getKind() { + return TypeKind.ERROR; + } + + public Type getOriginalType() { + return originalType; + } + + public R accept(TypeVisitor v, P p) { + return v.visitError(this, p); + } + } + + /** + * A visitor for types. A visitor is used to implement operations + * (or relations) on types. Most common operations on types are + * binary relations and this interface is designed for binary + * relations, that is, operations on the form + * Type × S → R. + * + * + * @param the return type of the operation implemented by this + * visitor; use Void if no return type is needed. + * @param the type of the second argument (the first being the + * type itself) of the operation implemented by this visitor; use + * Void if a second argument is not needed. + */ + public interface Visitor { + R visitClassType(ClassType t, S s); + R visitWildcardType(WildcardType t, S s); + R visitArrayType(ArrayType t, S s); + R visitMethodType(MethodType t, S s); + R visitPackageType(PackageType t, S s); + R visitTypeVar(TypeVar t, S s); + R visitCapturedType(CapturedType t, S s); + R visitForAll(ForAll t, S s); + R visitUndetVar(UndetVar t, S s); + R visitErrorType(ErrorType t, S s); + R visitType(Type t, S s); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/code/TypeAnnotationPosition.java b/douyu-javac/src/main/java/com/sun/tools/javac/code/TypeAnnotationPosition.java new file mode 100644 index 0000000..c3fba38 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/code/TypeAnnotationPosition.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.code; + +import com.sun.tools.javac.util.*; + +/** A type annotation position. +* +*

This is NOT part of any supported API. +* If you write code that depends on this, you do so at your own risk. +* This code and its internal interfaces are subject to change or +* deletion without notice. +*/ +public class TypeAnnotationPosition { + + public TargetType type = TargetType.UNKNOWN; + + // For generic/array types. + public List location = List.nil(); + + // Tree position. + public int pos = -1; + + // For typecasts, type tests, new (and locals, as start_pc). + public boolean isValidOffset = false; + public int offset = -1; + + // For locals. arrays same length + public int[] lvarOffset = null; + public int[] lvarLength = null; + public int[] lvarIndex = null; + + // For type parameter bound + public int bound_index = Integer.MIN_VALUE; + + // For type parameter and method parameter + public int parameter_index = Integer.MIN_VALUE; + + // For class extends, implements, and throws classes + public int type_index = Integer.MIN_VALUE; + + // For wildcards + public TypeAnnotationPosition wildcard_position = null; + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('['); + sb.append(type); + + switch (type) { + // type case + case TYPECAST: + case TYPECAST_GENERIC_OR_ARRAY: + // object creation + case INSTANCEOF: + case INSTANCEOF_GENERIC_OR_ARRAY: + // new expression + case NEW: + case NEW_GENERIC_OR_ARRAY: + case NEW_TYPE_ARGUMENT: + case NEW_TYPE_ARGUMENT_GENERIC_OR_ARRAY: + sb.append(", offset = "); + sb.append(offset); + break; + // local variable + case LOCAL_VARIABLE: + case LOCAL_VARIABLE_GENERIC_OR_ARRAY: + sb.append(", {"); + for (int i = 0; i < lvarOffset.length; ++i) { + if (i != 0) sb.append("; "); + sb.append(", start_pc = "); + sb.append(lvarOffset[i]); + sb.append(", length = "); + sb.append(lvarLength[i]); + sb.append(", index = "); + sb.append(lvarIndex[i]); + } + sb.append("}"); + break; + // method receiver + case METHOD_RECEIVER: + // Do nothing + break; + // type parameters + case CLASS_TYPE_PARAMETER: + case METHOD_TYPE_PARAMETER: + sb.append(", param_index = "); + sb.append(parameter_index); + break; + // type parameters bound + case CLASS_TYPE_PARAMETER_BOUND: + case CLASS_TYPE_PARAMETER_BOUND_GENERIC_OR_ARRAY: + case METHOD_TYPE_PARAMETER_BOUND: + case METHOD_TYPE_PARAMETER_BOUND_GENERIC_OR_ARRAY: + sb.append(", param_index = "); + sb.append(parameter_index); + sb.append(", bound_index = "); + sb.append(bound_index); + break; + // wildcard + case WILDCARD_BOUND: + case WILDCARD_BOUND_GENERIC_OR_ARRAY: + sb.append(", wild_card = "); + sb.append(wildcard_position); + break; + // Class extends and implements clauses + case CLASS_EXTENDS: + case CLASS_EXTENDS_GENERIC_OR_ARRAY: + sb.append(", type_index = "); + sb.append(type_index); + break; + // throws + case THROWS: + sb.append(", type_index = "); + sb.append(type_index); + break; + case CLASS_LITERAL: + case CLASS_LITERAL_GENERIC_OR_ARRAY: + sb.append(", offset = "); + sb.append(offset); + break; + // method parameter: not specified + case METHOD_PARAMETER_GENERIC_OR_ARRAY: + sb.append(", param_index = "); + sb.append(parameter_index); + break; + // method type argument: wasn't specified + case METHOD_TYPE_ARGUMENT: + case METHOD_TYPE_ARGUMENT_GENERIC_OR_ARRAY: + sb.append(", offset = "); + sb.append(offset); + sb.append(", type_index = "); + sb.append(type_index); + break; + // We don't need to worry abut these + case METHOD_RETURN_GENERIC_OR_ARRAY: + case FIELD_GENERIC_OR_ARRAY: + break; + case UNKNOWN: + break; + default: + // throw new AssertionError("unknown type: " + type); + } + + // Append location data for generics/arrays. + if (type.hasLocation()) { + sb.append(", location = ("); + sb.append(location); + sb.append(")"); + } + + sb.append(", pos = "); + sb.append(pos); + + sb.append(']'); + return sb.toString(); + } + + /** + * Indicates whether the target tree of the annotation has been optimized + * away from classfile or not. + * @return true if the target has not been optimized away + */ + public boolean emitToClassfile() { + if (type == TargetType.WILDCARD_BOUND + || type == TargetType.WILDCARD_BOUND_GENERIC_OR_ARRAY) + return wildcard_position.isValidOffset; + else + return !type.isLocal() || isValidOffset; + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/code/TypeTags.java b/douyu-javac/src/main/java/com/sun/tools/javac/code/TypeTags.java new file mode 100644 index 0000000..463def2 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/code/TypeTags.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 1999, 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.code; + + +/** An interface for type tag values, which distinguish between different + * sorts of types. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class TypeTags { + + private TypeTags() {} // uninstantiable + + /** The tag of the basic type `byte'. + */ + public static final int BYTE = 1; + + /** The tag of the basic type `char'. + */ + public static final int CHAR = BYTE+1; + + /** The tag of the basic type `short'. + */ + public static final int SHORT = CHAR+1; + + /** The tag of the basic type `int'. + */ + public static final int INT = SHORT+1; + + /** The tag of the basic type `long'. + */ + public static final int LONG = INT+1; + + /** The tag of the basic type `float'. + */ + public static final int FLOAT = LONG+1; + + /** The tag of the basic type `double'. + */ + public static final int DOUBLE = FLOAT+1; + + /** The tag of the basic type `boolean'. + */ + public static final int BOOLEAN = DOUBLE+1; + + /** The tag of the type `void'. + */ + public static final int VOID = BOOLEAN+1; + + /** The tag of all class and interface types. + */ + public static final int CLASS = VOID+1; + + /** The tag of all array types. + */ + public static final int ARRAY = CLASS+1; + + /** The tag of all (monomorphic) method types. + */ + public static final int METHOD = ARRAY+1; + + /** The tag of all package "types". + */ + public static final int PACKAGE = METHOD+1; + + /** The tag of all (source-level) type variables. + */ + public static final int TYPEVAR = PACKAGE+1; + + /** The tag of all type arguments. + */ + public static final int WILDCARD = TYPEVAR+1; + + /** The tag of all polymorphic (method-) types. + */ + public static final int FORALL = WILDCARD+1; + + /** The tag of the bottom type . + */ + public static final int BOT = FORALL+1; + + /** The tag of a missing type. + */ + public static final int NONE = BOT+1; + + /** The tag of the error type. + */ + public static final int ERROR = NONE+1; + + /** The tag of an unknown type + */ + public static final int UNKNOWN = ERROR+1; + + /** The tag of all instantiatable type variables. + */ + public static final int UNDETVAR = UNKNOWN+1; + + /** The number of type tags. + */ + public static final int TypeTagCount = UNDETVAR+1; + + /** The maximum tag of a basic type. + */ + public static final int lastBaseTag = BOOLEAN; + + /** The minimum tag of a partial type + */ + public static final int firstPartialTag = ERROR; +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/code/Types.java b/douyu-javac/src/main/java/com/sun/tools/javac/code/Types.java new file mode 100644 index 0000000..a0c094e --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/code/Types.java @@ -0,0 +1,3804 @@ +/* + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.code; + +import java.lang.ref.SoftReference; +import java.util.*; + +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.List; + +import com.sun.tools.javac.jvm.ClassReader; +import com.sun.tools.javac.code.Attribute.RetentionPolicy; +import com.sun.tools.javac.code.Lint.LintCategory; +import com.sun.tools.javac.comp.Check; + +import static com.sun.tools.javac.code.Scope.*; +import static com.sun.tools.javac.code.Type.*; +import static com.sun.tools.javac.code.TypeTags.*; +import static com.sun.tools.javac.code.Symbol.*; +import static com.sun.tools.javac.code.Flags.*; +import static com.sun.tools.javac.code.BoundKind.*; +import static com.sun.tools.javac.util.ListBuffer.lb; + +/** + * Utility class containing various operations on types. + * + *

Unless other names are more illustrative, the following naming + * conventions should be observed in this file: + * + *

+ *
t
+ *
If the first argument to an operation is a type, it should be named t.
+ *
s
+ *
Similarly, if the second argument to an operation is a type, it should be named s.
+ *
ts
+ *
If an operations takes a list of types, the first should be named ts.
+ *
ss
+ *
A second list of types should be named ss.
+ *
+ * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Types { + protected static final Context.Key typesKey = + new Context.Key(); + + final Symtab syms; + final JavacMessages messages; + final Names names; + final boolean allowBoxing; + final boolean allowCovariantReturns; + final boolean allowObjectToPrimitiveCast; + final ClassReader reader; + final Check chk; + List warnStack = List.nil(); + final Name capturedName; + + // + public static Types instance(Context context) { + Types instance = context.get(typesKey); + if (instance == null) + instance = new Types(context); + return instance; + } + + protected Types(Context context) { + context.put(typesKey, this); + syms = Symtab.instance(context); + names = Names.instance(context); + Source source = Source.instance(context); + allowBoxing = source.allowBoxing(); + allowCovariantReturns = source.allowCovariantReturns(); + allowObjectToPrimitiveCast = source.allowObjectToPrimitiveCast(); + reader = ClassReader.instance(context); + chk = Check.instance(context); + capturedName = names.fromString(""); + messages = JavacMessages.instance(context); + } + // + + // + /** + * The "rvalue conversion".
+ * The upper bound of most types is the type + * itself. Wildcards, on the other hand have upper + * and lower bounds. + * @param t a type + * @return the upper bound of the given type + */ + public Type upperBound(Type t) { + return upperBound.visit(t); + } + // where + private final MapVisitor upperBound = new MapVisitor() { + + @Override + public Type visitWildcardType(WildcardType t, Void ignored) { + if (t.isSuperBound()) + return t.bound == null ? syms.objectType : t.bound.bound; + else + return visit(t.type); + } + + @Override + public Type visitCapturedType(CapturedType t, Void ignored) { + return visit(t.bound); + } + }; + //
+ + // + /** + * The "lvalue conversion".
+ * The lower bound of most types is the type + * itself. Wildcards, on the other hand have upper + * and lower bounds. + * @param t a type + * @return the lower bound of the given type + */ + public Type lowerBound(Type t) { + return lowerBound.visit(t); + } + // where + private final MapVisitor lowerBound = new MapVisitor() { + + @Override + public Type visitWildcardType(WildcardType t, Void ignored) { + return t.isExtendsBound() ? syms.botType : visit(t.type); + } + + @Override + public Type visitCapturedType(CapturedType t, Void ignored) { + return visit(t.getLowerBound()); + } + }; + //
+ + // + /** + * Checks that all the arguments to a class are unbounded + * wildcards or something else that doesn't make any restrictions + * on the arguments. If a class isUnbounded, a raw super- or + * subclass can be cast to it without a warning. + * @param t a type + * @return true iff the given type is unbounded or raw + */ + public boolean isUnbounded(Type t) { + return isUnbounded.visit(t); + } + // where + private final UnaryVisitor isUnbounded = new UnaryVisitor() { + + public Boolean visitType(Type t, Void ignored) { + return true; + } + + @Override + public Boolean visitClassType(ClassType t, Void ignored) { + List parms = t.tsym.type.allparams(); + List args = t.allparams(); + while (parms.nonEmpty()) { + WildcardType unb = new WildcardType(syms.objectType, + BoundKind.UNBOUND, + syms.boundClass, + (TypeVar)parms.head); + if (!containsType(args.head, unb)) + return false; + parms = parms.tail; + args = args.tail; + } + return true; + } + }; + // + + // + /** + * Return the least specific subtype of t that starts with symbol + * sym. If none exists, return null. The least specific subtype + * is determined as follows: + * + *

If there is exactly one parameterized instance of sym that is a + * subtype of t, that parameterized instance is returned.
+ * Otherwise, if the plain type or raw type `sym' is a subtype of + * type t, the type `sym' itself is returned. Otherwise, null is + * returned. + */ + public Type asSub(Type t, Symbol sym) { + return asSub.visit(t, sym); + } + // where + private final SimpleVisitor asSub = new SimpleVisitor() { + + public Type visitType(Type t, Symbol sym) { + return null; + } + + @Override + public Type visitClassType(ClassType t, Symbol sym) { + if (t.tsym == sym) + return t; + Type base = asSuper(sym.type, t.tsym); + if (base == null) + return null; + ListBuffer from = new ListBuffer(); + ListBuffer to = new ListBuffer(); + try { + adapt(base, t, from, to); + } catch (AdaptFailure ex) { + return null; + } + Type res = subst(sym.type, from.toList(), to.toList()); + if (!isSubtype(res, t)) + return null; + ListBuffer openVars = new ListBuffer(); + for (List l = sym.type.allparams(); + l.nonEmpty(); l = l.tail) + if (res.contains(l.head) && !t.contains(l.head)) + openVars.append(l.head); + if (openVars.nonEmpty()) { + if (t.isRaw()) { + // The subtype of a raw type is raw + res = erasure(res); + } else { + // Unbound type arguments default to ? + List opens = openVars.toList(); + ListBuffer qs = new ListBuffer(); + for (List iter = opens; iter.nonEmpty(); iter = iter.tail) { + qs.append(new WildcardType(syms.objectType, BoundKind.UNBOUND, syms.boundClass, (TypeVar) iter.head)); + } + res = subst(res, opens, qs.toList()); + } + } + return res; + } + + @Override + public Type visitErrorType(ErrorType t, Symbol sym) { + return t; + } + }; + // + + // + /** + * Is t a subtype of or convertiable via boxing/unboxing + * convertions to s? + */ + public boolean isConvertible(Type t, Type s, Warner warn) { + boolean tPrimitive = t.isPrimitive(); + boolean sPrimitive = s.isPrimitive(); + if (tPrimitive == sPrimitive) { + checkUnsafeVarargsConversion(t, s, warn); + return isSubtypeUnchecked(t, s, warn); + } + if (!allowBoxing) return false; + return tPrimitive + ? isSubtype(boxedClass(t).type, s) + : isSubtype(unboxedType(t), s); + } + //where + private void checkUnsafeVarargsConversion(Type t, Type s, Warner warn) { + if (t.tag != ARRAY || isReifiable(t)) return; + ArrayType from = (ArrayType)t; + boolean shouldWarn = false; + switch (s.tag) { + case ARRAY: + ArrayType to = (ArrayType)s; + shouldWarn = from.isVarargs() && + !to.isVarargs() && + !isReifiable(from); + break; + case CLASS: + shouldWarn = from.isVarargs() && + isSubtype(from, s); + break; + } + if (shouldWarn) { + warn.warn(LintCategory.VARARGS); + } + } + + /** + * Is t a subtype of or convertiable via boxing/unboxing + * convertions to s? + */ + public boolean isConvertible(Type t, Type s) { + return isConvertible(t, s, Warner.noWarnings); + } + // + + // + /** + * Is t an unchecked subtype of s? + */ + public boolean isSubtypeUnchecked(Type t, Type s) { + return isSubtypeUnchecked(t, s, Warner.noWarnings); + } + /** + * Is t an unchecked subtype of s? + */ + public boolean isSubtypeUnchecked(Type t, Type s, Warner warn) { + if (t.tag == ARRAY && s.tag == ARRAY) { + if (((ArrayType)t).elemtype.tag <= lastBaseTag) { + return isSameType(elemtype(t), elemtype(s)); + } else { + ArrayType from = (ArrayType)t; + ArrayType to = (ArrayType)s; + if (from.isVarargs() && + !to.isVarargs() && + !isReifiable(from)) { + warn.warn(LintCategory.VARARGS); + } + return isSubtypeUnchecked(elemtype(t), elemtype(s), warn); + } + } else if (isSubtype(t, s)) { + return true; + } + else if (t.tag == TYPEVAR) { + return isSubtypeUnchecked(t.getUpperBound(), s, warn); + } + else if (s.tag == UNDETVAR) { + UndetVar uv = (UndetVar)s; + if (uv.inst != null) + return isSubtypeUnchecked(t, uv.inst, warn); + } + else if (!s.isRaw()) { + Type t2 = asSuper(t, s.tsym); + if (t2 != null && t2.isRaw()) { + if (isReifiable(s)) + warn.silentWarn(LintCategory.UNCHECKED); + else + warn.warn(LintCategory.UNCHECKED); + return true; + } + } + return false; + } + + /** + * Is t a subtype of s?
+ * (not defined for Method and ForAll types) + */ + final public boolean isSubtype(Type t, Type s) { + return isSubtype(t, s, true); + } + final public boolean isSubtypeNoCapture(Type t, Type s) { + return isSubtype(t, s, false); + } + public boolean isSubtype(Type t, Type s, boolean capture) { + if (t == s) + return true; + + if (s.tag >= firstPartialTag) + return isSuperType(s, t); + + if (s.isCompound()) { + for (Type s2 : interfaces(s).prepend(supertype(s))) { + if (!isSubtype(t, s2, capture)) + return false; + } + return true; + } + + Type lower = lowerBound(s); + if (s != lower) + return isSubtype(capture ? capture(t) : t, lower, false); + + return isSubtype.visit(capture ? capture(t) : t, s); + } + // where + private TypeRelation isSubtype = new TypeRelation() + { + public Boolean visitType(Type t, Type s) { + switch (t.tag) { + case BYTE: case CHAR: + return (t.tag == s.tag || + t.tag + 2 <= s.tag && s.tag <= DOUBLE); + case SHORT: case INT: case LONG: case FLOAT: case DOUBLE: + return t.tag <= s.tag && s.tag <= DOUBLE; + case BOOLEAN: case VOID: + return t.tag == s.tag; + case TYPEVAR: + return isSubtypeNoCapture(t.getUpperBound(), s); + case BOT: + return + s.tag == BOT || s.tag == CLASS || + s.tag == ARRAY || s.tag == TYPEVAR; + case WILDCARD: //we shouldn't be here - avoids crash (see 7034495) + case NONE: + return false; + default: + throw new AssertionError("isSubtype " + t.tag); + } + } + + private Set cache = new HashSet(); + + private boolean containsTypeRecursive(Type t, Type s) { + TypePair pair = new TypePair(t, s); + if (cache.add(pair)) { + try { + return containsType(t.getTypeArguments(), + s.getTypeArguments()); + } finally { + cache.remove(pair); + } + } else { + return containsType(t.getTypeArguments(), + rewriteSupers(s).getTypeArguments()); + } + } + + private Type rewriteSupers(Type t) { + if (!t.isParameterized()) + return t; + ListBuffer from = lb(); + ListBuffer to = lb(); + adaptSelf(t, from, to); + if (from.isEmpty()) + return t; + ListBuffer rewrite = lb(); + boolean changed = false; + for (Type orig : to.toList()) { + Type s = rewriteSupers(orig); + if (s.isSuperBound() && !s.isExtendsBound()) { + s = new WildcardType(syms.objectType, + BoundKind.UNBOUND, + syms.boundClass); + changed = true; + } else if (s != orig) { + s = new WildcardType(upperBound(s), + BoundKind.EXTENDS, + syms.boundClass); + changed = true; + } + rewrite.append(s); + } + if (changed) + return subst(t.tsym.type, from.toList(), rewrite.toList()); + else + return t; + } + + @Override + public Boolean visitClassType(ClassType t, Type s) { + Type sup = asSuper(t, s.tsym); + return sup != null + && sup.tsym == s.tsym + // You're not allowed to write + // Vector vec = new Vector(); + // But with wildcards you can write + // Vector vec = new Vector(); + // which means that subtype checking must be done + // here instead of same-type checking (via containsType). + && (!s.isParameterized() || containsTypeRecursive(s, sup)) + && isSubtypeNoCapture(sup.getEnclosingType(), + s.getEnclosingType()); + } + + @Override + public Boolean visitArrayType(ArrayType t, Type s) { + if (s.tag == ARRAY) { + if (t.elemtype.tag <= lastBaseTag) + return isSameType(t.elemtype, elemtype(s)); + else + return isSubtypeNoCapture(t.elemtype, elemtype(s)); + } + + if (s.tag == CLASS) { + Name sname = s.tsym.getQualifiedName(); + return sname == names.java_lang_Object + || sname == names.java_lang_Cloneable + || sname == names.java_io_Serializable; + } + + return false; + } + + @Override + public Boolean visitUndetVar(UndetVar t, Type s) { + //todo: test against origin needed? or replace with substitution? + if (t == s || t.qtype == s || s.tag == ERROR || s.tag == UNKNOWN) + return true; + + if (t.inst != null) + return isSubtypeNoCapture(t.inst, s); // TODO: ", warn"? + + t.hibounds = t.hibounds.prepend(s); + return true; + } + + @Override + public Boolean visitErrorType(ErrorType t, Type s) { + return true; + } + }; + + /** + * Is t a subtype of every type in given list `ts'?
+ * (not defined for Method and ForAll types)
+ * Allows unchecked conversions. + */ + public boolean isSubtypeUnchecked(Type t, List ts, Warner warn) { + for (List l = ts; l.nonEmpty(); l = l.tail) + if (!isSubtypeUnchecked(t, l.head, warn)) + return false; + return true; + } + + /** + * Are corresponding elements of ts subtypes of ss? If lists are + * of different length, return false. + */ + public boolean isSubtypes(List ts, List ss) { + while (ts.tail != null && ss.tail != null + /*inlined: ts.nonEmpty() && ss.nonEmpty()*/ && + isSubtype(ts.head, ss.head)) { + ts = ts.tail; + ss = ss.tail; + } + return ts.tail == null && ss.tail == null; + /*inlined: ts.isEmpty() && ss.isEmpty();*/ + } + + /** + * Are corresponding elements of ts subtypes of ss, allowing + * unchecked conversions? If lists are of different length, + * return false. + **/ + public boolean isSubtypesUnchecked(List ts, List ss, Warner warn) { + while (ts.tail != null && ss.tail != null + /*inlined: ts.nonEmpty() && ss.nonEmpty()*/ && + isSubtypeUnchecked(ts.head, ss.head, warn)) { + ts = ts.tail; + ss = ss.tail; + } + return ts.tail == null && ss.tail == null; + /*inlined: ts.isEmpty() && ss.isEmpty();*/ + } + // + + // + /** + * Is t a supertype of s? + */ + public boolean isSuperType(Type t, Type s) { + switch (t.tag) { + case ERROR: + return true; + case UNDETVAR: { + UndetVar undet = (UndetVar)t; + if (t == s || + undet.qtype == s || + s.tag == ERROR || + s.tag == BOT) return true; + if (undet.inst != null) + return isSubtype(s, undet.inst); + undet.lobounds = undet.lobounds.prepend(s); + return true; + } + default: + return isSubtype(s, t); + } + } + // + + // + /** + * Are corresponding elements of the lists the same type? If + * lists are of different length, return false. + */ + public boolean isSameTypes(List ts, List ss) { + while (ts.tail != null && ss.tail != null + /*inlined: ts.nonEmpty() && ss.nonEmpty()*/ && + isSameType(ts.head, ss.head)) { + ts = ts.tail; + ss = ss.tail; + } + return ts.tail == null && ss.tail == null; + /*inlined: ts.isEmpty() && ss.isEmpty();*/ + } + + /** + * Is t the same type as s? + */ + public boolean isSameType(Type t, Type s) { + return isSameType.visit(t, s); + } + // where + private TypeRelation isSameType = new TypeRelation() { + + public Boolean visitType(Type t, Type s) { + if (t == s) + return true; + + if (s.tag >= firstPartialTag) + return visit(s, t); + + switch (t.tag) { + case BYTE: case CHAR: case SHORT: case INT: case LONG: case FLOAT: + case DOUBLE: case BOOLEAN: case VOID: case BOT: case NONE: + return t.tag == s.tag; + case TYPEVAR: { + if (s.tag == TYPEVAR) { + //type-substitution does not preserve type-var types + //check that type var symbols and bounds are indeed the same + return t.tsym == s.tsym && + visit(t.getUpperBound(), s.getUpperBound()); + } + else { + //special case for s == ? super X, where upper(s) = u + //check that u == t, where u has been set by Type.withTypeVar + return s.isSuperBound() && + !s.isExtendsBound() && + visit(t, upperBound(s)); + } + } + default: + throw new AssertionError("isSameType " + t.tag); + } + } + + @Override + public Boolean visitWildcardType(WildcardType t, Type s) { + if (s.tag >= firstPartialTag) + return visit(s, t); + else + return false; + } + + @Override + public Boolean visitClassType(ClassType t, Type s) { + if (t == s) + return true; + + if (s.tag >= firstPartialTag) + return visit(s, t); + + if (s.isSuperBound() && !s.isExtendsBound()) + return visit(t, upperBound(s)) && visit(t, lowerBound(s)); + + if (t.isCompound() && s.isCompound()) { + if (!visit(supertype(t), supertype(s))) + return false; + + HashSet set = new HashSet(); + for (Type x : interfaces(t)) + set.add(new SingletonType(x)); + for (Type x : interfaces(s)) { + if (!set.remove(new SingletonType(x))) + return false; + } + return (set.isEmpty()); + } + return t.tsym == s.tsym + && visit(t.getEnclosingType(), s.getEnclosingType()) + && containsTypeEquivalent(t.getTypeArguments(), s.getTypeArguments()); + } + + @Override + public Boolean visitArrayType(ArrayType t, Type s) { + if (t == s) + return true; + + if (s.tag >= firstPartialTag) + return visit(s, t); + + return s.tag == ARRAY + && containsTypeEquivalent(t.elemtype, elemtype(s)); + } + + @Override + public Boolean visitMethodType(MethodType t, Type s) { + // isSameType for methods does not take thrown + // exceptions into account! + return hasSameArgs(t, s) && visit(t.getReturnType(), s.getReturnType()); + } + + @Override + public Boolean visitPackageType(PackageType t, Type s) { + return t == s; + } + + @Override + public Boolean visitForAll(ForAll t, Type s) { + if (s.tag != FORALL) + return false; + + ForAll forAll = (ForAll)s; + return hasSameBounds(t, forAll) + && visit(t.qtype, subst(forAll.qtype, forAll.tvars, t.tvars)); + } + + @Override + public Boolean visitUndetVar(UndetVar t, Type s) { + if (s.tag == WILDCARD) + // FIXME, this might be leftovers from before capture conversion + return false; + + if (t == s || t.qtype == s || s.tag == ERROR || s.tag == UNKNOWN) + return true; + + if (t.inst != null) + return visit(t.inst, s); + + t.inst = fromUnknownFun.apply(s); + for (List l = t.lobounds; l.nonEmpty(); l = l.tail) { + if (!isSubtype(l.head, t.inst)) + return false; + } + for (List l = t.hibounds; l.nonEmpty(); l = l.tail) { + if (!isSubtype(t.inst, l.head)) + return false; + } + return true; + } + + @Override + public Boolean visitErrorType(ErrorType t, Type s) { + return true; + } + }; + // + + // + /** + * A mapping that turns all unknown types in this type to fresh + * unknown variables. + */ + public Mapping fromUnknownFun = new Mapping("fromUnknownFun") { + public Type apply(Type t) { + if (t.tag == UNKNOWN) return new UndetVar(t); + else return t.map(this); + } + }; + // + + // + public boolean containedBy(Type t, Type s) { + switch (t.tag) { + case UNDETVAR: + if (s.tag == WILDCARD) { + UndetVar undetvar = (UndetVar)t; + WildcardType wt = (WildcardType)s; + switch(wt.kind) { + case UNBOUND: //similar to ? extends Object + case EXTENDS: { + Type bound = upperBound(s); + // We should check the new upper bound against any of the + // undetvar's lower bounds. + for (Type t2 : undetvar.lobounds) { + if (!isSubtype(t2, bound)) + return false; + } + undetvar.hibounds = undetvar.hibounds.prepend(bound); + break; + } + case SUPER: { + Type bound = lowerBound(s); + // We should check the new lower bound against any of the + // undetvar's lower bounds. + for (Type t2 : undetvar.hibounds) { + if (!isSubtype(bound, t2)) + return false; + } + undetvar.lobounds = undetvar.lobounds.prepend(bound); + break; + } + } + return true; + } else { + return isSameType(t, s); + } + case ERROR: + return true; + default: + return containsType(s, t); + } + } + + boolean containsType(List ts, List ss) { + while (ts.nonEmpty() && ss.nonEmpty() + && containsType(ts.head, ss.head)) { + ts = ts.tail; + ss = ss.tail; + } + return ts.isEmpty() && ss.isEmpty(); + } + + /** + * Check if t contains s. + * + *

T contains S if: + * + *

{@code L(T) <: L(S) && U(S) <: U(T)} + * + *

This relation is only used by ClassType.isSubtype(), that + * is, + * + *

{@code C <: C if T contains S.} + * + *

Because of F-bounds, this relation can lead to infinite + * recursion. Thus we must somehow break that recursion. Notice + * that containsType() is only called from ClassType.isSubtype(). + * Since the arguments have already been checked against their + * bounds, we know: + * + *

{@code U(S) <: U(T) if T is "super" bound (U(T) *is* the bound)} + * + *

{@code L(T) <: L(S) if T is "extends" bound (L(T) is bottom)} + * + * @param t a type + * @param s a type + */ + public boolean containsType(Type t, Type s) { + return containsType.visit(t, s); + } + // where + private TypeRelation containsType = new TypeRelation() { + + private Type U(Type t) { + while (t.tag == WILDCARD) { + WildcardType w = (WildcardType)t; + if (w.isSuperBound()) + return w.bound == null ? syms.objectType : w.bound.bound; + else + t = w.type; + } + return t; + } + + private Type L(Type t) { + while (t.tag == WILDCARD) { + WildcardType w = (WildcardType)t; + if (w.isExtendsBound()) + return syms.botType; + else + t = w.type; + } + return t; + } + + public Boolean visitType(Type t, Type s) { + if (s.tag >= firstPartialTag) + return containedBy(s, t); + else + return isSameType(t, s); + } + +// void debugContainsType(WildcardType t, Type s) { +// System.err.println(); +// System.err.format(" does %s contain %s?%n", t, s); +// System.err.format(" %s U(%s) <: U(%s) %s = %s%n", +// upperBound(s), s, t, U(t), +// t.isSuperBound() +// || isSubtypeNoCapture(upperBound(s), U(t))); +// System.err.format(" %s L(%s) <: L(%s) %s = %s%n", +// L(t), t, s, lowerBound(s), +// t.isExtendsBound() +// || isSubtypeNoCapture(L(t), lowerBound(s))); +// System.err.println(); +// } + + @Override + public Boolean visitWildcardType(WildcardType t, Type s) { + if (s.tag >= firstPartialTag) + return containedBy(s, t); + else { +// debugContainsType(t, s); + return isSameWildcard(t, s) + || isCaptureOf(s, t) + || ((t.isExtendsBound() || isSubtypeNoCapture(L(t), lowerBound(s))) && + (t.isSuperBound() || isSubtypeNoCapture(upperBound(s), U(t)))); + } + } + + @Override + public Boolean visitUndetVar(UndetVar t, Type s) { + if (s.tag != WILDCARD) + return isSameType(t, s); + else + return false; + } + + @Override + public Boolean visitErrorType(ErrorType t, Type s) { + return true; + } + }; + + public boolean isCaptureOf(Type s, WildcardType t) { + if (s.tag != TYPEVAR || !((TypeVar)s).isCaptured()) + return false; + return isSameWildcard(t, ((CapturedType)s).wildcard); + } + + public boolean isSameWildcard(WildcardType t, Type s) { + if (s.tag != WILDCARD) + return false; + WildcardType w = (WildcardType)s; + return w.kind == t.kind && w.type == t.type; + } + + public boolean containsTypeEquivalent(List ts, List ss) { + while (ts.nonEmpty() && ss.nonEmpty() + && containsTypeEquivalent(ts.head, ss.head)) { + ts = ts.tail; + ss = ss.tail; + } + return ts.isEmpty() && ss.isEmpty(); + } + // + + // + public boolean isCastable(Type t, Type s) { + return isCastable(t, s, Warner.noWarnings); + } + + /** + * Is t is castable to s?
+ * s is assumed to be an erased type.
+ * (not defined for Method and ForAll types). + */ + public boolean isCastable(Type t, Type s, Warner warn) { + if (t == s) + return true; + + if (t.isPrimitive() != s.isPrimitive()) + return allowBoxing && ( + isConvertible(t, s, warn) + || (allowObjectToPrimitiveCast && + s.isPrimitive() && + isSubtype(boxedClass(s).type, t))); + if (warn != warnStack.head) { + try { + warnStack = warnStack.prepend(warn); + checkUnsafeVarargsConversion(t, s, warn); + return isCastable.visit(t,s); + } finally { + warnStack = warnStack.tail; + } + } else { + return isCastable.visit(t,s); + } + } + // where + private TypeRelation isCastable = new TypeRelation() { + + public Boolean visitType(Type t, Type s) { + if (s.tag == ERROR) + return true; + + switch (t.tag) { + case BYTE: case CHAR: case SHORT: case INT: case LONG: case FLOAT: + case DOUBLE: + return s.tag <= DOUBLE; + case BOOLEAN: + return s.tag == BOOLEAN; + case VOID: + return false; + case BOT: + return isSubtype(t, s); + default: + throw new AssertionError(); + } + } + + @Override + public Boolean visitWildcardType(WildcardType t, Type s) { + return isCastable(upperBound(t), s, warnStack.head); + } + + @Override + public Boolean visitClassType(ClassType t, Type s) { + if (s.tag == ERROR || s.tag == BOT) + return true; + + if (s.tag == TYPEVAR) { + if (isCastable(t, s.getUpperBound(), Warner.noWarnings)) { + warnStack.head.warn(LintCategory.UNCHECKED); + return true; + } else { + return false; + } + } + + if (t.isCompound()) { + Warner oldWarner = warnStack.head; + warnStack.head = Warner.noWarnings; + if (!visit(supertype(t), s)) + return false; + for (Type intf : interfaces(t)) { + if (!visit(intf, s)) + return false; + } + if (warnStack.head.hasLint(LintCategory.UNCHECKED)) + oldWarner.warn(LintCategory.UNCHECKED); + return true; + } + + if (s.isCompound()) { + // call recursively to reuse the above code + return visitClassType((ClassType)s, t); + } + + if (s.tag == CLASS || s.tag == ARRAY) { + boolean upcast; + if ((upcast = isSubtype(erasure(t), erasure(s))) + || isSubtype(erasure(s), erasure(t))) { + if (!upcast && s.tag == ARRAY) { + if (!isReifiable(s)) + warnStack.head.warn(LintCategory.UNCHECKED); + return true; + } else if (s.isRaw()) { + return true; + } else if (t.isRaw()) { + if (!isUnbounded(s)) + warnStack.head.warn(LintCategory.UNCHECKED); + return true; + } + // Assume |a| <: |b| + final Type a = upcast ? t : s; + final Type b = upcast ? s : t; + final boolean HIGH = true; + final boolean LOW = false; + final boolean DONT_REWRITE_TYPEVARS = false; + Type aHigh = rewriteQuantifiers(a, HIGH, DONT_REWRITE_TYPEVARS); + Type aLow = rewriteQuantifiers(a, LOW, DONT_REWRITE_TYPEVARS); + Type bHigh = rewriteQuantifiers(b, HIGH, DONT_REWRITE_TYPEVARS); + Type bLow = rewriteQuantifiers(b, LOW, DONT_REWRITE_TYPEVARS); + Type lowSub = asSub(bLow, aLow.tsym); + Type highSub = (lowSub == null) ? null : asSub(bHigh, aHigh.tsym); + if (highSub == null) { + final boolean REWRITE_TYPEVARS = true; + aHigh = rewriteQuantifiers(a, HIGH, REWRITE_TYPEVARS); + aLow = rewriteQuantifiers(a, LOW, REWRITE_TYPEVARS); + bHigh = rewriteQuantifiers(b, HIGH, REWRITE_TYPEVARS); + bLow = rewriteQuantifiers(b, LOW, REWRITE_TYPEVARS); + lowSub = asSub(bLow, aLow.tsym); + highSub = (lowSub == null) ? null : asSub(bHigh, aHigh.tsym); + } + if (highSub != null) { + if (!(a.tsym == highSub.tsym && a.tsym == lowSub.tsym)) { + Assert.error(a.tsym + " != " + highSub.tsym + " != " + lowSub.tsym); + } + if (!disjointTypes(aHigh.allparams(), highSub.allparams()) + && !disjointTypes(aHigh.allparams(), lowSub.allparams()) + && !disjointTypes(aLow.allparams(), highSub.allparams()) + && !disjointTypes(aLow.allparams(), lowSub.allparams())) { + if (upcast ? giveWarning(a, b) : + giveWarning(b, a)) + warnStack.head.warn(LintCategory.UNCHECKED); + return true; + } + } + if (isReifiable(s)) + return isSubtypeUnchecked(a, b); + else + return isSubtypeUnchecked(a, b, warnStack.head); + } + + // Sidecast + if (s.tag == CLASS) { + if ((s.tsym.flags() & INTERFACE) != 0) { + return ((t.tsym.flags() & FINAL) == 0) + ? sideCast(t, s, warnStack.head) + : sideCastFinal(t, s, warnStack.head); + } else if ((t.tsym.flags() & INTERFACE) != 0) { + return ((s.tsym.flags() & FINAL) == 0) + ? sideCast(t, s, warnStack.head) + : sideCastFinal(t, s, warnStack.head); + } else { + // unrelated class types + return false; + } + } + } + return false; + } + + @Override + public Boolean visitArrayType(ArrayType t, Type s) { + switch (s.tag) { + case ERROR: + case BOT: + return true; + case TYPEVAR: + if (isCastable(s, t, Warner.noWarnings)) { + warnStack.head.warn(LintCategory.UNCHECKED); + return true; + } else { + return false; + } + case CLASS: + return isSubtype(t, s); + case ARRAY: + if (elemtype(t).tag <= lastBaseTag || + elemtype(s).tag <= lastBaseTag) { + return elemtype(t).tag == elemtype(s).tag; + } else { + return visit(elemtype(t), elemtype(s)); + } + default: + return false; + } + } + + @Override + public Boolean visitTypeVar(TypeVar t, Type s) { + switch (s.tag) { + case ERROR: + case BOT: + return true; + case TYPEVAR: + if (isSubtype(t, s)) { + return true; + } else if (isCastable(t.bound, s, Warner.noWarnings)) { + warnStack.head.warn(LintCategory.UNCHECKED); + return true; + } else { + return false; + } + default: + return isCastable(t.bound, s, warnStack.head); + } + } + + @Override + public Boolean visitErrorType(ErrorType t, Type s) { + return true; + } + }; + //
+ + // + public boolean disjointTypes(List ts, List ss) { + while (ts.tail != null && ss.tail != null) { + if (disjointType(ts.head, ss.head)) return true; + ts = ts.tail; + ss = ss.tail; + } + return false; + } + + /** + * Two types or wildcards are considered disjoint if it can be + * proven that no type can be contained in both. It is + * conservative in that it is allowed to say that two types are + * not disjoint, even though they actually are. + * + * The type C is castable to C exactly if X and Y are not + * disjoint. + */ + public boolean disjointType(Type t, Type s) { + return disjointType.visit(t, s); + } + // where + private TypeRelation disjointType = new TypeRelation() { + + private Set cache = new HashSet(); + + public Boolean visitType(Type t, Type s) { + if (s.tag == WILDCARD) + return visit(s, t); + else + return notSoftSubtypeRecursive(t, s) || notSoftSubtypeRecursive(s, t); + } + + private boolean isCastableRecursive(Type t, Type s) { + TypePair pair = new TypePair(t, s); + if (cache.add(pair)) { + try { + return Types.this.isCastable(t, s); + } finally { + cache.remove(pair); + } + } else { + return true; + } + } + + private boolean notSoftSubtypeRecursive(Type t, Type s) { + TypePair pair = new TypePair(t, s); + if (cache.add(pair)) { + try { + return Types.this.notSoftSubtype(t, s); + } finally { + cache.remove(pair); + } + } else { + return false; + } + } + + @Override + public Boolean visitWildcardType(WildcardType t, Type s) { + if (t.isUnbound()) + return false; + + if (s.tag != WILDCARD) { + if (t.isExtendsBound()) + return notSoftSubtypeRecursive(s, t.type); + else // isSuperBound() + return notSoftSubtypeRecursive(t.type, s); + } + + if (s.isUnbound()) + return false; + + if (t.isExtendsBound()) { + if (s.isExtendsBound()) + return !isCastableRecursive(t.type, upperBound(s)); + else if (s.isSuperBound()) + return notSoftSubtypeRecursive(lowerBound(s), t.type); + } else if (t.isSuperBound()) { + if (s.isExtendsBound()) + return notSoftSubtypeRecursive(t.type, upperBound(s)); + } + return false; + } + }; + // + + // + /** + * Returns the lower bounds of the formals of a method. + */ + public List lowerBoundArgtypes(Type t) { + return map(t.getParameterTypes(), lowerBoundMapping); + } + private final Mapping lowerBoundMapping = new Mapping("lowerBound") { + public Type apply(Type t) { + return lowerBound(t); + } + }; + // + + // + /** + * This relation answers the question: is impossible that + * something of type `t' can be a subtype of `s'? This is + * different from the question "is `t' not a subtype of `s'?" + * when type variables are involved: Integer is not a subtype of T + * where but it is not true that Integer cannot + * possibly be a subtype of T. + */ + public boolean notSoftSubtype(Type t, Type s) { + if (t == s) return false; + if (t.tag == TYPEVAR) { + TypeVar tv = (TypeVar) t; + return !isCastable(tv.bound, + relaxBound(s), + Warner.noWarnings); + } + if (s.tag != WILDCARD) + s = upperBound(s); + + return !isSubtype(t, relaxBound(s)); + } + + private Type relaxBound(Type t) { + if (t.tag == TYPEVAR) { + while (t.tag == TYPEVAR) + t = t.getUpperBound(); + t = rewriteQuantifiers(t, true, true); + } + return t; + } + // + + // + public boolean isReifiable(Type t) { + return isReifiable.visit(t); + } + // where + private UnaryVisitor isReifiable = new UnaryVisitor() { + + public Boolean visitType(Type t, Void ignored) { + return true; + } + + @Override + public Boolean visitClassType(ClassType t, Void ignored) { + if (t.isCompound()) + return false; + else { + if (!t.isParameterized()) + return true; + + for (Type param : t.allparams()) { + if (!param.isUnbound()) + return false; + } + return true; + } + } + + @Override + public Boolean visitArrayType(ArrayType t, Void ignored) { + return visit(t.elemtype); + } + + @Override + public Boolean visitTypeVar(TypeVar t, Void ignored) { + return false; + } + }; + // + + // + public boolean isArray(Type t) { + while (t.tag == WILDCARD) + t = upperBound(t); + return t.tag == ARRAY; + } + + /** + * The element type of an array. + */ + public Type elemtype(Type t) { + switch (t.tag) { + case WILDCARD: + return elemtype(upperBound(t)); + case ARRAY: + return ((ArrayType)t).elemtype; + case FORALL: + return elemtype(((ForAll)t).qtype); + case ERROR: + return t; + default: + return null; + } + } + + public Type elemtypeOrType(Type t) { + Type elemtype = elemtype(t); + return elemtype != null ? + elemtype : + t; + } + + /** + * Mapping to take element type of an arraytype + */ + private Mapping elemTypeFun = new Mapping ("elemTypeFun") { + public Type apply(Type t) { return elemtype(t); } + }; + + /** + * The number of dimensions of an array type. + */ + public int dimensions(Type t) { + int result = 0; + while (t.tag == ARRAY) { + result++; + t = elemtype(t); + } + return result; + } + // + + // + /** + * Return the (most specific) base type of t that starts with the + * given symbol. If none exists, return null. + * + * @param t a type + * @param sym a symbol + */ + public Type asSuper(Type t, Symbol sym) { + return asSuper.visit(t, sym); + } + // where + private SimpleVisitor asSuper = new SimpleVisitor() { + + public Type visitType(Type t, Symbol sym) { + return null; + } + + @Override + public Type visitClassType(ClassType t, Symbol sym) { + if (t.tsym == sym) + return t; + + Type st = supertype(t); + if (st.tag == CLASS || st.tag == TYPEVAR || st.tag == ERROR) { + Type x = asSuper(st, sym); + if (x != null) + return x; + } + if ((sym.flags() & INTERFACE) != 0) { + for (List l = interfaces(t); l.nonEmpty(); l = l.tail) { + Type x = asSuper(l.head, sym); + if (x != null) + return x; + } + } + return null; + } + + @Override + public Type visitArrayType(ArrayType t, Symbol sym) { + return isSubtype(t, sym.type) ? sym.type : null; + } + + @Override + public Type visitTypeVar(TypeVar t, Symbol sym) { + if (t.tsym == sym) + return t; + else + return asSuper(t.bound, sym); + } + + @Override + public Type visitErrorType(ErrorType t, Symbol sym) { + return t; + } + }; + + /** + * Return the base type of t or any of its outer types that starts + * with the given symbol. If none exists, return null. + * + * @param t a type + * @param sym a symbol + */ + public Type asOuterSuper(Type t, Symbol sym) { + switch (t.tag) { + case CLASS: + do { + Type s = asSuper(t, sym); + if (s != null) return s; + t = t.getEnclosingType(); + } while (t.tag == CLASS); + return null; + case ARRAY: + return isSubtype(t, sym.type) ? sym.type : null; + case TYPEVAR: + return asSuper(t, sym); + case ERROR: + return t; + default: + return null; + } + } + + /** + * Return the base type of t or any of its enclosing types that + * starts with the given symbol. If none exists, return null. + * + * @param t a type + * @param sym a symbol + */ + public Type asEnclosingSuper(Type t, Symbol sym) { + switch (t.tag) { + case CLASS: + do { + Type s = asSuper(t, sym); + if (s != null) return s; + Type outer = t.getEnclosingType(); + t = (outer.tag == CLASS) ? outer : + (t.tsym.owner.enclClass() != null) ? t.tsym.owner.enclClass().type : + Type.noType; + } while (t.tag == CLASS); + return null; + case ARRAY: + return isSubtype(t, sym.type) ? sym.type : null; + case TYPEVAR: + return asSuper(t, sym); + case ERROR: + return t; + default: + return null; + } + } + // + + // + /** + * The type of given symbol, seen as a member of t. + * + * @param t a type + * @param sym a symbol + */ + public Type memberType(Type t, Symbol sym) { + return (sym.flags() & STATIC) != 0 + ? sym.type + : memberType.visit(t, sym); + } + // where + private SimpleVisitor memberType = new SimpleVisitor() { + + public Type visitType(Type t, Symbol sym) { + return sym.type; + } + + @Override + public Type visitWildcardType(WildcardType t, Symbol sym) { + return memberType(upperBound(t), sym); + } + + @Override + public Type visitClassType(ClassType t, Symbol sym) { + Symbol owner = sym.owner; + long flags = sym.flags(); + if (((flags & STATIC) == 0) && owner.type.isParameterized()) { + Type base = asOuterSuper(t, owner); + //if t is an intersection type T = CT & I1 & I2 ... & In + //its supertypes CT, I1, ... In might contain wildcards + //so we need to go through capture conversion + base = t.isCompound() ? capture(base) : base; + if (base != null) { + List ownerParams = owner.type.allparams(); + List baseParams = base.allparams(); + if (ownerParams.nonEmpty()) { + if (baseParams.isEmpty()) { + // then base is a raw type + return erasure(sym.type); + } else { + return subst(sym.type, ownerParams, baseParams); + } + } + } + } + return sym.type; + } + + @Override + public Type visitTypeVar(TypeVar t, Symbol sym) { + return memberType(t.bound, sym); + } + + @Override + public Type visitErrorType(ErrorType t, Symbol sym) { + return t; + } + }; + // + + // + public boolean isAssignable(Type t, Type s) { + return isAssignable(t, s, Warner.noWarnings); + } + + /** + * Is t assignable to s?
+ * Equivalent to subtype except for constant values and raw + * types.
+ * (not defined for Method and ForAll types) + */ + public boolean isAssignable(Type t, Type s, Warner warn) { + if (t.tag == ERROR) + return true; + if (t.tag <= INT && t.constValue() != null) { + int value = ((Number)t.constValue()).intValue(); + switch (s.tag) { + case BYTE: + if (Byte.MIN_VALUE <= value && value <= Byte.MAX_VALUE) + return true; + break; + case CHAR: + if (Character.MIN_VALUE <= value && value <= Character.MAX_VALUE) + return true; + break; + case SHORT: + if (Short.MIN_VALUE <= value && value <= Short.MAX_VALUE) + return true; + break; + case INT: + return true; + case CLASS: + switch (unboxedType(s).tag) { + case BYTE: + case CHAR: + case SHORT: + return isAssignable(t, unboxedType(s), warn); + } + break; + } + } + return isConvertible(t, s, warn); + } + //
+ + // + /** + * The erasure of t {@code |t|} -- the type that results when all + * type parameters in t are deleted. + */ + public Type erasure(Type t) { + return erasure(t, false); + } + //where + private Type erasure(Type t, boolean recurse) { + if (t.tag <= lastBaseTag) + return t; /* fast special case */ + else + return erasure.visit(t, recurse); + } + // where + private SimpleVisitor erasure = new SimpleVisitor() { + public Type visitType(Type t, Boolean recurse) { + if (t.tag <= lastBaseTag) + return t; /*fast special case*/ + else + return t.map(recurse ? erasureRecFun : erasureFun); + } + + @Override + public Type visitWildcardType(WildcardType t, Boolean recurse) { + return erasure(upperBound(t), recurse); + } + + @Override + public Type visitClassType(ClassType t, Boolean recurse) { + Type erased = t.tsym.erasure(Types.this); + if (recurse) { + erased = new ErasedClassType(erased.getEnclosingType(),erased.tsym); + } + return erased; + } + + @Override + public Type visitTypeVar(TypeVar t, Boolean recurse) { + return erasure(t.bound, recurse); + } + + @Override + public Type visitErrorType(ErrorType t, Boolean recurse) { + return t; + } + }; + + private Mapping erasureFun = new Mapping ("erasure") { + public Type apply(Type t) { return erasure(t); } + }; + + private Mapping erasureRecFun = new Mapping ("erasureRecursive") { + public Type apply(Type t) { return erasureRecursive(t); } + }; + + public List erasure(List ts) { + return Type.map(ts, erasureFun); + } + + public Type erasureRecursive(Type t) { + return erasure(t, true); + } + + public List erasureRecursive(List ts) { + return Type.map(ts, erasureRecFun); + } + // + + // + /** + * Make a compound type from non-empty list of types + * + * @param bounds the types from which the compound type is formed + * @param supertype is objectType if all bounds are interfaces, + * null otherwise. + */ + public Type makeCompoundType(List bounds, + Type supertype) { + ClassSymbol bc = + new ClassSymbol(ABSTRACT|PUBLIC|SYNTHETIC|COMPOUND|ACYCLIC, + Type.moreInfo + ? names.fromString(bounds.toString()) + : names.empty, + syms.noSymbol); + if (bounds.head.tag == TYPEVAR) + // error condition, recover + bc.erasure_field = syms.objectType; + else + bc.erasure_field = erasure(bounds.head); + bc.members_field = new Scope(bc); + ClassType bt = (ClassType)bc.type; + bt.allparams_field = List.nil(); + if (supertype != null) { + bt.supertype_field = supertype; + bt.interfaces_field = bounds; + } else { + bt.supertype_field = bounds.head; + bt.interfaces_field = bounds.tail; + } + Assert.check(bt.supertype_field.tsym.completer != null + || !bt.supertype_field.isInterface(), + bt.supertype_field); + return bt; + } + + /** + * Same as {@link #makeCompoundType(List,Type)}, except that the + * second parameter is computed directly. Note that this might + * cause a symbol completion. Hence, this version of + * makeCompoundType may not be called during a classfile read. + */ + public Type makeCompoundType(List bounds) { + Type supertype = (bounds.head.tsym.flags() & INTERFACE) != 0 ? + supertype(bounds.head) : null; + return makeCompoundType(bounds, supertype); + } + + /** + * A convenience wrapper for {@link #makeCompoundType(List)}; the + * arguments are converted to a list and passed to the other + * method. Note that this might cause a symbol completion. + * Hence, this version of makeCompoundType may not be called + * during a classfile read. + */ + public Type makeCompoundType(Type bound1, Type bound2) { + return makeCompoundType(List.of(bound1, bound2)); + } + // + + // + public Type supertype(Type t) { + return supertype.visit(t); + } + // where + private UnaryVisitor supertype = new UnaryVisitor() { + + public Type visitType(Type t, Void ignored) { + // A note on wildcards: there is no good way to + // determine a supertype for a super bounded wildcard. + return null; + } + + @Override + public Type visitClassType(ClassType t, Void ignored) { + if (t.supertype_field == null) { + Type supertype = ((ClassSymbol)t.tsym).getSuperclass(); + // An interface has no superclass; its supertype is Object. + if (t.isInterface()) + supertype = ((ClassType)t.tsym.type).supertype_field; + if (t.supertype_field == null) { + List actuals = classBound(t).allparams(); + List formals = t.tsym.type.allparams(); + if (t.hasErasedSupertypes()) { + t.supertype_field = erasureRecursive(supertype); + } else if (formals.nonEmpty()) { + t.supertype_field = subst(supertype, formals, actuals); + } + else { + t.supertype_field = supertype; + } + } + } + return t.supertype_field; + } + + /** + * The supertype is always a class type. If the type + * variable's bounds start with a class type, this is also + * the supertype. Otherwise, the supertype is + * java.lang.Object. + */ + @Override + public Type visitTypeVar(TypeVar t, Void ignored) { + if (t.bound.tag == TYPEVAR || + (!t.bound.isCompound() && !t.bound.isInterface())) { + return t.bound; + } else { + return supertype(t.bound); + } + } + + @Override + public Type visitArrayType(ArrayType t, Void ignored) { + if (t.elemtype.isPrimitive() || isSameType(t.elemtype, syms.objectType)) + return arraySuperType(); + else + return new ArrayType(supertype(t.elemtype), t.tsym); + } + + @Override + public Type visitErrorType(ErrorType t, Void ignored) { + return t; + } + }; + // + + // + /** + * Return the interfaces implemented by this class. + */ + public List interfaces(Type t) { + return interfaces.visit(t); + } + // where + private UnaryVisitor> interfaces = new UnaryVisitor>() { + + public List visitType(Type t, Void ignored) { + return List.nil(); + } + + @Override + public List visitClassType(ClassType t, Void ignored) { + if (t.interfaces_field == null) { + List interfaces = ((ClassSymbol)t.tsym).getInterfaces(); + if (t.interfaces_field == null) { + // If t.interfaces_field is null, then t must + // be a parameterized type (not to be confused + // with a generic type declaration). + // Terminology: + // Parameterized type: List + // Generic type declaration: class List { ... } + // So t corresponds to List and + // t.tsym.type corresponds to List. + // The reason t must be parameterized type is + // that completion will happen as a side + // effect of calling + // ClassSymbol.getInterfaces. Since + // t.interfaces_field is null after + // completion, we can assume that t is not the + // type of a class/interface declaration. + Assert.check(t != t.tsym.type, t); + List actuals = t.allparams(); + List formals = t.tsym.type.allparams(); + if (t.hasErasedSupertypes()) { + t.interfaces_field = erasureRecursive(interfaces); + } else if (formals.nonEmpty()) { + t.interfaces_field = + upperBounds(subst(interfaces, formals, actuals)); + } + else { + t.interfaces_field = interfaces; + } + } + } + return t.interfaces_field; + } + + @Override + public List visitTypeVar(TypeVar t, Void ignored) { + if (t.bound.isCompound()) + return interfaces(t.bound); + + if (t.bound.isInterface()) + return List.of(t.bound); + + return List.nil(); + } + }; + // + + // + Map isDerivedRawCache = new HashMap(); + + public boolean isDerivedRaw(Type t) { + Boolean result = isDerivedRawCache.get(t); + if (result == null) { + result = isDerivedRawInternal(t); + isDerivedRawCache.put(t, result); + } + return result; + } + + public boolean isDerivedRawInternal(Type t) { + if (t.isErroneous()) + return false; + return + t.isRaw() || + supertype(t) != null && isDerivedRaw(supertype(t)) || + isDerivedRaw(interfaces(t)); + } + + public boolean isDerivedRaw(List ts) { + List l = ts; + while (l.nonEmpty() && !isDerivedRaw(l.head)) l = l.tail; + return l.nonEmpty(); + } + // + + // + /** + * Set the bounds field of the given type variable to reflect a + * (possibly multiple) list of bounds. + * @param t a type variable + * @param bounds the bounds, must be nonempty + * @param supertype is objectType if all bounds are interfaces, + * null otherwise. + */ + public void setBounds(TypeVar t, List bounds, Type supertype) { + if (bounds.tail.isEmpty()) + t.bound = bounds.head; + else + t.bound = makeCompoundType(bounds, supertype); + t.rank_field = -1; + } + + /** + * Same as {@link #setBounds(Type.TypeVar,List,Type)}, except that + * third parameter is computed directly, as follows: if all + * all bounds are interface types, the computed supertype is Object, + * otherwise the supertype is simply left null (in this case, the supertype + * is assumed to be the head of the bound list passed as second argument). + * Note that this check might cause a symbol completion. Hence, this version of + * setBounds may not be called during a classfile read. + */ + public void setBounds(TypeVar t, List bounds) { + Type supertype = (bounds.head.tsym.flags() & INTERFACE) != 0 ? + syms.objectType : null; + setBounds(t, bounds, supertype); + t.rank_field = -1; + } + // + + // + /** + * Return list of bounds of the given type variable. + */ + public List getBounds(TypeVar t) { + if (t.bound.isErroneous() || !t.bound.isCompound()) + return List.of(t.bound); + else if ((erasure(t).tsym.flags() & INTERFACE) == 0) + return interfaces(t).prepend(supertype(t)); + else + // No superclass was given in bounds. + // In this case, supertype is Object, erasure is first interface. + return interfaces(t); + } + // + + // + /** + * If the given type is a (possibly selected) type variable, + * return the bounding class of this type, otherwise return the + * type itself. + */ + public Type classBound(Type t) { + return classBound.visit(t); + } + // where + private UnaryVisitor classBound = new UnaryVisitor() { + + public Type visitType(Type t, Void ignored) { + return t; + } + + @Override + public Type visitClassType(ClassType t, Void ignored) { + Type outer1 = classBound(t.getEnclosingType()); + if (outer1 != t.getEnclosingType()) + return new ClassType(outer1, t.getTypeArguments(), t.tsym); + else + return t; + } + + @Override + public Type visitTypeVar(TypeVar t, Void ignored) { + return classBound(supertype(t)); + } + + @Override + public Type visitErrorType(ErrorType t, Void ignored) { + return t; + } + }; + // + + // + /** + * Returns true iff the first signature is a sub + * signature of the other. This is not an equivalence + * relation. + * + * @jls section 8.4.2. + * @see #overrideEquivalent(Type t, Type s) + * @param t first signature (possibly raw). + * @param s second signature (could be subjected to erasure). + * @return true if t is a sub signature of s. + */ + public boolean isSubSignature(Type t, Type s) { + return isSubSignature(t, s, true); + } + + public boolean isSubSignature(Type t, Type s, boolean strict) { + return hasSameArgs(t, s, strict) || hasSameArgs(t, erasure(s), strict); + } + + /** + * Returns true iff these signatures are related by override + * equivalence. This is the natural extension of + * isSubSignature to an equivalence relation. + * + * @jls section 8.4.2. + * @see #isSubSignature(Type t, Type s) + * @param t a signature (possible raw, could be subjected to + * erasure). + * @param s a signature (possible raw, could be subjected to + * erasure). + * @return true if either argument is a sub signature of the other. + */ + public boolean overrideEquivalent(Type t, Type s) { + return hasSameArgs(t, s) || + hasSameArgs(t, erasure(s)) || hasSameArgs(erasure(t), s); + } + + // + class ImplementationCache { + + private WeakHashMap>> _map = + new WeakHashMap>>(); + + class Entry { + final MethodSymbol cachedImpl; + final Filter implFilter; + final boolean checkResult; + final int prevMark; + + public Entry(MethodSymbol cachedImpl, + Filter scopeFilter, + boolean checkResult, + int prevMark) { + this.cachedImpl = cachedImpl; + this.implFilter = scopeFilter; + this.checkResult = checkResult; + this.prevMark = prevMark; + } + + boolean matches(Filter scopeFilter, boolean checkResult, int mark) { + return this.implFilter == scopeFilter && + this.checkResult == checkResult && + this.prevMark == mark; + } + } + + MethodSymbol get(MethodSymbol ms, TypeSymbol origin, boolean checkResult, Filter implFilter) { + SoftReference> ref_cache = _map.get(ms); + Map cache = ref_cache != null ? ref_cache.get() : null; + if (cache == null) { + cache = new HashMap(); + _map.put(ms, new SoftReference>(cache)); + } + Entry e = cache.get(origin); + CompoundScope members = membersClosure(origin.type); + if (e == null || + !e.matches(implFilter, checkResult, members.getMark())) { + MethodSymbol impl = implementationInternal(ms, origin, checkResult, implFilter); + cache.put(origin, new Entry(impl, implFilter, checkResult, members.getMark())); + return impl; + } + else { + return e.cachedImpl; + } + } + + private MethodSymbol implementationInternal(MethodSymbol ms, TypeSymbol origin, boolean checkResult, Filter implFilter) { + for (Type t = origin.type; t.tag == CLASS || t.tag == TYPEVAR; t = supertype(t)) { + while (t.tag == TYPEVAR) + t = t.getUpperBound(); + TypeSymbol c = t.tsym; + for (Scope.Entry e = c.members().lookup(ms.name, implFilter); + e.scope != null; + e = e.next(implFilter)) { + if (e.sym != null && + e.sym.overrides(ms, origin, Types.this, checkResult)) + return (MethodSymbol)e.sym; + } + } + return null; + } + } + + private ImplementationCache implCache = new ImplementationCache(); + + public MethodSymbol implementation(MethodSymbol ms, TypeSymbol origin, boolean checkResult, Filter implFilter) { + return implCache.get(ms, origin, checkResult, implFilter); + } + // + + // + public CompoundScope membersClosure(Type site) { + return membersClosure.visit(site); + } + + UnaryVisitor membersClosure = new UnaryVisitor() { + + public CompoundScope visitType(Type t, Void s) { + return null; + } + + @Override + public CompoundScope visitClassType(ClassType t, Void s) { + ClassSymbol csym = (ClassSymbol)t.tsym; + if (csym.membersClosure == null) { + CompoundScope membersClosure = new CompoundScope(csym); + for (Type i : interfaces(t)) { + membersClosure.addSubScope(visit(i)); + } + membersClosure.addSubScope(visit(supertype(t))); + membersClosure.addSubScope(csym.members()); + csym.membersClosure = membersClosure; + } + return csym.membersClosure; + } + + @Override + public CompoundScope visitTypeVar(TypeVar t, Void s) { + return visit(t.getUpperBound()); + } + }; + // + + /** + * Does t have the same arguments as s? It is assumed that both + * types are (possibly polymorphic) method types. Monomorphic + * method types "have the same arguments", if their argument lists + * are equal. Polymorphic method types "have the same arguments", + * if they have the same arguments after renaming all type + * variables of one to corresponding type variables in the other, + * where correspondence is by position in the type parameter list. + */ + public boolean hasSameArgs(Type t, Type s) { + return hasSameArgs(t, s, true); + } + + public boolean hasSameArgs(Type t, Type s, boolean strict) { + return hasSameArgs(t, s, strict ? hasSameArgs_strict : hasSameArgs_nonstrict); + } + + private boolean hasSameArgs(Type t, Type s, TypeRelation hasSameArgs) { + return hasSameArgs.visit(t, s); + } + // where + private class HasSameArgs extends TypeRelation { + + boolean strict; + + public HasSameArgs(boolean strict) { + this.strict = strict; + } + + public Boolean visitType(Type t, Type s) { + throw new AssertionError(); + } + + @Override + public Boolean visitMethodType(MethodType t, Type s) { + return s.tag == METHOD + && containsTypeEquivalent(t.argtypes, s.getParameterTypes()); + } + + @Override + public Boolean visitForAll(ForAll t, Type s) { + if (s.tag != FORALL) + return strict ? false : visitMethodType(t.asMethodType(), s); + + ForAll forAll = (ForAll)s; + return hasSameBounds(t, forAll) + && visit(t.qtype, subst(forAll.qtype, forAll.tvars, t.tvars)); + } + + @Override + public Boolean visitErrorType(ErrorType t, Type s) { + return false; + } + }; + + TypeRelation hasSameArgs_strict = new HasSameArgs(true); + TypeRelation hasSameArgs_nonstrict = new HasSameArgs(false); + + // + + // + public List subst(List ts, + List from, + List to) { + return new Subst(from, to).subst(ts); + } + + /** + * Substitute all occurrences of a type in `from' with the + * corresponding type in `to' in 't'. Match lists `from' and `to' + * from the right: If lists have different length, discard leading + * elements of the longer list. + */ + public Type subst(Type t, List from, List to) { + return new Subst(from, to).subst(t); + } + + private class Subst extends UnaryVisitor { + List from; + List to; + + public Subst(List from, List to) { + int fromLength = from.length(); + int toLength = to.length(); + while (fromLength > toLength) { + fromLength--; + from = from.tail; + } + while (fromLength < toLength) { + toLength--; + to = to.tail; + } + this.from = from; + this.to = to; + } + + Type subst(Type t) { + if (from.tail == null) + return t; + else + return visit(t); + } + + List subst(List ts) { + if (from.tail == null) + return ts; + boolean wild = false; + if (ts.nonEmpty() && from.nonEmpty()) { + Type head1 = subst(ts.head); + List tail1 = subst(ts.tail); + if (head1 != ts.head || tail1 != ts.tail) + return tail1.prepend(head1); + } + return ts; + } + + public Type visitType(Type t, Void ignored) { + return t; + } + + @Override + public Type visitMethodType(MethodType t, Void ignored) { + List argtypes = subst(t.argtypes); + Type restype = subst(t.restype); + List thrown = subst(t.thrown); + if (argtypes == t.argtypes && + restype == t.restype && + thrown == t.thrown) + return t; + else + return new MethodType(argtypes, restype, thrown, t.tsym); + } + + @Override + public Type visitTypeVar(TypeVar t, Void ignored) { + for (List from = this.from, to = this.to; + from.nonEmpty(); + from = from.tail, to = to.tail) { + if (t == from.head) { + return to.head.withTypeVar(t); + } + } + return t; + } + + @Override + public Type visitClassType(ClassType t, Void ignored) { + if (!t.isCompound()) { + List typarams = t.getTypeArguments(); + List typarams1 = subst(typarams); + Type outer = t.getEnclosingType(); + Type outer1 = subst(outer); + if (typarams1 == typarams && outer1 == outer) + return t; + else + return new ClassType(outer1, typarams1, t.tsym); + } else { + Type st = subst(supertype(t)); + List is = upperBounds(subst(interfaces(t))); + if (st == supertype(t) && is == interfaces(t)) + return t; + else + return makeCompoundType(is.prepend(st)); + } + } + + @Override + public Type visitWildcardType(WildcardType t, Void ignored) { + Type bound = t.type; + if (t.kind != BoundKind.UNBOUND) + bound = subst(bound); + if (bound == t.type) { + return t; + } else { + if (t.isExtendsBound() && bound.isExtendsBound()) + bound = upperBound(bound); + return new WildcardType(bound, t.kind, syms.boundClass, t.bound); + } + } + + @Override + public Type visitArrayType(ArrayType t, Void ignored) { + Type elemtype = subst(t.elemtype); + if (elemtype == t.elemtype) + return t; + else + return new ArrayType(upperBound(elemtype), t.tsym); + } + + @Override + public Type visitForAll(ForAll t, Void ignored) { + if (Type.containsAny(to, t.tvars)) { + //perform alpha-renaming of free-variables in 't' + //if 'to' types contain variables that are free in 't' + List freevars = newInstances(t.tvars); + t = new ForAll(freevars, + Types.this.subst(t.qtype, t.tvars, freevars)); + } + List tvars1 = substBounds(t.tvars, from, to); + Type qtype1 = subst(t.qtype); + if (tvars1 == t.tvars && qtype1 == t.qtype) { + return t; + } else if (tvars1 == t.tvars) { + return new ForAll(tvars1, qtype1); + } else { + return new ForAll(tvars1, Types.this.subst(qtype1, t.tvars, tvars1)); + } + } + + @Override + public Type visitErrorType(ErrorType t, Void ignored) { + return t; + } + } + + public List substBounds(List tvars, + List from, + List to) { + if (tvars.isEmpty()) + return tvars; + ListBuffer newBoundsBuf = lb(); + boolean changed = false; + // calculate new bounds + for (Type t : tvars) { + TypeVar tv = (TypeVar) t; + Type bound = subst(tv.bound, from, to); + if (bound != tv.bound) + changed = true; + newBoundsBuf.append(bound); + } + if (!changed) + return tvars; + ListBuffer newTvars = lb(); + // create new type variables without bounds + for (Type t : tvars) { + newTvars.append(new TypeVar(t.tsym, null, syms.botType)); + } + // the new bounds should use the new type variables in place + // of the old + List newBounds = newBoundsBuf.toList(); + from = tvars; + to = newTvars.toList(); + for (; !newBounds.isEmpty(); newBounds = newBounds.tail) { + newBounds.head = subst(newBounds.head, from, to); + } + newBounds = newBoundsBuf.toList(); + // set the bounds of new type variables to the new bounds + for (Type t : newTvars.toList()) { + TypeVar tv = (TypeVar) t; + tv.bound = newBounds.head; + newBounds = newBounds.tail; + } + return newTvars.toList(); + } + + public TypeVar substBound(TypeVar t, List from, List to) { + Type bound1 = subst(t.bound, from, to); + if (bound1 == t.bound) + return t; + else { + // create new type variable without bounds + TypeVar tv = new TypeVar(t.tsym, null, syms.botType); + // the new bound should use the new type variable in place + // of the old + tv.bound = subst(bound1, List.of(t), List.of(tv)); + return tv; + } + } + // + + // + /** + * Does t have the same bounds for quantified variables as s? + */ + boolean hasSameBounds(ForAll t, ForAll s) { + List l1 = t.tvars; + List l2 = s.tvars; + while (l1.nonEmpty() && l2.nonEmpty() && + isSameType(l1.head.getUpperBound(), + subst(l2.head.getUpperBound(), + s.tvars, + t.tvars))) { + l1 = l1.tail; + l2 = l2.tail; + } + return l1.isEmpty() && l2.isEmpty(); + } + // + + // + /** Create new vector of type variables from list of variables + * changing all recursive bounds from old to new list. + */ + public List newInstances(List tvars) { + List tvars1 = Type.map(tvars, newInstanceFun); + for (List l = tvars1; l.nonEmpty(); l = l.tail) { + TypeVar tv = (TypeVar) l.head; + tv.bound = subst(tv.bound, tvars, tvars1); + } + return tvars1; + } + static private Mapping newInstanceFun = new Mapping("newInstanceFun") { + public Type apply(Type t) { return new TypeVar(t.tsym, t.getUpperBound(), t.getLowerBound()); } + }; + // + + public Type createMethodTypeWithParameters(Type original, List newParams) { + return original.accept(methodWithParameters, newParams); + } + // where + private final MapVisitor> methodWithParameters = new MapVisitor>() { + public Type visitType(Type t, List newParams) { + throw new IllegalArgumentException("Not a method type: " + t); + } + public Type visitMethodType(MethodType t, List newParams) { + return new MethodType(newParams, t.restype, t.thrown, t.tsym); + } + public Type visitForAll(ForAll t, List newParams) { + return new ForAll(t.tvars, t.qtype.accept(this, newParams)); + } + }; + + public Type createMethodTypeWithThrown(Type original, List newThrown) { + return original.accept(methodWithThrown, newThrown); + } + // where + private final MapVisitor> methodWithThrown = new MapVisitor>() { + public Type visitType(Type t, List newThrown) { + throw new IllegalArgumentException("Not a method type: " + t); + } + public Type visitMethodType(MethodType t, List newThrown) { + return new MethodType(t.argtypes, t.restype, newThrown, t.tsym); + } + public Type visitForAll(ForAll t, List newThrown) { + return new ForAll(t.tvars, t.qtype.accept(this, newThrown)); + } + }; + + public Type createMethodTypeWithReturn(Type original, Type newReturn) { + return original.accept(methodWithReturn, newReturn); + } + // where + private final MapVisitor methodWithReturn = new MapVisitor() { + public Type visitType(Type t, Type newReturn) { + throw new IllegalArgumentException("Not a method type: " + t); + } + public Type visitMethodType(MethodType t, Type newReturn) { + return new MethodType(t.argtypes, newReturn, t.thrown, t.tsym); + } + public Type visitForAll(ForAll t, Type newReturn) { + return new ForAll(t.tvars, t.qtype.accept(this, newReturn)); + } + }; + + // + public Type createErrorType(Type originalType) { + return new ErrorType(originalType, syms.errSymbol); + } + + public Type createErrorType(ClassSymbol c, Type originalType) { + return new ErrorType(c, originalType); + } + + public Type createErrorType(Name name, TypeSymbol container, Type originalType) { + return new ErrorType(name, container, originalType); + } + // + + // + /** + * The rank of a class is the length of the longest path between + * the class and java.lang.Object in the class inheritance + * graph. Undefined for all but reference types. + */ + public int rank(Type t) { + switch(t.tag) { + case CLASS: { + ClassType cls = (ClassType)t; + if (cls.rank_field < 0) { + Name fullname = cls.tsym.getQualifiedName(); + if (fullname == names.java_lang_Object) + cls.rank_field = 0; + else { + int r = rank(supertype(cls)); + for (List l = interfaces(cls); + l.nonEmpty(); + l = l.tail) { + if (rank(l.head) > r) + r = rank(l.head); + } + cls.rank_field = r + 1; + } + } + return cls.rank_field; + } + case TYPEVAR: { + TypeVar tvar = (TypeVar)t; + if (tvar.rank_field < 0) { + int r = rank(supertype(tvar)); + for (List l = interfaces(tvar); + l.nonEmpty(); + l = l.tail) { + if (rank(l.head) > r) r = rank(l.head); + } + tvar.rank_field = r + 1; + } + return tvar.rank_field; + } + case ERROR: + return 0; + default: + throw new AssertionError(); + } + } + // + + /** + * Helper method for generating a string representation of a given type + * accordingly to a given locale + */ + public String toString(Type t, Locale locale) { + return Printer.createStandardPrinter(messages).visit(t, locale); + } + + /** + * Helper method for generating a string representation of a given type + * accordingly to a given locale + */ + public String toString(Symbol t, Locale locale) { + return Printer.createStandardPrinter(messages).visit(t, locale); + } + + // + /** + * This toString is slightly more descriptive than the one on Type. + * + * @deprecated Types.toString(Type t, Locale l) provides better support + * for localization + */ + @Deprecated + public String toString(Type t) { + if (t.tag == FORALL) { + ForAll forAll = (ForAll)t; + return typaramsString(forAll.tvars) + forAll.qtype; + } + return "" + t; + } + // where + private String typaramsString(List tvars) { + StringBuilder s = new StringBuilder(); + s.append('<'); + boolean first = true; + for (Type t : tvars) { + if (!first) s.append(", "); + first = false; + appendTyparamString(((TypeVar)t), s); + } + s.append('>'); + return s.toString(); + } + private void appendTyparamString(TypeVar t, StringBuilder buf) { + buf.append(t); + if (t.bound == null || + t.bound.tsym.getQualifiedName() == names.java_lang_Object) + return; + buf.append(" extends "); // Java syntax; no need for i18n + Type bound = t.bound; + if (!bound.isCompound()) { + buf.append(bound); + } else if ((erasure(t).tsym.flags() & INTERFACE) == 0) { + buf.append(supertype(t)); + for (Type intf : interfaces(t)) { + buf.append('&'); + buf.append(intf); + } + } else { + // No superclass was given in bounds. + // In this case, supertype is Object, erasure is first interface. + boolean first = true; + for (Type intf : interfaces(t)) { + if (!first) buf.append('&'); + first = false; + buf.append(intf); + } + } + } + // + + // + /** + * A cache for closures. + * + *

A closure is a list of all the supertypes and interfaces of + * a class or interface type, ordered by ClassSymbol.precedes + * (that is, subclasses come first, arbitrary but fixed + * otherwise). + */ + private Map> closureCache = new HashMap>(); + + /** + * Returns the closure of a class or interface type. + */ + public List closure(Type t) { + List cl = closureCache.get(t); + if (cl == null) { + Type st = supertype(t); + if (!t.isCompound()) { + if (st.tag == CLASS) { + cl = insert(closure(st), t); + } else if (st.tag == TYPEVAR) { + cl = closure(st).prepend(t); + } else { + cl = List.of(t); + } + } else { + cl = closure(supertype(t)); + } + for (List l = interfaces(t); l.nonEmpty(); l = l.tail) + cl = union(cl, closure(l.head)); + closureCache.put(t, cl); + } + return cl; + } + + /** + * Insert a type in a closure + */ + public List insert(List cl, Type t) { + if (cl.isEmpty() || t.tsym.precedes(cl.head.tsym, this)) { + return cl.prepend(t); + } else if (cl.head.tsym.precedes(t.tsym, this)) { + return insert(cl.tail, t).prepend(cl.head); + } else { + return cl; + } + } + + /** + * Form the union of two closures + */ + public List union(List cl1, List cl2) { + if (cl1.isEmpty()) { + return cl2; + } else if (cl2.isEmpty()) { + return cl1; + } else if (cl1.head.tsym.precedes(cl2.head.tsym, this)) { + return union(cl1.tail, cl2).prepend(cl1.head); + } else if (cl2.head.tsym.precedes(cl1.head.tsym, this)) { + return union(cl1, cl2.tail).prepend(cl2.head); + } else { + return union(cl1.tail, cl2.tail).prepend(cl1.head); + } + } + + /** + * Intersect two closures + */ + public List intersect(List cl1, List cl2) { + if (cl1 == cl2) + return cl1; + if (cl1.isEmpty() || cl2.isEmpty()) + return List.nil(); + if (cl1.head.tsym.precedes(cl2.head.tsym, this)) + return intersect(cl1.tail, cl2); + if (cl2.head.tsym.precedes(cl1.head.tsym, this)) + return intersect(cl1, cl2.tail); + if (isSameType(cl1.head, cl2.head)) + return intersect(cl1.tail, cl2.tail).prepend(cl1.head); + if (cl1.head.tsym == cl2.head.tsym && + cl1.head.tag == CLASS && cl2.head.tag == CLASS) { + if (cl1.head.isParameterized() && cl2.head.isParameterized()) { + Type merge = merge(cl1.head,cl2.head); + return intersect(cl1.tail, cl2.tail).prepend(merge); + } + if (cl1.head.isRaw() || cl2.head.isRaw()) + return intersect(cl1.tail, cl2.tail).prepend(erasure(cl1.head)); + } + return intersect(cl1.tail, cl2.tail); + } + // where + class TypePair { + final Type t1; + final Type t2; + TypePair(Type t1, Type t2) { + this.t1 = t1; + this.t2 = t2; + } + @Override + public int hashCode() { + return 127 * Types.hashCode(t1) + Types.hashCode(t2); + } + @Override + public boolean equals(Object obj) { + if (!(obj instanceof TypePair)) + return false; + TypePair typePair = (TypePair)obj; + return isSameType(t1, typePair.t1) + && isSameType(t2, typePair.t2); + } + } + Set mergeCache = new HashSet(); + private Type merge(Type c1, Type c2) { + ClassType class1 = (ClassType) c1; + List act1 = class1.getTypeArguments(); + ClassType class2 = (ClassType) c2; + List act2 = class2.getTypeArguments(); + ListBuffer merged = new ListBuffer(); + List typarams = class1.tsym.type.getTypeArguments(); + + while (act1.nonEmpty() && act2.nonEmpty() && typarams.nonEmpty()) { + if (containsType(act1.head, act2.head)) { + merged.append(act1.head); + } else if (containsType(act2.head, act1.head)) { + merged.append(act2.head); + } else { + TypePair pair = new TypePair(c1, c2); + Type m; + if (mergeCache.add(pair)) { + m = new WildcardType(lub(upperBound(act1.head), + upperBound(act2.head)), + BoundKind.EXTENDS, + syms.boundClass); + mergeCache.remove(pair); + } else { + m = new WildcardType(syms.objectType, + BoundKind.UNBOUND, + syms.boundClass); + } + merged.append(m.withTypeVar(typarams.head)); + } + act1 = act1.tail; + act2 = act2.tail; + typarams = typarams.tail; + } + Assert.check(act1.isEmpty() && act2.isEmpty() && typarams.isEmpty()); + return new ClassType(class1.getEnclosingType(), merged.toList(), class1.tsym); + } + + /** + * Return the minimum type of a closure, a compound type if no + * unique minimum exists. + */ + private Type compoundMin(List cl) { + if (cl.isEmpty()) return syms.objectType; + List compound = closureMin(cl); + if (compound.isEmpty()) + return null; + else if (compound.tail.isEmpty()) + return compound.head; + else + return makeCompoundType(compound); + } + + /** + * Return the minimum types of a closure, suitable for computing + * compoundMin or glb. + */ + private List closureMin(List cl) { + ListBuffer classes = lb(); + ListBuffer interfaces = lb(); + while (!cl.isEmpty()) { + Type current = cl.head; + if (current.isInterface()) + interfaces.append(current); + else + classes.append(current); + ListBuffer candidates = lb(); + for (Type t : cl.tail) { + if (!isSubtypeNoCapture(current, t)) + candidates.append(t); + } + cl = candidates.toList(); + } + return classes.appendList(interfaces).toList(); + } + + /** + * Return the least upper bound of pair of types. if the lub does + * not exist return null. + */ + public Type lub(Type t1, Type t2) { + return lub(List.of(t1, t2)); + } + + /** + * Return the least upper bound (lub) of set of types. If the lub + * does not exist return the type of null (bottom). + */ + public Type lub(List ts) { + final int ARRAY_BOUND = 1; + final int CLASS_BOUND = 2; + int boundkind = 0; + for (Type t : ts) { + switch (t.tag) { + case CLASS: + boundkind |= CLASS_BOUND; + break; + case ARRAY: + boundkind |= ARRAY_BOUND; + break; + case TYPEVAR: + do { + t = t.getUpperBound(); + } while (t.tag == TYPEVAR); + if (t.tag == ARRAY) { + boundkind |= ARRAY_BOUND; + } else { + boundkind |= CLASS_BOUND; + } + break; + default: + if (t.isPrimitive()) + return syms.errType; + } + } + switch (boundkind) { + case 0: + return syms.botType; + + case ARRAY_BOUND: + // calculate lub(A[], B[]) + List elements = Type.map(ts, elemTypeFun); + for (Type t : elements) { + if (t.isPrimitive()) { + // if a primitive type is found, then return + // arraySuperType unless all the types are the + // same + Type first = ts.head; + for (Type s : ts.tail) { + if (!isSameType(first, s)) { + // lub(int[], B[]) is Cloneable & Serializable + return arraySuperType(); + } + } + // all the array types are the same, return one + // lub(int[], int[]) is int[] + return first; + } + } + // lub(A[], B[]) is lub(A, B)[] + return new ArrayType(lub(elements), syms.arrayClass); + + case CLASS_BOUND: + // calculate lub(A, B) + while (ts.head.tag != CLASS && ts.head.tag != TYPEVAR) + ts = ts.tail; + Assert.check(!ts.isEmpty()); + //step 1 - compute erased candidate set (EC) + List cl = erasedSupertypes(ts.head); + for (Type t : ts.tail) { + if (t.tag == CLASS || t.tag == TYPEVAR) + cl = intersect(cl, erasedSupertypes(t)); + } + //step 2 - compute minimal erased candidate set (MEC) + List mec = closureMin(cl); + //step 3 - for each element G in MEC, compute lci(Inv(G)) + List candidates = List.nil(); + for (Type erasedSupertype : mec) { + List lci = List.of(asSuper(ts.head, erasedSupertype.tsym)); + for (Type t : ts) { + lci = intersect(lci, List.of(asSuper(t, erasedSupertype.tsym))); + } + candidates = candidates.appendList(lci); + } + //step 4 - let MEC be { G1, G2 ... Gn }, then we have that + //lub = lci(Inv(G1)) & lci(Inv(G2)) & ... & lci(Inv(Gn)) + return compoundMin(candidates); + + default: + // calculate lub(A, B[]) + List classes = List.of(arraySuperType()); + for (Type t : ts) { + if (t.tag != ARRAY) // Filter out any arrays + classes = classes.prepend(t); + } + // lub(A, B[]) is lub(A, arraySuperType) + return lub(classes); + } + } + // where + List erasedSupertypes(Type t) { + ListBuffer buf = lb(); + for (Type sup : closure(t)) { + if (sup.tag == TYPEVAR) { + buf.append(sup); + } else { + buf.append(erasure(sup)); + } + } + return buf.toList(); + } + + private Type arraySuperType = null; + private Type arraySuperType() { + // initialized lazily to avoid problems during compiler startup + if (arraySuperType == null) { + synchronized (this) { + if (arraySuperType == null) { + // JLS 10.8: all arrays implement Cloneable and Serializable. + arraySuperType = makeCompoundType(List.of(syms.serializableType, + syms.cloneableType), + syms.objectType); + } + } + } + return arraySuperType; + } + // + + // + public Type glb(List ts) { + Type t1 = ts.head; + for (Type t2 : ts.tail) { + if (t1.isErroneous()) + return t1; + t1 = glb(t1, t2); + } + return t1; + } + //where + public Type glb(Type t, Type s) { + if (s == null) + return t; + else if (t.isPrimitive() || s.isPrimitive()) + return syms.errType; + else if (isSubtypeNoCapture(t, s)) + return t; + else if (isSubtypeNoCapture(s, t)) + return s; + + List closure = union(closure(t), closure(s)); + List bounds = closureMin(closure); + + if (bounds.isEmpty()) { // length == 0 + return syms.objectType; + } else if (bounds.tail.isEmpty()) { // length == 1 + return bounds.head; + } else { // length > 1 + int classCount = 0; + for (Type bound : bounds) + if (!bound.isInterface()) + classCount++; + if (classCount > 1) + return createErrorType(t); + } + return makeCompoundType(bounds); + } + // + + // + /** + * Compute a hash code on a type. + */ + public static int hashCode(Type t) { + return hashCode.visit(t); + } + // where + private static final UnaryVisitor hashCode = new UnaryVisitor() { + + public Integer visitType(Type t, Void ignored) { + return t.tag; + } + + @Override + public Integer visitClassType(ClassType t, Void ignored) { + int result = visit(t.getEnclosingType()); + result *= 127; + result += t.tsym.flatName().hashCode(); + for (Type s : t.getTypeArguments()) { + result *= 127; + result += visit(s); + } + return result; + } + + @Override + public Integer visitWildcardType(WildcardType t, Void ignored) { + int result = t.kind.hashCode(); + if (t.type != null) { + result *= 127; + result += visit(t.type); + } + return result; + } + + @Override + public Integer visitArrayType(ArrayType t, Void ignored) { + return visit(t.elemtype) + 12; + } + + @Override + public Integer visitTypeVar(TypeVar t, Void ignored) { + return System.identityHashCode(t.tsym); + } + + @Override + public Integer visitUndetVar(UndetVar t, Void ignored) { + return System.identityHashCode(t); + } + + @Override + public Integer visitErrorType(ErrorType t, Void ignored) { + return 0; + } + }; + // + + // + /** + * Does t have a result that is a subtype of the result type of s, + * suitable for covariant returns? It is assumed that both types + * are (possibly polymorphic) method types. Monomorphic method + * types are handled in the obvious way. Polymorphic method types + * require renaming all type variables of one to corresponding + * type variables in the other, where correspondence is by + * position in the type parameter list. */ + public boolean resultSubtype(Type t, Type s, Warner warner) { + List tvars = t.getTypeArguments(); + List svars = s.getTypeArguments(); + Type tres = t.getReturnType(); + Type sres = subst(s.getReturnType(), svars, tvars); + return covariantReturnType(tres, sres, warner); + } + + /** + * Return-Type-Substitutable. + * @jls section 8.4.5 + */ + public boolean returnTypeSubstitutable(Type r1, Type r2) { + if (hasSameArgs(r1, r2)) + return resultSubtype(r1, r2, Warner.noWarnings); + else + return covariantReturnType(r1.getReturnType(), + erasure(r2.getReturnType()), + Warner.noWarnings); + } + + public boolean returnTypeSubstitutable(Type r1, + Type r2, Type r2res, + Warner warner) { + if (isSameType(r1.getReturnType(), r2res)) + return true; + if (r1.getReturnType().isPrimitive() || r2res.isPrimitive()) + return false; + + if (hasSameArgs(r1, r2)) + return covariantReturnType(r1.getReturnType(), r2res, warner); + if (!allowCovariantReturns) + return false; + if (isSubtypeUnchecked(r1.getReturnType(), r2res, warner)) + return true; + if (!isSubtype(r1.getReturnType(), erasure(r2res))) + return false; + warner.warn(LintCategory.UNCHECKED); + return true; + } + + /** + * Is t an appropriate return type in an overrider for a + * method that returns s? + */ + public boolean covariantReturnType(Type t, Type s, Warner warner) { + return + isSameType(t, s) || + allowCovariantReturns && + !t.isPrimitive() && + !s.isPrimitive() && + isAssignable(t, s, warner); + } + // + + // + /** + * Return the class that boxes the given primitive. + */ + public ClassSymbol boxedClass(Type t) { + return reader.enterClass(syms.boxedName[t.tag]); + } + + /** + * Return the boxed type if 't' is primitive, otherwise return 't' itself. + */ + public Type boxedTypeOrType(Type t) { + return t.isPrimitive() ? + boxedClass(t).type : + t; + } + + /** + * Return the primitive type corresponding to a boxed type. + */ + public Type unboxedType(Type t) { + if (allowBoxing) { + for (int i=0; i + + // + /* + * JLS 5.1.10 Capture Conversion: + * + * Let G name a generic type declaration with n formal type + * parameters A1 ... An with corresponding bounds U1 ... Un. There + * exists a capture conversion from G to G, + * where, for 1 <= i <= n: + * + * + If Ti is a wildcard type argument (4.5.1) of the form ? then + * Si is a fresh type variable whose upper bound is + * Ui[A1 := S1, ..., An := Sn] and whose lower bound is the null + * type. + * + * + If Ti is a wildcard type argument of the form ? extends Bi, + * then Si is a fresh type variable whose upper bound is + * glb(Bi, Ui[A1 := S1, ..., An := Sn]) and whose lower bound is + * the null type, where glb(V1,... ,Vm) is V1 & ... & Vm. It is + * a compile-time error if for any two classes (not interfaces) + * Vi and Vj,Vi is not a subclass of Vj or vice versa. + * + * + If Ti is a wildcard type argument of the form ? super Bi, + * then Si is a fresh type variable whose upper bound is + * Ui[A1 := S1, ..., An := Sn] and whose lower bound is Bi. + * + * + Otherwise, Si = Ti. + * + * Capture conversion on any type other than a parameterized type + * (4.5) acts as an identity conversion (5.1.1). Capture + * conversions never require a special action at run time and + * therefore never throw an exception at run time. + * + * Capture conversion is not applied recursively. + */ + /** + * Capture conversion as specified by the JLS. + */ + + public List capture(List ts) { + List buf = List.nil(); + for (Type t : ts) { + buf = buf.prepend(capture(t)); + } + return buf.reverse(); + } + public Type capture(Type t) { + if (t.tag != CLASS) + return t; + if (t.getEnclosingType() != Type.noType) { + Type capturedEncl = capture(t.getEnclosingType()); + if (capturedEncl != t.getEnclosingType()) { + Type type1 = memberType(capturedEncl, t.tsym); + t = subst(type1, t.tsym.type.getTypeArguments(), t.getTypeArguments()); + } + } + ClassType cls = (ClassType)t; + if (cls.isRaw() || !cls.isParameterized()) + return cls; + + ClassType G = (ClassType)cls.asElement().asType(); + List A = G.getTypeArguments(); + List T = cls.getTypeArguments(); + List S = freshTypeVariables(T); + + List currentA = A; + List currentT = T; + List currentS = S; + boolean captured = false; + while (!currentA.isEmpty() && + !currentT.isEmpty() && + !currentS.isEmpty()) { + if (currentS.head != currentT.head) { + captured = true; + WildcardType Ti = (WildcardType)currentT.head; + Type Ui = currentA.head.getUpperBound(); + CapturedType Si = (CapturedType)currentS.head; + if (Ui == null) + Ui = syms.objectType; + switch (Ti.kind) { + case UNBOUND: + Si.bound = subst(Ui, A, S); + Si.lower = syms.botType; + break; + case EXTENDS: + Si.bound = glb(Ti.getExtendsBound(), subst(Ui, A, S)); + Si.lower = syms.botType; + break; + case SUPER: + Si.bound = subst(Ui, A, S); + Si.lower = Ti.getSuperBound(); + break; + } + if (Si.bound == Si.lower) + currentS.head = Si.bound; + } + currentA = currentA.tail; + currentT = currentT.tail; + currentS = currentS.tail; + } + if (!currentA.isEmpty() || !currentT.isEmpty() || !currentS.isEmpty()) + return erasure(t); // some "rare" type involved + + if (captured) + return new ClassType(cls.getEnclosingType(), S, cls.tsym); + else + return t; + } + // where + public List freshTypeVariables(List types) { + ListBuffer result = lb(); + for (Type t : types) { + if (t.tag == WILDCARD) { + Type bound = ((WildcardType)t).getExtendsBound(); + if (bound == null) + bound = syms.objectType; + result.append(new CapturedType(capturedName, + syms.noSymbol, + bound, + syms.botType, + (WildcardType)t)); + } else { + result.append(t); + } + } + return result.toList(); + } + // + + // + private List upperBounds(List ss) { + if (ss.isEmpty()) return ss; + Type head = upperBound(ss.head); + List tail = upperBounds(ss.tail); + if (head != ss.head || tail != ss.tail) + return tail.prepend(head); + else + return ss; + } + + private boolean sideCast(Type from, Type to, Warner warn) { + // We are casting from type $from$ to type $to$, which are + // non-final unrelated types. This method + // tries to reject a cast by transferring type parameters + // from $to$ to $from$ by common superinterfaces. + boolean reverse = false; + Type target = to; + if ((to.tsym.flags() & INTERFACE) == 0) { + Assert.check((from.tsym.flags() & INTERFACE) != 0); + reverse = true; + to = from; + from = target; + } + List commonSupers = superClosure(to, erasure(from)); + boolean giveWarning = commonSupers.isEmpty(); + // The arguments to the supers could be unified here to + // get a more accurate analysis + while (commonSupers.nonEmpty()) { + Type t1 = asSuper(from, commonSupers.head.tsym); + Type t2 = commonSupers.head; // same as asSuper(to, commonSupers.head.tsym); + if (disjointTypes(t1.getTypeArguments(), t2.getTypeArguments())) + return false; + giveWarning = giveWarning || (reverse ? giveWarning(t2, t1) : giveWarning(t1, t2)); + commonSupers = commonSupers.tail; + } + if (giveWarning && !isReifiable(reverse ? from : to)) + warn.warn(LintCategory.UNCHECKED); + if (!allowCovariantReturns) + // reject if there is a common method signature with + // incompatible return types. + chk.checkCompatibleAbstracts(warn.pos(), from, to); + return true; + } + + private boolean sideCastFinal(Type from, Type to, Warner warn) { + // We are casting from type $from$ to type $to$, which are + // unrelated types one of which is final and the other of + // which is an interface. This method + // tries to reject a cast by transferring type parameters + // from the final class to the interface. + boolean reverse = false; + Type target = to; + if ((to.tsym.flags() & INTERFACE) == 0) { + Assert.check((from.tsym.flags() & INTERFACE) != 0); + reverse = true; + to = from; + from = target; + } + Assert.check((from.tsym.flags() & FINAL) != 0); + Type t1 = asSuper(from, to.tsym); + if (t1 == null) return false; + Type t2 = to; + if (disjointTypes(t1.getTypeArguments(), t2.getTypeArguments())) + return false; + if (!allowCovariantReturns) + // reject if there is a common method signature with + // incompatible return types. + chk.checkCompatibleAbstracts(warn.pos(), from, to); + if (!isReifiable(target) && + (reverse ? giveWarning(t2, t1) : giveWarning(t1, t2))) + warn.warn(LintCategory.UNCHECKED); + return true; + } + + private boolean giveWarning(Type from, Type to) { + Type subFrom = asSub(from, to.tsym); + return to.isParameterized() && + (!(isUnbounded(to) || + isSubtype(from, to) || + ((subFrom != null) && containsType(to.allparams(), subFrom.allparams())))); + } + + private List superClosure(Type t, Type s) { + List cl = List.nil(); + for (List l = interfaces(t); l.nonEmpty(); l = l.tail) { + if (isSubtype(s, erasure(l.head))) { + cl = insert(cl, l.head); + } else { + cl = union(cl, superClosure(l.head, s)); + } + } + return cl; + } + + private boolean containsTypeEquivalent(Type t, Type s) { + return + isSameType(t, s) || // shortcut + containsType(t, s) && containsType(s, t); + } + + // + /** + * Adapt a type by computing a substitution which maps a source + * type to a target type. + * + * @param source the source type + * @param target the target type + * @param from the type variables of the computed substitution + * @param to the types of the computed substitution. + */ + public void adapt(Type source, + Type target, + ListBuffer from, + ListBuffer to) throws AdaptFailure { + new Adapter(from, to).adapt(source, target); + } + + class Adapter extends SimpleVisitor { + + ListBuffer from; + ListBuffer to; + Map mapping; + + Adapter(ListBuffer from, ListBuffer to) { + this.from = from; + this.to = to; + mapping = new HashMap(); + } + + public void adapt(Type source, Type target) throws AdaptFailure { + visit(source, target); + List fromList = from.toList(); + List toList = to.toList(); + while (!fromList.isEmpty()) { + Type val = mapping.get(fromList.head.tsym); + if (toList.head != val) + toList.head = val; + fromList = fromList.tail; + toList = toList.tail; + } + } + + @Override + public Void visitClassType(ClassType source, Type target) throws AdaptFailure { + if (target.tag == CLASS) + adaptRecursive(source.allparams(), target.allparams()); + return null; + } + + @Override + public Void visitArrayType(ArrayType source, Type target) throws AdaptFailure { + if (target.tag == ARRAY) + adaptRecursive(elemtype(source), elemtype(target)); + return null; + } + + @Override + public Void visitWildcardType(WildcardType source, Type target) throws AdaptFailure { + if (source.isExtendsBound()) + adaptRecursive(upperBound(source), upperBound(target)); + else if (source.isSuperBound()) + adaptRecursive(lowerBound(source), lowerBound(target)); + return null; + } + + @Override + public Void visitTypeVar(TypeVar source, Type target) throws AdaptFailure { + // Check to see if there is + // already a mapping for $source$, in which case + // the old mapping will be merged with the new + Type val = mapping.get(source.tsym); + if (val != null) { + if (val.isSuperBound() && target.isSuperBound()) { + val = isSubtype(lowerBound(val), lowerBound(target)) + ? target : val; + } else if (val.isExtendsBound() && target.isExtendsBound()) { + val = isSubtype(upperBound(val), upperBound(target)) + ? val : target; + } else if (!isSameType(val, target)) { + throw new AdaptFailure(); + } + } else { + val = target; + from.append(source); + to.append(target); + } + mapping.put(source.tsym, val); + return null; + } + + @Override + public Void visitType(Type source, Type target) { + return null; + } + + private Set cache = new HashSet(); + + private void adaptRecursive(Type source, Type target) { + TypePair pair = new TypePair(source, target); + if (cache.add(pair)) { + try { + visit(source, target); + } finally { + cache.remove(pair); + } + } + } + + private void adaptRecursive(List source, List target) { + if (source.length() == target.length()) { + while (source.nonEmpty()) { + adaptRecursive(source.head, target.head); + source = source.tail; + target = target.tail; + } + } + } + } + + public static class AdaptFailure extends RuntimeException { + static final long serialVersionUID = -7490231548272701566L; + } + + private void adaptSelf(Type t, + ListBuffer from, + ListBuffer to) { + try { + //if (t.tsym.type != t) + adapt(t.tsym.type, t, from, to); + } catch (AdaptFailure ex) { + // Adapt should never fail calculating a mapping from + // t.tsym.type to t as there can be no merge problem. + throw new AssertionError(ex); + } + } + // + + /** + * Rewrite all type variables (universal quantifiers) in the given + * type to wildcards (existential quantifiers). This is used to + * determine if a cast is allowed. For example, if high is true + * and {@code T <: Number}, then {@code List} is rewritten to + * {@code List}. Since {@code List <: + * List} a {@code List} can be cast to {@code + * List} with a warning. + * @param t a type + * @param high if true return an upper bound; otherwise a lower + * bound + * @param rewriteTypeVars only rewrite captured wildcards if false; + * otherwise rewrite all type variables + * @return the type rewritten with wildcards (existential + * quantifiers) only + */ + private Type rewriteQuantifiers(Type t, boolean high, boolean rewriteTypeVars) { + return new Rewriter(high, rewriteTypeVars).visit(t); + } + + class Rewriter extends UnaryVisitor { + + boolean high; + boolean rewriteTypeVars; + + Rewriter(boolean high, boolean rewriteTypeVars) { + this.high = high; + this.rewriteTypeVars = rewriteTypeVars; + } + + @Override + public Type visitClassType(ClassType t, Void s) { + ListBuffer rewritten = new ListBuffer(); + boolean changed = false; + for (Type arg : t.allparams()) { + Type bound = visit(arg); + if (arg != bound) { + changed = true; + } + rewritten.append(bound); + } + if (changed) + return subst(t.tsym.type, + t.tsym.type.allparams(), + rewritten.toList()); + else + return t; + } + + public Type visitType(Type t, Void s) { + return high ? upperBound(t) : lowerBound(t); + } + + @Override + public Type visitCapturedType(CapturedType t, Void s) { + Type bound = visitWildcardType(t.wildcard, null); + return (bound.contains(t)) ? + erasure(bound) : + bound; + } + + @Override + public Type visitTypeVar(TypeVar t, Void s) { + if (rewriteTypeVars) { + Type bound = high ? + (t.bound.contains(t) ? + erasure(t.bound) : + visit(t.bound)) : + syms.botType; + return rewriteAsWildcardType(bound, t); + } + else + return t; + } + + @Override + public Type visitWildcardType(WildcardType t, Void s) { + Type bound = high ? t.getExtendsBound() : + t.getSuperBound(); + if (bound == null) + bound = high ? syms.objectType : syms.botType; + return rewriteAsWildcardType(visit(bound), t.bound); + } + + private Type rewriteAsWildcardType(Type bound, TypeVar formal) { + return high ? + makeExtendsWildcard(B(bound), formal) : + makeSuperWildcard(B(bound), formal); + } + + Type B(Type t) { + while (t.tag == WILDCARD) { + WildcardType w = (WildcardType)t; + t = high ? + w.getExtendsBound() : + w.getSuperBound(); + if (t == null) { + t = high ? syms.objectType : syms.botType; + } + } + return t; + } + } + + + /** + * Create a wildcard with the given upper (extends) bound; create + * an unbounded wildcard if bound is Object. + * + * @param bound the upper bound + * @param formal the formal type parameter that will be + * substituted by the wildcard + */ + private WildcardType makeExtendsWildcard(Type bound, TypeVar formal) { + if (bound == syms.objectType) { + return new WildcardType(syms.objectType, + BoundKind.UNBOUND, + syms.boundClass, + formal); + } else { + return new WildcardType(bound, + BoundKind.EXTENDS, + syms.boundClass, + formal); + } + } + + /** + * Create a wildcard with the given lower (super) bound; create an + * unbounded wildcard if bound is bottom (type of {@code null}). + * + * @param bound the lower bound + * @param formal the formal type parameter that will be + * substituted by the wildcard + */ + private WildcardType makeSuperWildcard(Type bound, TypeVar formal) { + if (bound.tag == BOT) { + return new WildcardType(syms.objectType, + BoundKind.UNBOUND, + syms.boundClass, + formal); + } else { + return new WildcardType(bound, + BoundKind.SUPER, + syms.boundClass, + formal); + } + } + + /** + * A wrapper for a type that allows use in sets. + */ + class SingletonType { + final Type t; + SingletonType(Type t) { + this.t = t; + } + public int hashCode() { + return Types.hashCode(t); + } + public boolean equals(Object obj) { + return (obj instanceof SingletonType) && + isSameType(t, ((SingletonType)obj).t); + } + public String toString() { + return t.toString(); + } + } + // + + // + /** + * A default visitor for types. All visitor methods except + * visitType are implemented by delegating to visitType. Concrete + * subclasses must provide an implementation of visitType and can + * override other methods as needed. + * + * @param the return type of the operation implemented by this + * visitor; use Void if no return type is needed. + * @param the type of the second argument (the first being the + * type itself) of the operation implemented by this visitor; use + * Void if a second argument is not needed. + */ + public static abstract class DefaultTypeVisitor implements Type.Visitor { + final public R visit(Type t, S s) { return t.accept(this, s); } + public R visitClassType(ClassType t, S s) { return visitType(t, s); } + public R visitWildcardType(WildcardType t, S s) { return visitType(t, s); } + public R visitArrayType(ArrayType t, S s) { return visitType(t, s); } + public R visitMethodType(MethodType t, S s) { return visitType(t, s); } + public R visitPackageType(PackageType t, S s) { return visitType(t, s); } + public R visitTypeVar(TypeVar t, S s) { return visitType(t, s); } + public R visitCapturedType(CapturedType t, S s) { return visitType(t, s); } + public R visitForAll(ForAll t, S s) { return visitType(t, s); } + public R visitUndetVar(UndetVar t, S s) { return visitType(t, s); } + public R visitErrorType(ErrorType t, S s) { return visitType(t, s); } + } + + /** + * A default visitor for symbols. All visitor methods except + * visitSymbol are implemented by delegating to visitSymbol. Concrete + * subclasses must provide an implementation of visitSymbol and can + * override other methods as needed. + * + * @param the return type of the operation implemented by this + * visitor; use Void if no return type is needed. + * @param the type of the second argument (the first being the + * symbol itself) of the operation implemented by this visitor; use + * Void if a second argument is not needed. + */ + public static abstract class DefaultSymbolVisitor implements Symbol.Visitor { + final public R visit(Symbol s, S arg) { return s.accept(this, arg); } + public R visitClassSymbol(ClassSymbol s, S arg) { return visitSymbol(s, arg); } + public R visitMethodSymbol(MethodSymbol s, S arg) { return visitSymbol(s, arg); } + public R visitOperatorSymbol(OperatorSymbol s, S arg) { return visitSymbol(s, arg); } + public R visitPackageSymbol(PackageSymbol s, S arg) { return visitSymbol(s, arg); } + public R visitTypeSymbol(TypeSymbol s, S arg) { return visitSymbol(s, arg); } + public R visitVarSymbol(VarSymbol s, S arg) { return visitSymbol(s, arg); } + } + + /** + * A simple visitor for types. This visitor is simple as + * captured wildcards, for-all types (generic methods), and + * undetermined type variables (part of inference) are hidden. + * Captured wildcards are hidden by treating them as type + * variables and the rest are hidden by visiting their qtypes. + * + * @param the return type of the operation implemented by this + * visitor; use Void if no return type is needed. + * @param the type of the second argument (the first being the + * type itself) of the operation implemented by this visitor; use + * Void if a second argument is not needed. + */ + public static abstract class SimpleVisitor extends DefaultTypeVisitor { + @Override + public R visitCapturedType(CapturedType t, S s) { + return visitTypeVar(t, s); + } + @Override + public R visitForAll(ForAll t, S s) { + return visit(t.qtype, s); + } + @Override + public R visitUndetVar(UndetVar t, S s) { + return visit(t.qtype, s); + } + } + + /** + * A plain relation on types. That is a 2-ary function on the + * form Type × Type → Boolean. + * + */ + public static abstract class TypeRelation extends SimpleVisitor {} + + /** + * A convenience visitor for implementing operations that only + * require one argument (the type itself), that is, unary + * operations. + * + * @param the return type of the operation implemented by this + * visitor; use Void if no return type is needed. + */ + public static abstract class UnaryVisitor extends SimpleVisitor { + final public R visit(Type t) { return t.accept(this, null); } + } + + /** + * A visitor for implementing a mapping from types to types. The + * default behavior of this class is to implement the identity + * mapping (mapping a type to itself). This can be overridden in + * subclasses. + * + * @param the type of the second argument (the first being the + * type itself) of this mapping; use Void if a second argument is + * not needed. + */ + public static class MapVisitor extends DefaultTypeVisitor { + final public Type visit(Type t) { return t.accept(this, null); } + public Type visitType(Type t, S s) { return t; } + } + // + + + // + + public RetentionPolicy getRetention(Attribute.Compound a) { + RetentionPolicy vis = RetentionPolicy.CLASS; // the default + Attribute.Compound c = a.type.tsym.attribute(syms.retentionType.tsym); + if (c != null) { + Attribute value = c.member(names.value); + if (value != null && value instanceof Attribute.Enum) { + Name levelName = ((Attribute.Enum)value).value.name; + if (levelName == names.SOURCE) vis = RetentionPolicy.SOURCE; + else if (levelName == names.CLASS) vis = RetentionPolicy.CLASS; + else if (levelName == names.RUNTIME) vis = RetentionPolicy.RUNTIME; + else ;// /* fail soft */ throw new AssertionError(levelName); + } + } + return vis; + } + // +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/comp/Annotate.java b/douyu-javac/src/main/java/com/sun/tools/javac/comp/Annotate.java new file mode 100644 index 0000000..3d13527 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/comp/Annotate.java @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.comp; + +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.tree.*; +import com.sun.tools.javac.tree.JCTree.*; + +/** Enter annotations on symbols. Annotations accumulate in a queue, + * which is processed at the top level of any set of recursive calls + * requesting it be processed. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Annotate { + protected static final Context.Key annotateKey = + new Context.Key(); + + public static Annotate instance(Context context) { + Annotate instance = context.get(annotateKey); + if (instance == null) + instance = new Annotate(context); + return instance; + } + + final Attr attr; + final TreeMaker make; + final Log log; + final Symtab syms; + final Names names; + final Resolve rs; + final Types types; + final ConstFold cfolder; + final Check chk; + + protected Annotate(Context context) { + context.put(annotateKey, this); + attr = Attr.instance(context); + make = TreeMaker.instance(context); + log = Log.instance(context); + syms = Symtab.instance(context); + names = Names.instance(context); + rs = Resolve.instance(context); + types = Types.instance(context); + cfolder = ConstFold.instance(context); + chk = Check.instance(context); + } + +/* ******************************************************************** + * Queue maintenance + *********************************************************************/ + + private int enterCount = 0; + + ListBuffer q = new ListBuffer(); + + public void later(Annotator a) { + q.append(a); + } + + public void earlier(Annotator a) { + q.prepend(a); + } + + /** Called when the Enter phase starts. */ + public void enterStart() { + enterCount++; + } + + /** Called after the Enter phase completes. */ + public void enterDone() { + enterCount--; + flush(); + } + + public void flush() { + if (enterCount != 0) return; + enterCount++; + try { + while (q.nonEmpty()) + q.next().enterAnnotation(); + } finally { + enterCount--; + } + } + + /** A client that has annotations to add registers an annotator, + * the method it will use to add the annotation. There are no + * parameters; any needed data should be captured by the + * Annotator. + */ + public interface Annotator { + void enterAnnotation(); + String toString(); + } + + +/* ******************************************************************** + * Compute an attribute from its annotation. + *********************************************************************/ + + /** Process a single compound annotation, returning its + * Attribute. Used from MemberEnter for attaching the attributes + * to the annotated symbol. + */ + Attribute.Compound enterAnnotation(JCAnnotation a, + Type expected, + Env env) { + // The annotation might have had its type attributed (but not checked) + // by attr.attribAnnotationTypes during MemberEnter, in which case we do not + // need to do it again. + Type at = (a.annotationType.type != null ? a.annotationType.type + : attr.attribType(a.annotationType, env)); + a.type = chk.checkType(a.annotationType.pos(), at, expected); + if (a.type.isErroneous()) + return new Attribute.Compound(a.type, List.>nil()); + if ((a.type.tsym.flags() & Flags.ANNOTATION) == 0) { + log.error(a.annotationType.pos(), + "not.annotation.type", a.type.toString()); + return new Attribute.Compound(a.type, List.>nil()); + } + List args = a.args; + if (args.length() == 1 && args.head.getTag() != JCTree.ASSIGN) { + // special case: elided "value=" assumed + args.head = make.at(args.head.pos). + Assign(make.Ident(names.value), args.head); + } + ListBuffer> buf = + new ListBuffer>(); + for (List tl = args; tl.nonEmpty(); tl = tl.tail) { + JCExpression t = tl.head; + if (t.getTag() != JCTree.ASSIGN) { + log.error(t.pos(), "annotation.value.must.be.name.value"); + continue; + } + JCAssign assign = (JCAssign)t; + if (assign.lhs.getTag() != JCTree.IDENT) { + log.error(t.pos(), "annotation.value.must.be.name.value"); + continue; + } + JCIdent left = (JCIdent)assign.lhs; + Symbol method = rs.resolveQualifiedMethod(left.pos(), + env, + a.type, + left.name, + List.nil(), + null); + left.sym = method; + left.type = method.type; + if (method.owner != a.type.tsym) + log.error(left.pos(), "no.annotation.member", left.name, a.type); + Type result = method.type.getReturnType(); + Attribute value = enterAttributeValue(result, assign.rhs, env); + if (!method.type.isErroneous()) + buf.append(new Pair + ((MethodSymbol)method, value)); + t.type = result; + } + return new Attribute.Compound(a.type, buf.toList()); + } + + Attribute enterAttributeValue(Type expected, + JCExpression tree, + Env env) { + //first, try completing the attribution value sym - if a completion + //error is thrown, we should recover gracefully, and display an + //ordinary resolution diagnostic. + try { + expected.tsym.complete(); + } catch(CompletionFailure e) { + log.error(tree.pos(), "cant.resolve", Kinds.kindName(e.sym), e.sym); + return new Attribute.Error(expected); + } + if (expected.isPrimitive() || types.isSameType(expected, syms.stringType)) { + Type result = attr.attribExpr(tree, env, expected); + if (result.isErroneous()) + return new Attribute.Error(expected); + if (result.constValue() == null) { + log.error(tree.pos(), "attribute.value.must.be.constant"); + return new Attribute.Error(expected); + } + result = cfolder.coerce(result, expected); + return new Attribute.Constant(expected, result.constValue()); + } + if (expected.tsym == syms.classType.tsym) { + Type result = attr.attribExpr(tree, env, expected); + if (result.isErroneous()) + return new Attribute.Error(expected); + if (TreeInfo.name(tree) != names._class) { + log.error(tree.pos(), "annotation.value.must.be.class.literal"); + return new Attribute.Error(expected); + } + return new Attribute.Class(types, + (((JCFieldAccess) tree).selected).type); + } + if ((expected.tsym.flags() & Flags.ANNOTATION) != 0) { + if (tree.getTag() != JCTree.ANNOTATION) { + log.error(tree.pos(), "annotation.value.must.be.annotation"); + expected = syms.errorType; + } + return enterAnnotation((JCAnnotation)tree, expected, env); + } + if (expected.tag == TypeTags.ARRAY) { // should really be isArray() + if (tree.getTag() != JCTree.NEWARRAY) { + tree = make.at(tree.pos). + NewArray(null, List.nil(), List.of(tree)); + } + JCNewArray na = (JCNewArray)tree; + if (na.elemtype != null) { + log.error(na.elemtype.pos(), "new.not.allowed.in.annotation"); + return new Attribute.Error(expected); + } + ListBuffer buf = new ListBuffer(); + for (List l = na.elems; l.nonEmpty(); l=l.tail) { + buf.append(enterAttributeValue(types.elemtype(expected), + l.head, + env)); + } + na.type = expected; + return new Attribute. + Array(expected, buf.toArray(new Attribute[buf.length()])); + } + if (expected.tag == TypeTags.CLASS && + (expected.tsym.flags() & Flags.ENUM) != 0) { + attr.attribExpr(tree, env, expected); + Symbol sym = TreeInfo.symbol(tree); + if (sym == null || + TreeInfo.nonstaticSelect(tree) || + sym.kind != Kinds.VAR || + (sym.flags() & Flags.ENUM) == 0) { + log.error(tree.pos(), "enum.annotation.must.be.enum.constant"); + return new Attribute.Error(expected); + } + VarSymbol enumerator = (VarSymbol) sym; + return new Attribute.Enum(expected, enumerator); + } + if (!expected.isErroneous()) + log.error(tree.pos(), "annotation.value.not.allowable.type"); + return new Attribute.Error(attr.attribExpr(tree, env, expected)); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/comp/Attr.java b/douyu-javac/src/main/java/com/sun/tools/javac/comp/Attr.java new file mode 100644 index 0000000..2c228c3 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/comp/Attr.java @@ -0,0 +1,3432 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.comp; + +import java.util.*; +import java.util.Set; +import javax.lang.model.element.ElementKind; +import javax.tools.JavaFileObject; + +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.jvm.*; +import com.sun.tools.javac.tree.*; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; +import com.sun.tools.javac.util.List; + +import com.sun.tools.javac.jvm.Target; +import com.sun.tools.javac.code.Lint.LintCategory; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.tree.JCTree.*; +import com.sun.tools.javac.code.Type.*; + +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.TreeVisitor; +import com.sun.source.util.SimpleTreeVisitor; + +import static com.sun.tools.javac.code.Flags.*; +import static com.sun.tools.javac.code.Kinds.*; +import static com.sun.tools.javac.code.TypeTags.*; + +/** This is the main context-dependent analysis phase in GJC. It + * encompasses name resolution, type checking and constant folding as + * subtasks. Some subtasks involve auxiliary classes. + * @see Check + * @see Resolve + * @see ConstFold + * @see Infer + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Attr extends JCTree.Visitor { + protected static final Context.Key attrKey = + new Context.Key(); + + final Names names; + final Log log; + final Symtab syms; + final Resolve rs; + final Infer infer; + final Check chk; + final MemberEnter memberEnter; + final TreeMaker make; + final ConstFold cfolder; + final Enter enter; + final Target target; + final Types types; + final JCDiagnostic.Factory diags; + final Annotate annotate; + final DeferredLintHandler deferredLintHandler; + + public static Attr instance(Context context) { + Attr instance = context.get(attrKey); + if (instance == null) + instance = new Attr(context); + return instance; + } + + protected Attr(Context context) { + context.put(attrKey, this); + + names = Names.instance(context); + log = Log.instance(context); + syms = Symtab.instance(context); + rs = Resolve.instance(context); + chk = Check.instance(context); + memberEnter = MemberEnter.instance(context); + make = TreeMaker.instance(context); + enter = Enter.instance(context); + infer = Infer.instance(context); + cfolder = ConstFold.instance(context); + target = Target.instance(context); + types = Types.instance(context); + diags = JCDiagnostic.Factory.instance(context); + annotate = Annotate.instance(context); + deferredLintHandler = DeferredLintHandler.instance(context); + + Options options = Options.instance(context); + + Source source = Source.instance(context); + allowGenerics = source.allowGenerics(); + allowVarargs = source.allowVarargs(); + allowEnums = source.allowEnums(); + allowBoxing = source.allowBoxing(); + allowCovariantReturns = source.allowCovariantReturns(); + allowAnonOuterThis = source.allowAnonOuterThis(); + allowStringsInSwitch = source.allowStringsInSwitch(); + sourceName = source.name; + relax = (options.isSet("-retrofit") || + options.isSet("-relax")); + findDiamonds = options.get("findDiamond") != null && + source.allowDiamond(); + useBeforeDeclarationWarning = options.isSet("useBeforeDeclarationWarning"); + } + + /** Switch: relax some constraints for retrofit mode. + */ + boolean relax; + + /** Switch: support generics? + */ + boolean allowGenerics; + + /** Switch: allow variable-arity methods. + */ + boolean allowVarargs; + + /** Switch: support enums? + */ + boolean allowEnums; + + /** Switch: support boxing and unboxing? + */ + boolean allowBoxing; + + /** Switch: support covariant result types? + */ + boolean allowCovariantReturns; + + /** Switch: allow references to surrounding object from anonymous + * objects during constructor call? + */ + boolean allowAnonOuterThis; + + /** Switch: generates a warning if diamond can be safely applied + * to a given new expression + */ + boolean findDiamonds; + + /** + * Internally enables/disables diamond finder feature + */ + static final boolean allowDiamondFinder = true; + + /** + * Switch: warn about use of variable before declaration? + * RFE: 6425594 + */ + boolean useBeforeDeclarationWarning; + + /** + * Switch: allow strings in switch? + */ + boolean allowStringsInSwitch; + + /** + * Switch: name of source level; used for error reporting. + */ + String sourceName; + + /** Check kind and type of given tree against protokind and prototype. + * If check succeeds, store type in tree and return it. + * If check fails, store errType in tree and return it. + * No checks are performed if the prototype is a method type. + * It is not necessary in this case since we know that kind and type + * are correct. + * + * @param tree The tree whose kind and type is checked + * @param owntype The computed type of the tree + * @param ownkind The computed kind of the tree + * @param pkind The expected kind (or: protokind) of the tree + * @param pt The expected type (or: prototype) of the tree + */ + Type check(JCTree tree, Type owntype, int ownkind, int pkind, Type pt) { + if (owntype.tag != ERROR && pt.tag != METHOD && pt.tag != FORALL) { + if ((ownkind & ~pkind) == 0) { + owntype = chk.checkType(tree.pos(), owntype, pt, errKey); + } else { + log.error(tree.pos(), "unexpected.type", + kindNames(pkind), + kindName(ownkind)); + owntype = types.createErrorType(owntype); + } + } + tree.type = owntype; + return owntype; + } + + /** Is given blank final variable assignable, i.e. in a scope where it + * may be assigned to even though it is final? + * @param v The blank final variable. + * @param env The current environment. + */ + boolean isAssignableAsBlankFinal(VarSymbol v, Env env) { + Symbol owner = env.info.scope.owner; + // owner refers to the innermost variable, method or + // initializer block declaration at this point. + return + v.owner == owner + || + ((owner.name == names.init || // i.e. we are in a constructor + owner.kind == VAR || // i.e. we are in a variable initializer + (owner.flags() & BLOCK) != 0) // i.e. we are in an initializer block + && + v.owner == owner.owner + && + ((v.flags() & STATIC) != 0) == Resolve.isStatic(env)); + } + + /** Check that variable can be assigned to. + * @param pos The current source code position. + * @param v The assigned varaible + * @param base If the variable is referred to in a Select, the part + * to the left of the `.', null otherwise. + * @param env The current environment. + */ + void checkAssignable(DiagnosticPosition pos, VarSymbol v, JCTree base, Env env) { + if ((v.flags() & FINAL) != 0 && + ((v.flags() & HASINIT) != 0 + || + !((base == null || + (base.getTag() == JCTree.IDENT && TreeInfo.name(base) == names._this)) && + isAssignableAsBlankFinal(v, env)))) { + if (v.isResourceVariable()) { //TWR resource + log.error(pos, "try.resource.may.not.be.assigned", v); + } else { + log.error(pos, "cant.assign.val.to.final.var", v); + } + } else if ((v.flags() & EFFECTIVELY_FINAL) != 0) { + v.flags_field &= ~EFFECTIVELY_FINAL; + } + } + + /** Does tree represent a static reference to an identifier? + * It is assumed that tree is either a SELECT or an IDENT. + * We have to weed out selects from non-type names here. + * @param tree The candidate tree. + */ + boolean isStaticReference(JCTree tree) { + if (tree.getTag() == JCTree.SELECT) { + Symbol lsym = TreeInfo.symbol(((JCFieldAccess) tree).selected); + if (lsym == null || lsym.kind != TYP) { + return false; + } + } + return true; + } + + /** Is this symbol a type? + */ + static boolean isType(Symbol sym) { + return sym != null && sym.kind == TYP; + } + + /** The current `this' symbol. + * @param env The current environment. + */ + Symbol thisSym(DiagnosticPosition pos, Env env) { + return rs.resolveSelf(pos, env, env.enclClass.sym, names._this); + } + + /** Attribute a parsed identifier. + * @param tree Parsed identifier name + * @param topLevel The toplevel to use + */ + public Symbol attribIdent(JCTree tree, JCCompilationUnit topLevel) { + Env localEnv = enter.topLevelEnv(topLevel); + localEnv.enclClass = make.ClassDef(make.Modifiers(0), + syms.errSymbol.name, + null, null, null, null); + localEnv.enclClass.sym = syms.errSymbol; + return tree.accept(identAttributer, localEnv); + } + // where + private TreeVisitor> identAttributer = new IdentAttributer(); + private class IdentAttributer extends SimpleTreeVisitor> { + @Override + public Symbol visitMemberSelect(MemberSelectTree node, Env env) { + Symbol site = visit(node.getExpression(), env); + if (site.kind == ERR) + return site; + Name name = (Name)node.getIdentifier(); + if (site.kind == PCK) { + env.toplevel.packge = (PackageSymbol)site; + return rs.findIdentInPackage(env, (TypeSymbol)site, name, TYP | PCK); + } else { + env.enclClass.sym = (ClassSymbol)site; + return rs.findMemberType(env, site.asType(), name, (TypeSymbol)site); + } + } + + @Override + public Symbol visitIdentifier(IdentifierTree node, Env env) { + return rs.findIdent(env, (Name)node.getName(), TYP | PCK); + } + } + + public Type coerce(Type etype, Type ttype) { + return cfolder.coerce(etype, ttype); + } + + public Type attribType(JCTree node, TypeSymbol sym) { + Env env = enter.typeEnvs.get(sym); + Env localEnv = env.dup(node, env.info.dup()); + return attribTree(node, localEnv, Kinds.TYP, Type.noType); + } + + public Env attribExprToTree(JCTree expr, Env env, JCTree tree) { + breakTree = tree; + JavaFileObject prev = log.useSource(env.toplevel.sourcefile); + try { + attribExpr(expr, env); + } catch (BreakAttr b) { + return b.env; + } catch (AssertionError ae) { + if (ae.getCause() instanceof BreakAttr) { + return ((BreakAttr)(ae.getCause())).env; + } else { + throw ae; + } + } finally { + breakTree = null; + log.useSource(prev); + } + return env; + } + + public Env attribStatToTree(JCTree stmt, Env env, JCTree tree) { + breakTree = tree; + JavaFileObject prev = log.useSource(env.toplevel.sourcefile); + try { + attribStat(stmt, env); + } catch (BreakAttr b) { + return b.env; + } catch (AssertionError ae) { + if (ae.getCause() instanceof BreakAttr) { + return ((BreakAttr)(ae.getCause())).env; + } else { + throw ae; + } + } finally { + breakTree = null; + log.useSource(prev); + } + return env; + } + + private JCTree breakTree = null; + + private static class BreakAttr extends RuntimeException { + static final long serialVersionUID = -6924771130405446405L; + private Env env; + private BreakAttr(Env env) { + this.env = env; + } + } + + +/* ************************************************************************ + * Visitor methods + *************************************************************************/ + + /** Visitor argument: the current environment. + */ + Env env; + + /** Visitor argument: the currently expected proto-kind. + */ + int pkind; + + /** Visitor argument: the currently expected proto-type. + */ + Type pt; + + /** Visitor argument: the error key to be generated when a type error occurs + */ + String errKey; + + /** Visitor result: the computed type. + */ + Type result; + + /** Visitor method: attribute a tree, catching any completion failure + * exceptions. Return the tree's type. + * + * @param tree The tree to be visited. + * @param env The environment visitor argument. + * @param pkind The protokind visitor argument. + * @param pt The prototype visitor argument. + */ + Type attribTree(JCTree tree, Env env, int pkind, Type pt) { + return attribTree(tree, env, pkind, pt, "incompatible.types"); + } + + Type attribTree(JCTree tree, Env env, int pkind, Type pt, String errKey) { + Env prevEnv = this.env; + int prevPkind = this.pkind; + Type prevPt = this.pt; + String prevErrKey = this.errKey; + try { + this.env = env; + this.pkind = pkind; + this.pt = pt; + this.errKey = errKey; + tree.accept(this); + if (tree == breakTree) + throw new BreakAttr(env); + return result; + } catch (CompletionFailure ex) { + tree.type = syms.errType; + return chk.completionError(tree.pos(), ex); + } finally { + this.env = prevEnv; + this.pkind = prevPkind; + this.pt = prevPt; + this.errKey = prevErrKey; + } + } + + /** Derived visitor method: attribute an expression tree. + */ + public Type attribExpr(JCTree tree, Env env, Type pt) { + return attribTree(tree, env, VAL, pt.tag != ERROR ? pt : Type.noType); + } + + public Type attribExpr(JCTree tree, Env env, Type pt, String key) { + return attribTree(tree, env, VAL, pt.tag != ERROR ? pt : Type.noType, key); + } + + /** Derived visitor method: attribute an expression tree with + * no constraints on the computed type. + */ + Type attribExpr(JCTree tree, Env env) { + return attribTree(tree, env, VAL, Type.noType); + } + + /** Derived visitor method: attribute a type tree. + */ + Type attribType(JCTree tree, Env env) { + Type result = attribType(tree, env, Type.noType); + return result; + } + + /** Derived visitor method: attribute a type tree. + */ + Type attribType(JCTree tree, Env env, Type pt) { + Type result = attribTree(tree, env, TYP, pt); + return result; + } + + /** Derived visitor method: attribute a statement or definition tree. + */ + public Type attribStat(JCTree tree, Env env) { + return attribTree(tree, env, NIL, Type.noType); + } + + /** Attribute a list of expressions, returning a list of types. + */ + List attribExprs(List trees, Env env, Type pt) { + ListBuffer ts = new ListBuffer(); + for (List l = trees; l.nonEmpty(); l = l.tail) + ts.append(attribExpr(l.head, env, pt)); + return ts.toList(); + } + + /** Attribute a list of statements, returning nothing. + */ + void attribStats(List trees, Env env) { + for (List l = trees; l.nonEmpty(); l = l.tail) + attribStat(l.head, env); + } + + /** Attribute the arguments in a method call, returning a list of types. + */ + List attribArgs(List trees, Env env) { + ListBuffer argtypes = new ListBuffer(); + for (List l = trees; l.nonEmpty(); l = l.tail) + argtypes.append(chk.checkNonVoid( + l.head.pos(), types.upperBound(attribTree(l.head, env, VAL, Infer.anyPoly)))); + return argtypes.toList(); + } + + /** Attribute a type argument list, returning a list of types. + * Caller is responsible for calling checkRefTypes. + */ + List attribAnyTypes(List trees, Env env) { + ListBuffer argtypes = new ListBuffer(); + for (List l = trees; l.nonEmpty(); l = l.tail) + argtypes.append(attribType(l.head, env)); + return argtypes.toList(); + } + + /** Attribute a type argument list, returning a list of types. + * Check that all the types are references. + */ + List attribTypes(List trees, Env env) { + List types = attribAnyTypes(trees, env); + return chk.checkRefTypes(trees, types); + } + + /** + * Attribute type variables (of generic classes or methods). + * Compound types are attributed later in attribBounds. + * @param typarams the type variables to enter + * @param env the current environment + */ + void attribTypeVariables(List typarams, Env env) { + for (JCTypeParameter tvar : typarams) { + TypeVar a = (TypeVar)tvar.type; + a.tsym.flags_field |= UNATTRIBUTED; + a.bound = Type.noType; + if (!tvar.bounds.isEmpty()) { + List bounds = List.of(attribType(tvar.bounds.head, env)); + for (JCExpression bound : tvar.bounds.tail) + bounds = bounds.prepend(attribType(bound, env)); + types.setBounds(a, bounds.reverse()); + } else { + // if no bounds are given, assume a single bound of + // java.lang.Object. + types.setBounds(a, List.of(syms.objectType)); + } + a.tsym.flags_field &= ~UNATTRIBUTED; + } + for (JCTypeParameter tvar : typarams) + chk.checkNonCyclic(tvar.pos(), (TypeVar)tvar.type); + attribStats(typarams, env); + } + + void attribBounds(List typarams) { + for (JCTypeParameter typaram : typarams) { + Type bound = typaram.type.getUpperBound(); + if (bound != null && bound.tsym instanceof ClassSymbol) { + ClassSymbol c = (ClassSymbol)bound.tsym; + if ((c.flags_field & COMPOUND) != 0) { + Assert.check((c.flags_field & UNATTRIBUTED) != 0, c); + attribClass(typaram.pos(), c); + } + } + } + } + + /** + * Attribute the type references in a list of annotations. + */ + void attribAnnotationTypes(List annotations, + Env env) { + for (List al = annotations; al.nonEmpty(); al = al.tail) { + JCAnnotation a = al.head; + attribType(a.annotationType, env); + } + } + + /** + * Attribute a "lazy constant value". + * @param env The env for the const value + * @param initializer The initializer for the const value + * @param type The expected type, or null + * @see VarSymbol#setlazyConstValue + */ + public Object attribLazyConstantValue(Env env, + JCTree.JCExpression initializer, + Type type) { + + // in case no lint value has been set up for this env, scan up + // env stack looking for smallest enclosing env for which it is set. + Env lintEnv = env; + while (lintEnv.info.lint == null) + lintEnv = lintEnv.next; + + // Having found the enclosing lint value, we can initialize the lint value for this class + env.info.lint = lintEnv.info.lint.augment(env.info.enclVar.attributes_field, env.info.enclVar.flags()); + + Lint prevLint = chk.setLint(env.info.lint); + JavaFileObject prevSource = log.useSource(env.toplevel.sourcefile); + + try { + Type itype = attribExpr(initializer, env, type); + if (itype.constValue() != null) + return coerce(itype, type).constValue(); + else + return null; + } finally { + env.info.lint = prevLint; + log.useSource(prevSource); + } + } + + /** Attribute type reference in an `extends' or `implements' clause. + * Supertypes of anonymous inner classes are usually already attributed. + * + * @param tree The tree making up the type reference. + * @param env The environment current at the reference. + * @param classExpected true if only a class is expected here. + * @param interfaceExpected true if only an interface is expected here. + */ + Type attribBase(JCTree tree, + Env env, + boolean classExpected, + boolean interfaceExpected, + boolean checkExtensible) { + Type t = tree.type != null ? + tree.type : + attribType(tree, env); + return checkBase(t, tree, env, classExpected, interfaceExpected, checkExtensible); + } + Type checkBase(Type t, + JCTree tree, + Env env, + boolean classExpected, + boolean interfaceExpected, + boolean checkExtensible) { + if (t.isErroneous()) + return t; + if (t.tag == TYPEVAR && !classExpected && !interfaceExpected) { + // check that type variable is already visible + if (t.getUpperBound() == null) { + log.error(tree.pos(), "illegal.forward.ref"); + return types.createErrorType(t); + } + } else { + t = chk.checkClassType(tree.pos(), t, checkExtensible|!allowGenerics); + } + if (interfaceExpected && (t.tsym.flags() & INTERFACE) == 0) { + log.error(tree.pos(), "intf.expected.here"); + // return errType is necessary since otherwise there might + // be undetected cycles which cause attribution to loop + return types.createErrorType(t); + } else if (checkExtensible && + classExpected && + (t.tsym.flags() & INTERFACE) != 0) { + log.error(tree.pos(), "no.intf.expected.here"); + return types.createErrorType(t); + } + if (checkExtensible && + ((t.tsym.flags() & FINAL) != 0)) { + log.error(tree.pos(), + "cant.inherit.from.final", t.tsym); + } + chk.checkNonCyclic(tree.pos(), t); + return t; + } + + public void visitClassDef(JCClassDecl tree) { + // Local classes have not been entered yet, so we need to do it now: + if ((env.info.scope.owner.kind & (VAR | MTH)) != 0) + enter.classEnter(tree, env); + + ClassSymbol c = tree.sym; + if (c == null) { + // exit in case something drastic went wrong during enter. + result = null; + } else { + // make sure class has been completed: + c.complete(); + + // If this class appears as an anonymous class + // in a superclass constructor call where + // no explicit outer instance is given, + // disable implicit outer instance from being passed. + // (This would be an illegal access to "this before super"). + if (env.info.isSelfCall && + env.tree.getTag() == JCTree.NEWCLASS && + ((JCNewClass) env.tree).encl == null) + { + c.flags_field |= NOOUTERTHIS; + } + attribClass(tree.pos(), c); + result = tree.type = c.type; + } + } + + public void visitMethodDef(JCMethodDecl tree) { + MethodSymbol m = tree.sym; + + Lint lint = env.info.lint.augment(m.attributes_field, m.flags()); + Lint prevLint = chk.setLint(lint); + MethodSymbol prevMethod = chk.setMethod(m); + try { + deferredLintHandler.flush(tree.pos()); + chk.checkDeprecatedAnnotation(tree.pos(), m); + + attribBounds(tree.typarams); + + // If we override any other methods, check that we do so properly. + // JLS ??? + if (m.isStatic()) { + chk.checkHideClashes(tree.pos(), env.enclClass.type, m); + } else { + chk.checkOverrideClashes(tree.pos(), env.enclClass.type, m); + } + chk.checkOverride(tree, m); + + // Create a new environment with local scope + // for attributing the method. + Env localEnv = memberEnter.methodEnv(tree, env); + + localEnv.info.lint = lint; + + // Enter all type parameters into the local method scope. + for (List l = tree.typarams; l.nonEmpty(); l = l.tail) + localEnv.info.scope.enterIfAbsent(l.head.type.tsym); + + ClassSymbol owner = env.enclClass.sym; + if ((owner.flags() & ANNOTATION) != 0 && + tree.params.nonEmpty()) + log.error(tree.params.head.pos(), + "intf.annotation.members.cant.have.params"); + + // Attribute all value parameters. + for (List l = tree.params; l.nonEmpty(); l = l.tail) { + attribStat(l.head, localEnv); + } + + chk.checkVarargsMethodDecl(localEnv, tree); + + // Check that type parameters are well-formed. + chk.validate(tree.typarams, localEnv); + + // Check that result type is well-formed. + chk.validate(tree.restype, localEnv); + + // annotation method checks + if ((owner.flags() & ANNOTATION) != 0) { + // annotation method cannot have throws clause + if (tree.thrown.nonEmpty()) { + log.error(tree.thrown.head.pos(), + "throws.not.allowed.in.intf.annotation"); + } + // annotation method cannot declare type-parameters + if (tree.typarams.nonEmpty()) { + log.error(tree.typarams.head.pos(), + "intf.annotation.members.cant.have.type.params"); + } + // validate annotation method's return type (could be an annotation type) + chk.validateAnnotationType(tree.restype); + // ensure that annotation method does not clash with members of Object/Annotation + chk.validateAnnotationMethod(tree.pos(), m); + + if (tree.defaultValue != null) { + // if default value is an annotation, check it is a well-formed + // annotation value (e.g. no duplicate values, no missing values, etc.) + chk.validateAnnotationTree(tree.defaultValue); + } + } + + for (List l = tree.thrown; l.nonEmpty(); l = l.tail) + chk.checkType(l.head.pos(), l.head.type, syms.throwableType); + + if (tree.body == null) { + // Empty bodies are only allowed for + // abstract, native, or interface methods, or for methods + // in a retrofit signature class. + if ((owner.flags() & INTERFACE) == 0 && + (tree.mods.flags & (ABSTRACT | NATIVE)) == 0 && + !relax) + log.error(tree.pos(), "missing.meth.body.or.decl.abstract"); + if (tree.defaultValue != null) { + if ((owner.flags() & ANNOTATION) == 0) + log.error(tree.pos(), + "default.allowed.in.intf.annotation.member"); + } + } else if ((owner.flags() & INTERFACE) != 0) { + log.error(tree.body.pos(), "intf.meth.cant.have.body"); + } else if ((tree.mods.flags & ABSTRACT) != 0) { + log.error(tree.pos(), "abstract.meth.cant.have.body"); + } else if ((tree.mods.flags & NATIVE) != 0) { + log.error(tree.pos(), "native.meth.cant.have.body"); + } else { + // Add an implicit super() call unless an explicit call to + // super(...) or this(...) is given + // or we are compiling class java.lang.Object. + if (tree.name == names.init && owner.type != syms.objectType) { + JCBlock body = tree.body; + if (body.stats.isEmpty() || + !TreeInfo.isSelfCall(body.stats.head)) { + body.stats = body.stats. + prepend(memberEnter.SuperCall(make.at(body.pos), + List.nil(), + List.nil(), + false)); + } else if ((env.enclClass.sym.flags() & ENUM) != 0 && + (tree.mods.flags & GENERATEDCONSTR) == 0 && + TreeInfo.isSuperCall(body.stats.head)) { + // enum constructors are not allowed to call super + // directly, so make sure there aren't any super calls + // in enum constructors, except in the compiler + // generated one. + log.error(tree.body.stats.head.pos(), + "call.to.super.not.allowed.in.enum.ctor", + env.enclClass.sym); + } + } + + // Attribute method body. + attribStat(tree.body, localEnv); + } + localEnv.info.scope.leave(); + result = tree.type = m.type; + chk.validateAnnotations(tree.mods.annotations, m); + } + finally { + chk.setLint(prevLint); + chk.setMethod(prevMethod); + } + } + + public void visitVarDef(JCVariableDecl tree) { + // Local variables have not been entered yet, so we need to do it now: + if (env.info.scope.owner.kind == MTH) { + if (tree.sym != null) { + // parameters have already been entered + env.info.scope.enter(tree.sym); + } else { + memberEnter.memberEnter(tree, env); + annotate.flush(); + } + tree.sym.flags_field |= EFFECTIVELY_FINAL; + } + + VarSymbol v = tree.sym; + Lint lint = env.info.lint.augment(v.attributes_field, v.flags()); + Lint prevLint = chk.setLint(lint); + + // Check that the variable's declared type is well-formed. + chk.validate(tree.vartype, env); + deferredLintHandler.flush(tree.pos()); + + try { + chk.checkDeprecatedAnnotation(tree.pos(), v); + + if (tree.init != null) { + if ((v.flags_field & FINAL) != 0 && tree.init.getTag() != JCTree.NEWCLASS) { + // In this case, `v' is final. Ensure that it's initializer is + // evaluated. + v.getConstValue(); // ensure initializer is evaluated + } else { + // Attribute initializer in a new environment + // with the declared variable as owner. + // Check that initializer conforms to variable's declared type. + Env initEnv = memberEnter.initEnv(tree, env); + initEnv.info.lint = lint; + // In order to catch self-references, we set the variable's + // declaration position to maximal possible value, effectively + // marking the variable as undefined. + initEnv.info.enclVar = v; + attribExpr(tree.init, initEnv, v.type); + } + } + result = tree.type = v.type; + chk.validateAnnotations(tree.mods.annotations, v); + } + finally { + chk.setLint(prevLint); + } + } + + public void visitSkip(JCSkip tree) { + result = null; + } + + public void visitBlock(JCBlock tree) { + if (env.info.scope.owner.kind == TYP) { + // Block is a static or instance initializer; + // let the owner of the environment be a freshly + // created BLOCK-method. + Env localEnv = + env.dup(tree, env.info.dup(env.info.scope.dupUnshared())); + localEnv.info.scope.owner = + new MethodSymbol(tree.flags | BLOCK, names.empty, null, + env.info.scope.owner); + if ((tree.flags & STATIC) != 0) localEnv.info.staticLevel++; + attribStats(tree.stats, localEnv); + } else { + // Create a new local environment with a local scope. + Env localEnv = + env.dup(tree, env.info.dup(env.info.scope.dup())); + attribStats(tree.stats, localEnv); + localEnv.info.scope.leave(); + } + result = null; + } + + public void visitDoLoop(JCDoWhileLoop tree) { + attribStat(tree.body, env.dup(tree)); + attribExpr(tree.cond, env, syms.booleanType); + result = null; + } + + public void visitWhileLoop(JCWhileLoop tree) { + attribExpr(tree.cond, env, syms.booleanType); + attribStat(tree.body, env.dup(tree)); + result = null; + } + + public void visitForLoop(JCForLoop tree) { + Env loopEnv = + env.dup(env.tree, env.info.dup(env.info.scope.dup())); + attribStats(tree.init, loopEnv); + if (tree.cond != null) attribExpr(tree.cond, loopEnv, syms.booleanType); + loopEnv.tree = tree; // before, we were not in loop! + attribStats(tree.step, loopEnv); + attribStat(tree.body, loopEnv); + loopEnv.info.scope.leave(); + result = null; + } + + public void visitForeachLoop(JCEnhancedForLoop tree) { + Env loopEnv = + env.dup(env.tree, env.info.dup(env.info.scope.dup())); + attribStat(tree.var, loopEnv); + Type exprType = types.upperBound(attribExpr(tree.expr, loopEnv)); + chk.checkNonVoid(tree.pos(), exprType); + Type elemtype = types.elemtype(exprType); // perhaps expr is an array? + if (elemtype == null) { + // or perhaps expr implements Iterable? + Type base = types.asSuper(exprType, syms.iterableType.tsym); + if (base == null) { + log.error(tree.expr.pos(), + "foreach.not.applicable.to.type", + exprType, + diags.fragment("type.req.array.or.iterable")); + elemtype = types.createErrorType(exprType); + } else { + List iterableParams = base.allparams(); + elemtype = iterableParams.isEmpty() + ? syms.objectType + : types.upperBound(iterableParams.head); + } + } + chk.checkType(tree.expr.pos(), elemtype, tree.var.sym.type); + loopEnv.tree = tree; // before, we were not in loop! + attribStat(tree.body, loopEnv); + loopEnv.info.scope.leave(); + result = null; + } + + public void visitLabelled(JCLabeledStatement tree) { + // Check that label is not used in an enclosing statement + Env env1 = env; + while (env1 != null && env1.tree.getTag() != JCTree.CLASSDEF) { + if (env1.tree.getTag() == JCTree.LABELLED && + ((JCLabeledStatement) env1.tree).label == tree.label) { + log.error(tree.pos(), "label.already.in.use", + tree.label); + break; + } + env1 = env1.next; + } + + attribStat(tree.body, env.dup(tree)); + result = null; + } + + public void visitSwitch(JCSwitch tree) { + Type seltype = attribExpr(tree.selector, env); + + Env switchEnv = + env.dup(tree, env.info.dup(env.info.scope.dup())); + + boolean enumSwitch = + allowEnums && + (seltype.tsym.flags() & Flags.ENUM) != 0; + boolean stringSwitch = false; + if (types.isSameType(seltype, syms.stringType)) { + if (allowStringsInSwitch) { + stringSwitch = true; + } else { + log.error(tree.selector.pos(), "string.switch.not.supported.in.source", sourceName); + } + } + if (!enumSwitch && !stringSwitch) + seltype = chk.checkType(tree.selector.pos(), seltype, syms.intType); + + // Attribute all cases and + // check that there are no duplicate case labels or default clauses. + Set labels = new HashSet(); // The set of case labels. + boolean hasDefault = false; // Is there a default label? + for (List l = tree.cases; l.nonEmpty(); l = l.tail) { + JCCase c = l.head; + Env caseEnv = + switchEnv.dup(c, env.info.dup(switchEnv.info.scope.dup())); + if (c.pat != null) { + if (enumSwitch) { + Symbol sym = enumConstant(c.pat, seltype); + if (sym == null) { + log.error(c.pat.pos(), "enum.label.must.be.unqualified.enum"); + } else if (!labels.add(sym)) { + log.error(c.pos(), "duplicate.case.label"); + } + } else { + Type pattype = attribExpr(c.pat, switchEnv, seltype); + if (pattype.tag != ERROR) { + if (pattype.constValue() == null) { + log.error(c.pat.pos(), + (stringSwitch ? "string.const.req" : "const.expr.req")); + } else if (labels.contains(pattype.constValue())) { + log.error(c.pos(), "duplicate.case.label"); + } else { + labels.add(pattype.constValue()); + } + } + } + } else if (hasDefault) { + log.error(c.pos(), "duplicate.default.label"); + } else { + hasDefault = true; + } + attribStats(c.stats, caseEnv); + caseEnv.info.scope.leave(); + addVars(c.stats, switchEnv.info.scope); + } + + switchEnv.info.scope.leave(); + result = null; + } + // where + /** Add any variables defined in stats to the switch scope. */ + private static void addVars(List stats, Scope switchScope) { + for (;stats.nonEmpty(); stats = stats.tail) { + JCTree stat = stats.head; + if (stat.getTag() == JCTree.VARDEF) + switchScope.enter(((JCVariableDecl) stat).sym); + } + } + // where + /** Return the selected enumeration constant symbol, or null. */ + private Symbol enumConstant(JCTree tree, Type enumType) { + if (tree.getTag() != JCTree.IDENT) { + log.error(tree.pos(), "enum.label.must.be.unqualified.enum"); + return syms.errSymbol; + } + JCIdent ident = (JCIdent)tree; + Name name = ident.name; + for (Scope.Entry e = enumType.tsym.members().lookup(name); + e.scope != null; e = e.next()) { + if (e.sym.kind == VAR) { + Symbol s = ident.sym = e.sym; + ((VarSymbol)s).getConstValue(); // ensure initializer is evaluated + ident.type = s.type; + return ((s.flags_field & Flags.ENUM) == 0) + ? null : s; + } + } + return null; + } + + public void visitSynchronized(JCSynchronized tree) { + chk.checkRefType(tree.pos(), attribExpr(tree.lock, env)); + attribStat(tree.body, env); + result = null; + } + + public void visitTry(JCTry tree) { + // Create a new local environment with a local + Env localEnv = env.dup(tree, env.info.dup(env.info.scope.dup())); + boolean isTryWithResource = tree.resources.nonEmpty(); + // Create a nested environment for attributing the try block if needed + Env tryEnv = isTryWithResource ? + env.dup(tree, localEnv.info.dup(localEnv.info.scope.dup())) : + localEnv; + // Attribute resource declarations + for (JCTree resource : tree.resources) { + if (resource.getTag() == JCTree.VARDEF) { + attribStat(resource, tryEnv); + chk.checkType(resource, resource.type, syms.autoCloseableType, "try.not.applicable.to.type"); + + //check that resource type cannot throw InterruptedException + checkAutoCloseable(resource.pos(), localEnv, resource.type); + + VarSymbol var = (VarSymbol)TreeInfo.symbolFor(resource); + var.setData(ElementKind.RESOURCE_VARIABLE); + } else { + attribExpr(resource, tryEnv, syms.autoCloseableType, "try.not.applicable.to.type"); + } + } + // Attribute body + attribStat(tree.body, tryEnv); + if (isTryWithResource) + tryEnv.info.scope.leave(); + + // Attribute catch clauses + for (List l = tree.catchers; l.nonEmpty(); l = l.tail) { + JCCatch c = l.head; + Env catchEnv = + localEnv.dup(c, localEnv.info.dup(localEnv.info.scope.dup())); + Type ctype = attribStat(c.param, catchEnv); + if (TreeInfo.isMultiCatch(c)) { + //multi-catch parameter is implicitly marked as final + c.param.sym.flags_field |= FINAL | UNION; + } + if (c.param.sym.kind == Kinds.VAR) { + c.param.sym.setData(ElementKind.EXCEPTION_PARAMETER); + } + chk.checkType(c.param.vartype.pos(), + chk.checkClassType(c.param.vartype.pos(), ctype), + syms.throwableType); + attribStat(c.body, catchEnv); + catchEnv.info.scope.leave(); + } + + // Attribute finalizer + if (tree.finalizer != null) attribStat(tree.finalizer, localEnv); + + localEnv.info.scope.leave(); + result = null; + } + + void checkAutoCloseable(DiagnosticPosition pos, Env env, Type resource) { + if (!resource.isErroneous() && + types.asSuper(resource, syms.autoCloseableType.tsym) != null) { + Symbol close = syms.noSymbol; + boolean prevDeferDiags = log.deferDiagnostics; + Queue prevDeferredDiags = log.deferredDiagnostics; + try { + log.deferDiagnostics = true; + log.deferredDiagnostics = ListBuffer.lb(); + close = rs.resolveQualifiedMethod(pos, + env, + resource, + names.close, + List.nil(), + List.nil()); + } + finally { + log.deferDiagnostics = prevDeferDiags; + log.deferredDiagnostics = prevDeferredDiags; + } + if (close.kind == MTH && + close.overrides(syms.autoCloseableClose, resource.tsym, types, true) && + chk.isHandled(syms.interruptedExceptionType, types.memberType(resource, close).getThrownTypes()) && + env.info.lint.isEnabled(LintCategory.TRY)) { + log.warning(LintCategory.TRY, pos, "try.resource.throws.interrupted.exc", resource); + } + } + } + + public void visitConditional(JCConditional tree) { + attribExpr(tree.cond, env, syms.booleanType); + attribExpr(tree.truepart, env); + attribExpr(tree.falsepart, env); + result = check(tree, + capture(condType(tree.pos(), tree.cond.type, + tree.truepart.type, tree.falsepart.type)), + VAL, pkind, pt); + } + //where + /** Compute the type of a conditional expression, after + * checking that it exists. See Spec 15.25. + * + * @param pos The source position to be used for + * error diagnostics. + * @param condtype The type of the expression's condition. + * @param thentype The type of the expression's then-part. + * @param elsetype The type of the expression's else-part. + */ + private Type condType(DiagnosticPosition pos, + Type condtype, + Type thentype, + Type elsetype) { + Type ctype = condType1(pos, condtype, thentype, elsetype); + + // If condition and both arms are numeric constants, + // evaluate at compile-time. + return ((condtype.constValue() != null) && + (thentype.constValue() != null) && + (elsetype.constValue() != null)) + ? cfolder.coerce(condtype.isTrue()?thentype:elsetype, ctype) + : ctype; + } + /** Compute the type of a conditional expression, after + * checking that it exists. Does not take into + * account the special case where condition and both arms + * are constants. + * + * @param pos The source position to be used for error + * diagnostics. + * @param condtype The type of the expression's condition. + * @param thentype The type of the expression's then-part. + * @param elsetype The type of the expression's else-part. + */ + private Type condType1(DiagnosticPosition pos, Type condtype, + Type thentype, Type elsetype) { + // If same type, that is the result + if (types.isSameType(thentype, elsetype)) + return thentype.baseType(); + + Type thenUnboxed = (!allowBoxing || thentype.isPrimitive()) + ? thentype : types.unboxedType(thentype); + Type elseUnboxed = (!allowBoxing || elsetype.isPrimitive()) + ? elsetype : types.unboxedType(elsetype); + + // Otherwise, if both arms can be converted to a numeric + // type, return the least numeric type that fits both arms + // (i.e. return larger of the two, or return int if one + // arm is short, the other is char). + if (thenUnboxed.isPrimitive() && elseUnboxed.isPrimitive()) { + // If one arm has an integer subrange type (i.e., byte, + // short, or char), and the other is an integer constant + // that fits into the subrange, return the subrange type. + if (thenUnboxed.tag < INT && elseUnboxed.tag == INT && + types.isAssignable(elseUnboxed, thenUnboxed)) + return thenUnboxed.baseType(); + if (elseUnboxed.tag < INT && thenUnboxed.tag == INT && + types.isAssignable(thenUnboxed, elseUnboxed)) + return elseUnboxed.baseType(); + + for (int i = BYTE; i < VOID; i++) { + Type candidate = syms.typeOfTag[i]; + if (types.isSubtype(thenUnboxed, candidate) && + types.isSubtype(elseUnboxed, candidate)) + return candidate; + } + } + + // Those were all the cases that could result in a primitive + if (allowBoxing) { + if (thentype.isPrimitive()) + thentype = types.boxedClass(thentype).type; + if (elsetype.isPrimitive()) + elsetype = types.boxedClass(elsetype).type; + } + + if (types.isSubtype(thentype, elsetype)) + return elsetype.baseType(); + if (types.isSubtype(elsetype, thentype)) + return thentype.baseType(); + + if (!allowBoxing || thentype.tag == VOID || elsetype.tag == VOID) { + log.error(pos, "neither.conditional.subtype", + thentype, elsetype); + return thentype.baseType(); + } + + // both are known to be reference types. The result is + // lub(thentype,elsetype). This cannot fail, as it will + // always be possible to infer "Object" if nothing better. + return types.lub(thentype.baseType(), elsetype.baseType()); + } + + public void visitIf(JCIf tree) { + attribExpr(tree.cond, env, syms.booleanType); + attribStat(tree.thenpart, env); + if (tree.elsepart != null) + attribStat(tree.elsepart, env); + chk.checkEmptyIf(tree); + result = null; + } + + public void visitExec(JCExpressionStatement tree) { + //a fresh environment is required for 292 inference to work properly --- + //see Infer.instantiatePolymorphicSignatureInstance() + Env localEnv = env.dup(tree); + attribExpr(tree.expr, localEnv); + result = null; + } + + public void visitBreak(JCBreak tree) { + tree.target = findJumpTarget(tree.pos(), tree.getTag(), tree.label, env); + result = null; + } + + public void visitContinue(JCContinue tree) { + tree.target = findJumpTarget(tree.pos(), tree.getTag(), tree.label, env); + result = null; + } + //where + /** Return the target of a break or continue statement, if it exists, + * report an error if not. + * Note: The target of a labelled break or continue is the + * (non-labelled) statement tree referred to by the label, + * not the tree representing the labelled statement itself. + * + * @param pos The position to be used for error diagnostics + * @param tag The tag of the jump statement. This is either + * Tree.BREAK or Tree.CONTINUE. + * @param label The label of the jump statement, or null if no + * label is given. + * @param env The environment current at the jump statement. + */ + private JCTree findJumpTarget(DiagnosticPosition pos, + int tag, + Name label, + Env env) { + // Search environments outwards from the point of jump. + Env env1 = env; + LOOP: + while (env1 != null) { + switch (env1.tree.getTag()) { + case JCTree.LABELLED: + JCLabeledStatement labelled = (JCLabeledStatement)env1.tree; + if (label == labelled.label) { + // If jump is a continue, check that target is a loop. + if (tag == JCTree.CONTINUE) { + if (labelled.body.getTag() != JCTree.DOLOOP && + labelled.body.getTag() != JCTree.WHILELOOP && + labelled.body.getTag() != JCTree.FORLOOP && + labelled.body.getTag() != JCTree.FOREACHLOOP) + log.error(pos, "not.loop.label", label); + // Found labelled statement target, now go inwards + // to next non-labelled tree. + return TreeInfo.referencedStatement(labelled); + } else { + return labelled; + } + } + break; + case JCTree.DOLOOP: + case JCTree.WHILELOOP: + case JCTree.FORLOOP: + case JCTree.FOREACHLOOP: + if (label == null) return env1.tree; + break; + case JCTree.SWITCH: + if (label == null && tag == JCTree.BREAK) return env1.tree; + break; + case JCTree.METHODDEF: + case JCTree.CLASSDEF: + break LOOP; + default: + } + env1 = env1.next; + } + if (label != null) + log.error(pos, "undef.label", label); + else if (tag == JCTree.CONTINUE) + log.error(pos, "cont.outside.loop"); + else + log.error(pos, "break.outside.switch.loop"); + return null; + } + + public void visitReturn(JCReturn tree) { + // Check that there is an enclosing method which is + // nested within than the enclosing class. + if (env.enclMethod == null || + env.enclMethod.sym.owner != env.enclClass.sym) { + log.error(tree.pos(), "ret.outside.meth"); + + } else { + // Attribute return expression, if it exists, and check that + // it conforms to result type of enclosing method. + Symbol m = env.enclMethod.sym; + if (m.type.getReturnType().tag == VOID) { + if (tree.expr != null) + log.error(tree.expr.pos(), + "cant.ret.val.from.meth.decl.void"); + } else if (tree.expr == null) { + log.error(tree.pos(), "missing.ret.val"); + } else { + attribExpr(tree.expr, env, m.type.getReturnType()); + } + } + result = null; + } + + public void visitThrow(JCThrow tree) { + attribExpr(tree.expr, env, syms.throwableType); + result = null; + } + + public void visitAssert(JCAssert tree) { + attribExpr(tree.cond, env, syms.booleanType); + if (tree.detail != null) { + chk.checkNonVoid(tree.detail.pos(), attribExpr(tree.detail, env)); + } + result = null; + } + + /** Visitor method for method invocations. + * NOTE: The method part of an application will have in its type field + * the return type of the method, not the method's type itself! + */ + public void visitApply(JCMethodInvocation tree) { + // The local environment of a method application is + // a new environment nested in the current one. + Env localEnv = env.dup(tree, env.info.dup()); + + // The types of the actual method arguments. + List argtypes; + + // The types of the actual method type arguments. + List typeargtypes = null; + + Name methName = TreeInfo.name(tree.meth); + + boolean isConstructorCall = + methName == names._this || methName == names._super; + + if (isConstructorCall) { + // We are seeing a ...this(...) or ...super(...) call. + // Check that this is the first statement in a constructor. + if (checkFirstConstructorStat(tree, env)) { + + // Record the fact + // that this is a constructor call (using isSelfCall). + localEnv.info.isSelfCall = true; + + // Attribute arguments, yielding list of argument types. + argtypes = attribArgs(tree.args, localEnv); + typeargtypes = attribTypes(tree.typeargs, localEnv); + + // Variable `site' points to the class in which the called + // constructor is defined. + Type site = env.enclClass.sym.type; + if (methName == names._super) { + if (site == syms.objectType) { + log.error(tree.meth.pos(), "no.superclass", site); + site = types.createErrorType(syms.objectType); + } else { + site = types.supertype(site); + } + } + + if (site.tag == CLASS) { + Type encl = site.getEnclosingType(); + while (encl != null && encl.tag == TYPEVAR) + encl = encl.getUpperBound(); + if (encl.tag == CLASS) { + // we are calling a nested class + + if (tree.meth.getTag() == JCTree.SELECT) { + JCTree qualifier = ((JCFieldAccess) tree.meth).selected; + + // We are seeing a prefixed call, of the form + // .super(...). + // Check that the prefix expression conforms + // to the outer instance type of the class. + chk.checkRefType(qualifier.pos(), + attribExpr(qualifier, localEnv, + encl)); + } else if (methName == names._super) { + // qualifier omitted; check for existence + // of an appropriate implicit qualifier. + rs.resolveImplicitThis(tree.meth.pos(), + localEnv, site, true); + } + } else if (tree.meth.getTag() == JCTree.SELECT) { + log.error(tree.meth.pos(), "illegal.qual.not.icls", + site.tsym); + } + + // if we're calling a java.lang.Enum constructor, + // prefix the implicit String and int parameters + if (site.tsym == syms.enumSym && allowEnums) + argtypes = argtypes.prepend(syms.intType).prepend(syms.stringType); + + // Resolve the called constructor under the assumption + // that we are referring to a superclass instance of the + // current instance (JLS ???). + boolean selectSuperPrev = localEnv.info.selectSuper; + localEnv.info.selectSuper = true; + localEnv.info.varArgs = false; + Symbol sym = rs.resolveConstructor( + tree.meth.pos(), localEnv, site, argtypes, typeargtypes); + localEnv.info.selectSuper = selectSuperPrev; + + // Set method symbol to resolved constructor... + TreeInfo.setSymbol(tree.meth, sym); + + // ...and check that it is legal in the current context. + // (this will also set the tree's type) + Type mpt = newMethTemplate(argtypes, typeargtypes); + checkId(tree.meth, site, sym, localEnv, MTH, + mpt, tree.varargsElement != null); + } + // Otherwise, `site' is an error type and we do nothing + } + result = tree.type = syms.voidType; + } else { + // Otherwise, we are seeing a regular method call. + // Attribute the arguments, yielding list of argument types, ... + argtypes = attribArgs(tree.args, localEnv); + typeargtypes = attribAnyTypes(tree.typeargs, localEnv); + + // ... and attribute the method using as a prototype a methodtype + // whose formal argument types is exactly the list of actual + // arguments (this will also set the method symbol). + Type mpt = newMethTemplate(argtypes, typeargtypes); + localEnv.info.varArgs = false; + Type mtype = attribExpr(tree.meth, localEnv, mpt); + if (localEnv.info.varArgs) + Assert.check(mtype.isErroneous() || tree.varargsElement != null); + + // Compute the result type. + Type restype = mtype.getReturnType(); + if (restype.tag == WILDCARD) + throw new AssertionError(mtype); + + // as a special case, array.clone() has a result that is + // the same as static type of the array being cloned + if (tree.meth.getTag() == JCTree.SELECT && + allowCovariantReturns && + methName == names.clone && + types.isArray(((JCFieldAccess) tree.meth).selected.type)) + restype = ((JCFieldAccess) tree.meth).selected.type; + + // as a special case, x.getClass() has type Class + if (allowGenerics && + methName == names.getClass && tree.args.isEmpty()) { + Type qualifier = (tree.meth.getTag() == JCTree.SELECT) + ? ((JCFieldAccess) tree.meth).selected.type + : env.enclClass.sym.type; + restype = new + ClassType(restype.getEnclosingType(), + List.of(new WildcardType(types.erasure(qualifier), + BoundKind.EXTENDS, + syms.boundClass)), + restype.tsym); + } + + chk.checkRefTypes(tree.typeargs, typeargtypes); + + // Check that value of resulting type is admissible in the + // current context. Also, capture the return type + result = check(tree, capture(restype), VAL, pkind, pt); + + //M + //---------------------------------------- + Symbol meth = TreeInfo.symbol(tree.meth); + if(meth.owner != null && + types.isSubtype(meth.owner.type, syms.viewManagerType)) { + if(tree.meth.getTag() == JCTree.SELECT && meth.name.toString().equals("out")) { + ListBuffer varList = new ListBuffer(); //M + + for(Symbol s : localEnv.info.scope.getElements()) { + if(s != null && s.kind == VAR) { + VarSymbol var = (VarSymbol)s; + if(var.name == names._this + || var.name == names._super + || (s.flags() & SYNTHETIC) != 0) + continue; + varList.prepend(var); + } + } + + outVarList.add(varList); + } + } + } + chk.validate(tree.typeargs, localEnv); + } + + java.util.LinkedList> outVarList = + new java.util.LinkedList>(); //M + + //where + /** Check that given application node appears as first statement + * in a constructor call. + * @param tree The application node + * @param env The environment current at the application. + */ + boolean checkFirstConstructorStat(JCMethodInvocation tree, Env env) { + JCMethodDecl enclMethod = env.enclMethod; + if (enclMethod != null && enclMethod.name == names.init) { + JCBlock body = enclMethod.body; + if (body.stats.head.getTag() == JCTree.EXEC && + ((JCExpressionStatement) body.stats.head).expr == tree) + return true; + } + log.error(tree.pos(),"call.must.be.first.stmt.in.ctor", + TreeInfo.name(tree.meth)); + return false; + } + + /** Obtain a method type with given argument types. + */ + Type newMethTemplate(List argtypes, List typeargtypes) { + MethodType mt = new MethodType(argtypes, null, null, syms.methodClass); + return (typeargtypes == null) ? mt : (Type)new ForAll(typeargtypes, mt); + } + + public void visitNewClass(JCNewClass tree) { + Type owntype = types.createErrorType(tree.type); + + // The local environment of a class creation is + // a new environment nested in the current one. + Env localEnv = env.dup(tree, env.info.dup()); + + // The anonymous inner class definition of the new expression, + // if one is defined by it. + JCClassDecl cdef = tree.def; + + // If enclosing class is given, attribute it, and + // complete class name to be fully qualified + JCExpression clazz = tree.clazz; // Class field following new + JCExpression clazzid = // Identifier in class field + (clazz.getTag() == JCTree.TYPEAPPLY) + ? ((JCTypeApply) clazz).clazz + : clazz; + + JCExpression clazzid1 = clazzid; // The same in fully qualified form + + if (tree.encl != null) { + // We are seeing a qualified new, of the form + // .new C <...> (...) ... + // In this case, we let clazz stand for the name of the + // allocated class C prefixed with the type of the qualifier + // expression, so that we can + // resolve it with standard techniques later. I.e., if + // has type T, then .new C <...> (...) + // yields a clazz T.C. + Type encltype = chk.checkRefType(tree.encl.pos(), + attribExpr(tree.encl, env)); + clazzid1 = make.at(clazz.pos).Select(make.Type(encltype), + ((JCIdent) clazzid).name); + if (clazz.getTag() == JCTree.TYPEAPPLY) + clazz = make.at(tree.pos). + TypeApply(clazzid1, + ((JCTypeApply) clazz).arguments); + else + clazz = clazzid1; + } + + // Attribute clazz expression and store + // symbol + type back into the attributed tree. + Type clazztype = attribType(clazz, env); + Pair mapping = getSyntheticScopeMapping(clazztype); + clazztype = chk.checkDiamond(tree, clazztype); + chk.validate(clazz, localEnv); + if (tree.encl != null) { + // We have to work in this case to store + // symbol + type back into the attributed tree. + tree.clazz.type = clazztype; + TreeInfo.setSymbol(clazzid, TreeInfo.symbol(clazzid1)); + clazzid.type = ((JCIdent) clazzid).sym.type; + if (!clazztype.isErroneous()) { + if (cdef != null && clazztype.tsym.isInterface()) { + log.error(tree.encl.pos(), "anon.class.impl.intf.no.qual.for.new"); + } else if (clazztype.tsym.isStatic()) { + log.error(tree.encl.pos(), "qualified.new.of.static.class", clazztype.tsym); + } + } + } else if (!clazztype.tsym.isInterface() && + clazztype.getEnclosingType().tag == CLASS) { + // Check for the existence of an apropos outer instance + rs.resolveImplicitThis(tree.pos(), env, clazztype); + } + + // Attribute constructor arguments. + List argtypes = attribArgs(tree.args, localEnv); + List typeargtypes = attribTypes(tree.typeargs, localEnv); + + if (TreeInfo.isDiamond(tree) && !clazztype.isErroneous()) { + clazztype = attribDiamond(localEnv, tree, clazztype, mapping, argtypes, typeargtypes); + clazz.type = clazztype; + } else if (allowDiamondFinder && + tree.def == null && + !clazztype.isErroneous() && + clazztype.getTypeArguments().nonEmpty() && + findDiamonds) { + boolean prevDeferDiags = log.deferDiagnostics; + Queue prevDeferredDiags = log.deferredDiagnostics; + Type inferred = null; + try { + //disable diamond-related diagnostics + log.deferDiagnostics = true; + log.deferredDiagnostics = ListBuffer.lb(); + inferred = attribDiamond(localEnv, + tree, + clazztype, + mapping, + argtypes, + typeargtypes); + } + finally { + log.deferDiagnostics = prevDeferDiags; + log.deferredDiagnostics = prevDeferredDiags; + } + if (inferred != null && + !inferred.isErroneous() && + inferred.tag == CLASS && + types.isAssignable(inferred, pt.tag == NONE ? clazztype : pt, Warner.noWarnings)) { + String key = types.isSameType(clazztype, inferred) ? + "diamond.redundant.args" : + "diamond.redundant.args.1"; + log.warning(tree.clazz.pos(), key, clazztype, inferred); + } + } + + // If we have made no mistakes in the class type... + if (clazztype.tag == CLASS) { + // Enums may not be instantiated except implicitly + if (allowEnums && + (clazztype.tsym.flags_field&Flags.ENUM) != 0 && + (env.tree.getTag() != JCTree.VARDEF || + (((JCVariableDecl) env.tree).mods.flags&Flags.ENUM) == 0 || + ((JCVariableDecl) env.tree).init != tree)) + log.error(tree.pos(), "enum.cant.be.instantiated"); + // Check that class is not abstract + if (cdef == null && + (clazztype.tsym.flags() & (ABSTRACT | INTERFACE)) != 0) { + log.error(tree.pos(), "abstract.cant.be.instantiated", + clazztype.tsym); + } else if (cdef != null && clazztype.tsym.isInterface()) { + // Check that no constructor arguments are given to + // anonymous classes implementing an interface + if (!argtypes.isEmpty()) + log.error(tree.args.head.pos(), "anon.class.impl.intf.no.args"); + + if (!typeargtypes.isEmpty()) + log.error(tree.typeargs.head.pos(), "anon.class.impl.intf.no.typeargs"); + + // Error recovery: pretend no arguments were supplied. + argtypes = List.nil(); + typeargtypes = List.nil(); + } + + // Resolve the called constructor under the assumption + // that we are referring to a superclass instance of the + // current instance (JLS ???). + else { + //the following code alters some of the fields in the current + //AttrContext - hence, the current context must be dup'ed in + //order to avoid downstream failures + Env rsEnv = localEnv.dup(tree); + rsEnv.info.selectSuper = cdef != null; + rsEnv.info.varArgs = false; + tree.constructor = rs.resolveConstructor( + tree.pos(), rsEnv, clazztype, argtypes, typeargtypes); + tree.constructorType = tree.constructor.type.isErroneous() ? + syms.errType : + checkMethod(clazztype, + tree.constructor, + rsEnv, + tree.args, + argtypes, + typeargtypes, + rsEnv.info.varArgs); + if (rsEnv.info.varArgs) + Assert.check(tree.constructorType.isErroneous() || tree.varargsElement != null); + } + + if (cdef != null) { + // We are seeing an anonymous class instance creation. + // In this case, the class instance creation + // expression + // + // E.new C(args) { ... } + // + // is represented internally as + // + // E . new C(args) ( class { ... } ) . + // + // This expression is then *transformed* as follows: + // + // (1) add a STATIC flag to the class definition + // if the current environment is static + // (2) add an extends or implements clause + // (3) add a constructor. + // + // For instance, if C is a class, and ET is the type of E, + // the expression + // + // E.new C(args) { ... } + // + // is translated to (where X is a fresh name and typarams is the + // parameter list of the super constructor): + // + // new X(<*nullchk*>E, args) where + // X extends C { + // X(ET e, args) { + // e.super(args) + // } + // ... + // } + if (Resolve.isStatic(env)) cdef.mods.flags |= STATIC; + + if (clazztype.tsym.isInterface()) { + cdef.implementing = List.of(clazz); + } else { + cdef.extending = clazz; + } + + attribStat(cdef, localEnv); + + // If an outer instance is given, + // prefix it to the constructor arguments + // and delete it from the new expression + if (tree.encl != null && !clazztype.tsym.isInterface()) { + tree.args = tree.args.prepend(makeNullCheck(tree.encl)); + argtypes = argtypes.prepend(tree.encl.type); + tree.encl = null; + } + + // Reassign clazztype and recompute constructor. + clazztype = cdef.sym.type; + boolean useVarargs = tree.varargsElement != null; + Symbol sym = rs.resolveConstructor( + tree.pos(), localEnv, clazztype, argtypes, + typeargtypes, true, useVarargs); + Assert.check(sym.kind < AMBIGUOUS || tree.constructor.type.isErroneous()); + tree.constructor = sym; + if (tree.constructor.kind > ERRONEOUS) { + tree.constructorType = syms.errType; + } + else { + tree.constructorType = checkMethod(clazztype, + tree.constructor, + localEnv, + tree.args, + argtypes, + typeargtypes, + useVarargs); + } + } + + if (tree.constructor != null && tree.constructor.kind == MTH) + owntype = clazztype; + } + result = check(tree, owntype, VAL, pkind, pt); + chk.validate(tree.typeargs, localEnv); + } + + Type attribDiamond(Env env, + JCNewClass tree, + Type clazztype, + Pair mapping, + List argtypes, + List typeargtypes) { + if (clazztype.isErroneous() || + clazztype.isInterface() || + mapping == erroneousMapping) { + //if the type of the instance creation expression is erroneous, + //or if it's an interface, or if something prevented us to form a valid + //mapping, return the (possibly erroneous) type unchanged + return clazztype; + } + + //dup attribution environment and augment the set of inference variables + Env localEnv = env.dup(tree); + localEnv.info.tvars = clazztype.tsym.type.getTypeArguments(); + + //if the type of the instance creation expression is a class type + //apply method resolution inference (JLS 15.12.2.7). The return type + //of the resolved constructor will be a partially instantiated type + ((ClassSymbol) clazztype.tsym).members_field = mapping.snd; + Symbol constructor; + try { + constructor = rs.resolveDiamond(tree.pos(), + localEnv, + clazztype.tsym.type, + argtypes, + typeargtypes); + } finally { + ((ClassSymbol) clazztype.tsym).members_field = mapping.fst; + } + if (constructor.kind == MTH) { + ClassType ct = new ClassType(clazztype.getEnclosingType(), + clazztype.tsym.type.getTypeArguments(), + clazztype.tsym); + clazztype = checkMethod(ct, + constructor, + localEnv, + tree.args, + argtypes, + typeargtypes, + localEnv.info.varArgs).getReturnType(); + } else { + clazztype = syms.errType; + } + + if (clazztype.tag == FORALL && !pt.isErroneous()) { + //if the resolved constructor's return type has some uninferred + //type-variables, infer them using the expected type and declared + //bounds (JLS 15.12.2.8). + try { + clazztype = infer.instantiateExpr((ForAll) clazztype, + pt.tag == NONE ? syms.objectType : pt, + Warner.noWarnings); + } catch (Infer.InferenceException ex) { + //an error occurred while inferring uninstantiated type-variables + log.error(tree.clazz.pos(), + "cant.apply.diamond.1", + diags.fragment("diamond", clazztype.tsym), + ex.diagnostic); + } + } + return chk.checkClassType(tree.clazz.pos(), + clazztype, + true); + } + + /** Creates a synthetic scope containing fake generic constructors. + * Assuming that the original scope contains a constructor of the kind: + * Foo(X x, Y y), where X,Y are class type-variables declared in Foo, + * the synthetic scope is added a generic constructor of the kind: + * Foo(X x, Y y). This is crucial in order to enable diamond + * inference. The inferred return type of the synthetic constructor IS + * the inferred type for the diamond operator. + */ + private Pair getSyntheticScopeMapping(Type ctype) { + if (ctype.tag != CLASS) { + return erroneousMapping; + } + + Pair mapping = + new Pair(ctype.tsym.members(), new Scope(ctype.tsym)); + + //for each constructor in the original scope, create a synthetic constructor + //whose return type is the type of the class in which the constructor is + //declared, and insert it into the new scope. + for (Scope.Entry e = mapping.fst.lookup(names.init); + e.scope != null; + e = e.next()) { + Type synthRestype = new ClassType(ctype.getEnclosingType(), + ctype.tsym.type.getTypeArguments(), + ctype.tsym); + MethodSymbol synhConstr = new MethodSymbol(e.sym.flags(), + names.init, + types.createMethodTypeWithReturn(e.sym.type, synthRestype), + e.sym.owner); + mapping.snd.enter(synhConstr); + } + return mapping; + } + + private final Pair erroneousMapping = new Pair(null, null); + + /** Make an attributed null check tree. + */ + public JCExpression makeNullCheck(JCExpression arg) { + // optimization: X.this is never null; skip null check + Name name = TreeInfo.name(arg); + if (name == names._this || name == names._super) return arg; + + int optag = JCTree.NULLCHK; + JCUnary tree = make.at(arg.pos).Unary(optag, arg); + tree.operator = syms.nullcheck; + tree.type = arg.type; + return tree; + } + + public void visitNewArray(JCNewArray tree) { + Type owntype = types.createErrorType(tree.type); + Type elemtype; + if (tree.elemtype != null) { + elemtype = attribType(tree.elemtype, env); + chk.validate(tree.elemtype, env); + owntype = elemtype; + for (List l = tree.dims; l.nonEmpty(); l = l.tail) { + attribExpr(l.head, env, syms.intType); + owntype = new ArrayType(owntype, syms.arrayClass); + } + } else { + // we are seeing an untyped aggregate { ... } + // this is allowed only if the prototype is an array + if (pt.tag == ARRAY) { + elemtype = types.elemtype(pt); + } else { + if (pt.tag != ERROR) { + log.error(tree.pos(), "illegal.initializer.for.type", + pt); + } + elemtype = types.createErrorType(pt); + } + } + if (tree.elems != null) { + attribExprs(tree.elems, env, elemtype); + owntype = new ArrayType(elemtype, syms.arrayClass); + } + if (!types.isReifiable(elemtype)) + log.error(tree.pos(), "generic.array.creation"); + result = check(tree, owntype, VAL, pkind, pt); + } + + public void visitParens(JCParens tree) { + Type owntype = attribTree(tree.expr, env, pkind, pt); + result = check(tree, owntype, pkind, pkind, pt); + Symbol sym = TreeInfo.symbol(tree); + if (sym != null && (sym.kind&(TYP|PCK)) != 0) + log.error(tree.pos(), "illegal.start.of.type"); + } + + public void visitAssign(JCAssign tree) { + Type owntype = attribTree(tree.lhs, env.dup(tree), VAR, Type.noType); + Type capturedType = capture(owntype); + attribExpr(tree.rhs, env, owntype); + result = check(tree, capturedType, VAL, pkind, pt); + } + + public void visitAssignop(JCAssignOp tree) { + // Attribute arguments. + Type owntype = attribTree(tree.lhs, env, VAR, Type.noType); + Type operand = attribExpr(tree.rhs, env); + // Find operator. + Symbol operator = tree.operator = rs.resolveBinaryOperator( + tree.pos(), tree.getTag() - JCTree.ASGOffset, env, + owntype, operand); + + if (operator.kind == MTH && + !owntype.isErroneous() && + !operand.isErroneous()) { + chk.checkOperator(tree.pos(), + (OperatorSymbol)operator, + tree.getTag() - JCTree.ASGOffset, + owntype, + operand); + chk.checkDivZero(tree.rhs.pos(), operator, operand); + chk.checkCastable(tree.rhs.pos(), + operator.type.getReturnType(), + owntype); + } + result = check(tree, owntype, VAL, pkind, pt); + } + + public void visitUnary(JCUnary tree) { + // Attribute arguments. + Type argtype = (JCTree.PREINC <= tree.getTag() && tree.getTag() <= JCTree.POSTDEC) + ? attribTree(tree.arg, env, VAR, Type.noType) + : chk.checkNonVoid(tree.arg.pos(), attribExpr(tree.arg, env)); + + // Find operator. + Symbol operator = tree.operator = + rs.resolveUnaryOperator(tree.pos(), tree.getTag(), env, argtype); + + Type owntype = types.createErrorType(tree.type); + if (operator.kind == MTH && + !argtype.isErroneous()) { + owntype = (JCTree.PREINC <= tree.getTag() && tree.getTag() <= JCTree.POSTDEC) + ? tree.arg.type + : operator.type.getReturnType(); + int opc = ((OperatorSymbol)operator).opcode; + + // If the argument is constant, fold it. + if (argtype.constValue() != null) { + Type ctype = cfolder.fold1(opc, argtype); + if (ctype != null) { + owntype = cfolder.coerce(ctype, owntype); + + // Remove constant types from arguments to + // conserve space. The parser will fold concatenations + // of string literals; the code here also + // gets rid of intermediate results when some of the + // operands are constant identifiers. + if (tree.arg.type.tsym == syms.stringType.tsym) { + tree.arg.type = syms.stringType; + } + } + } + } + result = check(tree, owntype, VAL, pkind, pt); + } + + public void visitBinary(JCBinary tree) { + // Attribute arguments. + Type left = chk.checkNonVoid(tree.lhs.pos(), attribExpr(tree.lhs, env)); + Type right = chk.checkNonVoid(tree.lhs.pos(), attribExpr(tree.rhs, env)); + + // Find operator. + Symbol operator = tree.operator = + rs.resolveBinaryOperator(tree.pos(), tree.getTag(), env, left, right); + + Type owntype = types.createErrorType(tree.type); + if (operator.kind == MTH && + !left.isErroneous() && + !right.isErroneous()) { + owntype = operator.type.getReturnType(); + int opc = chk.checkOperator(tree.lhs.pos(), + (OperatorSymbol)operator, + tree.getTag(), + left, + right); + + // If both arguments are constants, fold them. + if (left.constValue() != null && right.constValue() != null) { + Type ctype = cfolder.fold2(opc, left, right); + if (ctype != null) { + owntype = cfolder.coerce(ctype, owntype); + + // Remove constant types from arguments to + // conserve space. The parser will fold concatenations + // of string literals; the code here also + // gets rid of intermediate results when some of the + // operands are constant identifiers. + if (tree.lhs.type.tsym == syms.stringType.tsym) { + tree.lhs.type = syms.stringType; + } + if (tree.rhs.type.tsym == syms.stringType.tsym) { + tree.rhs.type = syms.stringType; + } + } + } + + // Check that argument types of a reference ==, != are + // castable to each other, (JLS???). + if ((opc == ByteCodes.if_acmpeq || opc == ByteCodes.if_acmpne)) { + if (!types.isCastable(left, right, new Warner(tree.pos()))) { + log.error(tree.pos(), "incomparable.types", left, right); + } + } + + chk.checkDivZero(tree.rhs.pos(), operator, right); + } + result = check(tree, owntype, VAL, pkind, pt); + } + + public void visitTypeCast(JCTypeCast tree) { + Type clazztype = attribType(tree.clazz, env); + chk.validate(tree.clazz, env, false); + //a fresh environment is required for 292 inference to work properly --- + //see Infer.instantiatePolymorphicSignatureInstance() + Env localEnv = env.dup(tree); + Type exprtype = attribExpr(tree.expr, localEnv, Infer.anyPoly); + Type owntype = chk.checkCastable(tree.expr.pos(), exprtype, clazztype); + if (exprtype.constValue() != null) + owntype = cfolder.coerce(exprtype, owntype); + result = check(tree, capture(owntype), VAL, pkind, pt); + } + + public void visitTypeTest(JCInstanceOf tree) { + Type exprtype = chk.checkNullOrRefType( + tree.expr.pos(), attribExpr(tree.expr, env)); + Type clazztype = chk.checkReifiableReferenceType( + tree.clazz.pos(), attribType(tree.clazz, env)); + chk.validate(tree.clazz, env, false); + chk.checkCastable(tree.expr.pos(), exprtype, clazztype); + result = check(tree, syms.booleanType, VAL, pkind, pt); + } + + public void visitIndexed(JCArrayAccess tree) { + Type owntype = types.createErrorType(tree.type); + Type atype = attribExpr(tree.indexed, env); + attribExpr(tree.index, env, syms.intType); + if (types.isArray(atype)) + owntype = types.elemtype(atype); + else if (atype.tag != ERROR) + log.error(tree.pos(), "array.req.but.found", atype); + if ((pkind & VAR) == 0) owntype = capture(owntype); + result = check(tree, owntype, VAR, pkind, pt); + } + + public void visitIdent(JCIdent tree) { + Symbol sym; + boolean varArgs = false; + + // Find symbol + if (pt.tag == METHOD || pt.tag == FORALL) { + // If we are looking for a method, the prototype `pt' will be a + // method type with the type of the call's arguments as parameters. + env.info.varArgs = false; + sym = rs.resolveMethod(tree.pos(), env, tree.name, pt.getParameterTypes(), pt.getTypeArguments()); + varArgs = env.info.varArgs; + } else if (tree.sym != null && tree.sym.kind != VAR) { + sym = tree.sym; + } else { + sym = rs.resolveIdent(tree.pos(), env, tree.name, pkind); + } + tree.sym = sym; + + // (1) Also find the environment current for the class where + // sym is defined (`symEnv'). + // Only for pre-tiger versions (1.4 and earlier): + // (2) Also determine whether we access symbol out of an anonymous + // class in a this or super call. This is illegal for instance + // members since such classes don't carry a this$n link. + // (`noOuterThisPath'). + Env symEnv = env; + boolean noOuterThisPath = false; + if (env.enclClass.sym.owner.kind != PCK && // we are in an inner class + (sym.kind & (VAR | MTH | TYP)) != 0 && + sym.owner.kind == TYP && + tree.name != names._this && tree.name != names._super) { + + // Find environment in which identifier is defined. + while (symEnv.outer != null && + !sym.isMemberOf(symEnv.enclClass.sym, types)) { + if ((symEnv.enclClass.sym.flags() & NOOUTERTHIS) != 0) + noOuterThisPath = !allowAnonOuterThis; + symEnv = symEnv.outer; + } + } + + // If symbol is a variable, ... + if (sym.kind == VAR) { + VarSymbol v = (VarSymbol)sym; + + // ..., evaluate its initializer, if it has one, and check for + // illegal forward reference. + checkInit(tree, env, v, false); + + // If symbol is a local variable accessed from an embedded + // inner class check that it is final. + if (v.owner.kind == MTH && + v.owner != env.info.scope.owner && + (v.flags_field & FINAL) == 0) { + log.error(tree.pos(), + "local.var.accessed.from.icls.needs.final", + v); + } + + // If we are expecting a variable (as opposed to a value), check + // that the variable is assignable in the current environment. + if (pkind == VAR) + checkAssignable(tree.pos(), v, null, env); + } + + // In a constructor body, + // if symbol is a field or instance method, check that it is + // not accessed before the supertype constructor is called. + if ((symEnv.info.isSelfCall || noOuterThisPath) && + (sym.kind & (VAR | MTH)) != 0 && + sym.owner.kind == TYP && + (sym.flags() & STATIC) == 0) { + chk.earlyRefError(tree.pos(), sym.kind == VAR ? sym : thisSym(tree.pos(), env)); + } + Env env1 = env; + if (sym.kind != ERR && sym.kind != TYP && sym.owner != null && sym.owner != env1.enclClass.sym) { + // If the found symbol is inaccessible, then it is + // accessed through an enclosing instance. Locate this + // enclosing instance: + while (env1.outer != null && !rs.isAccessible(env, env1.enclClass.sym.type, sym)) + env1 = env1.outer; + } + result = checkId(tree, env1.enclClass.sym.type, sym, env, pkind, pt, varArgs); + } + + public void visitSelect(JCFieldAccess tree) { + // Determine the expected kind of the qualifier expression. + int skind = 0; + if (tree.name == names._this || tree.name == names._super || + tree.name == names._class) + { + skind = TYP; + } else { + if ((pkind & PCK) != 0) skind = skind | PCK; + if ((pkind & TYP) != 0) skind = skind | TYP | PCK; + if ((pkind & (VAL | MTH)) != 0) skind = skind | VAL | TYP; + } + + // Attribute the qualifier expression, and determine its symbol (if any). + Type site = attribTree(tree.selected, env, skind, Infer.anyPoly); + if ((pkind & (PCK | TYP)) == 0) + site = capture(site); // Capture field access + + // don't allow T.class T[].class, etc + if (skind == TYP) { + Type elt = site; + while (elt.tag == ARRAY) + elt = ((ArrayType)elt).elemtype; + if (elt.tag == TYPEVAR) { + log.error(tree.pos(), "type.var.cant.be.deref"); + result = types.createErrorType(tree.type); + return; + } + } + + // If qualifier symbol is a type or `super', assert `selectSuper' + // for the selection. This is relevant for determining whether + // protected symbols are accessible. + Symbol sitesym = TreeInfo.symbol(tree.selected); + boolean selectSuperPrev = env.info.selectSuper; + env.info.selectSuper = + sitesym != null && + sitesym.name == names._super; + + // If selected expression is polymorphic, strip + // type parameters and remember in env.info.tvars, so that + // they can be added later (in Attr.checkId and Infer.instantiateMethod). + if (tree.selected.type.tag == FORALL) { + ForAll pstype = (ForAll)tree.selected.type; + env.info.tvars = pstype.tvars; + site = tree.selected.type = pstype.qtype; + } + + // Determine the symbol represented by the selection. + env.info.varArgs = false; + Symbol sym = selectSym(tree, sitesym, site, env, pt, pkind); + if (sym.exists() && !isType(sym) && (pkind & (PCK | TYP)) != 0) { + site = capture(site); + sym = selectSym(tree, sitesym, site, env, pt, pkind); + } + boolean varArgs = env.info.varArgs; + tree.sym = sym; + + if (site.tag == TYPEVAR && !isType(sym) && sym.kind != ERR) { + while (site.tag == TYPEVAR) site = site.getUpperBound(); + site = capture(site); + } + + // If that symbol is a variable, ... + if (sym.kind == VAR) { + VarSymbol v = (VarSymbol)sym; + + // ..., evaluate its initializer, if it has one, and check for + // illegal forward reference. + checkInit(tree, env, v, true); + + // If we are expecting a variable (as opposed to a value), check + // that the variable is assignable in the current environment. + if (pkind == VAR) + checkAssignable(tree.pos(), v, tree.selected, env); + } + + if (sitesym != null && + sitesym.kind == VAR && + ((VarSymbol)sitesym).isResourceVariable() && + sym.kind == MTH && + sym.name.equals(names.close) && + sym.overrides(syms.autoCloseableClose, sitesym.type.tsym, types, true) && + env.info.lint.isEnabled(LintCategory.TRY)) { + log.warning(LintCategory.TRY, tree, "try.explicit.close.call"); + } + + // Disallow selecting a type from an expression + if (isType(sym) && (sitesym==null || (sitesym.kind&(TYP|PCK)) == 0)) { + tree.type = check(tree.selected, pt, + sitesym == null ? VAL : sitesym.kind, TYP|PCK, pt); + } + + if (isType(sitesym)) { + if (sym.name == names._this) { + // If `C' is the currently compiled class, check that + // C.this' does not appear in a call to a super(...) + if (env.info.isSelfCall && + site.tsym == env.enclClass.sym) { + chk.earlyRefError(tree.pos(), sym); + } + } else { + // Check if type-qualified fields or methods are static (JLS) + if ((sym.flags() & STATIC) == 0 && + sym.name != names._super && + (sym.kind == VAR || sym.kind == MTH)) { + rs.access(rs.new StaticError(sym), + tree.pos(), site, sym.name, true); + } + } + } else if (sym.kind != ERR && (sym.flags() & STATIC) != 0 && sym.name != names._class) { + // If the qualified item is not a type and the selected item is static, report + // a warning. Make allowance for the class of an array type e.g. Object[].class) + chk.warnStatic(tree, "static.not.qualified.by.type", Kinds.kindName(sym.kind), sym.owner); + } + + // If we are selecting an instance member via a `super', ... + if (env.info.selectSuper && (sym.flags() & STATIC) == 0) { + + // Check that super-qualified symbols are not abstract (JLS) + rs.checkNonAbstract(tree.pos(), sym); + + if (site.isRaw()) { + // Determine argument types for site. + Type site1 = types.asSuper(env.enclClass.sym.type, site.tsym); + if (site1 != null) site = site1; + } + } + + env.info.selectSuper = selectSuperPrev; + result = checkId(tree, site, sym, env, pkind, pt, varArgs); + env.info.tvars = List.nil(); + } + //where + /** Determine symbol referenced by a Select expression, + * + * @param tree The select tree. + * @param site The type of the selected expression, + * @param env The current environment. + * @param pt The current prototype. + * @param pkind The expected kind(s) of the Select expression. + */ + private Symbol selectSym(JCFieldAccess tree, + Type site, + Env env, + Type pt, + int pkind) { + return selectSym(tree, site.tsym, site, env, pt, pkind); + } + private Symbol selectSym(JCFieldAccess tree, + Symbol location, + Type site, + Env env, + Type pt, + int pkind) { + DiagnosticPosition pos = tree.pos(); + Name name = tree.name; + switch (site.tag) { + case PACKAGE: + return rs.access( + rs.findIdentInPackage(env, site.tsym, name, pkind), + pos, location, site, name, true); + case ARRAY: + case CLASS: + if (pt.tag == METHOD || pt.tag == FORALL) { + return rs.resolveQualifiedMethod( + pos, env, location, site, name, pt.getParameterTypes(), pt.getTypeArguments()); + } else if (name == names._this || name == names._super) { + return rs.resolveSelf(pos, env, site.tsym, name); + } else if (name == names._class) { + // In this case, we have already made sure in + // visitSelect that qualifier expression is a type. + Type t = syms.classType; + List typeargs = allowGenerics + ? List.of(types.erasure(site)) + : List.nil(); + t = new ClassType(t.getEnclosingType(), typeargs, t.tsym); + return new VarSymbol( + STATIC | PUBLIC | FINAL, names._class, t, site.tsym); + } else { + // We are seeing a plain identifier as selector. + Symbol sym = rs.findIdentInType(env, site, name, pkind); + if ((pkind & ERRONEOUS) == 0) + sym = rs.access(sym, pos, location, site, name, true); + return sym; + } + case WILDCARD: + throw new AssertionError(tree); + case TYPEVAR: + // Normally, site.getUpperBound() shouldn't be null. + // It should only happen during memberEnter/attribBase + // when determining the super type which *must* beac + // done before attributing the type variables. In + // other words, we are seeing this illegal program: + // class B extends A {} + Symbol sym = (site.getUpperBound() != null) + ? selectSym(tree, location, capture(site.getUpperBound()), env, pt, pkind) + : null; + if (sym == null) { + log.error(pos, "type.var.cant.be.deref"); + return syms.errSymbol; + } else { + Symbol sym2 = (sym.flags() & Flags.PRIVATE) != 0 ? + rs.new AccessError(env, site, sym) : + sym; + rs.access(sym2, pos, location, site, name, true); + return sym; + } + case ERROR: + // preserve identifier names through errors + return types.createErrorType(name, site.tsym, site).tsym; + default: + // The qualifier expression is of a primitive type -- only + // .class is allowed for these. + if (name == names._class) { + // In this case, we have already made sure in Select that + // qualifier expression is a type. + Type t = syms.classType; + Type arg = types.boxedClass(site).type; + t = new ClassType(t.getEnclosingType(), List.of(arg), t.tsym); + return new VarSymbol( + STATIC | PUBLIC | FINAL, names._class, t, site.tsym); + } else { + log.error(pos, "cant.deref", site); + return syms.errSymbol; + } + } + } + + /** Determine type of identifier or select expression and check that + * (1) the referenced symbol is not deprecated + * (2) the symbol's type is safe (@see checkSafe) + * (3) if symbol is a variable, check that its type and kind are + * compatible with the prototype and protokind. + * (4) if symbol is an instance field of a raw type, + * which is being assigned to, issue an unchecked warning if its + * type changes under erasure. + * (5) if symbol is an instance method of a raw type, issue an + * unchecked warning if its argument types change under erasure. + * If checks succeed: + * If symbol is a constant, return its constant type + * else if symbol is a method, return its result type + * otherwise return its type. + * Otherwise return errType. + * + * @param tree The syntax tree representing the identifier + * @param site If this is a select, the type of the selected + * expression, otherwise the type of the current class. + * @param sym The symbol representing the identifier. + * @param env The current environment. + * @param pkind The set of expected kinds. + * @param pt The expected type. + */ + Type checkId(JCTree tree, + Type site, + Symbol sym, + Env env, + int pkind, + Type pt, + boolean useVarargs) { + if (pt.isErroneous()) return types.createErrorType(site); + Type owntype; // The computed type of this identifier occurrence. + switch (sym.kind) { + case TYP: + // For types, the computed type equals the symbol's type, + // except for two situations: + owntype = sym.type; + if (owntype.tag == CLASS) { + Type ownOuter = owntype.getEnclosingType(); + + // (a) If the symbol's type is parameterized, erase it + // because no type parameters were given. + // We recover generic outer type later in visitTypeApply. + if (owntype.tsym.type.getTypeArguments().nonEmpty()) { + owntype = types.erasure(owntype); + } + + // (b) If the symbol's type is an inner class, then + // we have to interpret its outer type as a superclass + // of the site type. Example: + // + // class Tree { class Visitor { ... } } + // class PointTree extends Tree { ... } + // ...PointTree.Visitor... + // + // Then the type of the last expression above is + // Tree.Visitor. + else if (ownOuter.tag == CLASS && site != ownOuter) { + Type normOuter = site; + if (normOuter.tag == CLASS) + normOuter = types.asEnclosingSuper(site, ownOuter.tsym); + if (normOuter == null) // perhaps from an import + normOuter = types.erasure(ownOuter); + if (normOuter != ownOuter) + owntype = new ClassType( + normOuter, List.nil(), owntype.tsym); + } + } + break; + case VAR: + VarSymbol v = (VarSymbol)sym; + // Test (4): if symbol is an instance field of a raw type, + // which is being assigned to, issue an unchecked warning if + // its type changes under erasure. + if (allowGenerics && + pkind == VAR && + v.owner.kind == TYP && + (v.flags() & STATIC) == 0 && + (site.tag == CLASS || site.tag == TYPEVAR)) { + Type s = types.asOuterSuper(site, v.owner); + if (s != null && + s.isRaw() && + !types.isSameType(v.type, v.erasure(types))) { + chk.warnUnchecked(tree.pos(), + "unchecked.assign.to.var", + v, s); + } + } + // The computed type of a variable is the type of the + // variable symbol, taken as a member of the site type. + owntype = (sym.owner.kind == TYP && + sym.name != names._this && sym.name != names._super) + ? types.memberType(site, sym) + : sym.type; + + if (env.info.tvars.nonEmpty()) { + Type owntype1 = new ForAll(env.info.tvars, owntype); + for (List l = env.info.tvars; l.nonEmpty(); l = l.tail) + if (!owntype.contains(l.head)) { + log.error(tree.pos(), "undetermined.type", owntype1); + owntype1 = types.createErrorType(owntype1); + } + owntype = owntype1; + } + + // If the variable is a constant, record constant value in + // computed type. + if (v.getConstValue() != null && isStaticReference(tree)) + owntype = owntype.constType(v.getConstValue()); + + if (pkind == VAL) { + owntype = capture(owntype); // capture "names as expressions" + } + break; + case MTH: { + JCMethodInvocation app = (JCMethodInvocation)env.tree; + owntype = checkMethod(site, sym, env, app.args, + pt.getParameterTypes(), pt.getTypeArguments(), + env.info.varArgs); + break; + } + case PCK: case ERR: + owntype = sym.type; + break; + default: + throw new AssertionError("unexpected kind: " + sym.kind + + " in tree " + tree); + } + + // Test (1): emit a `deprecation' warning if symbol is deprecated. + // (for constructors, the error was given when the constructor was + // resolved) + + if (sym.name != names.init) { + chk.checkDeprecated(tree.pos(), env.info.scope.owner, sym); + chk.checkSunAPI(tree.pos(), sym); + } + + // Test (3): if symbol is a variable, check that its type and + // kind are compatible with the prototype and protokind. + return check(tree, owntype, sym.kind, pkind, pt); + } + + /** Check that variable is initialized and evaluate the variable's + * initializer, if not yet done. Also check that variable is not + * referenced before it is defined. + * @param tree The tree making up the variable reference. + * @param env The current environment. + * @param v The variable's symbol. + */ + private void checkInit(JCTree tree, + Env env, + VarSymbol v, + boolean onlyWarning) { +// System.err.println(v + " " + ((v.flags() & STATIC) != 0) + " " + +// tree.pos + " " + v.pos + " " + +// Resolve.isStatic(env));//DEBUG + + // A forward reference is diagnosed if the declaration position + // of the variable is greater than the current tree position + // and the tree and variable definition occur in the same class + // definition. Note that writes don't count as references. + // This check applies only to class and instance + // variables. Local variables follow different scope rules, + // and are subject to definite assignment checking. + if ((env.info.enclVar == v || v.pos > tree.pos) && + v.owner.kind == TYP && + canOwnInitializer(env.info.scope.owner) && + v.owner == env.info.scope.owner.enclClass() && + ((v.flags() & STATIC) != 0) == Resolve.isStatic(env) && + (env.tree.getTag() != JCTree.ASSIGN || + TreeInfo.skipParens(((JCAssign) env.tree).lhs) != tree)) { + String suffix = (env.info.enclVar == v) ? + "self.ref" : "forward.ref"; + if (!onlyWarning || isStaticEnumField(v)) { + log.error(tree.pos(), "illegal." + suffix); + } else if (useBeforeDeclarationWarning) { + log.warning(tree.pos(), suffix, v); + } + } + + v.getConstValue(); // ensure initializer is evaluated + + checkEnumInitializer(tree, env, v); + } + + /** + * Check for illegal references to static members of enum. In + * an enum type, constructors and initializers may not + * reference its static members unless they are constant. + * + * @param tree The tree making up the variable reference. + * @param env The current environment. + * @param v The variable's symbol. + * @jls section 8.9 Enums + */ + private void checkEnumInitializer(JCTree tree, Env env, VarSymbol v) { + // JLS: + // + // "It is a compile-time error to reference a static field + // of an enum type that is not a compile-time constant + // (15.28) from constructors, instance initializer blocks, + // or instance variable initializer expressions of that + // type. It is a compile-time error for the constructors, + // instance initializer blocks, or instance variable + // initializer expressions of an enum constant e to refer + // to itself or to an enum constant of the same type that + // is declared to the right of e." + if (isStaticEnumField(v)) { + ClassSymbol enclClass = env.info.scope.owner.enclClass(); + + if (enclClass == null || enclClass.owner == null) + return; + + // See if the enclosing class is the enum (or a + // subclass thereof) declaring v. If not, this + // reference is OK. + if (v.owner != enclClass && !types.isSubtype(enclClass.type, v.owner.type)) + return; + + // If the reference isn't from an initializer, then + // the reference is OK. + if (!Resolve.isInitializer(env)) + return; + + log.error(tree.pos(), "illegal.enum.static.ref"); + } + } + + /** Is the given symbol a static, non-constant field of an Enum? + * Note: enum literals should not be regarded as such + */ + private boolean isStaticEnumField(VarSymbol v) { + return Flags.isEnum(v.owner) && + Flags.isStatic(v) && + !Flags.isConstant(v) && + v.name != names._class; + } + + /** Can the given symbol be the owner of code which forms part + * if class initialization? This is the case if the symbol is + * a type or field, or if the symbol is the synthetic method. + * owning a block. + */ + private boolean canOwnInitializer(Symbol sym) { + return + (sym.kind & (VAR | TYP)) != 0 || + (sym.kind == MTH && (sym.flags() & BLOCK) != 0); + } + + Warner noteWarner = new Warner(); + + /** + * Check that method arguments conform to its instantation. + **/ + public Type checkMethod(Type site, + Symbol sym, + Env env, + final List argtrees, + List argtypes, + List typeargtypes, + boolean useVarargs) { + // Test (5): if symbol is an instance method of a raw type, issue + // an unchecked warning if its argument types change under erasure. + if (allowGenerics && + (sym.flags() & STATIC) == 0 && + (site.tag == CLASS || site.tag == TYPEVAR)) { + Type s = types.asOuterSuper(site, sym.owner); + if (s != null && s.isRaw() && + !types.isSameTypes(sym.type.getParameterTypes(), + sym.erasure(types).getParameterTypes())) { + chk.warnUnchecked(env.tree.pos(), + "unchecked.call.mbr.of.raw.type", + sym, s); + } + } + + // Compute the identifier's instantiated type. + // For methods, we need to compute the instance type by + // Resolve.instantiate from the symbol's type as well as + // any type arguments and value arguments. + noteWarner.clear(); + Type owntype = rs.instantiate(env, + site, + sym, + argtypes, + typeargtypes, + true, + useVarargs, + noteWarner); + boolean warned = noteWarner.hasNonSilentLint(LintCategory.UNCHECKED); + + // If this fails, something went wrong; we should not have + // found the identifier in the first place. + if (owntype == null) { + if (!pt.isErroneous()) + log.error(env.tree.pos(), + "internal.error.cant.instantiate", + sym, site, + Type.toString(pt.getParameterTypes())); + owntype = types.createErrorType(site); + } else { + // System.out.println("call : " + env.tree); + // System.out.println("method : " + owntype); + // System.out.println("actuals: " + argtypes); + List formals = owntype.getParameterTypes(); + Type last = useVarargs ? formals.last() : null; + if (sym.name==names.init && + sym.owner == syms.enumSym) + formals = formals.tail.tail; + List args = argtrees; + while (formals.head != last) { + JCTree arg = args.head; + Warner warn = chk.convertWarner(arg.pos(), arg.type, formals.head); + assertConvertible(arg, arg.type, formals.head, warn); + warned |= warn.hasNonSilentLint(LintCategory.UNCHECKED); + args = args.tail; + formals = formals.tail; + } + if (useVarargs) { + Type varArg = types.elemtype(last); + while (args.tail != null) { + JCTree arg = args.head; + Warner warn = chk.convertWarner(arg.pos(), arg.type, varArg); + assertConvertible(arg, arg.type, varArg, warn); + warned |= warn.hasNonSilentLint(LintCategory.UNCHECKED); + args = args.tail; + } + } else if ((sym.flags() & VARARGS) != 0 && allowVarargs) { + // non-varargs call to varargs method + Type varParam = owntype.getParameterTypes().last(); + Type lastArg = argtypes.last(); + if (types.isSubtypeUnchecked(lastArg, types.elemtype(varParam)) && + !types.isSameType(types.erasure(varParam), types.erasure(lastArg))) + log.warning(argtrees.last().pos(), "inexact.non-varargs.call", + types.elemtype(varParam), + varParam); + } + + if (warned && sym.type.tag == FORALL) { + chk.warnUnchecked(env.tree.pos(), + "unchecked.meth.invocation.applied", + kindName(sym), + sym.name, + rs.methodArguments(sym.type.getParameterTypes()), + rs.methodArguments(argtypes), + kindName(sym.location()), + sym.location()); + owntype = new MethodType(owntype.getParameterTypes(), + types.erasure(owntype.getReturnType()), + types.erasure(owntype.getThrownTypes()), + syms.methodClass); + } + if (useVarargs) { + JCTree tree = env.tree; + Type argtype = owntype.getParameterTypes().last(); + if (owntype.getReturnType().tag != FORALL || warned) { + chk.checkVararg(env.tree.pos(), owntype.getParameterTypes(), sym); + } + Type elemtype = types.elemtype(argtype); + switch (tree.getTag()) { + case JCTree.APPLY: + ((JCMethodInvocation) tree).varargsElement = elemtype; + break; + case JCTree.NEWCLASS: + ((JCNewClass) tree).varargsElement = elemtype; + break; + default: + throw new AssertionError(""+tree); + } + } + } + return owntype; + } + + private void assertConvertible(JCTree tree, Type actual, Type formal, Warner warn) { + if (types.isConvertible(actual, formal, warn)) + return; + + if (formal.isCompound() + && types.isSubtype(actual, types.supertype(formal)) + && types.isSubtypeUnchecked(actual, types.interfaces(formal), warn)) + return; + + if (false) { + // TODO: make assertConvertible work + chk.typeError(tree.pos(), diags.fragment("incompatible.types"), actual, formal); + throw new AssertionError("Tree: " + tree + + " actual:" + actual + + " formal: " + formal); + } + } + + public void visitLiteral(JCLiteral tree) { + result = check( + tree, litType(tree.typetag).constType(tree.value), VAL, pkind, pt); + } + //where + /** Return the type of a literal with given type tag. + */ + Type litType(int tag) { + return (tag == TypeTags.CLASS) ? syms.stringType : syms.typeOfTag[tag]; + } + + public void visitTypeIdent(JCPrimitiveTypeTree tree) { + result = check(tree, syms.typeOfTag[tree.typetag], TYP, pkind, pt); + } + + public void visitTypeArray(JCArrayTypeTree tree) { + Type etype = attribType(tree.elemtype, env); + Type type = new ArrayType(etype, syms.arrayClass); + result = check(tree, type, TYP, pkind, pt); + } + + /** Visitor method for parameterized types. + * Bound checking is left until later, since types are attributed + * before supertype structure is completely known + */ + public void visitTypeApply(JCTypeApply tree) { + Type owntype = types.createErrorType(tree.type); + + // Attribute functor part of application and make sure it's a class. + Type clazztype = chk.checkClassType(tree.clazz.pos(), attribType(tree.clazz, env)); + + // Attribute type parameters + List actuals = attribTypes(tree.arguments, env); + + if (clazztype.tag == CLASS) { + List formals = clazztype.tsym.type.getTypeArguments(); + + if (actuals.length() == formals.length() || actuals.length() == 0) { + List a = actuals; + List f = formals; + while (a.nonEmpty()) { + a.head = a.head.withTypeVar(f.head); + a = a.tail; + f = f.tail; + } + // Compute the proper generic outer + Type clazzOuter = clazztype.getEnclosingType(); + if (clazzOuter.tag == CLASS) { + Type site; + JCExpression clazz = TreeInfo.typeIn(tree.clazz); + if (clazz.getTag() == JCTree.IDENT) { + site = env.enclClass.sym.type; + } else if (clazz.getTag() == JCTree.SELECT) { + site = ((JCFieldAccess) clazz).selected.type; + } else throw new AssertionError(""+tree); + if (clazzOuter.tag == CLASS && site != clazzOuter) { + if (site.tag == CLASS) + site = types.asOuterSuper(site, clazzOuter.tsym); + if (site == null) + site = types.erasure(clazzOuter); + clazzOuter = site; + } + } + owntype = new ClassType(clazzOuter, actuals, clazztype.tsym); + } else { + if (formals.length() != 0) { + log.error(tree.pos(), "wrong.number.type.args", + Integer.toString(formals.length())); + } else { + log.error(tree.pos(), "type.doesnt.take.params", clazztype.tsym); + } + owntype = types.createErrorType(tree.type); + } + } + result = check(tree, owntype, TYP, pkind, pt); + } + + public void visitTypeUnion(JCTypeUnion tree) { + ListBuffer multicatchTypes = ListBuffer.lb(); + ListBuffer all_multicatchTypes = null; // lazy, only if needed + for (JCExpression typeTree : tree.alternatives) { + Type ctype = attribType(typeTree, env); + ctype = chk.checkType(typeTree.pos(), + chk.checkClassType(typeTree.pos(), ctype), + syms.throwableType); + if (!ctype.isErroneous()) { + //check that alternatives of a union type are pairwise + //unrelated w.r.t. subtyping + if (chk.intersects(ctype, multicatchTypes.toList())) { + for (Type t : multicatchTypes) { + boolean sub = types.isSubtype(ctype, t); + boolean sup = types.isSubtype(t, ctype); + if (sub || sup) { + //assume 'a' <: 'b' + Type a = sub ? ctype : t; + Type b = sub ? t : ctype; + log.error(typeTree.pos(), "multicatch.types.must.be.disjoint", a, b); + } + } + } + multicatchTypes.append(ctype); + if (all_multicatchTypes != null) + all_multicatchTypes.append(ctype); + } else { + if (all_multicatchTypes == null) { + all_multicatchTypes = ListBuffer.lb(); + all_multicatchTypes.appendList(multicatchTypes); + } + all_multicatchTypes.append(ctype); + } + } + Type t = check(tree, types.lub(multicatchTypes.toList()), TYP, pkind, pt); + if (t.tag == CLASS) { + List alternatives = + ((all_multicatchTypes == null) ? multicatchTypes : all_multicatchTypes).toList(); + t = new UnionClassType((ClassType) t, alternatives); + } + tree.type = result = t; + } + + public void visitTypeParameter(JCTypeParameter tree) { + TypeVar a = (TypeVar)tree.type; + Set boundSet = new HashSet(); + if (a.bound.isErroneous()) + return; + List bs = types.getBounds(a); + if (tree.bounds.nonEmpty()) { + // accept class or interface or typevar as first bound. + Type b = checkBase(bs.head, tree.bounds.head, env, false, false, false); + boundSet.add(types.erasure(b)); + if (b.isErroneous()) { + a.bound = b; + } + else if (b.tag == TYPEVAR) { + // if first bound was a typevar, do not accept further bounds. + if (tree.bounds.tail.nonEmpty()) { + log.error(tree.bounds.tail.head.pos(), + "type.var.may.not.be.followed.by.other.bounds"); + tree.bounds = List.of(tree.bounds.head); + a.bound = bs.head; + } + } else { + // if first bound was a class or interface, accept only interfaces + // as further bounds. + for (JCExpression bound : tree.bounds.tail) { + bs = bs.tail; + Type i = checkBase(bs.head, bound, env, false, true, false); + if (i.isErroneous()) + a.bound = i; + else if (i.tag == CLASS) + chk.checkNotRepeated(bound.pos(), types.erasure(i), boundSet); + } + } + } + bs = types.getBounds(a); + + // in case of multiple bounds ... + if (bs.length() > 1) { + // ... the variable's bound is a class type flagged COMPOUND + // (see comment for TypeVar.bound). + // In this case, generate a class tree that represents the + // bound class, ... + JCExpression extending; + List implementing; + if ((bs.head.tsym.flags() & INTERFACE) == 0) { + extending = tree.bounds.head; + implementing = tree.bounds.tail; + } else { + extending = null; + implementing = tree.bounds; + } + JCClassDecl cd = make.at(tree.pos).ClassDef( + make.Modifiers(PUBLIC | ABSTRACT), + tree.name, List.nil(), + extending, implementing, List.nil()); + + ClassSymbol c = (ClassSymbol)a.getUpperBound().tsym; + Assert.check((c.flags() & COMPOUND) != 0); + cd.sym = c; + c.sourcefile = env.toplevel.sourcefile; + + // ... and attribute the bound class + c.flags_field |= UNATTRIBUTED; + Env cenv = enter.classEnv(cd, env); + enter.typeEnvs.put(c, cenv); + } + } + + + public void visitWildcard(JCWildcard tree) { + //- System.err.println("visitWildcard("+tree+");");//DEBUG + Type type = (tree.kind.kind == BoundKind.UNBOUND) + ? syms.objectType + : attribType(tree.inner, env); + result = check(tree, new WildcardType(chk.checkRefType(tree.pos(), type), + tree.kind.kind, + syms.boundClass), + TYP, pkind, pt); + } + + public void visitAnnotation(JCAnnotation tree) { + log.error(tree.pos(), "annotation.not.valid.for.type", pt); + result = tree.type = syms.errType; + } + + public void visitErroneous(JCErroneous tree) { + if (tree.errs != null) + for (JCTree err : tree.errs) + attribTree(err, env, ERR, pt); + result = tree.type = syms.errType; + } + + /** Default visitor method for all other trees. + */ + public void visitTree(JCTree tree) { + throw new AssertionError(); + } + + /** + * Attribute an env for either a top level tree or class declaration. + */ + public void attrib(Env env) { + if (env.tree.getTag() == JCTree.TOPLEVEL) + attribTopLevel(env); + else + attribClass(env.tree.pos(), env.enclClass.sym); + } + + /** + * Attribute a top level tree. These trees are encountered when the + * package declaration has annotations. + */ + public void attribTopLevel(Env env) { + JCCompilationUnit toplevel = env.toplevel; + try { + annotate.flush(); + chk.validateAnnotations(toplevel.packageAnnotations, toplevel.packge); + } catch (CompletionFailure ex) { + chk.completionError(toplevel.pos(), ex); + } + } + + /** Main method: attribute class definition associated with given class symbol. + * reporting completion failures at the given position. + * @param pos The source position at which completion errors are to be + * reported. + * @param c The class symbol whose definition will be attributed. + */ + public void attribClass(DiagnosticPosition pos, ClassSymbol c) { + try { + annotate.flush(); + attribClass(c); + } catch (CompletionFailure ex) { + chk.completionError(pos, ex); + } + } + + /** Attribute class definition associated with given class symbol. + * @param c The class symbol whose definition will be attributed. + */ + void attribClass(ClassSymbol c) throws CompletionFailure { + if (c.type.tag == ERROR) return; + + // Check for cycles in the inheritance graph, which can arise from + // ill-formed class files. + chk.checkNonCyclic(null, c.type); + + Type st = types.supertype(c.type); + if ((c.flags_field & Flags.COMPOUND) == 0) { + // First, attribute superclass. + if (st.tag == CLASS) + attribClass((ClassSymbol)st.tsym); + + // Next attribute owner, if it is a class. + if (c.owner.kind == TYP && c.owner.type.tag == CLASS) + attribClass((ClassSymbol)c.owner); + } + + // The previous operations might have attributed the current class + // if there was a cycle. So we test first whether the class is still + // UNATTRIBUTED. + if ((c.flags_field & UNATTRIBUTED) != 0) { + c.flags_field &= ~UNATTRIBUTED; + + // Get environment current at the point of class definition. + Env env = enter.typeEnvs.get(c); + + // The info.lint field in the envs stored in enter.typeEnvs is deliberately uninitialized, + // because the annotations were not available at the time the env was created. Therefore, + // we look up the environment chain for the first enclosing environment for which the + // lint value is set. Typically, this is the parent env, but might be further if there + // are any envs created as a result of TypeParameter nodes. + Env lintEnv = env; + while (lintEnv.info.lint == null) + lintEnv = lintEnv.next; + + // Having found the enclosing lint value, we can initialize the lint value for this class + env.info.lint = lintEnv.info.lint.augment(c.attributes_field, c.flags()); + + Lint prevLint = chk.setLint(env.info.lint); + JavaFileObject prev = log.useSource(c.sourcefile); + + try { + // java.lang.Enum may not be subclassed by a non-enum + if (st.tsym == syms.enumSym && + ((c.flags_field & (Flags.ENUM|Flags.COMPOUND)) == 0)) + log.error(env.tree.pos(), "enum.no.subclassing"); + + // Enums may not be extended by source-level classes + if (st.tsym != null && + ((st.tsym.flags_field & Flags.ENUM) != 0) && + ((c.flags_field & (Flags.ENUM | Flags.COMPOUND)) == 0) && + !target.compilerBootstrap(c)) { + log.error(env.tree.pos(), "enum.types.not.extensible"); + } + attribClassBody(env, c); + + chk.checkDeprecatedAnnotation(env.tree.pos(), c); + } finally { + log.useSource(prev); + chk.setLint(prevLint); + } + + } + } + + public void visitImport(JCImport tree) { + // nothing to do + } + + /** Finish the attribution of a class. */ + private void attribClassBody(Env env, ClassSymbol c) { + JCClassDecl tree = (JCClassDecl)env.tree; + Assert.check(c == tree.sym); + + // Validate annotations + chk.validateAnnotations(tree.mods.annotations, c); + + // Validate type parameters, supertype and interfaces. + attribBounds(tree.typarams); + if (!c.isAnonymous()) { + //already checked if anonymous + chk.validate(tree.typarams, env); + chk.validate(tree.extending, env); + chk.validate(tree.implementing, env); + } + + // If this is a non-abstract class, check that it has no abstract + // methods or unimplemented methods of an implemented interface. + if ((c.flags() & (ABSTRACT | INTERFACE)) == 0) { + if (!relax) + chk.checkAllDefined(tree.pos(), c); + } + + if ((c.flags() & ANNOTATION) != 0) { + if (tree.implementing.nonEmpty()) + log.error(tree.implementing.head.pos(), + "cant.extend.intf.annotation"); + if (tree.typarams.nonEmpty()) + log.error(tree.typarams.head.pos(), + "intf.annotation.cant.have.type.params"); + } else { + // Check that all extended classes and interfaces + // are compatible (i.e. no two define methods with same arguments + // yet different return types). (JLS 8.4.6.3) + chk.checkCompatibleSupertypes(tree.pos(), c.type); + } + + // Check that class does not import the same parameterized interface + // with two different argument lists. + chk.checkClassBounds(tree.pos(), c.type); + + tree.type = c.type; + + for (List l = tree.typarams; + l.nonEmpty(); l = l.tail) { + Assert.checkNonNull(env.info.scope.lookup(l.head.name).scope); + } + + // Check that a generic class doesn't extend Throwable + if (!c.type.allparams().isEmpty() && types.isSubtype(c.type, syms.throwableType)) + log.error(tree.extending.pos(), "generic.throwable"); + + // Check that all methods which implement some + // method conform to the method they implement. + chk.checkImplementations(tree); + + //check that a resource implementing AutoCloseable cannot throw InterruptedException + checkAutoCloseable(tree.pos(), env, c.type); + + for (List l = tree.defs; l.nonEmpty(); l = l.tail) { + // Attribute declaration + attribStat(l.head, env); + // Check that declarations in inner classes are not static (JLS 8.1.2) + // Make an exception for static constants. + if (c.owner.kind != PCK && + ((c.flags() & STATIC) == 0 || c.name == names.empty) && + (TreeInfo.flags(l.head) & (STATIC | INTERFACE)) != 0) { + Symbol sym = null; + if (l.head.getTag() == JCTree.VARDEF) sym = ((JCVariableDecl) l.head).sym; + if (sym == null || + sym.kind != VAR || + ((VarSymbol) sym).getConstValue() == null) + log.error(l.head.pos(), "icls.cant.have.static.decl", c); + } + } + + // Check for cycles among non-initial constructors. + chk.checkCyclicConstructors(tree); + + // Check for cycles among annotation elements. + chk.checkNonCyclicElements(tree); + + // Check for proper use of serialVersionUID + if (env.info.lint.isEnabled(LintCategory.SERIAL) && + isSerializable(c) && + (c.flags() & Flags.ENUM) == 0 && + (c.flags() & ABSTRACT) == 0) { + checkSerialVersionUID(tree, c); + } + } + // where + /** check if a class is a subtype of Serializable, if that is available. */ + private boolean isSerializable(ClassSymbol c) { + try { + syms.serializableType.complete(); + } + catch (CompletionFailure e) { + return false; + } + return types.isSubtype(c.type, syms.serializableType); + } + + /** Check that an appropriate serialVersionUID member is defined. */ + private void checkSerialVersionUID(JCClassDecl tree, ClassSymbol c) { + + // check for presence of serialVersionUID + Scope.Entry e = c.members().lookup(names.serialVersionUID); + while (e.scope != null && e.sym.kind != VAR) e = e.next(); + if (e.scope == null) { + log.warning(LintCategory.SERIAL, + tree.pos(), "missing.SVUID", c); + return; + } + + // check that it is static final + VarSymbol svuid = (VarSymbol)e.sym; + if ((svuid.flags() & (STATIC | FINAL)) != + (STATIC | FINAL)) + log.warning(LintCategory.SERIAL, + TreeInfo.diagnosticPositionFor(svuid, tree), "improper.SVUID", c); + + // check that it is long + else if (svuid.type.tag != TypeTags.LONG) + log.warning(LintCategory.SERIAL, + TreeInfo.diagnosticPositionFor(svuid, tree), "long.SVUID", c); + + // check constant + else if (svuid.getConstValue() == null) + log.warning(LintCategory.SERIAL, + TreeInfo.diagnosticPositionFor(svuid, tree), "constant.SVUID", c); + } + + private Type capture(Type type) { + return types.capture(type); + } + + // + + /** + * Handle missing types/symbols in an AST. This routine is useful when + * the compiler has encountered some errors (which might have ended up + * terminating attribution abruptly); if the compiler is used in fail-over + * mode (e.g. by an IDE) and the AST contains semantic errors, this routine + * prevents NPE to be progagated during subsequent compilation steps. + */ + public void postAttr(Env env) { + new PostAttrAnalyzer().scan(env.tree); + } + + class PostAttrAnalyzer extends TreeScanner { + + private void initTypeIfNeeded(JCTree that) { + if (that.type == null) { + that.type = syms.unknownType; + } + } + + @Override + public void scan(JCTree tree) { + if (tree == null) return; + if (tree instanceof JCExpression) { + initTypeIfNeeded(tree); + } + super.scan(tree); + } + + @Override + public void visitIdent(JCIdent that) { + if (that.sym == null) { + that.sym = syms.unknownSymbol; + } + } + + @Override + public void visitSelect(JCFieldAccess that) { + if (that.sym == null) { + that.sym = syms.unknownSymbol; + } + super.visitSelect(that); + } + + @Override + public void visitClassDef(JCClassDecl that) { + initTypeIfNeeded(that); + if (that.sym == null) { + that.sym = new ClassSymbol(0, that.name, that.type, syms.noSymbol); + } + super.visitClassDef(that); + } + + @Override + public void visitMethodDef(JCMethodDecl that) { + initTypeIfNeeded(that); + if (that.sym == null) { + that.sym = new MethodSymbol(0, that.name, that.type, syms.noSymbol); + } + super.visitMethodDef(that); + } + + @Override + public void visitVarDef(JCVariableDecl that) { + initTypeIfNeeded(that); + if (that.sym == null) { + that.sym = new VarSymbol(0, that.name, that.type, syms.noSymbol); + that.sym.adr = 0; + } + super.visitVarDef(that); + } + + @Override + public void visitNewClass(JCNewClass that) { + if (that.constructor == null) { + that.constructor = new MethodSymbol(0, names.init, syms.unknownType, syms.noSymbol); + } + if (that.constructorType == null) { + that.constructorType = syms.unknownType; + } + super.visitNewClass(that); + } + + @Override + public void visitBinary(JCBinary that) { + if (that.operator == null) + that.operator = new OperatorSymbol(names.empty, syms.unknownType, -1, syms.noSymbol); + super.visitBinary(that); + } + + @Override + public void visitUnary(JCUnary that) { + if (that.operator == null) + that.operator = new OperatorSymbol(names.empty, syms.unknownType, -1, syms.noSymbol); + super.visitUnary(that); + } + } + // +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/comp/AttrContext.java b/douyu-javac/src/main/java/com/sun/tools/javac/comp/AttrContext.java new file mode 100644 index 0000000..1589ccb --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/comp/AttrContext.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 1999, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.comp; + +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.code.*; + +/** Contains information specific to the attribute and enter + * passes, to be used in place of the generic field in environments. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class AttrContext { + + /** The scope of local symbols. + */ + Scope scope = null; + + /** The number of enclosing `static' modifiers. + */ + int staticLevel = 0; + + /** Is this an environment for a this(...) or super(...) call? + */ + boolean isSelfCall = false; + + /** Are we evaluating the selector of a `super' or type name? + */ + boolean selectSuper = false; + + /** Are arguments to current function applications boxed into an array for varargs? + */ + boolean varArgs = false; + + /** A list of type variables that are all-quantifed in current context. + */ + List tvars = List.nil(); + + /** A record of the lint/SuppressWarnings currently in effect + */ + Lint lint; + + /** The variable whose initializer is being attributed + * useful for detecting self-references in variable initializers + */ + Symbol enclVar = null; + + /** Duplicate this context, replacing scope field and copying all others. + */ + AttrContext dup(Scope scope) { + AttrContext info = new AttrContext(); + info.scope = scope; + info.staticLevel = staticLevel; + info.isSelfCall = isSelfCall; + info.selectSuper = selectSuper; + info.varArgs = varArgs; + info.tvars = tvars; + info.lint = lint; + info.enclVar = enclVar; + return info; + } + + /** Duplicate this context, copying all fields. + */ + AttrContext dup() { + return dup(scope); + } + + public Iterable getLocalElements() { + if (scope == null) + return List.nil(); + return scope.getElements(); + } + + public String toString() { + return "AttrContext[" + scope.toString() + "]"; + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/comp/AttrContextEnv.java b/douyu-javac/src/main/java/com/sun/tools/javac/comp/AttrContextEnv.java new file mode 100644 index 0000000..3c55067 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/comp/AttrContextEnv.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2000, 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.comp; + +import com.sun.tools.javac.tree.JCTree; + + +/** {@code Env} specialized as {@code Env} + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class AttrContextEnv extends Env { + + /** Create an outermost environment for a given (toplevel)tree, + * with a given info field. + */ + public AttrContextEnv(JCTree tree, AttrContext info) { + super(tree, info); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/comp/Check.java b/douyu-javac/src/main/java/com/sun/tools/javac/comp/Check.java new file mode 100644 index 0000000..fd05456 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/comp/Check.java @@ -0,0 +1,2793 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.comp; + +import java.util.*; +import java.util.Set; + +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.jvm.*; +import com.sun.tools.javac.tree.*; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; +import com.sun.tools.javac.util.List; + +import com.sun.tools.javac.tree.JCTree.*; +import com.sun.tools.javac.code.Lint; +import com.sun.tools.javac.code.Lint.LintCategory; +import com.sun.tools.javac.code.Type.*; +import com.sun.tools.javac.code.Symbol.*; + +import static com.sun.tools.javac.code.Flags.*; +import static com.sun.tools.javac.code.Kinds.*; +import static com.sun.tools.javac.code.TypeTags.*; + +import static com.sun.tools.javac.main.OptionName.*; + +/** Type checking helper class for the attribution phase. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Check { + protected static final Context.Key checkKey = + new Context.Key(); + + private final Names names; + private final Log log; + private final Symtab syms; + private final Enter enter; + private final Infer infer; + private final Types types; + private final JCDiagnostic.Factory diags; + private final boolean skipAnnotations; + private boolean warnOnSyntheticConflicts; + private boolean suppressAbortOnBadClassFile; + private boolean enableSunApiLintControl; + private final TreeInfo treeinfo; + + // The set of lint options currently in effect. It is initialized + // from the context, and then is set/reset as needed by Attr as it + // visits all the various parts of the trees during attribution. + private Lint lint; + + // The method being analyzed in Attr - it is set/reset as needed by + // Attr as it visits new method declarations. + private MethodSymbol method; + + public static Check instance(Context context) { + Check instance = context.get(checkKey); + if (instance == null) + instance = new Check(context); + return instance; + } + + protected Check(Context context) { + context.put(checkKey, this); + + names = Names.instance(context); + log = Log.instance(context); + syms = Symtab.instance(context); + enter = Enter.instance(context); + infer = Infer.instance(context); + this.types = Types.instance(context); + diags = JCDiagnostic.Factory.instance(context); + Options options = Options.instance(context); + lint = Lint.instance(context); + treeinfo = TreeInfo.instance(context); + + Source source = Source.instance(context); + allowGenerics = source.allowGenerics(); + allowAnnotations = source.allowAnnotations(); + allowCovariantReturns = source.allowCovariantReturns(); + allowSimplifiedVarargs = source.allowSimplifiedVarargs(); + complexInference = options.isSet(COMPLEXINFERENCE); + skipAnnotations = options.isSet("skipAnnotations"); + warnOnSyntheticConflicts = options.isSet("warnOnSyntheticConflicts"); + suppressAbortOnBadClassFile = options.isSet("suppressAbortOnBadClassFile"); + enableSunApiLintControl = options.isSet("enableSunApiLintControl"); + + Target target = Target.instance(context); + syntheticNameChar = target.syntheticNameChar(); + + boolean verboseDeprecated = lint.isEnabled(LintCategory.DEPRECATION); + boolean verboseUnchecked = lint.isEnabled(LintCategory.UNCHECKED); + boolean verboseSunApi = lint.isEnabled(LintCategory.SUNAPI); + boolean enforceMandatoryWarnings = source.enforceMandatoryWarnings(); + + deprecationHandler = new MandatoryWarningHandler(log, verboseDeprecated, + enforceMandatoryWarnings, "deprecated", LintCategory.DEPRECATION); + uncheckedHandler = new MandatoryWarningHandler(log, verboseUnchecked, + enforceMandatoryWarnings, "unchecked", LintCategory.UNCHECKED); + sunApiHandler = new MandatoryWarningHandler(log, verboseSunApi, + enforceMandatoryWarnings, "sunapi", null); + + deferredLintHandler = DeferredLintHandler.immediateHandler; + } + + /** Switch: generics enabled? + */ + boolean allowGenerics; + + /** Switch: annotations enabled? + */ + boolean allowAnnotations; + + /** Switch: covariant returns enabled? + */ + boolean allowCovariantReturns; + + /** Switch: simplified varargs enabled? + */ + boolean allowSimplifiedVarargs; + + /** Switch: -complexinference option set? + */ + boolean complexInference; + + /** Character for synthetic names + */ + char syntheticNameChar; + + /** A table mapping flat names of all compiled classes in this run to their + * symbols; maintained from outside. + */ + public Map compiled = new HashMap(); + + /** A handler for messages about deprecated usage. + */ + private MandatoryWarningHandler deprecationHandler; + + /** A handler for messages about unchecked or unsafe usage. + */ + private MandatoryWarningHandler uncheckedHandler; + + /** A handler for messages about using proprietary API. + */ + private MandatoryWarningHandler sunApiHandler; + + /** A handler for deferred lint warnings. + */ + private DeferredLintHandler deferredLintHandler; + +/* ************************************************************************* + * Errors and Warnings + **************************************************************************/ + + Lint setLint(Lint newLint) { + Lint prev = lint; + lint = newLint; + return prev; + } + + DeferredLintHandler setDeferredLintHandler(DeferredLintHandler newDeferredLintHandler) { + DeferredLintHandler prev = deferredLintHandler; + deferredLintHandler = newDeferredLintHandler; + return prev; + } + + MethodSymbol setMethod(MethodSymbol newMethod) { + MethodSymbol prev = method; + method = newMethod; + return prev; + } + + /** Warn about deprecated symbol. + * @param pos Position to be used for error reporting. + * @param sym The deprecated symbol. + */ + void warnDeprecated(DiagnosticPosition pos, Symbol sym) { + if (!lint.isSuppressed(LintCategory.DEPRECATION)) + deprecationHandler.report(pos, "has.been.deprecated", sym, sym.location()); + } + + /** Warn about unchecked operation. + * @param pos Position to be used for error reporting. + * @param msg A string describing the problem. + */ + public void warnUnchecked(DiagnosticPosition pos, String msg, Object... args) { + if (!lint.isSuppressed(LintCategory.UNCHECKED)) + uncheckedHandler.report(pos, msg, args); + } + + /** Warn about unsafe vararg method decl. + * @param pos Position to be used for error reporting. + * @param sym The deprecated symbol. + */ + void warnUnsafeVararg(DiagnosticPosition pos, String key, Object... args) { + if (lint.isEnabled(LintCategory.VARARGS) && allowSimplifiedVarargs) + log.warning(LintCategory.VARARGS, pos, key, args); + } + + /** Warn about using proprietary API. + * @param pos Position to be used for error reporting. + * @param msg A string describing the problem. + */ + public void warnSunApi(DiagnosticPosition pos, String msg, Object... args) { + if (!lint.isSuppressed(LintCategory.SUNAPI)) + sunApiHandler.report(pos, msg, args); + } + + public void warnStatic(DiagnosticPosition pos, String msg, Object... args) { + if (lint.isEnabled(LintCategory.STATIC)) + log.warning(LintCategory.STATIC, pos, msg, args); + } + + /** + * Report any deferred diagnostics. + */ + public void reportDeferredDiagnostics() { + deprecationHandler.reportDeferredDiagnostic(); + uncheckedHandler.reportDeferredDiagnostic(); + sunApiHandler.reportDeferredDiagnostic(); + } + + + /** Report a failure to complete a class. + * @param pos Position to be used for error reporting. + * @param ex The failure to report. + */ + public Type completionError(DiagnosticPosition pos, CompletionFailure ex) { + log.error(pos, "cant.access", ex.sym, ex.getDetailValue()); + if (ex instanceof ClassReader.BadClassFile + && !suppressAbortOnBadClassFile) throw new Abort(); + else return syms.errType; + } + + /** Report a type error. + * @param pos Position to be used for error reporting. + * @param problem A string describing the error. + * @param found The type that was found. + * @param req The type that was required. + */ + Type typeError(DiagnosticPosition pos, Object problem, Type found, Type req) { + log.error(pos, "prob.found.req", + problem, found, req); + return types.createErrorType(found); + } + + Type typeError(DiagnosticPosition pos, String problem, Type found, Type req, Object explanation) { + log.error(pos, "prob.found.req.1", problem, found, req, explanation); + return types.createErrorType(found); + } + + /** Report an error that wrong type tag was found. + * @param pos Position to be used for error reporting. + * @param required An internationalized string describing the type tag + * required. + * @param found The type that was found. + */ + Type typeTagError(DiagnosticPosition pos, Object required, Object found) { + // this error used to be raised by the parser, + // but has been delayed to this point: + if (found instanceof Type && ((Type)found).tag == VOID) { + log.error(pos, "illegal.start.of.type"); + return syms.errType; + } + log.error(pos, "type.found.req", found, required); + return types.createErrorType(found instanceof Type ? (Type)found : syms.errType); + } + + /** Report an error that symbol cannot be referenced before super + * has been called. + * @param pos Position to be used for error reporting. + * @param sym The referenced symbol. + */ + void earlyRefError(DiagnosticPosition pos, Symbol sym) { + log.error(pos, "cant.ref.before.ctor.called", sym); + } + + /** Report duplicate declaration error. + */ + void duplicateError(DiagnosticPosition pos, Symbol sym) { + if (!sym.type.isErroneous()) { + log.error(pos, "already.defined", sym, sym.location()); + } + } + + /** Report array/varargs duplicate declaration + */ + void varargsDuplicateError(DiagnosticPosition pos, Symbol sym1, Symbol sym2) { + if (!sym1.type.isErroneous() && !sym2.type.isErroneous()) { + log.error(pos, "array.and.varargs", sym1, sym2, sym2.location()); + } + } + +/* ************************************************************************ + * duplicate declaration checking + *************************************************************************/ + + /** Check that variable does not hide variable with same name in + * immediately enclosing local scope. + * @param pos Position for error reporting. + * @param v The symbol. + * @param s The scope. + */ + void checkTransparentVar(DiagnosticPosition pos, VarSymbol v, Scope s) { + if (s.next != null) { + for (Scope.Entry e = s.next.lookup(v.name); + e.scope != null && e.sym.owner == v.owner; + e = e.next()) { + if (e.sym.kind == VAR && + (e.sym.owner.kind & (VAR | MTH)) != 0 && + v.name != names.error) { + duplicateError(pos, e.sym); + return; + } + } + } + } + + /** Check that a class or interface does not hide a class or + * interface with same name in immediately enclosing local scope. + * @param pos Position for error reporting. + * @param c The symbol. + * @param s The scope. + */ + void checkTransparentClass(DiagnosticPosition pos, ClassSymbol c, Scope s) { + if (s.next != null) { + for (Scope.Entry e = s.next.lookup(c.name); + e.scope != null && e.sym.owner == c.owner; + e = e.next()) { + if (e.sym.kind == TYP && e.sym.type.tag != TYPEVAR && + (e.sym.owner.kind & (VAR | MTH)) != 0 && + c.name != names.error) { + duplicateError(pos, e.sym); + return; + } + } + } + } + + /** Check that class does not have the same name as one of + * its enclosing classes, or as a class defined in its enclosing scope. + * return true if class is unique in its enclosing scope. + * @param pos Position for error reporting. + * @param name The class name. + * @param s The enclosing scope. + */ + boolean checkUniqueClassName(DiagnosticPosition pos, Name name, Scope s) { + for (Scope.Entry e = s.lookup(name); e.scope == s; e = e.next()) { + if (e.sym.kind == TYP && e.sym.name != names.error) { + duplicateError(pos, e.sym); + return false; + } + } + for (Symbol sym = s.owner; sym != null; sym = sym.owner) { + if (sym.kind == TYP && sym.name == name && sym.name != names.error) { + duplicateError(pos, sym); + return true; + } + } + return true; + } + +/* ************************************************************************* + * Class name generation + **************************************************************************/ + + /** Return name of local class. + * This is of the form $ n + * where + * enclClass is the flat name of the enclosing class, + * classname is the simple name of the local class + */ + Name localClassName(ClassSymbol c) { + for (int i=1; ; i++) { + Name flatname = names. + fromString("" + c.owner.enclClass().flatname + + syntheticNameChar + i + + c.name); + if (compiled.get(flatname) == null) return flatname; + } + } + +/* ************************************************************************* + * Type Checking + **************************************************************************/ + + /** Check that a given type is assignable to a given proto-type. + * If it is, return the type, otherwise return errType. + * @param pos Position to be used for error reporting. + * @param found The type that was found. + * @param req The type that was required. + */ + Type checkType(DiagnosticPosition pos, Type found, Type req) { + return checkType(pos, found, req, "incompatible.types"); + } + + Type checkType(DiagnosticPosition pos, Type found, Type req, String errKey) { + if (req.tag == ERROR) + return req; + if (found.tag == FORALL) + return instantiatePoly(pos, (ForAll)found, req, convertWarner(pos, found, req)); + if (req.tag == NONE) + return found; + if (types.isAssignable(found, req, convertWarner(pos, found, req))) + return found; + if (found.tag <= DOUBLE && req.tag <= DOUBLE) + return typeError(pos, diags.fragment("possible.loss.of.precision"), found, req); + if (found.isSuperBound()) { + log.error(pos, "assignment.from.super-bound", found); + return types.createErrorType(found); + } + if (req.isExtendsBound()) { + log.error(pos, "assignment.to.extends-bound", req); + return types.createErrorType(found); + } + return typeError(pos, diags.fragment(errKey), found, req); + } + + /** Instantiate polymorphic type to some prototype, unless + * prototype is `anyPoly' in which case polymorphic type + * is returned unchanged. + */ + Type instantiatePoly(DiagnosticPosition pos, ForAll t, Type pt, Warner warn) throws Infer.NoInstanceException { + if (pt == Infer.anyPoly && complexInference) { + return t; + } else if (pt == Infer.anyPoly || pt.tag == NONE) { + Type newpt = t.qtype.tag <= VOID ? t.qtype : syms.objectType; + return instantiatePoly(pos, t, newpt, warn); + } else if (pt.tag == ERROR) { + return pt; + } else { + try { + return infer.instantiateExpr(t, pt, warn); + } catch (Infer.NoInstanceException ex) { + if (ex.isAmbiguous) { + JCDiagnostic d = ex.getDiagnostic(); + log.error(pos, + "undetermined.type" + (d!=null ? ".1" : ""), + t, d); + return types.createErrorType(pt); + } else { + JCDiagnostic d = ex.getDiagnostic(); + return typeError(pos, + diags.fragment("incompatible.types" + (d!=null ? ".1" : ""), d), + t, pt); + } + } catch (Infer.InvalidInstanceException ex) { + JCDiagnostic d = ex.getDiagnostic(); + log.error(pos, "invalid.inferred.types", t.tvars, d); + return types.createErrorType(pt); + } + } + } + + /** Check that a given type can be cast to a given target type. + * Return the result of the cast. + * @param pos Position to be used for error reporting. + * @param found The type that is being cast. + * @param req The target type of the cast. + */ + Type checkCastable(DiagnosticPosition pos, Type found, Type req) { + if (found.tag == FORALL) { + instantiatePoly(pos, (ForAll) found, req, castWarner(pos, found, req)); + return req; + } else if (types.isCastable(found, req, castWarner(pos, found, req))) { + return req; + } else { + return typeError(pos, + diags.fragment("inconvertible.types"), + found, req); + } + } +//where + /** Is type a type variable, or a (possibly multi-dimensional) array of + * type variables? + */ + boolean isTypeVar(Type t) { + return t.tag == TYPEVAR || t.tag == ARRAY && isTypeVar(types.elemtype(t)); + } + + /** Check that a type is within some bounds. + * + * Used in TypeApply to verify that, e.g., X in V is a valid + * type argument. + * @param pos Position to be used for error reporting. + * @param a The type that should be bounded by bs. + * @param bs The bound. + */ + private boolean checkExtends(Type a, TypeVar bs) { + if (a.isUnbound()) { + return true; + } else if (a.tag != WILDCARD) { + a = types.upperBound(a); + return types.isSubtype(a, bs.bound); + } else if (a.isExtendsBound()) { + return types.isCastable(bs.getUpperBound(), types.upperBound(a), Warner.noWarnings); + } else if (a.isSuperBound()) { + return !types.notSoftSubtype(types.lowerBound(a), bs.getUpperBound()); + } + return true; + } + + /** Check that type is different from 'void'. + * @param pos Position to be used for error reporting. + * @param t The type to be checked. + */ + Type checkNonVoid(DiagnosticPosition pos, Type t) { + if (t.tag == VOID) { + log.error(pos, "void.not.allowed.here"); + return types.createErrorType(t); + } else { + return t; + } + } + + /** Check that type is a class or interface type. + * @param pos Position to be used for error reporting. + * @param t The type to be checked. + */ + Type checkClassType(DiagnosticPosition pos, Type t) { + if (t.tag != CLASS && t.tag != ERROR) + return typeTagError(pos, + diags.fragment("type.req.class"), + (t.tag == TYPEVAR) + ? diags.fragment("type.parameter", t) + : t); + else + return t; + } + + /** Check that type is a class or interface type. + * @param pos Position to be used for error reporting. + * @param t The type to be checked. + * @param noBounds True if type bounds are illegal here. + */ + Type checkClassType(DiagnosticPosition pos, Type t, boolean noBounds) { + t = checkClassType(pos, t); + if (noBounds && t.isParameterized()) { + List args = t.getTypeArguments(); + while (args.nonEmpty()) { + if (args.head.tag == WILDCARD) + return typeTagError(pos, + diags.fragment("type.req.exact"), + args.head); + args = args.tail; + } + } + return t; + } + + /** Check that type is a reifiable class, interface or array type. + * @param pos Position to be used for error reporting. + * @param t The type to be checked. + */ + Type checkReifiableReferenceType(DiagnosticPosition pos, Type t) { + if (t.tag != CLASS && t.tag != ARRAY && t.tag != ERROR) { + return typeTagError(pos, + diags.fragment("type.req.class.array"), + t); + } else if (!types.isReifiable(t)) { + log.error(pos, "illegal.generic.type.for.instof"); + return types.createErrorType(t); + } else { + return t; + } + } + + /** Check that type is a reference type, i.e. a class, interface or array type + * or a type variable. + * @param pos Position to be used for error reporting. + * @param t The type to be checked. + */ + Type checkRefType(DiagnosticPosition pos, Type t) { + switch (t.tag) { + case CLASS: + case ARRAY: + case TYPEVAR: + case WILDCARD: + case ERROR: + return t; + default: + return typeTagError(pos, + diags.fragment("type.req.ref"), + t); + } + } + + /** Check that each type is a reference type, i.e. a class, interface or array type + * or a type variable. + * @param trees Original trees, used for error reporting. + * @param types The types to be checked. + */ + List checkRefTypes(List trees, List types) { + List tl = trees; + for (List l = types; l.nonEmpty(); l = l.tail) { + l.head = checkRefType(tl.head.pos(), l.head); + tl = tl.tail; + } + return types; + } + + /** Check that type is a null or reference type. + * @param pos Position to be used for error reporting. + * @param t The type to be checked. + */ + Type checkNullOrRefType(DiagnosticPosition pos, Type t) { + switch (t.tag) { + case CLASS: + case ARRAY: + case TYPEVAR: + case WILDCARD: + case BOT: + case ERROR: + return t; + default: + return typeTagError(pos, + diags.fragment("type.req.ref"), + t); + } + } + + /** Check that flag set does not contain elements of two conflicting sets. s + * Return true if it doesn't. + * @param pos Position to be used for error reporting. + * @param flags The set of flags to be checked. + * @param set1 Conflicting flags set #1. + * @param set2 Conflicting flags set #2. + */ + boolean checkDisjoint(DiagnosticPosition pos, long flags, long set1, long set2) { + if ((flags & set1) != 0 && (flags & set2) != 0) { + log.error(pos, + "illegal.combination.of.modifiers", + asFlagSet(TreeInfo.firstFlag(flags & set1)), + asFlagSet(TreeInfo.firstFlag(flags & set2))); + return false; + } else + return true; + } + + /** Check that usage of diamond operator is correct (i.e. diamond should not + * be used with non-generic classes or in anonymous class creation expressions) + */ + Type checkDiamond(JCNewClass tree, Type t) { + if (!TreeInfo.isDiamond(tree) || + t.isErroneous()) { + return checkClassType(tree.clazz.pos(), t, true); + } else if (tree.def != null) { + log.error(tree.clazz.pos(), + "cant.apply.diamond.1", + t, diags.fragment("diamond.and.anon.class", t)); + return types.createErrorType(t); + } else if (t.tsym.type.getTypeArguments().isEmpty()) { + log.error(tree.clazz.pos(), + "cant.apply.diamond.1", + t, diags.fragment("diamond.non.generic", t)); + return types.createErrorType(t); + } else if (tree.typeargs != null && + tree.typeargs.nonEmpty()) { + log.error(tree.clazz.pos(), + "cant.apply.diamond.1", + t, diags.fragment("diamond.and.explicit.params", t)); + return types.createErrorType(t); + } else { + return t; + } + } + + void checkVarargsMethodDecl(Env env, JCMethodDecl tree) { + MethodSymbol m = tree.sym; + if (!allowSimplifiedVarargs) return; + boolean hasTrustMeAnno = m.attribute(syms.trustMeType.tsym) != null; + Type varargElemType = null; + if (m.isVarArgs()) { + varargElemType = types.elemtype(tree.params.last().type); + } + if (hasTrustMeAnno && !isTrustMeAllowedOnMethod(m)) { + if (varargElemType != null) { + log.error(tree, + "varargs.invalid.trustme.anno", + syms.trustMeType.tsym, + diags.fragment("varargs.trustme.on.virtual.varargs", m)); + } else { + log.error(tree, + "varargs.invalid.trustme.anno", + syms.trustMeType.tsym, + diags.fragment("varargs.trustme.on.non.varargs.meth", m)); + } + } else if (hasTrustMeAnno && varargElemType != null && + types.isReifiable(varargElemType)) { + warnUnsafeVararg(tree, + "varargs.redundant.trustme.anno", + syms.trustMeType.tsym, + diags.fragment("varargs.trustme.on.reifiable.varargs", varargElemType)); + } + else if (!hasTrustMeAnno && varargElemType != null && + !types.isReifiable(varargElemType)) { + warnUnchecked(tree.params.head.pos(), "unchecked.varargs.non.reifiable.type", varargElemType); + } + } + //where + private boolean isTrustMeAllowedOnMethod(Symbol s) { + return (s.flags() & VARARGS) != 0 && + (s.isConstructor() || + (s.flags() & (STATIC | FINAL)) != 0); + } + + /** + * Check that vararg method call is sound + * @param pos Position to be used for error reporting. + * @param argtypes Actual arguments supplied to vararg method. + */ + void checkVararg(DiagnosticPosition pos, List argtypes, Symbol msym) { + Type argtype = argtypes.last(); + if (!types.isReifiable(argtype) && + (!allowSimplifiedVarargs || + msym.attribute(syms.trustMeType.tsym) == null || + !isTrustMeAllowedOnMethod(msym))) { + warnUnchecked(pos, + "unchecked.generic.array.creation", + argtype); + } + } + + /** + * Check that type 't' is a valid instantiation of a generic class + * (see JLS 4.5) + * + * @param t class type to be checked + * @return true if 't' is well-formed + */ + public boolean checkValidGenericType(Type t) { + return firstIncompatibleTypeArg(t) == null; + } + //WHERE + private Type firstIncompatibleTypeArg(Type type) { + List formals = type.tsym.type.allparams(); + List actuals = type.allparams(); + List args = type.getTypeArguments(); + List forms = type.tsym.type.getTypeArguments(); + ListBuffer tvars_buf = new ListBuffer(); + + // For matching pairs of actual argument types `a' and + // formal type parameters with declared bound `b' ... + while (args.nonEmpty() && forms.nonEmpty()) { + // exact type arguments needs to know their + // bounds (for upper and lower bound + // calculations). So we create new TypeVars with + // bounds substed with actuals. + tvars_buf.append(types.substBound(((TypeVar)forms.head), + formals, + actuals)); + args = args.tail; + forms = forms.tail; + } + + args = type.getTypeArguments(); + List tvars_cap = types.substBounds(formals, + formals, + types.capture(type).allparams()); + while (args.nonEmpty() && tvars_cap.nonEmpty()) { + // Let the actual arguments know their bound + args.head.withTypeVar((TypeVar)tvars_cap.head); + args = args.tail; + tvars_cap = tvars_cap.tail; + } + + args = type.getTypeArguments(); + List tvars = tvars_buf.toList(); + + while (args.nonEmpty() && tvars.nonEmpty()) { + Type actual = types.subst(args.head, + type.tsym.type.getTypeArguments(), + tvars_buf.toList()); + if (!isTypeArgErroneous(actual) && + !tvars.head.getUpperBound().isErroneous() && + !checkExtends(actual, (TypeVar)tvars.head)) { + return args.head; + } + args = args.tail; + tvars = tvars.tail; + } + + args = type.getTypeArguments(); + tvars = tvars_buf.toList(); + + for (Type arg : types.capture(type).getTypeArguments()) { + if (arg.tag == TYPEVAR && + arg.getUpperBound().isErroneous() && + !tvars.head.getUpperBound().isErroneous() && + !isTypeArgErroneous(args.head)) { + return args.head; + } + tvars = tvars.tail; + args = args.tail; + } + + return null; + } + //where + boolean isTypeArgErroneous(Type t) { + return isTypeArgErroneous.visit(t); + } + + Types.UnaryVisitor isTypeArgErroneous = new Types.UnaryVisitor() { + public Boolean visitType(Type t, Void s) { + return t.isErroneous(); + } + @Override + public Boolean visitTypeVar(TypeVar t, Void s) { + return visit(t.getUpperBound()); + } + @Override + public Boolean visitCapturedType(CapturedType t, Void s) { + return visit(t.getUpperBound()) || + visit(t.getLowerBound()); + } + @Override + public Boolean visitWildcardType(WildcardType t, Void s) { + return visit(t.type); + } + }; + + /** Check that given modifiers are legal for given symbol and + * return modifiers together with any implicit modififiers for that symbol. + * Warning: we can't use flags() here since this method + * is called during class enter, when flags() would cause a premature + * completion. + * @param pos Position to be used for error reporting. + * @param flags The set of modifiers given in a definition. + * @param sym The defined symbol. + */ + long checkFlags(DiagnosticPosition pos, long flags, Symbol sym, JCTree tree) { + long mask; + long implicit = 0; + switch (sym.kind) { + case VAR: + if (sym.owner.kind != TYP) + mask = LocalVarFlags; + else if ((sym.owner.flags_field & INTERFACE) != 0) + mask = implicit = InterfaceVarFlags; + else + mask = VarFlags; + break; + case MTH: + if (sym.name == names.init) { + if ((sym.owner.flags_field & ENUM) != 0) { + // enum constructors cannot be declared public or + // protected and must be implicitly or explicitly + // private + implicit = PRIVATE; + mask = PRIVATE; + } else + mask = ConstructorFlags; + } else if ((sym.owner.flags_field & INTERFACE) != 0) + mask = implicit = InterfaceMethodFlags; + else { + mask = MethodFlags; + } + // Imply STRICTFP if owner has STRICTFP set. + if (((flags|implicit) & Flags.ABSTRACT) == 0) + implicit |= sym.owner.flags_field & STRICTFP; + break; + case TYP: + if (sym.isLocal()) { + mask = LocalClassFlags; + if (sym.name.isEmpty()) { // Anonymous class + // Anonymous classes in static methods are themselves static; + // that's why we admit STATIC here. + mask |= STATIC; + // JLS: Anonymous classes are final. + implicit |= FINAL; + } + if ((sym.owner.flags_field & STATIC) == 0 && + (flags & ENUM) != 0) + log.error(pos, "enums.must.be.static"); + } else if (sym.owner.kind == TYP) { + mask = MemberClassFlags; + if (sym.owner.owner.kind == PCK || + (sym.owner.flags_field & STATIC) != 0) + mask |= STATIC; + else if ((flags & ENUM) != 0) + log.error(pos, "enums.must.be.static"); + // Nested interfaces and enums are always STATIC (Spec ???) + if ((flags & (INTERFACE | ENUM)) != 0 ) implicit = STATIC; + } else { + mask = ClassFlags; + } + // Interfaces are always ABSTRACT + if ((flags & INTERFACE) != 0) implicit |= ABSTRACT; + + if ((flags & ENUM) != 0) { + // enums can't be declared abstract or final + mask &= ~(ABSTRACT | FINAL); + implicit |= implicitEnumFinalFlag(tree); + } + // Imply STRICTFP if owner has STRICTFP set. + implicit |= sym.owner.flags_field & STRICTFP; + break; + default: + throw new AssertionError(); + } + long illegal = flags & StandardFlags & ~mask; + if (illegal != 0) { + if ((illegal & INTERFACE) != 0) { + log.error(pos, "intf.not.allowed.here"); + mask |= INTERFACE; + } + else { + log.error(pos, + "mod.not.allowed.here", asFlagSet(illegal)); + } + } + else if ((sym.kind == TYP || + // ISSUE: Disallowing abstract&private is no longer appropriate + // in the presence of inner classes. Should it be deleted here? + checkDisjoint(pos, flags, + ABSTRACT, + PRIVATE | STATIC)) + && + checkDisjoint(pos, flags, + ABSTRACT | INTERFACE, + FINAL | NATIVE | SYNCHRONIZED) + && + checkDisjoint(pos, flags, + PUBLIC, + PRIVATE | PROTECTED) + && + checkDisjoint(pos, flags, + PRIVATE, + PUBLIC | PROTECTED) + && + checkDisjoint(pos, flags, + FINAL, + VOLATILE) + && + (sym.kind == TYP || + checkDisjoint(pos, flags, + ABSTRACT | NATIVE, + STRICTFP))) { + // skip + } + return flags & (mask | ~StandardFlags) | implicit; + } + + + /** Determine if this enum should be implicitly final. + * + * If the enum has no specialized enum contants, it is final. + * + * If the enum does have specialized enum contants, it is + * not final. + */ + private long implicitEnumFinalFlag(JCTree tree) { + if (tree.getTag() != JCTree.CLASSDEF) return 0; + class SpecialTreeVisitor extends JCTree.Visitor { + boolean specialized; + SpecialTreeVisitor() { + this.specialized = false; + }; + + @Override + public void visitTree(JCTree tree) { /* no-op */ } + + @Override + public void visitVarDef(JCVariableDecl tree) { + if ((tree.mods.flags & ENUM) != 0) { + if (tree.init instanceof JCNewClass && + ((JCNewClass) tree.init).def != null) { + specialized = true; + } + } + } + } + + SpecialTreeVisitor sts = new SpecialTreeVisitor(); + JCClassDecl cdef = (JCClassDecl) tree; + for (JCTree defs: cdef.defs) { + defs.accept(sts); + if (sts.specialized) return 0; + } + return FINAL; + } + +/* ************************************************************************* + * Type Validation + **************************************************************************/ + + /** Validate a type expression. That is, + * check that all type arguments of a parametric type are within + * their bounds. This must be done in a second phase after type attributon + * since a class might have a subclass as type parameter bound. E.g: + * + * class B { ... } + * class C extends B { ... } + * + * and we can't make sure that the bound is already attributed because + * of possible cycles. + * + * Visitor method: Validate a type expression, if it is not null, catching + * and reporting any completion failures. + */ + void validate(JCTree tree, Env env) { + validate(tree, env, true); + } + void validate(JCTree tree, Env env, boolean checkRaw) { + new Validator(env).validateTree(tree, checkRaw, true); + } + + /** Visitor method: Validate a list of type expressions. + */ + void validate(List trees, Env env) { + for (List l = trees; l.nonEmpty(); l = l.tail) + validate(l.head, env); + } + + /** A visitor class for type validation. + */ + class Validator extends JCTree.Visitor { + + boolean isOuter; + Env env; + + Validator(Env env) { + this.env = env; + } + + @Override + public void visitTypeArray(JCArrayTypeTree tree) { + tree.elemtype.accept(this); + } + + @Override + public void visitTypeApply(JCTypeApply tree) { + if (tree.type.tag == CLASS) { + List args = tree.arguments; + List forms = tree.type.tsym.type.getTypeArguments(); + + Type incompatibleArg = firstIncompatibleTypeArg(tree.type); + if (incompatibleArg != null) { + for (JCTree arg : tree.arguments) { + if (arg.type == incompatibleArg) { + log.error(arg, "not.within.bounds", incompatibleArg, forms.head); + } + forms = forms.tail; + } + } + + forms = tree.type.tsym.type.getTypeArguments(); + + boolean is_java_lang_Class = tree.type.tsym.flatName() == names.java_lang_Class; + + // For matching pairs of actual argument types `a' and + // formal type parameters with declared bound `b' ... + while (args.nonEmpty() && forms.nonEmpty()) { + validateTree(args.head, + !(isOuter && is_java_lang_Class), + false); + args = args.tail; + forms = forms.tail; + } + + // Check that this type is either fully parameterized, or + // not parameterized at all. + if (tree.type.getEnclosingType().isRaw()) + log.error(tree.pos(), "improperly.formed.type.inner.raw.param"); + if (tree.clazz.getTag() == JCTree.SELECT) + visitSelectInternal((JCFieldAccess)tree.clazz); + } + } + + @Override + public void visitTypeParameter(JCTypeParameter tree) { + validateTrees(tree.bounds, true, isOuter); + checkClassBounds(tree.pos(), tree.type); + } + + @Override + public void visitWildcard(JCWildcard tree) { + if (tree.inner != null) + validateTree(tree.inner, true, isOuter); + } + + @Override + public void visitSelect(JCFieldAccess tree) { + if (tree.type.tag == CLASS) { + visitSelectInternal(tree); + + // Check that this type is either fully parameterized, or + // not parameterized at all. + if (tree.selected.type.isParameterized() && tree.type.tsym.type.getTypeArguments().nonEmpty()) + log.error(tree.pos(), "improperly.formed.type.param.missing"); + } + } + + public void visitSelectInternal(JCFieldAccess tree) { + if (tree.type.tsym.isStatic() && + tree.selected.type.isParameterized()) { + // The enclosing type is not a class, so we are + // looking at a static member type. However, the + // qualifying expression is parameterized. + log.error(tree.pos(), "cant.select.static.class.from.param.type"); + } else { + // otherwise validate the rest of the expression + tree.selected.accept(this); + } + } + + /** Default visitor method: do nothing. + */ + @Override + public void visitTree(JCTree tree) { + } + + public void validateTree(JCTree tree, boolean checkRaw, boolean isOuter) { + try { + if (tree != null) { + this.isOuter = isOuter; + tree.accept(this); + if (checkRaw) + checkRaw(tree, env); + } + } catch (CompletionFailure ex) { + completionError(tree.pos(), ex); + } + } + + public void validateTrees(List trees, boolean checkRaw, boolean isOuter) { + for (List l = trees; l.nonEmpty(); l = l.tail) + validateTree(l.head, checkRaw, isOuter); + } + + void checkRaw(JCTree tree, Env env) { + if (lint.isEnabled(LintCategory.RAW) && + tree.type.tag == CLASS && + !TreeInfo.isDiamond(tree) && + !env.enclClass.name.isEmpty() && //anonymous or intersection + tree.type.isRaw()) { + log.warning(LintCategory.RAW, + tree.pos(), "raw.class.use", tree.type, tree.type.tsym.type); + } + } + } + +/* ************************************************************************* + * Exception checking + **************************************************************************/ + + /* The following methods treat classes as sets that contain + * the class itself and all their subclasses + */ + + /** Is given type a subtype of some of the types in given list? + */ + boolean subset(Type t, List ts) { + for (List l = ts; l.nonEmpty(); l = l.tail) + if (types.isSubtype(t, l.head)) return true; + return false; + } + + /** Is given type a subtype or supertype of + * some of the types in given list? + */ + boolean intersects(Type t, List ts) { + for (List l = ts; l.nonEmpty(); l = l.tail) + if (types.isSubtype(t, l.head) || types.isSubtype(l.head, t)) return true; + return false; + } + + /** Add type set to given type list, unless it is a subclass of some class + * in the list. + */ + List incl(Type t, List ts) { + return subset(t, ts) ? ts : excl(t, ts).prepend(t); + } + + /** Remove type set from type set list. + */ + List excl(Type t, List ts) { + if (ts.isEmpty()) { + return ts; + } else { + List ts1 = excl(t, ts.tail); + if (types.isSubtype(ts.head, t)) return ts1; + else if (ts1 == ts.tail) return ts; + else return ts1.prepend(ts.head); + } + } + + /** Form the union of two type set lists. + */ + List union(List ts1, List ts2) { + List ts = ts1; + for (List l = ts2; l.nonEmpty(); l = l.tail) + ts = incl(l.head, ts); + return ts; + } + + /** Form the difference of two type lists. + */ + List diff(List ts1, List ts2) { + List ts = ts1; + for (List l = ts2; l.nonEmpty(); l = l.tail) + ts = excl(l.head, ts); + return ts; + } + + /** Form the intersection of two type lists. + */ + public List intersect(List ts1, List ts2) { + List ts = List.nil(); + for (List l = ts1; l.nonEmpty(); l = l.tail) + if (subset(l.head, ts2)) ts = incl(l.head, ts); + for (List l = ts2; l.nonEmpty(); l = l.tail) + if (subset(l.head, ts1)) ts = incl(l.head, ts); + return ts; + } + + /** Is exc an exception symbol that need not be declared? + */ + boolean isUnchecked(ClassSymbol exc) { + return + exc.kind == ERR || + exc.isSubClass(syms.errorType.tsym, types) || + exc.isSubClass(syms.runtimeExceptionType.tsym, types); + } + + /** Is exc an exception type that need not be declared? + */ + boolean isUnchecked(Type exc) { + return + (exc.tag == TYPEVAR) ? isUnchecked(types.supertype(exc)) : + (exc.tag == CLASS) ? isUnchecked((ClassSymbol)exc.tsym) : + exc.tag == BOT; + } + + /** Same, but handling completion failures. + */ + boolean isUnchecked(DiagnosticPosition pos, Type exc) { + try { + return isUnchecked(exc); + } catch (CompletionFailure ex) { + completionError(pos, ex); + return true; + } + } + + /** Is exc handled by given exception list? + */ + boolean isHandled(Type exc, List handled) { + return isUnchecked(exc) || subset(exc, handled); + } + + /** Return all exceptions in thrown list that are not in handled list. + * @param thrown The list of thrown exceptions. + * @param handled The list of handled exceptions. + */ + List unhandled(List thrown, List handled) { + List unhandled = List.nil(); + for (List l = thrown; l.nonEmpty(); l = l.tail) + if (!isHandled(l.head, handled)) unhandled = unhandled.prepend(l.head); + return unhandled; + } + +/* ************************************************************************* + * Overriding/Implementation checking + **************************************************************************/ + + /** The level of access protection given by a flag set, + * where PRIVATE is highest and PUBLIC is lowest. + */ + static int protection(long flags) { + switch ((short)(flags & AccessFlags)) { + case PRIVATE: return 3; + case PROTECTED: return 1; + default: + case PUBLIC: return 0; + case 0: return 2; + } + } + + /** A customized "cannot override" error message. + * @param m The overriding method. + * @param other The overridden method. + * @return An internationalized string. + */ + Object cannotOverride(MethodSymbol m, MethodSymbol other) { + String key; + if ((other.owner.flags() & INTERFACE) == 0) + key = "cant.override"; + else if ((m.owner.flags() & INTERFACE) == 0) + key = "cant.implement"; + else + key = "clashes.with"; + return diags.fragment(key, m, m.location(), other, other.location()); + } + + /** A customized "override" warning message. + * @param m The overriding method. + * @param other The overridden method. + * @return An internationalized string. + */ + Object uncheckedOverrides(MethodSymbol m, MethodSymbol other) { + String key; + if ((other.owner.flags() & INTERFACE) == 0) + key = "unchecked.override"; + else if ((m.owner.flags() & INTERFACE) == 0) + key = "unchecked.implement"; + else + key = "unchecked.clash.with"; + return diags.fragment(key, m, m.location(), other, other.location()); + } + + /** A customized "override" warning message. + * @param m The overriding method. + * @param other The overridden method. + * @return An internationalized string. + */ + Object varargsOverrides(MethodSymbol m, MethodSymbol other) { + String key; + if ((other.owner.flags() & INTERFACE) == 0) + key = "varargs.override"; + else if ((m.owner.flags() & INTERFACE) == 0) + key = "varargs.implement"; + else + key = "varargs.clash.with"; + return diags.fragment(key, m, m.location(), other, other.location()); + } + + /** Check that this method conforms with overridden method 'other'. + * where `origin' is the class where checking started. + * Complications: + * (1) Do not check overriding of synthetic methods + * (reason: they might be final). + * todo: check whether this is still necessary. + * (2) Admit the case where an interface proxy throws fewer exceptions + * than the method it implements. Augment the proxy methods with the + * undeclared exceptions in this case. + * (3) When generics are enabled, admit the case where an interface proxy + * has a result type + * extended by the result type of the method it implements. + * Change the proxies result type to the smaller type in this case. + * + * @param tree The tree from which positions + * are extracted for errors. + * @param m The overriding method. + * @param other The overridden method. + * @param origin The class of which the overriding method + * is a member. + */ + void checkOverride(JCTree tree, + MethodSymbol m, + MethodSymbol other, + ClassSymbol origin) { + // Don't check overriding of synthetic methods or by bridge methods. + if ((m.flags() & (SYNTHETIC|BRIDGE)) != 0 || (other.flags() & SYNTHETIC) != 0) { + return; + } + + // Error if static method overrides instance method (JLS 8.4.6.2). + if ((m.flags() & STATIC) != 0 && + (other.flags() & STATIC) == 0) { + log.error(TreeInfo.diagnosticPositionFor(m, tree), "override.static", + cannotOverride(m, other)); + return; + } + + // Error if instance method overrides static or final + // method (JLS 8.4.6.1). + if ((other.flags() & FINAL) != 0 || + (m.flags() & STATIC) == 0 && + (other.flags() & STATIC) != 0) { + log.error(TreeInfo.diagnosticPositionFor(m, tree), "override.meth", + cannotOverride(m, other), + asFlagSet(other.flags() & (FINAL | STATIC))); + return; + } + + if ((m.owner.flags() & ANNOTATION) != 0) { + // handled in validateAnnotationMethod + return; + } + + // Error if overriding method has weaker access (JLS 8.4.6.3). + if ((origin.flags() & INTERFACE) == 0 && + protection(m.flags()) > protection(other.flags())) { + log.error(TreeInfo.diagnosticPositionFor(m, tree), "override.weaker.access", + cannotOverride(m, other), + other.flags() == 0 ? + Flag.PACKAGE : + asFlagSet(other.flags() & AccessFlags)); + return; + } + + Type mt = types.memberType(origin.type, m); + Type ot = types.memberType(origin.type, other); + // Error if overriding result type is different + // (or, in the case of generics mode, not a subtype) of + // overridden result type. We have to rename any type parameters + // before comparing types. + List mtvars = mt.getTypeArguments(); + List otvars = ot.getTypeArguments(); + Type mtres = mt.getReturnType(); + Type otres = types.subst(ot.getReturnType(), otvars, mtvars); + + overrideWarner.clear(); + boolean resultTypesOK = + types.returnTypeSubstitutable(mt, ot, otres, overrideWarner); + if (!resultTypesOK) { + if (!allowCovariantReturns && + m.owner != origin && + m.owner.isSubClass(other.owner, types)) { + // allow limited interoperability with covariant returns + } else { + log.error(TreeInfo.diagnosticPositionFor(m, tree), + "override.incompatible.ret", + cannotOverride(m, other), + mtres, otres); + return; + } + } else if (overrideWarner.hasNonSilentLint(LintCategory.UNCHECKED)) { + warnUnchecked(TreeInfo.diagnosticPositionFor(m, tree), + "override.unchecked.ret", + uncheckedOverrides(m, other), + mtres, otres); + } + + // Error if overriding method throws an exception not reported + // by overridden method. + List otthrown = types.subst(ot.getThrownTypes(), otvars, mtvars); + List unhandledErased = unhandled(mt.getThrownTypes(), types.erasure(otthrown)); + List unhandledUnerased = unhandled(mt.getThrownTypes(), otthrown); + if (unhandledErased.nonEmpty()) { + log.error(TreeInfo.diagnosticPositionFor(m, tree), + "override.meth.doesnt.throw", + cannotOverride(m, other), + unhandledUnerased.head); + return; + } + else if (unhandledUnerased.nonEmpty()) { + warnUnchecked(TreeInfo.diagnosticPositionFor(m, tree), + "override.unchecked.thrown", + cannotOverride(m, other), + unhandledUnerased.head); + return; + } + + // Optional warning if varargs don't agree + if ((((m.flags() ^ other.flags()) & Flags.VARARGS) != 0) + && lint.isEnabled(LintCategory.OVERRIDES)) { + log.warning(TreeInfo.diagnosticPositionFor(m, tree), + ((m.flags() & Flags.VARARGS) != 0) + ? "override.varargs.missing" + : "override.varargs.extra", + varargsOverrides(m, other)); + } + + // Warn if instance method overrides bridge method (compiler spec ??) + if ((other.flags() & BRIDGE) != 0) { + log.warning(TreeInfo.diagnosticPositionFor(m, tree), "override.bridge", + uncheckedOverrides(m, other)); + } + + // Warn if a deprecated method overridden by a non-deprecated one. + if (!isDeprecatedOverrideIgnorable(other, origin)) { + checkDeprecated(TreeInfo.diagnosticPositionFor(m, tree), m, other); + } + } + // where + private boolean isDeprecatedOverrideIgnorable(MethodSymbol m, ClassSymbol origin) { + // If the method, m, is defined in an interface, then ignore the issue if the method + // is only inherited via a supertype and also implemented in the supertype, + // because in that case, we will rediscover the issue when examining the method + // in the supertype. + // If the method, m, is not defined in an interface, then the only time we need to + // address the issue is when the method is the supertype implemementation: any other + // case, we will have dealt with when examining the supertype classes + ClassSymbol mc = m.enclClass(); + Type st = types.supertype(origin.type); + if (st.tag != CLASS) + return true; + MethodSymbol stimpl = m.implementation((ClassSymbol)st.tsym, types, false); + + if (mc != null && ((mc.flags() & INTERFACE) != 0)) { + List intfs = types.interfaces(origin.type); + return (intfs.contains(mc.type) ? false : (stimpl != null)); + } + else + return (stimpl != m); + } + + + // used to check if there were any unchecked conversions + Warner overrideWarner = new Warner(); + + /** Check that a class does not inherit two concrete methods + * with the same signature. + * @param pos Position to be used for error reporting. + * @param site The class type to be checked. + */ + public void checkCompatibleConcretes(DiagnosticPosition pos, Type site) { + Type sup = types.supertype(site); + if (sup.tag != CLASS) return; + + for (Type t1 = sup; + t1.tsym.type.isParameterized(); + t1 = types.supertype(t1)) { + for (Scope.Entry e1 = t1.tsym.members().elems; + e1 != null; + e1 = e1.sibling) { + Symbol s1 = e1.sym; + if (s1.kind != MTH || + (s1.flags() & (STATIC|SYNTHETIC|BRIDGE)) != 0 || + !s1.isInheritedIn(site.tsym, types) || + ((MethodSymbol)s1).implementation(site.tsym, + types, + true) != s1) + continue; + Type st1 = types.memberType(t1, s1); + int s1ArgsLength = st1.getParameterTypes().length(); + if (st1 == s1.type) continue; + + for (Type t2 = sup; + t2.tag == CLASS; + t2 = types.supertype(t2)) { + for (Scope.Entry e2 = t2.tsym.members().lookup(s1.name); + e2.scope != null; + e2 = e2.next()) { + Symbol s2 = e2.sym; + if (s2 == s1 || + s2.kind != MTH || + (s2.flags() & (STATIC|SYNTHETIC|BRIDGE)) != 0 || + s2.type.getParameterTypes().length() != s1ArgsLength || + !s2.isInheritedIn(site.tsym, types) || + ((MethodSymbol)s2).implementation(site.tsym, + types, + true) != s2) + continue; + Type st2 = types.memberType(t2, s2); + if (types.overrideEquivalent(st1, st2)) + log.error(pos, "concrete.inheritance.conflict", + s1, t1, s2, t2, sup); + } + } + } + } + } + + /** Check that classes (or interfaces) do not each define an abstract + * method with same name and arguments but incompatible return types. + * @param pos Position to be used for error reporting. + * @param t1 The first argument type. + * @param t2 The second argument type. + */ + public boolean checkCompatibleAbstracts(DiagnosticPosition pos, + Type t1, + Type t2) { + return checkCompatibleAbstracts(pos, t1, t2, + types.makeCompoundType(t1, t2)); + } + + public boolean checkCompatibleAbstracts(DiagnosticPosition pos, + Type t1, + Type t2, + Type site) { + return firstIncompatibility(pos, t1, t2, site) == null; + } + + /** Return the first method which is defined with same args + * but different return types in two given interfaces, or null if none + * exists. + * @param t1 The first type. + * @param t2 The second type. + * @param site The most derived type. + * @returns symbol from t2 that conflicts with one in t1. + */ + private Symbol firstIncompatibility(DiagnosticPosition pos, Type t1, Type t2, Type site) { + Map interfaces1 = new HashMap(); + closure(t1, interfaces1); + Map interfaces2; + if (t1 == t2) + interfaces2 = interfaces1; + else + closure(t2, interfaces1, interfaces2 = new HashMap()); + + for (Type t3 : interfaces1.values()) { + for (Type t4 : interfaces2.values()) { + Symbol s = firstDirectIncompatibility(pos, t3, t4, site); + if (s != null) return s; + } + } + return null; + } + + /** Compute all the supertypes of t, indexed by type symbol. */ + private void closure(Type t, Map typeMap) { + if (t.tag != CLASS) return; + if (typeMap.put(t.tsym, t) == null) { + closure(types.supertype(t), typeMap); + for (Type i : types.interfaces(t)) + closure(i, typeMap); + } + } + + /** Compute all the supertypes of t, indexed by type symbol (except thise in typesSkip). */ + private void closure(Type t, Map typesSkip, Map typeMap) { + if (t.tag != CLASS) return; + if (typesSkip.get(t.tsym) != null) return; + if (typeMap.put(t.tsym, t) == null) { + closure(types.supertype(t), typesSkip, typeMap); + for (Type i : types.interfaces(t)) + closure(i, typesSkip, typeMap); + } + } + + /** Return the first method in t2 that conflicts with a method from t1. */ + private Symbol firstDirectIncompatibility(DiagnosticPosition pos, Type t1, Type t2, Type site) { + for (Scope.Entry e1 = t1.tsym.members().elems; e1 != null; e1 = e1.sibling) { + Symbol s1 = e1.sym; + Type st1 = null; + if (s1.kind != MTH || !s1.isInheritedIn(site.tsym, types)) continue; + Symbol impl = ((MethodSymbol)s1).implementation(site.tsym, types, false); + if (impl != null && (impl.flags() & ABSTRACT) == 0) continue; + for (Scope.Entry e2 = t2.tsym.members().lookup(s1.name); e2.scope != null; e2 = e2.next()) { + Symbol s2 = e2.sym; + if (s1 == s2) continue; + if (s2.kind != MTH || !s2.isInheritedIn(site.tsym, types)) continue; + if (st1 == null) st1 = types.memberType(t1, s1); + Type st2 = types.memberType(t2, s2); + if (types.overrideEquivalent(st1, st2)) { + List tvars1 = st1.getTypeArguments(); + List tvars2 = st2.getTypeArguments(); + Type rt1 = st1.getReturnType(); + Type rt2 = types.subst(st2.getReturnType(), tvars2, tvars1); + boolean compat = + types.isSameType(rt1, rt2) || + rt1.tag >= CLASS && rt2.tag >= CLASS && + (types.covariantReturnType(rt1, rt2, Warner.noWarnings) || + types.covariantReturnType(rt2, rt1, Warner.noWarnings)) || + checkCommonOverriderIn(s1,s2,site); + if (!compat) { + log.error(pos, "types.incompatible.diff.ret", + t1, t2, s2.name + + "(" + types.memberType(t2, s2).getParameterTypes() + ")"); + return s2; + } + } else if (checkNameClash((ClassSymbol)site.tsym, s1, s2) && + !checkCommonOverriderIn(s1, s2, site)) { + log.error(pos, + "name.clash.same.erasure.no.override", + s1, s1.location(), + s2, s2.location()); + return s2; + } + } + } + return null; + } + //WHERE + boolean checkCommonOverriderIn(Symbol s1, Symbol s2, Type site) { + Map supertypes = new HashMap(); + Type st1 = types.memberType(site, s1); + Type st2 = types.memberType(site, s2); + closure(site, supertypes); + for (Type t : supertypes.values()) { + for (Scope.Entry e = t.tsym.members().lookup(s1.name); e.scope != null; e = e.next()) { + Symbol s3 = e.sym; + if (s3 == s1 || s3 == s2 || s3.kind != MTH || (s3.flags() & (BRIDGE|SYNTHETIC)) != 0) continue; + Type st3 = types.memberType(site,s3); + if (types.overrideEquivalent(st3, st1) && types.overrideEquivalent(st3, st2)) { + if (s3.owner == site.tsym) { + return true; + } + List tvars1 = st1.getTypeArguments(); + List tvars2 = st2.getTypeArguments(); + List tvars3 = st3.getTypeArguments(); + Type rt1 = st1.getReturnType(); + Type rt2 = st2.getReturnType(); + Type rt13 = types.subst(st3.getReturnType(), tvars3, tvars1); + Type rt23 = types.subst(st3.getReturnType(), tvars3, tvars2); + boolean compat = + rt13.tag >= CLASS && rt23.tag >= CLASS && + (types.covariantReturnType(rt13, rt1, Warner.noWarnings) && + types.covariantReturnType(rt23, rt2, Warner.noWarnings)); + if (compat) + return true; + } + } + } + return false; + } + + /** Check that a given method conforms with any method it overrides. + * @param tree The tree from which positions are extracted + * for errors. + * @param m The overriding method. + */ + void checkOverride(JCTree tree, MethodSymbol m) { + ClassSymbol origin = (ClassSymbol)m.owner; + if ((origin.flags() & ENUM) != 0 && names.finalize.equals(m.name)) + if (m.overrides(syms.enumFinalFinalize, origin, types, false)) { + log.error(tree.pos(), "enum.no.finalize"); + return; + } + for (Type t = origin.type; t.tag == CLASS; + t = types.supertype(t)) { + if (t != origin.type) { + checkOverride(tree, t, origin, m); + } + for (Type t2 : types.interfaces(t)) { + checkOverride(tree, t2, origin, m); + } + } + } + + void checkOverride(JCTree tree, Type site, ClassSymbol origin, MethodSymbol m) { + TypeSymbol c = site.tsym; + Scope.Entry e = c.members().lookup(m.name); + while (e.scope != null) { + if (m.overrides(e.sym, origin, types, false)) { + if ((e.sym.flags() & ABSTRACT) == 0) { + checkOverride(tree, m, (MethodSymbol)e.sym, origin); + } + } + e = e.next(); + } + } + + private boolean checkNameClash(ClassSymbol origin, Symbol s1, Symbol s2) { + ClashFilter cf = new ClashFilter(origin.type); + return (cf.accepts(s1) && + cf.accepts(s2) && + types.hasSameArgs(s1.erasure(types), s2.erasure(types))); + } + + + /** Check that all abstract members of given class have definitions. + * @param pos Position to be used for error reporting. + * @param c The class. + */ + void checkAllDefined(DiagnosticPosition pos, ClassSymbol c) { + try { + MethodSymbol undef = firstUndef(c, c); + if (undef != null) { + if ((c.flags() & ENUM) != 0 && + types.supertype(c.type).tsym == syms.enumSym && + (c.flags() & FINAL) == 0) { + // add the ABSTRACT flag to an enum + c.flags_field |= ABSTRACT; + } else { + MethodSymbol undef1 = + new MethodSymbol(undef.flags(), undef.name, + types.memberType(c.type, undef), undef.owner); + log.error(pos, "does.not.override.abstract", + c, undef1, undef1.location()); + } + } + } catch (CompletionFailure ex) { + completionError(pos, ex); + } + } +//where + /** Return first abstract member of class `c' that is not defined + * in `impl', null if there is none. + */ + private MethodSymbol firstUndef(ClassSymbol impl, ClassSymbol c) { + MethodSymbol undef = null; + // Do not bother to search in classes that are not abstract, + // since they cannot have abstract members. + if (c == impl || (c.flags() & (ABSTRACT | INTERFACE)) != 0) { + Scope s = c.members(); + for (Scope.Entry e = s.elems; + undef == null && e != null; + e = e.sibling) { + if (e.sym.kind == MTH && + (e.sym.flags() & (ABSTRACT|IPROXY)) == ABSTRACT) { + MethodSymbol absmeth = (MethodSymbol)e.sym; + MethodSymbol implmeth = absmeth.implementation(impl, types, true); + if (implmeth == null || implmeth == absmeth) + undef = absmeth; + } + } + if (undef == null) { + Type st = types.supertype(c.type); + if (st.tag == CLASS) + undef = firstUndef(impl, (ClassSymbol)st.tsym); + } + for (List l = types.interfaces(c.type); + undef == null && l.nonEmpty(); + l = l.tail) { + undef = firstUndef(impl, (ClassSymbol)l.head.tsym); + } + } + return undef; + } + + void checkNonCyclicDecl(JCClassDecl tree) { + CycleChecker cc = new CycleChecker(); + cc.scan(tree); + if (!cc.errorFound && !cc.partialCheck) { + tree.sym.flags_field |= ACYCLIC; + } + } + + class CycleChecker extends TreeScanner { + + List seenClasses = List.nil(); + boolean errorFound = false; + boolean partialCheck = false; + + private void checkSymbol(DiagnosticPosition pos, Symbol sym) { + if (sym != null && sym.kind == TYP) { + Env classEnv = enter.getEnv((TypeSymbol)sym); + if (classEnv != null) { + DiagnosticSource prevSource = log.currentSource(); + try { + log.useSource(classEnv.toplevel.sourcefile); + scan(classEnv.tree); + } + finally { + log.useSource(prevSource.getFile()); + } + } else if (sym.kind == TYP) { + checkClass(pos, sym, List.nil()); + } + } else { + //not completed yet + partialCheck = true; + } + } + + @Override + public void visitSelect(JCFieldAccess tree) { + super.visitSelect(tree); + checkSymbol(tree.pos(), tree.sym); + } + + @Override + public void visitIdent(JCIdent tree) { + checkSymbol(tree.pos(), tree.sym); + } + + @Override + public void visitTypeApply(JCTypeApply tree) { + scan(tree.clazz); + } + + @Override + public void visitTypeArray(JCArrayTypeTree tree) { + scan(tree.elemtype); + } + + @Override + public void visitClassDef(JCClassDecl tree) { + List supertypes = List.nil(); + if (tree.getExtendsClause() != null) { + supertypes = supertypes.prepend(tree.getExtendsClause()); + } + if (tree.getImplementsClause() != null) { + for (JCTree intf : tree.getImplementsClause()) { + supertypes = supertypes.prepend(intf); + } + } + checkClass(tree.pos(), tree.sym, supertypes); + } + + void checkClass(DiagnosticPosition pos, Symbol c, List supertypes) { + if ((c.flags_field & ACYCLIC) != 0) + return; + if (seenClasses.contains(c)) { + errorFound = true; + noteCyclic(pos, (ClassSymbol)c); + } else if (!c.type.isErroneous()) { + try { + seenClasses = seenClasses.prepend(c); + if (c.type.tag == CLASS) { + if (supertypes.nonEmpty()) { + scan(supertypes); + } + else { + ClassType ct = (ClassType)c.type; + if (ct.supertype_field == null || + ct.interfaces_field == null) { + //not completed yet + partialCheck = true; + return; + } + checkSymbol(pos, ct.supertype_field.tsym); + for (Type intf : ct.interfaces_field) { + checkSymbol(pos, intf.tsym); + } + } + if (c.owner.kind == TYP) { + checkSymbol(pos, c.owner); + } + } + } finally { + seenClasses = seenClasses.tail; + } + } + } + } + + /** Check for cyclic references. Issue an error if the + * symbol of the type referred to has a LOCKED flag set. + * + * @param pos Position to be used for error reporting. + * @param t The type referred to. + */ + void checkNonCyclic(DiagnosticPosition pos, Type t) { + checkNonCyclicInternal(pos, t); + } + + + void checkNonCyclic(DiagnosticPosition pos, TypeVar t) { + checkNonCyclic1(pos, t, List.nil()); + } + + private void checkNonCyclic1(DiagnosticPosition pos, Type t, List seen) { + final TypeVar tv; + if (t.tag == TYPEVAR && (t.tsym.flags() & UNATTRIBUTED) != 0) + return; + if (seen.contains(t)) { + tv = (TypeVar)t; + tv.bound = types.createErrorType(t); + log.error(pos, "cyclic.inheritance", t); + } else if (t.tag == TYPEVAR) { + tv = (TypeVar)t; + seen = seen.prepend(tv); + for (Type b : types.getBounds(tv)) + checkNonCyclic1(pos, b, seen); + } + } + + /** Check for cyclic references. Issue an error if the + * symbol of the type referred to has a LOCKED flag set. + * + * @param pos Position to be used for error reporting. + * @param t The type referred to. + * @returns True if the check completed on all attributed classes + */ + private boolean checkNonCyclicInternal(DiagnosticPosition pos, Type t) { + boolean complete = true; // was the check complete? + //- System.err.println("checkNonCyclicInternal("+t+");");//DEBUG + Symbol c = t.tsym; + if ((c.flags_field & ACYCLIC) != 0) return true; + + if ((c.flags_field & LOCKED) != 0) { + noteCyclic(pos, (ClassSymbol)c); + } else if (!c.type.isErroneous()) { + try { + c.flags_field |= LOCKED; + if (c.type.tag == CLASS) { + ClassType clazz = (ClassType)c.type; + if (clazz.interfaces_field != null) + for (List l=clazz.interfaces_field; l.nonEmpty(); l=l.tail) + complete &= checkNonCyclicInternal(pos, l.head); + if (clazz.supertype_field != null) { + Type st = clazz.supertype_field; + if (st != null && st.tag == CLASS) + complete &= checkNonCyclicInternal(pos, st); + } + if (c.owner.kind == TYP) + complete &= checkNonCyclicInternal(pos, c.owner.type); + } + } finally { + c.flags_field &= ~LOCKED; + } + } + if (complete) + complete = ((c.flags_field & UNATTRIBUTED) == 0) && c.completer == null; + if (complete) c.flags_field |= ACYCLIC; + return complete; + } + + /** Note that we found an inheritance cycle. */ + private void noteCyclic(DiagnosticPosition pos, ClassSymbol c) { + log.error(pos, "cyclic.inheritance", c); + for (List l=types.interfaces(c.type); l.nonEmpty(); l=l.tail) + l.head = types.createErrorType((ClassSymbol)l.head.tsym, Type.noType); + Type st = types.supertype(c.type); + if (st.tag == CLASS) + ((ClassType)c.type).supertype_field = types.createErrorType((ClassSymbol)st.tsym, Type.noType); + c.type = types.createErrorType(c, c.type); + c.flags_field |= ACYCLIC; + } + + /** Check that all methods which implement some + * method conform to the method they implement. + * @param tree The class definition whose members are checked. + */ + void checkImplementations(JCClassDecl tree) { + checkImplementations(tree, tree.sym); + } +//where + /** Check that all methods which implement some + * method in `ic' conform to the method they implement. + */ + void checkImplementations(JCClassDecl tree, ClassSymbol ic) { + ClassSymbol origin = tree.sym; + for (List l = types.closure(ic.type); l.nonEmpty(); l = l.tail) { + ClassSymbol lc = (ClassSymbol)l.head.tsym; + if ((allowGenerics || origin != lc) && (lc.flags() & ABSTRACT) != 0) { + for (Scope.Entry e=lc.members().elems; e != null; e=e.sibling) { + if (e.sym.kind == MTH && + (e.sym.flags() & (STATIC|ABSTRACT)) == ABSTRACT) { + MethodSymbol absmeth = (MethodSymbol)e.sym; + MethodSymbol implmeth = absmeth.implementation(origin, types, false); + if (implmeth != null && implmeth != absmeth && + (implmeth.owner.flags() & INTERFACE) == + (origin.flags() & INTERFACE)) { + // don't check if implmeth is in a class, yet + // origin is an interface. This case arises only + // if implmeth is declared in Object. The reason is + // that interfaces really don't inherit from + // Object it's just that the compiler represents + // things that way. + checkOverride(tree, implmeth, absmeth, origin); + } + } + } + } + } + } + + /** Check that all abstract methods implemented by a class are + * mutually compatible. + * @param pos Position to be used for error reporting. + * @param c The class whose interfaces are checked. + */ + void checkCompatibleSupertypes(DiagnosticPosition pos, Type c) { + List supertypes = types.interfaces(c); + Type supertype = types.supertype(c); + if (supertype.tag == CLASS && + (supertype.tsym.flags() & ABSTRACT) != 0) + supertypes = supertypes.prepend(supertype); + for (List l = supertypes; l.nonEmpty(); l = l.tail) { + if (allowGenerics && !l.head.getTypeArguments().isEmpty() && + !checkCompatibleAbstracts(pos, l.head, l.head, c)) + return; + for (List m = supertypes; m != l; m = m.tail) + if (!checkCompatibleAbstracts(pos, l.head, m.head, c)) + return; + } + checkCompatibleConcretes(pos, c); + } + + void checkConflicts(DiagnosticPosition pos, Symbol sym, TypeSymbol c) { + for (Type ct = c.type; ct != Type.noType ; ct = types.supertype(ct)) { + for (Scope.Entry e = ct.tsym.members().lookup(sym.name); e.scope == ct.tsym.members(); e = e.next()) { + // VM allows methods and variables with differing types + if (sym.kind == e.sym.kind && + types.isSameType(types.erasure(sym.type), types.erasure(e.sym.type)) && + sym != e.sym && + (sym.flags() & Flags.SYNTHETIC) != (e.sym.flags() & Flags.SYNTHETIC) && + (sym.flags() & IPROXY) == 0 && (e.sym.flags() & IPROXY) == 0 && + (sym.flags() & BRIDGE) == 0 && (e.sym.flags() & BRIDGE) == 0) { + syntheticError(pos, (e.sym.flags() & SYNTHETIC) == 0 ? e.sym : sym); + return; + } + } + } + } + + /** Check that all non-override equivalent methods accessible from 'site' + * are mutually compatible (JLS 8.4.8/9.4.1). + * + * @param pos Position to be used for error reporting. + * @param site The class whose methods are checked. + * @param sym The method symbol to be checked. + */ + void checkOverrideClashes(DiagnosticPosition pos, Type site, MethodSymbol sym) { + ClashFilter cf = new ClashFilter(site); + //for each method m1 that is a member of 'site'... + for (Symbol s1 : types.membersClosure(site).getElementsByName(sym.name, cf)) { + //...find another method m2 that is overridden (directly or indirectly) + //by method 'sym' in 'site' + for (Symbol s2 : types.membersClosure(site).getElementsByName(sym.name, cf)) { + if (s1 == s2 || !sym.overrides(s2, site.tsym, types, false)) continue; + //if (i) the signature of 'sym' is not a subsignature of m1 (seen as + //a member of 'site') and (ii) m1 has the same erasure as m2, issue an error + if (!types.isSubSignature(sym.type, types.memberType(site, s1), false) && + types.hasSameArgs(s1.erasure(types), s2.erasure(types))) { + sym.flags_field |= CLASH; + String key = s2 == sym ? + "name.clash.same.erasure.no.override" : + "name.clash.same.erasure.no.override.1"; + log.error(pos, + key, + sym, sym.location(), + s1, s1.location(), + s2, s2.location()); + return; + } + } + } + } + + + + /** Check that all static methods accessible from 'site' are + * mutually compatible (JLS 8.4.8). + * + * @param pos Position to be used for error reporting. + * @param site The class whose methods are checked. + * @param sym The method symbol to be checked. + */ + void checkHideClashes(DiagnosticPosition pos, Type site, MethodSymbol sym) { + ClashFilter cf = new ClashFilter(site); + //for each method m1 that is a member of 'site'... + for (Symbol s : types.membersClosure(site).getElementsByName(sym.name, cf)) { + //if (i) the signature of 'sym' is not a subsignature of m1 (seen as + //a member of 'site') and (ii) 'sym' has the same erasure as m1, issue an error + if (!types.isSubSignature(sym.type, types.memberType(site, s), false) && + types.hasSameArgs(s.erasure(types), sym.erasure(types))) { + log.error(pos, + "name.clash.same.erasure.no.hide", + sym, sym.location(), + s, s.location()); + return; + } + } + } + + //where + private class ClashFilter implements Filter { + + Type site; + + ClashFilter(Type site) { + this.site = site; + } + + boolean shouldSkip(Symbol s) { + return (s.flags() & CLASH) != 0 && + s.owner == site.tsym; + } + + public boolean accepts(Symbol s) { + return s.kind == MTH && + (s.flags() & SYNTHETIC) == 0 && + !shouldSkip(s) && + s.isInheritedIn(site.tsym, types) && + !s.isConstructor(); + } + } + + /** Report a conflict between a user symbol and a synthetic symbol. + */ + private void syntheticError(DiagnosticPosition pos, Symbol sym) { + if (!sym.type.isErroneous()) { + if (warnOnSyntheticConflicts) { + log.warning(pos, "synthetic.name.conflict", sym, sym.location()); + } + else { + log.error(pos, "synthetic.name.conflict", sym, sym.location()); + } + } + } + + /** Check that class c does not implement directly or indirectly + * the same parameterized interface with two different argument lists. + * @param pos Position to be used for error reporting. + * @param type The type whose interfaces are checked. + */ + void checkClassBounds(DiagnosticPosition pos, Type type) { + checkClassBounds(pos, new HashMap(), type); + } +//where + /** Enter all interfaces of type `type' into the hash table `seensofar' + * with their class symbol as key and their type as value. Make + * sure no class is entered with two different types. + */ + void checkClassBounds(DiagnosticPosition pos, + Map seensofar, + Type type) { + if (type.isErroneous()) return; + for (List l = types.interfaces(type); l.nonEmpty(); l = l.tail) { + Type it = l.head; + Type oldit = seensofar.put(it.tsym, it); + if (oldit != null) { + List oldparams = oldit.allparams(); + List newparams = it.allparams(); + if (!types.containsTypeEquivalent(oldparams, newparams)) + log.error(pos, "cant.inherit.diff.arg", + it.tsym, Type.toString(oldparams), + Type.toString(newparams)); + } + checkClassBounds(pos, seensofar, it); + } + Type st = types.supertype(type); + if (st != null) checkClassBounds(pos, seensofar, st); + } + + /** Enter interface into into set. + * If it existed already, issue a "repeated interface" error. + */ + void checkNotRepeated(DiagnosticPosition pos, Type it, Set its) { + if (its.contains(it)) + log.error(pos, "repeated.interface"); + else { + its.add(it); + } + } + +/* ************************************************************************* + * Check annotations + **************************************************************************/ + + /** + * Recursively validate annotations values + */ + void validateAnnotationTree(JCTree tree) { + class AnnotationValidator extends TreeScanner { + @Override + public void visitAnnotation(JCAnnotation tree) { + super.visitAnnotation(tree); + validateAnnotation(tree); + } + } + tree.accept(new AnnotationValidator()); + } + + /** Annotation types are restricted to primitives, String, an + * enum, an annotation, Class, Class, Class, arrays of the preceding. + */ + void validateAnnotationType(JCTree restype) { + // restype may be null if an error occurred, so don't bother validating it + if (restype != null) { + validateAnnotationType(restype.pos(), restype.type); + } + } + + void validateAnnotationType(DiagnosticPosition pos, Type type) { + if (type.isPrimitive()) return; + if (types.isSameType(type, syms.stringType)) return; + if ((type.tsym.flags() & Flags.ENUM) != 0) return; + if ((type.tsym.flags() & Flags.ANNOTATION) != 0) return; + if (types.lowerBound(type).tsym == syms.classType.tsym) return; + if (types.isArray(type) && !types.isArray(types.elemtype(type))) { + validateAnnotationType(pos, types.elemtype(type)); + return; + } + log.error(pos, "invalid.annotation.member.type"); + } + + /** + * "It is also a compile-time error if any method declared in an + * annotation type has a signature that is override-equivalent to + * that of any public or protected method declared in class Object + * or in the interface annotation.Annotation." + * + * @jls 9.6 Annotation Types + */ + void validateAnnotationMethod(DiagnosticPosition pos, MethodSymbol m) { + for (Type sup = syms.annotationType; sup.tag == CLASS; sup = types.supertype(sup)) { + Scope s = sup.tsym.members(); + for (Scope.Entry e = s.lookup(m.name); e.scope != null; e = e.next()) { + if (e.sym.kind == MTH && + (e.sym.flags() & (PUBLIC | PROTECTED)) != 0 && + types.overrideEquivalent(m.type, e.sym.type)) + log.error(pos, "intf.annotation.member.clash", e.sym, sup); + } + } + } + + /** Check the annotations of a symbol. + */ + public void validateAnnotations(List annotations, Symbol s) { + if (skipAnnotations) return; + for (JCAnnotation a : annotations) + validateAnnotation(a, s); + } + + /** Check an annotation of a symbol. + */ + public void validateAnnotation(JCAnnotation a, Symbol s) { + validateAnnotationTree(a); + + if (!annotationApplicable(a, s)) + log.error(a.pos(), "annotation.type.not.applicable"); + + if (a.annotationType.type.tsym == syms.overrideType.tsym) { + if (!isOverrider(s)) + log.error(a.pos(), "method.does.not.override.superclass"); + } + } + + /** Is s a method symbol that overrides a method in a superclass? */ + boolean isOverrider(Symbol s) { + if (s.kind != MTH || s.isStatic()) + return false; + MethodSymbol m = (MethodSymbol)s; + TypeSymbol owner = (TypeSymbol)m.owner; + for (Type sup : types.closure(owner.type)) { + if (sup == owner.type) + continue; // skip "this" + Scope scope = sup.tsym.members(); + for (Scope.Entry e = scope.lookup(m.name); e.scope != null; e = e.next()) { + if (!e.sym.isStatic() && m.overrides(e.sym, owner, types, true)) + return true; + } + } + return false; + } + + /** Is the annotation applicable to the symbol? */ + boolean annotationApplicable(JCAnnotation a, Symbol s) { + Attribute.Compound atTarget = + a.annotationType.type.tsym.attribute(syms.annotationTargetType.tsym); + if (atTarget == null) return true; + Attribute atValue = atTarget.member(names.value); + if (!(atValue instanceof Attribute.Array)) return true; // error recovery + Attribute.Array arr = (Attribute.Array) atValue; + for (Attribute app : arr.values) { + if (!(app instanceof Attribute.Enum)) return true; // recovery + Attribute.Enum e = (Attribute.Enum) app; + if (e.value.name == names.TYPE) + { if (s.kind == TYP) return true; } + else if (e.value.name == names.FIELD) + { if (s.kind == VAR && s.owner.kind != MTH) return true; } + else if (e.value.name == names.METHOD) + { if (s.kind == MTH && !s.isConstructor()) return true; } + else if (e.value.name == names.PARAMETER) + { if (s.kind == VAR && + s.owner.kind == MTH && + (s.flags() & PARAMETER) != 0) + return true; + } + else if (e.value.name == names.CONSTRUCTOR) + { if (s.kind == MTH && s.isConstructor()) return true; } + else if (e.value.name == names.LOCAL_VARIABLE) + { if (s.kind == VAR && s.owner.kind == MTH && + (s.flags() & PARAMETER) == 0) + return true; + } + else if (e.value.name == names.ANNOTATION_TYPE) + { if (s.kind == TYP && (s.flags() & ANNOTATION) != 0) + return true; + } + else if (e.value.name == names.PACKAGE) + { if (s.kind == PCK) return true; } + else if (e.value.name == names.TYPE_USE) + { if (s.kind == TYP || + s.kind == VAR || + (s.kind == MTH && !s.isConstructor() && + s.type.getReturnType().tag != VOID)) + return true; + } + else + return true; // recovery + } + return false; + } + + /** Check an annotation value. + */ + public void validateAnnotation(JCAnnotation a) { + if (a.type.isErroneous()) return; + + // collect an inventory of the members (sorted alphabetically) + Set members = new TreeSet(new Comparator() { + public int compare(Symbol t, Symbol t1) { + return t.name.compareTo(t1.name); + } + }); + for (Scope.Entry e = a.annotationType.type.tsym.members().elems; + e != null; + e = e.sibling) + if (e.sym.kind == MTH) + members.add((MethodSymbol) e.sym); + + // count them off as they're annotated + for (JCTree arg : a.args) { + if (arg.getTag() != JCTree.ASSIGN) continue; // recovery + JCAssign assign = (JCAssign) arg; + Symbol m = TreeInfo.symbol(assign.lhs); + if (m == null || m.type.isErroneous()) continue; + if (!members.remove(m)) + log.error(assign.lhs.pos(), "duplicate.annotation.member.value", + m.name, a.type); + } + + // all the remaining ones better have default values + ListBuffer missingDefaults = ListBuffer.lb(); + for (MethodSymbol m : members) { + if (m.defaultValue == null && !m.type.isErroneous()) { + missingDefaults.append(m.name); + } + } + if (missingDefaults.nonEmpty()) { + String key = (missingDefaults.size() > 1) + ? "annotation.missing.default.value.1" + : "annotation.missing.default.value"; + log.error(a.pos(), key, a.type, missingDefaults); + } + + // special case: java.lang.annotation.Target must not have + // repeated values in its value member + if (a.annotationType.type.tsym != syms.annotationTargetType.tsym || + a.args.tail == null) + return; + + if (a.args.head.getTag() != JCTree.ASSIGN) return; // error recovery + JCAssign assign = (JCAssign) a.args.head; + Symbol m = TreeInfo.symbol(assign.lhs); + if (m.name != names.value) return; + JCTree rhs = assign.rhs; + if (rhs.getTag() != JCTree.NEWARRAY) return; + JCNewArray na = (JCNewArray) rhs; + Set targets = new HashSet(); + for (JCTree elem : na.elems) { + if (!targets.add(TreeInfo.symbol(elem))) { + log.error(elem.pos(), "repeated.annotation.target"); + } + } + } + + void checkDeprecatedAnnotation(DiagnosticPosition pos, Symbol s) { + if (allowAnnotations && + lint.isEnabled(LintCategory.DEP_ANN) && + (s.flags() & DEPRECATED) != 0 && + !syms.deprecatedType.isErroneous() && + s.attribute(syms.deprecatedType.tsym) == null) { + log.warning(LintCategory.DEP_ANN, + pos, "missing.deprecated.annotation"); + } + } + + void checkDeprecated(final DiagnosticPosition pos, final Symbol other, final Symbol s) { + if ((s.flags() & DEPRECATED) != 0 && + (other.flags() & DEPRECATED) == 0 && + s.outermostClass() != other.outermostClass()) { + deferredLintHandler.report(new DeferredLintHandler.LintLogger() { + @Override + public void report() { + warnDeprecated(pos, s); + } + }); + }; + } + + void checkSunAPI(final DiagnosticPosition pos, final Symbol s) { + if ((s.flags() & PROPRIETARY) != 0) { + deferredLintHandler.report(new DeferredLintHandler.LintLogger() { + public void report() { + if (enableSunApiLintControl) + warnSunApi(pos, "sun.proprietary", s); + else + log.strictWarning(pos, "sun.proprietary", s); + } + }); + } + } + +/* ************************************************************************* + * Check for recursive annotation elements. + **************************************************************************/ + + /** Check for cycles in the graph of annotation elements. + */ + void checkNonCyclicElements(JCClassDecl tree) { + if ((tree.sym.flags_field & ANNOTATION) == 0) return; + Assert.check((tree.sym.flags_field & LOCKED) == 0); + try { + tree.sym.flags_field |= LOCKED; + for (JCTree def : tree.defs) { + if (def.getTag() != JCTree.METHODDEF) continue; + JCMethodDecl meth = (JCMethodDecl)def; + checkAnnotationResType(meth.pos(), meth.restype.type); + } + } finally { + tree.sym.flags_field &= ~LOCKED; + tree.sym.flags_field |= ACYCLIC_ANN; + } + } + + void checkNonCyclicElementsInternal(DiagnosticPosition pos, TypeSymbol tsym) { + if ((tsym.flags_field & ACYCLIC_ANN) != 0) + return; + if ((tsym.flags_field & LOCKED) != 0) { + log.error(pos, "cyclic.annotation.element"); + return; + } + try { + tsym.flags_field |= LOCKED; + for (Scope.Entry e = tsym.members().elems; e != null; e = e.sibling) { + Symbol s = e.sym; + if (s.kind != Kinds.MTH) + continue; + checkAnnotationResType(pos, ((MethodSymbol)s).type.getReturnType()); + } + } finally { + tsym.flags_field &= ~LOCKED; + tsym.flags_field |= ACYCLIC_ANN; + } + } + + void checkAnnotationResType(DiagnosticPosition pos, Type type) { + switch (type.tag) { + case TypeTags.CLASS: + if ((type.tsym.flags() & ANNOTATION) != 0) + checkNonCyclicElementsInternal(pos, type.tsym); + break; + case TypeTags.ARRAY: + checkAnnotationResType(pos, types.elemtype(type)); + break; + default: + break; // int etc + } + } + +/* ************************************************************************* + * Check for cycles in the constructor call graph. + **************************************************************************/ + + /** Check for cycles in the graph of constructors calling other + * constructors. + */ + void checkCyclicConstructors(JCClassDecl tree) { + Map callMap = new HashMap(); + + // enter each constructor this-call into the map + for (List l = tree.defs; l.nonEmpty(); l = l.tail) { + JCMethodInvocation app = TreeInfo.firstConstructorCall(l.head); + if (app == null) continue; + JCMethodDecl meth = (JCMethodDecl) l.head; + if (TreeInfo.name(app.meth) == names._this) { + callMap.put(meth.sym, TreeInfo.symbol(app.meth)); + } else { + meth.sym.flags_field |= ACYCLIC; + } + } + + // Check for cycles in the map + Symbol[] ctors = new Symbol[0]; + ctors = callMap.keySet().toArray(ctors); + for (Symbol caller : ctors) { + checkCyclicConstructor(tree, caller, callMap); + } + } + + /** Look in the map to see if the given constructor is part of a + * call cycle. + */ + private void checkCyclicConstructor(JCClassDecl tree, Symbol ctor, + Map callMap) { + if (ctor != null && (ctor.flags_field & ACYCLIC) == 0) { + if ((ctor.flags_field & LOCKED) != 0) { + log.error(TreeInfo.diagnosticPositionFor(ctor, tree), + "recursive.ctor.invocation"); + } else { + ctor.flags_field |= LOCKED; + checkCyclicConstructor(tree, callMap.remove(ctor), callMap); + ctor.flags_field &= ~LOCKED; + } + ctor.flags_field |= ACYCLIC; + } + } + +/* ************************************************************************* + * Miscellaneous + **************************************************************************/ + + /** + * Return the opcode of the operator but emit an error if it is an + * error. + * @param pos position for error reporting. + * @param operator an operator + * @param tag a tree tag + * @param left type of left hand side + * @param right type of right hand side + */ + int checkOperator(DiagnosticPosition pos, + OperatorSymbol operator, + int tag, + Type left, + Type right) { + if (operator.opcode == ByteCodes.error) { + log.error(pos, + "operator.cant.be.applied.1", + treeinfo.operatorName(tag), + left, right); + } + return operator.opcode; + } + + + /** + * Check for division by integer constant zero + * @param pos Position for error reporting. + * @param operator The operator for the expression + * @param operand The right hand operand for the expression + */ + void checkDivZero(DiagnosticPosition pos, Symbol operator, Type operand) { + if (operand.constValue() != null + && lint.isEnabled(LintCategory.DIVZERO) + && operand.tag <= LONG + && ((Number) (operand.constValue())).longValue() == 0) { + int opc = ((OperatorSymbol)operator).opcode; + if (opc == ByteCodes.idiv || opc == ByteCodes.imod + || opc == ByteCodes.ldiv || opc == ByteCodes.lmod) { + log.warning(LintCategory.DIVZERO, pos, "div.zero"); + } + } + } + + /** + * Check for empty statements after if + */ + void checkEmptyIf(JCIf tree) { + if (tree.thenpart.getTag() == JCTree.SKIP && tree.elsepart == null && lint.isEnabled(LintCategory.EMPTY)) + log.warning(LintCategory.EMPTY, tree.thenpart.pos(), "empty.if"); + } + + /** Check that symbol is unique in given scope. + * @param pos Position for error reporting. + * @param sym The symbol. + * @param s The scope. + */ + boolean checkUnique(DiagnosticPosition pos, Symbol sym, Scope s) { + if (sym.type.isErroneous()) + return true; + if (sym.owner.name == names.any) return false; + for (Scope.Entry e = s.lookup(sym.name); e.scope == s; e = e.next()) { + if (sym != e.sym && + (e.sym.flags() & CLASH) == 0 && + sym.kind == e.sym.kind && + sym.name != names.error && + (sym.kind != MTH || types.hasSameArgs(types.erasure(sym.type), types.erasure(e.sym.type)))) { + if ((sym.flags() & VARARGS) != (e.sym.flags() & VARARGS)) { + varargsDuplicateError(pos, sym, e.sym); + return true; + } else if (sym.kind == MTH && !types.hasSameArgs(sym.type, e.sym.type, false)) { + duplicateErasureError(pos, sym, e.sym); + sym.flags_field |= CLASH; + return true; + } else { + duplicateError(pos, e.sym); + return false; + } + } + } + return true; + } + + /** Report duplicate declaration error. + */ + void duplicateErasureError(DiagnosticPosition pos, Symbol sym1, Symbol sym2) { + if (!sym1.type.isErroneous() && !sym2.type.isErroneous()) { + log.error(pos, "name.clash.same.erasure", sym1, sym2); + } + } + + /** Check that single-type import is not already imported or top-level defined, + * but make an exception for two single-type imports which denote the same type. + * @param pos Position for error reporting. + * @param sym The symbol. + * @param s The scope + */ + boolean checkUniqueImport(DiagnosticPosition pos, Symbol sym, Scope s) { + return checkUniqueImport(pos, sym, s, false); + } + + /** Check that static single-type import is not already imported or top-level defined, + * but make an exception for two single-type imports which denote the same type. + * @param pos Position for error reporting. + * @param sym The symbol. + * @param s The scope + * @param staticImport Whether or not this was a static import + */ + boolean checkUniqueStaticImport(DiagnosticPosition pos, Symbol sym, Scope s) { + return checkUniqueImport(pos, sym, s, true); + } + + /** Check that single-type import is not already imported or top-level defined, + * but make an exception for two single-type imports which denote the same type. + * @param pos Position for error reporting. + * @param sym The symbol. + * @param s The scope. + * @param staticImport Whether or not this was a static import + */ + private boolean checkUniqueImport(DiagnosticPosition pos, Symbol sym, Scope s, boolean staticImport) { + for (Scope.Entry e = s.lookup(sym.name); e.scope != null; e = e.next()) { + // is encountered class entered via a class declaration? + boolean isClassDecl = e.scope == s; + if ((isClassDecl || sym != e.sym) && + sym.kind == e.sym.kind && + sym.name != names.error) { + if (!e.sym.type.isErroneous()) { + String what = e.sym.toString(); + if (!isClassDecl) { + if (staticImport) + log.error(pos, "already.defined.static.single.import", what); + else + log.error(pos, "already.defined.single.import", what); + } + else if (sym != e.sym) + log.error(pos, "already.defined.this.unit", what); + } + return false; + } + } + return true; + } + + /** Check that a qualified name is in canonical form (for import decls). + */ + public void checkCanonical(JCTree tree) { + if (!isCanonical(tree)) + log.error(tree.pos(), "import.requires.canonical", + TreeInfo.symbol(tree)); + } + // where + private boolean isCanonical(JCTree tree) { + while (tree.getTag() == JCTree.SELECT) { + JCFieldAccess s = (JCFieldAccess) tree; + if (s.sym.owner != TreeInfo.symbol(s.selected)) + return false; + tree = s.selected; + } + return true; + } + + private class ConversionWarner extends Warner { + final String uncheckedKey; + final Type found; + final Type expected; + public ConversionWarner(DiagnosticPosition pos, String uncheckedKey, Type found, Type expected) { + super(pos); + this.uncheckedKey = uncheckedKey; + this.found = found; + this.expected = expected; + } + + @Override + public void warn(LintCategory lint) { + boolean warned = this.warned; + super.warn(lint); + if (warned) return; // suppress redundant diagnostics + switch (lint) { + case UNCHECKED: + Check.this.warnUnchecked(pos(), "prob.found.req", diags.fragment(uncheckedKey), found, expected); + break; + case VARARGS: + if (method != null && + method.attribute(syms.trustMeType.tsym) != null && + isTrustMeAllowedOnMethod(method) && + !types.isReifiable(method.type.getParameterTypes().last())) { + Check.this.warnUnsafeVararg(pos(), "varargs.unsafe.use.varargs.param", method.params.last()); + } + break; + default: + throw new AssertionError("Unexpected lint: " + lint); + } + } + } + + public Warner castWarner(DiagnosticPosition pos, Type found, Type expected) { + return new ConversionWarner(pos, "unchecked.cast.to.type", found, expected); + } + + public Warner convertWarner(DiagnosticPosition pos, Type found, Type expected) { + return new ConversionWarner(pos, "unchecked.assign", found, expected); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/comp/ConstFold.java b/douyu-javac/src/main/java/com/sun/tools/javac/comp/ConstFold.java new file mode 100644 index 0000000..cd017d4 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/comp/ConstFold.java @@ -0,0 +1,358 @@ +/* + * Copyright (c) 1999, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.comp; + +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.jvm.*; +import com.sun.tools.javac.util.*; + +import com.sun.tools.javac.code.Type.*; + +import static com.sun.tools.javac.code.TypeTags.*; +import static com.sun.tools.javac.jvm.ByteCodes.*; + +/** Helper class for constant folding, used by the attribution phase. + * This class is marked strictfp as mandated by JLS 15.4. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +strictfp class ConstFold { + protected static final Context.Key constFoldKey = + new Context.Key(); + + private Symtab syms; + + public static ConstFold instance(Context context) { + ConstFold instance = context.get(constFoldKey); + if (instance == null) + instance = new ConstFold(context); + return instance; + } + + private ConstFold(Context context) { + context.put(constFoldKey, this); + + syms = Symtab.instance(context); + } + + static Integer minusOne = -1; + static Integer zero = 0; + static Integer one = 1; + + /** Convert boolean to integer (true = 1, false = 0). + */ + private static Integer b2i(boolean b) { + return b ? one : zero; + } + private static int intValue(Object x) { return ((Number)x).intValue(); } + private static long longValue(Object x) { return ((Number)x).longValue(); } + private static float floatValue(Object x) { return ((Number)x).floatValue(); } + private static double doubleValue(Object x) { return ((Number)x).doubleValue(); } + + /** Fold binary or unary operation, returning constant type reflecting the + * operations result. Return null if fold failed due to an + * arithmetic exception. + * @param opcode The operation's opcode instruction (usually a byte code), + * as entered by class Symtab. + * @param argtypes The operation's argument types (a list of length 1 or 2). + * Argument types are assumed to have non-null constValue's. + */ + Type fold(int opcode, List argtypes) { + int argCount = argtypes.length(); + if (argCount == 1) + return fold1(opcode, argtypes.head); + else if (argCount == 2) + return fold2(opcode, argtypes.head, argtypes.tail.head); + else + throw new AssertionError(); + } + + /** Fold unary operation. + * @param opcode The operation's opcode instruction (usually a byte code), + * as entered by class Symtab. + * opcode's ifeq to ifge are for postprocessing + * xcmp; ifxx pairs of instructions. + * @param operand The operation's operand type. + * Argument types are assumed to have non-null constValue's. + */ + Type fold1(int opcode, Type operand) { + try { + Object od = operand.constValue(); + switch (opcode) { + case nop: + return operand; + case ineg: // unary - + return syms.intType.constType(-intValue(od)); + case ixor: // ~ + return syms.intType.constType(~intValue(od)); + case bool_not: // ! + return syms.booleanType.constType(b2i(intValue(od) == 0)); + case ifeq: + return syms.booleanType.constType(b2i(intValue(od) == 0)); + case ifne: + return syms.booleanType.constType(b2i(intValue(od) != 0)); + case iflt: + return syms.booleanType.constType(b2i(intValue(od) < 0)); + case ifgt: + return syms.booleanType.constType(b2i(intValue(od) > 0)); + case ifle: + return syms.booleanType.constType(b2i(intValue(od) <= 0)); + case ifge: + return syms.booleanType.constType(b2i(intValue(od) >= 0)); + + case lneg: // unary - + return syms.longType.constType(new Long(-longValue(od))); + case lxor: // ~ + return syms.longType.constType(new Long(~longValue(od))); + + case fneg: // unary - + return syms.floatType.constType(new Float(-floatValue(od))); + + case dneg: // ~ + return syms.doubleType.constType(new Double(-doubleValue(od))); + + default: + return null; + } + } catch (ArithmeticException e) { + return null; + } + } + + /** Fold binary operation. + * @param opcode The operation's opcode instruction (usually a byte code), + * as entered by class Symtab. + * opcode's ifeq to ifge are for postprocessing + * xcmp; ifxx pairs of instructions. + * @param left The type of the operation's left operand. + * @param right The type of the operation's right operand. + */ + Type fold2(int opcode, Type left, Type right) { + try { + if (opcode > ByteCodes.preMask) { + // we are seeing a composite instruction of the form xcmp; ifxx. + // In this case fold both instructions separately. + Type t1 = fold2(opcode >> ByteCodes.preShift, left, right); + return (t1.constValue() == null) ? t1 + : fold1(opcode & ByteCodes.preMask, t1); + } else { + Object l = left.constValue(); + Object r = right.constValue(); + switch (opcode) { + case iadd: + return syms.intType.constType(intValue(l) + intValue(r)); + case isub: + return syms.intType.constType(intValue(l) - intValue(r)); + case imul: + return syms.intType.constType(intValue(l) * intValue(r)); + case idiv: + return syms.intType.constType(intValue(l) / intValue(r)); + case imod: + return syms.intType.constType(intValue(l) % intValue(r)); + case iand: + return (left.tag == BOOLEAN + ? syms.booleanType : syms.intType) + .constType(intValue(l) & intValue(r)); + case bool_and: + return syms.booleanType.constType(b2i((intValue(l) & intValue(r)) != 0)); + case ior: + return (left.tag == BOOLEAN + ? syms.booleanType : syms.intType) + .constType(intValue(l) | intValue(r)); + case bool_or: + return syms.booleanType.constType(b2i((intValue(l) | intValue(r)) != 0)); + case ixor: + return (left.tag == BOOLEAN + ? syms.booleanType : syms.intType) + .constType(intValue(l) ^ intValue(r)); + case ishl: case ishll: + return syms.intType.constType(intValue(l) << intValue(r)); + case ishr: case ishrl: + return syms.intType.constType(intValue(l) >> intValue(r)); + case iushr: case iushrl: + return syms.intType.constType(intValue(l) >>> intValue(r)); + case if_icmpeq: + return syms.booleanType.constType( + b2i(intValue(l) == intValue(r))); + case if_icmpne: + return syms.booleanType.constType( + b2i(intValue(l) != intValue(r))); + case if_icmplt: + return syms.booleanType.constType( + b2i(intValue(l) < intValue(r))); + case if_icmpgt: + return syms.booleanType.constType( + b2i(intValue(l) > intValue(r))); + case if_icmple: + return syms.booleanType.constType( + b2i(intValue(l) <= intValue(r))); + case if_icmpge: + return syms.booleanType.constType( + b2i(intValue(l) >= intValue(r))); + + case ladd: + return syms.longType.constType( + new Long(longValue(l) + longValue(r))); + case lsub: + return syms.longType.constType( + new Long(longValue(l) - longValue(r))); + case lmul: + return syms.longType.constType( + new Long(longValue(l) * longValue(r))); + case ldiv: + return syms.longType.constType( + new Long(longValue(l) / longValue(r))); + case lmod: + return syms.longType.constType( + new Long(longValue(l) % longValue(r))); + case land: + return syms.longType.constType( + new Long(longValue(l) & longValue(r))); + case lor: + return syms.longType.constType( + new Long(longValue(l) | longValue(r))); + case lxor: + return syms.longType.constType( + new Long(longValue(l) ^ longValue(r))); + case lshl: case lshll: + return syms.longType.constType( + new Long(longValue(l) << intValue(r))); + case lshr: case lshrl: + return syms.longType.constType( + new Long(longValue(l) >> intValue(r))); + case lushr: + return syms.longType.constType( + new Long(longValue(l) >>> intValue(r))); + case lcmp: + if (longValue(l) < longValue(r)) + return syms.intType.constType(minusOne); + else if (longValue(l) > longValue(r)) + return syms.intType.constType(one); + else + return syms.intType.constType(zero); + case fadd: + return syms.floatType.constType( + new Float(floatValue(l) + floatValue(r))); + case fsub: + return syms.floatType.constType( + new Float(floatValue(l) - floatValue(r))); + case fmul: + return syms.floatType.constType( + new Float(floatValue(l) * floatValue(r))); + case fdiv: + return syms.floatType.constType( + new Float(floatValue(l) / floatValue(r))); + case fmod: + return syms.floatType.constType( + new Float(floatValue(l) % floatValue(r))); + case fcmpg: case fcmpl: + if (floatValue(l) < floatValue(r)) + return syms.intType.constType(minusOne); + else if (floatValue(l) > floatValue(r)) + return syms.intType.constType(one); + else if (floatValue(l) == floatValue(r)) + return syms.intType.constType(zero); + else if (opcode == fcmpg) + return syms.intType.constType(one); + else + return syms.intType.constType(minusOne); + case dadd: + return syms.doubleType.constType( + new Double(doubleValue(l) + doubleValue(r))); + case dsub: + return syms.doubleType.constType( + new Double(doubleValue(l) - doubleValue(r))); + case dmul: + return syms.doubleType.constType( + new Double(doubleValue(l) * doubleValue(r))); + case ddiv: + return syms.doubleType.constType( + new Double(doubleValue(l) / doubleValue(r))); + case dmod: + return syms.doubleType.constType( + new Double(doubleValue(l) % doubleValue(r))); + case dcmpg: case dcmpl: + if (doubleValue(l) < doubleValue(r)) + return syms.intType.constType(minusOne); + else if (doubleValue(l) > doubleValue(r)) + return syms.intType.constType(one); + else if (doubleValue(l) == doubleValue(r)) + return syms.intType.constType(zero); + else if (opcode == dcmpg) + return syms.intType.constType(one); + else + return syms.intType.constType(minusOne); + case if_acmpeq: + return syms.booleanType.constType(b2i(l.equals(r))); + case if_acmpne: + return syms.booleanType.constType(b2i(!l.equals(r))); + case string_add: + return syms.stringType.constType( + left.stringValue() + right.stringValue()); + default: + return null; + } + } + } catch (ArithmeticException e) { + return null; + } + } + + /** Coerce constant type to target type. + * @param etype The source type of the coercion, + * which is assumed to be a constant type compatble with + * ttype. + * @param ttype The target type of the coercion. + */ + Type coerce(Type etype, Type ttype) { + // WAS if (etype.baseType() == ttype.baseType()) + if (etype.tsym.type == ttype.tsym.type) + return etype; + if (etype.tag <= DOUBLE) { + Object n = etype.constValue(); + switch (ttype.tag) { + case BYTE: + return syms.byteType.constType(0 + (byte)intValue(n)); + case CHAR: + return syms.charType.constType(0 + (char)intValue(n)); + case SHORT: + return syms.shortType.constType(0 + (short)intValue(n)); + case INT: + return syms.intType.constType(intValue(n)); + case LONG: + return syms.longType.constType(longValue(n)); + case FLOAT: + return syms.floatType.constType(floatValue(n)); + case DOUBLE: + return syms.doubleType.constType(doubleValue(n)); + } + } + return ttype; + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/comp/Enter.java b/douyu-javac/src/main/java/com/sun/tools/javac/comp/Enter.java new file mode 100644 index 0000000..8646984 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/comp/Enter.java @@ -0,0 +1,513 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.comp; + +import java.util.*; +import javax.tools.JavaFileObject; +import javax.tools.JavaFileManager; + +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Scope.*; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.code.Type.*; +import com.sun.tools.javac.jvm.*; +import com.sun.tools.javac.main.RecognizedOptions.PkgInfo; +import com.sun.tools.javac.tree.*; +import com.sun.tools.javac.tree.JCTree.*; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; +import com.sun.tools.javac.util.List; + + +import static com.sun.tools.javac.code.Flags.*; +import static com.sun.tools.javac.code.Kinds.*; + +/** This class enters symbols for all encountered definitions into + * the symbol table. The pass consists of two phases, organized as + * follows: + * + *

In the first phase, all class symbols are intered into their + * enclosing scope, descending recursively down the tree for classes + * which are members of other classes. The class symbols are given a + * MemberEnter object as completer. + * + *

In the second phase classes are completed using + * MemberEnter.complete(). Completion might occur on demand, but + * any classes that are not completed that way will be eventually + * completed by processing the `uncompleted' queue. Completion + * entails (1) determination of a class's parameters, supertype and + * interfaces, as well as (2) entering all symbols defined in the + * class into its scope, with the exception of class symbols which + * have been entered in phase 1. (2) depends on (1) having been + * completed for a class and all its superclasses and enclosing + * classes. That's why, after doing (1), we put classes in a + * `halfcompleted' queue. Only when we have performed (1) for a class + * and all it's superclasses and enclosing classes, we proceed to + * (2). + * + *

Whereas the first phase is organized as a sweep through all + * compiled syntax trees, the second phase is demand. Members of a + * class are entered when the contents of a class are first + * accessed. This is accomplished by installing completer objects in + * class symbols for compiled classes which invoke the member-enter + * phase for the corresponding class tree. + * + *

Classes migrate from one phase to the next via queues: + * + *

+ *  class enter -> (Enter.uncompleted)         --> member enter (1)
+ *              -> (MemberEnter.halfcompleted) --> member enter (2)
+ *              -> (Todo)                      --> attribute
+ *                                              (only for toplevel classes)
+ *  
+ * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Enter extends JCTree.Visitor { + protected static final Context.Key enterKey = + new Context.Key(); + + Log log; + Symtab syms; + Check chk; + TreeMaker make; + ClassReader reader; + Annotate annotate; + MemberEnter memberEnter; + Types types; + Lint lint; + Names names; + JavaFileManager fileManager; + PkgInfo pkginfoOpt; + + private final Todo todo; + + public static Enter instance(Context context) { + Enter instance = context.get(enterKey); + if (instance == null) + instance = new Enter(context); + return instance; + } + + protected Enter(Context context) { + context.put(enterKey, this); + + log = Log.instance(context); + reader = ClassReader.instance(context); + make = TreeMaker.instance(context); + syms = Symtab.instance(context); + chk = Check.instance(context); + memberEnter = MemberEnter.instance(context); + types = Types.instance(context); + annotate = Annotate.instance(context); + lint = Lint.instance(context); + names = Names.instance(context); + + predefClassDef = make.ClassDef( + make.Modifiers(PUBLIC), + syms.predefClass.name, null, null, null, null); + predefClassDef.sym = syms.predefClass; + todo = Todo.instance(context); + fileManager = context.get(JavaFileManager.class); + + Options options = Options.instance(context); + pkginfoOpt = PkgInfo.get(options); + } + + /** A hashtable mapping classes and packages to the environments current + * at the points of their definitions. + */ + Map> typeEnvs = + new HashMap>(); + + /** Accessor for typeEnvs + */ + public Env getEnv(TypeSymbol sym) { + return typeEnvs.get(sym); + } + + public Env getClassEnv(TypeSymbol sym) { + Env localEnv = getEnv(sym); + Env lintEnv = localEnv; + while (lintEnv.info.lint == null) + lintEnv = lintEnv.next; + localEnv.info.lint = lintEnv.info.lint.augment(sym.attributes_field, sym.flags()); + return localEnv; + } + + /** The queue of all classes that might still need to be completed; + * saved and initialized by main(). + */ + ListBuffer uncompleted; + + /** A dummy class to serve as enclClass for toplevel environments. + */ + private JCClassDecl predefClassDef; + +/* ************************************************************************ + * environment construction + *************************************************************************/ + + + /** Create a fresh environment for class bodies. + * This will create a fresh scope for local symbols of a class, referred + * to by the environments info.scope field. + * This scope will contain + * - symbols for this and super + * - symbols for any type parameters + * In addition, it serves as an anchor for scopes of methods and initializers + * which are nested in this scope via Scope.dup(). + * This scope should not be confused with the members scope of a class. + * + * @param tree The class definition. + * @param env The environment current outside of the class definition. + */ + public Env classEnv(JCClassDecl tree, Env env) { + Env localEnv = + env.dup(tree, env.info.dup(new Scope(tree.sym))); + localEnv.enclClass = tree; + localEnv.outer = env; + localEnv.info.isSelfCall = false; + localEnv.info.lint = null; // leave this to be filled in by Attr, + // when annotations have been processed + return localEnv; + } + + /** Create a fresh environment for toplevels. + * @param tree The toplevel tree. + */ + Env topLevelEnv(JCCompilationUnit tree) { + Env localEnv = new Env(tree, new AttrContext()); + localEnv.toplevel = tree; + localEnv.enclClass = predefClassDef; + tree.namedImportScope = new ImportScope(tree.packge); + tree.starImportScope = new StarImportScope(tree.packge); + localEnv.info.scope = tree.namedImportScope; + localEnv.info.lint = lint; + return localEnv; + } + + public Env getTopLevelEnv(JCCompilationUnit tree) { + Env localEnv = new Env(tree, new AttrContext()); + localEnv.toplevel = tree; + localEnv.enclClass = predefClassDef; + localEnv.info.scope = tree.namedImportScope; + localEnv.info.lint = lint; + return localEnv; + } + + /** The scope in which a member definition in environment env is to be entered + * This is usually the environment's scope, except for class environments, + * where the local scope is for type variables, and the this and super symbol + * only, and members go into the class member scope. + */ + Scope enterScope(Env env) { + return (env.tree.getTag() == JCTree.CLASSDEF) + ? ((JCClassDecl) env.tree).sym.members_field + : env.info.scope; + } + +/* ************************************************************************ + * Visitor methods for phase 1: class enter + *************************************************************************/ + + /** Visitor argument: the current environment. + */ + protected Env env; + + /** Visitor result: the computed type. + */ + Type result; + + /** Visitor method: enter all classes in given tree, catching any + * completion failure exceptions. Return the tree's type. + * + * @param tree The tree to be visited. + * @param env The environment visitor argument. + */ + Type classEnter(JCTree tree, Env env) { + Env prevEnv = this.env; + try { + this.env = env; + tree.accept(this); + return result; + } catch (CompletionFailure ex) { + return chk.completionError(tree.pos(), ex); + } finally { + this.env = prevEnv; + } + } + + /** Visitor method: enter classes of a list of trees, returning a list of types. + */ + List classEnter(List trees, Env env) { + ListBuffer ts = new ListBuffer(); + for (List l = trees; l.nonEmpty(); l = l.tail) { + Type t = classEnter(l.head, env); + if (t != null) + ts.append(t); + } + return ts.toList(); + } + + @Override + public void visitTopLevel(JCCompilationUnit tree) { + JavaFileObject prev = log.useSource(tree.sourcefile); + boolean addEnv = false; + boolean isPkgInfo = tree.sourcefile.isNameCompatible("package-info", + JavaFileObject.Kind.SOURCE); + if (tree.pid != null) { + tree.packge = reader.enterPackage(TreeInfo.fullName(tree.pid)); + if (tree.packageAnnotations.nonEmpty() || pkginfoOpt == PkgInfo.ALWAYS) { + if (isPkgInfo) { + addEnv = true; + } else { + log.error(tree.packageAnnotations.head.pos(), + "pkg.annotations.sb.in.package-info.java"); + } + } + } else { + tree.packge = syms.unnamedPackage; + } + tree.packge.complete(); // Find all classes in package. + Env topEnv = topLevelEnv(tree); + + // Save environment of package-info.java file. + if (isPkgInfo) { + Env env0 = typeEnvs.get(tree.packge); + if (env0 == null) { + typeEnvs.put(tree.packge, topEnv); + } else { + JCCompilationUnit tree0 = env0.toplevel; + if (!fileManager.isSameFile(tree.sourcefile, tree0.sourcefile)) { + log.warning(tree.pid != null ? tree.pid.pos() + : null, + "pkg-info.already.seen", + tree.packge); + if (addEnv || (tree0.packageAnnotations.isEmpty() && + tree.docComments != null && + tree.docComments.get(tree) != null)) { + typeEnvs.put(tree.packge, topEnv); + } + } + } + + for (Symbol q = tree.packge; q != null && q.kind == PCK; q = q.owner) + q.flags_field |= EXISTS; + + Name name = names.package_info; + ClassSymbol c = reader.enterClass(name, tree.packge); + c.flatname = names.fromString(tree.packge + "." + name); + c.sourcefile = tree.sourcefile; + c.completer = null; + c.members_field = new Scope(c); + tree.packge.package_info = c; + } + classEnter(tree.defs, topEnv); + if (addEnv) { + todo.append(topEnv); + } + log.useSource(prev); + result = null; + } + + @Override + public void visitClassDef(JCClassDecl tree) { + Symbol owner = env.info.scope.owner; + Scope enclScope = enterScope(env); + ClassSymbol c; + if (owner.kind == PCK) { + // We are seeing a toplevel class. + PackageSymbol packge = (PackageSymbol)owner; + for (Symbol q = packge; q != null && q.kind == PCK; q = q.owner) + q.flags_field |= EXISTS; + c = reader.enterClass(tree.name, packge); + packge.members().enterIfAbsent(c); + if ((tree.mods.flags & PUBLIC) != 0 && !classNameMatchesFileName(c, env)) { + log.error(tree.pos(), + "class.public.should.be.in.file", tree.name); + } + } else { + if (!tree.name.isEmpty() && + !chk.checkUniqueClassName(tree.pos(), tree.name, enclScope)) { + result = null; + return; + } + if (owner.kind == TYP) { + // We are seeing a member class. + c = reader.enterClass(tree.name, (TypeSymbol)owner); + if ((owner.flags_field & INTERFACE) != 0) { + tree.mods.flags |= PUBLIC | STATIC; + } + } else { + // We are seeing a local class. + c = reader.defineClass(tree.name, owner); + c.flatname = chk.localClassName(c); + if (!c.name.isEmpty()) + chk.checkTransparentClass(tree.pos(), c, env.info.scope); + } + } + tree.sym = c; + + // Enter class into `compiled' table and enclosing scope. + if (chk.compiled.get(c.flatname) != null) { + duplicateClass(tree.pos(), c); + result = types.createErrorType(tree.name, (TypeSymbol)owner, Type.noType); + tree.sym = (ClassSymbol)result.tsym; + return; + } + chk.compiled.put(c.flatname, c); + enclScope.enter(c); + + // Set up an environment for class block and store in `typeEnvs' + // table, to be retrieved later in memberEnter and attribution. + Env localEnv = classEnv(tree, env); + typeEnvs.put(c, localEnv); + + // Fill out class fields. + c.completer = memberEnter; + c.flags_field = chk.checkFlags(tree.pos(), tree.mods.flags, c, tree); + c.sourcefile = env.toplevel.sourcefile; + c.members_field = new Scope(c); + + ClassType ct = (ClassType)c.type; + if (owner.kind != PCK && (c.flags_field & STATIC) == 0) { + // We are seeing a local or inner class. + // Set outer_field of this class to closest enclosing class + // which contains this class in a non-static context + // (its "enclosing instance class"), provided such a class exists. + Symbol owner1 = owner; + while ((owner1.kind & (VAR | MTH)) != 0 && + (owner1.flags_field & STATIC) == 0) { + owner1 = owner1.owner; + } + if (owner1.kind == TYP) { + ct.setEnclosingType(owner1.type); + } + } + + // Enter type parameters. + ct.typarams_field = classEnter(tree.typarams, localEnv); + + // Add non-local class to uncompleted, to make sure it will be + // completed later. + if (!c.isLocal() && uncompleted != null) uncompleted.append(c); +// System.err.println("entering " + c.fullname + " in " + c.owner);//DEBUG + + // Recursively enter all member classes. + classEnter(tree.defs, localEnv); + + result = c.type; + } + //where + /** Does class have the same name as the file it appears in? + */ + private static boolean classNameMatchesFileName(ClassSymbol c, + Env env) { + return env.toplevel.sourcefile.isNameCompatible(c.name.toString(), + JavaFileObject.Kind.SOURCE); + } + + /** Complain about a duplicate class. */ + protected void duplicateClass(DiagnosticPosition pos, ClassSymbol c) { + log.error(pos, "duplicate.class", c.fullname); + } + + /** Class enter visitor method for type parameters. + * Enter a symbol for type parameter in local scope, after checking that it + * is unique. + */ + @Override + public void visitTypeParameter(JCTypeParameter tree) { + TypeVar a = (tree.type != null) + ? (TypeVar)tree.type + : new TypeVar(tree.name, env.info.scope.owner, syms.botType); + tree.type = a; + if (chk.checkUnique(tree.pos(), a.tsym, env.info.scope)) { + env.info.scope.enter(a.tsym); + } + result = a; + } + + /** Default class enter visitor method: do nothing. + */ + @Override + public void visitTree(JCTree tree) { + result = null; + } + + /** Main method: enter all classes in a list of toplevel trees. + * @param trees The list of trees to be processed. + */ + public void main(List trees) { + complete(trees, null); + } + + /** Main method: enter one class from a list of toplevel trees and + * place the rest on uncompleted for later processing. + * @param trees The list of trees to be processed. + * @param c The class symbol to be processed. + */ + public void complete(List trees, ClassSymbol c) { + annotate.enterStart(); + ListBuffer prevUncompleted = uncompleted; + if (memberEnter.completionEnabled) uncompleted = new ListBuffer(); + + try { + // enter all classes, and construct uncompleted list + classEnter(trees, null); + + // complete all uncompleted classes in memberEnter + if (memberEnter.completionEnabled) { + while (uncompleted.nonEmpty()) { + ClassSymbol clazz = uncompleted.next(); + if (c == null || c == clazz || prevUncompleted == null) + clazz.complete(); + else + // defer + prevUncompleted.append(clazz); + } + + // if there remain any unimported toplevels (these must have + // no classes at all), process their import statements as well. + for (JCCompilationUnit tree : trees) { + if (tree.starImportScope.elems == null) { + JavaFileObject prev = log.useSource(tree.sourcefile); + Env topEnv = topLevelEnv(tree); + memberEnter.memberEnter(tree, topEnv); + log.useSource(prev); + } + } + } + } finally { + uncompleted = prevUncompleted; + annotate.enterDone(); + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/comp/Env.java b/douyu-javac/src/main/java/com/sun/tools/javac/comp/Env.java new file mode 100644 index 0000000..c46cafe --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/comp/Env.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 1999, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.comp; + +import com.sun.tools.javac.tree.*; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** A class for environments, instances of which are passed as + * arguments to tree visitors. Environments refer to important ancestors + * of the subtree that's currently visited, such as the enclosing method, + * the enclosing class, or the enclosing toplevel node. They also contain + * a generic component, represented as a type parameter, to carry further + * information specific to individual passes. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Env implements Iterable> { + + /** The next enclosing environment. + */ + public Env next; + + /** The environment enclosing the current class. + */ + public Env outer; + + /** The tree with which this environment is associated. + */ + public JCTree tree; + + /** The enclosing toplevel tree. + */ + public JCTree.JCCompilationUnit toplevel; + + /** The next enclosing class definition. + */ + public JCTree.JCClassDecl enclClass; + + /** The next enclosing method definition. + */ + public JCTree.JCMethodDecl enclMethod; + + /** A generic field for further information. + */ + public A info; + + /** Is this an environment for evaluating a base clause? + */ + public boolean baseClause = false; + + /** Create an outermost environment for a given (toplevel)tree, + * with a given info field. + */ + public Env(JCTree tree, A info) { + this.next = null; + this.outer = null; + this.tree = tree; + this.toplevel = null; + this.enclClass = null; + this.enclMethod = null; + this.info = info; + } + + /** Duplicate this environment, updating with given tree and info, + * and copying all other fields. + */ + public Env dup(JCTree tree, A info) { + return dupto(new Env(tree, info)); + } + + /** Duplicate this environment into a given Environment, + * using its tree and info, and copying all other fields. + */ + public Env dupto(Env that) { + that.next = this; + that.outer = this.outer; + that.toplevel = this.toplevel; + that.enclClass = this.enclClass; + that.enclMethod = this.enclMethod; + return that; + } + + /** Duplicate this environment, updating with given tree, + * and copying all other fields. + */ + public Env dup(JCTree tree) { + return dup(tree, this.info); + } + + /** Return closest enclosing environment which points to a tree with given tag. + */ + public Env enclosing(int tag) { + Env env1 = this; + while (env1 != null && env1.tree.getTag() != tag) env1 = env1.next; + return env1; + } + + public String toString() { + return "Env[" + info + (outer == null ? "" : ",outer=" + outer) + "]"; + } + + public Iterator> iterator() { + return new Iterator>() { + Env next = Env.this; + public boolean hasNext() { + return next.outer != null; + } + public Env next() { + if (hasNext()) { + Env current = next; + next = current.outer; + return current; + } + throw new NoSuchElementException(); + + } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/comp/Flow.java b/douyu-javac/src/main/java/com/sun/tools/javac/comp/Flow.java new file mode 100644 index 0000000..0c685f9 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/comp/Flow.java @@ -0,0 +1,1481 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +//todo: one might eliminate uninits.andSets when monotonic + +package com.sun.tools.javac.comp; + +import java.util.HashMap; +import java.util.Map; +import java.util.LinkedHashMap; + +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.tree.*; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; + +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.tree.JCTree.*; + +import static com.sun.tools.javac.code.Flags.*; +import static com.sun.tools.javac.code.Kinds.*; +import static com.sun.tools.javac.code.TypeTags.*; + +/** This pass implements dataflow analysis for Java programs. + * Liveness analysis checks that every statement is reachable. + * Exception analysis ensures that every checked exception that is + * thrown is declared or caught. Definite assignment analysis + * ensures that each variable is assigned when used. Definite + * unassignment analysis ensures that no final variable is assigned + * more than once. + * + *

The JLS has a number of problems in the + * specification of these flow analysis problems. This implementation + * attempts to address those issues. + * + *

First, there is no accommodation for a finally clause that cannot + * complete normally. For liveness analysis, an intervening finally + * clause can cause a break, continue, or return not to reach its + * target. For exception analysis, an intervening finally clause can + * cause any exception to be "caught". For DA/DU analysis, the finally + * clause can prevent a transfer of control from propagating DA/DU + * state to the target. In addition, code in the finally clause can + * affect the DA/DU status of variables. + * + *

For try statements, we introduce the idea of a variable being + * definitely unassigned "everywhere" in a block. A variable V is + * "unassigned everywhere" in a block iff it is unassigned at the + * beginning of the block and there is no reachable assignment to V + * in the block. An assignment V=e is reachable iff V is not DA + * after e. Then we can say that V is DU at the beginning of the + * catch block iff V is DU everywhere in the try block. Similarly, V + * is DU at the beginning of the finally block iff V is DU everywhere + * in the try block and in every catch block. Specifically, the + * following bullet is added to 16.2.2 + *

+ *      V is unassigned everywhere in a block if it is
+ *      unassigned before the block and there is no reachable
+ *      assignment to V within the block.
+ *  
+ *

In 16.2.15, the third bullet (and all of its sub-bullets) for all + * try blocks is changed to + *

+ *      V is definitely unassigned before a catch block iff V is
+ *      definitely unassigned everywhere in the try block.
+ *  
+ *

The last bullet (and all of its sub-bullets) for try blocks that + * have a finally block is changed to + *

+ *      V is definitely unassigned before the finally block iff
+ *      V is definitely unassigned everywhere in the try block
+ *      and everywhere in each catch block of the try statement.
+ *  
+ *

In addition, + *

+ *      V is definitely assigned at the end of a constructor iff
+ *      V is definitely assigned after the block that is the body
+ *      of the constructor and V is definitely assigned at every
+ *      return that can return from the constructor.
+ *  
+ *

In addition, each continue statement with the loop as its target + * is treated as a jump to the end of the loop body, and "intervening" + * finally clauses are treated as follows: V is DA "due to the + * continue" iff V is DA before the continue statement or V is DA at + * the end of any intervening finally block. V is DU "due to the + * continue" iff any intervening finally cannot complete normally or V + * is DU at the end of every intervening finally block. This "due to + * the continue" concept is then used in the spec for the loops. + * + *

Similarly, break statements must consider intervening finally + * blocks. For liveness analysis, a break statement for which any + * intervening finally cannot complete normally is not considered to + * cause the target statement to be able to complete normally. Then + * we say V is DA "due to the break" iff V is DA before the break or + * V is DA at the end of any intervening finally block. V is DU "due + * to the break" iff any intervening finally cannot complete normally + * or V is DU at the break and at the end of every intervening + * finally block. (I suspect this latter condition can be + * simplified.) This "due to the break" is then used in the spec for + * all statements that can be "broken". + * + *

The return statement is treated similarly. V is DA "due to a + * return statement" iff V is DA before the return statement or V is + * DA at the end of any intervening finally block. Note that we + * don't have to worry about the return expression because this + * concept is only used for construcrors. + * + *

There is no spec in the JLS for when a variable is definitely + * assigned at the end of a constructor, which is needed for final + * fields (8.3.1.2). We implement the rule that V is DA at the end + * of the constructor iff it is DA and the end of the body of the + * constructor and V is DA "due to" every return of the constructor. + * + *

Intervening finally blocks similarly affect exception analysis. An + * intervening finally that cannot complete normally allows us to ignore + * an otherwise uncaught exception. + * + *

To implement the semantics of intervening finally clauses, all + * nonlocal transfers (break, continue, return, throw, method call that + * can throw a checked exception, and a constructor invocation that can + * thrown a checked exception) are recorded in a queue, and removed + * from the queue when we complete processing the target of the + * nonlocal transfer. This allows us to modify the queue in accordance + * with the above rules when we encounter a finally clause. The only + * exception to this [no pun intended] is that checked exceptions that + * are known to be caught or declared to be caught in the enclosing + * method are not recorded in the queue, but instead are recorded in a + * global variable "Set thrown" that records the type of all + * exceptions that can be thrown. + * + *

Other minor issues the treatment of members of other classes + * (always considered DA except that within an anonymous class + * constructor, where DA status from the enclosing scope is + * preserved), treatment of the case expression (V is DA before the + * case expression iff V is DA after the switch expression), + * treatment of variables declared in a switch block (the implied + * DA/DU status after the switch expression is DU and not DA for + * variables defined in a switch block), the treatment of boolean ?: + * expressions (The JLS rules only handle b and c non-boolean; the + * new rule is that if b and c are boolean valued, then V is + * (un)assigned after a?b:c when true/false iff V is (un)assigned + * after b when true/false and V is (un)assigned after c when + * true/false). + * + *

There is the remaining question of what syntactic forms constitute a + * reference to a variable. It is conventional to allow this.x on the + * left-hand-side to initialize a final instance field named x, yet + * this.x isn't considered a "use" when appearing on a right-hand-side + * in most implementations. Should parentheses affect what is + * considered a variable reference? The simplest rule would be to + * allow unqualified forms only, parentheses optional, and phase out + * support for assigning to a final field via this.x. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Flow extends TreeScanner { + protected static final Context.Key flowKey = + new Context.Key(); + + private final Names names; + private final Log log; + private final Symtab syms; + private final Types types; + private final Check chk; + private TreeMaker make; + private final Resolve rs; + private Env attrEnv; + private Lint lint; + private final boolean allowImprovedRethrowAnalysis; + private final boolean allowImprovedCatchAnalysis; + + public static Flow instance(Context context) { + Flow instance = context.get(flowKey); + if (instance == null) + instance = new Flow(context); + return instance; + } + + protected Flow(Context context) { + context.put(flowKey, this); + names = Names.instance(context); + log = Log.instance(context); + syms = Symtab.instance(context); + types = Types.instance(context); + chk = Check.instance(context); + lint = Lint.instance(context); + rs = Resolve.instance(context); + Source source = Source.instance(context); + allowImprovedRethrowAnalysis = source.allowImprovedRethrowAnalysis(); + allowImprovedCatchAnalysis = source.allowImprovedCatchAnalysis(); + } + + /** A flag that indicates whether the last statement could + * complete normally. + */ + private boolean alive; + + /** The set of definitely assigned variables. + */ + Bits inits; + + /** The set of definitely unassigned variables. + */ + Bits uninits; + + HashMap> preciseRethrowTypes; + + /** The set of variables that are definitely unassigned everywhere + * in current try block. This variable is maintained lazily; it is + * updated only when something gets removed from uninits, + * typically by being assigned in reachable code. To obtain the + * correct set of variables which are definitely unassigned + * anywhere in current try block, intersect uninitsTry and + * uninits. + */ + Bits uninitsTry; + + /** When analyzing a condition, inits and uninits are null. + * Instead we have: + */ + Bits initsWhenTrue; + Bits initsWhenFalse; + Bits uninitsWhenTrue; + Bits uninitsWhenFalse; + + /** A mapping from addresses to variable symbols. + */ + VarSymbol[] vars; + + /** The current class being defined. + */ + JCClassDecl classDef; + + /** The first variable sequence number in this class definition. + */ + int firstadr; + + /** The next available variable sequence number. + */ + int nextadr; + + /** The list of possibly thrown declarable exceptions. + */ + List thrown; + + /** The list of exceptions that are either caught or declared to be + * thrown. + */ + List caught; + + /** The list of unreferenced automatic resources. + */ + Scope unrefdResources; + + /** Set when processing a loop body the second time for DU analysis. */ + boolean loopPassTwo = false; + + /*-------------------- Environments ----------------------*/ + + /** A pending exit. These are the statements return, break, and + * continue. In addition, exception-throwing expressions or + * statements are put here when not known to be caught. This + * will typically result in an error unless it is within a + * try-finally whose finally block cannot complete normally. + */ + static class PendingExit { + JCTree tree; + Bits inits; + Bits uninits; + Type thrown; + PendingExit(JCTree tree, Bits inits, Bits uninits) { + this.tree = tree; + this.inits = inits.dup(); + this.uninits = uninits.dup(); + } + PendingExit(JCTree tree, Type thrown) { + this.tree = tree; + this.thrown = thrown; + } + } + + /** The currently pending exits that go from current inner blocks + * to an enclosing block, in source order. + */ + ListBuffer pendingExits; + + /*-------------------- Exceptions ----------------------*/ + + /** Complain that pending exceptions are not caught. + */ + void errorUncaught() { + for (PendingExit exit = pendingExits.next(); + exit != null; + exit = pendingExits.next()) { + if (classDef != null && + classDef.pos == exit.tree.pos) { + log.error(exit.tree.pos(), + "unreported.exception.default.constructor", + exit.thrown); + } else if (exit.tree.getTag() == JCTree.VARDEF && + ((JCVariableDecl)exit.tree).sym.isResourceVariable()) { + log.error(exit.tree.pos(), + "unreported.exception.implicit.close", + exit.thrown, + ((JCVariableDecl)exit.tree).sym.name); + } else { + log.error(exit.tree.pos(), + "unreported.exception.need.to.catch.or.throw", + exit.thrown); + } + } + } + + /** Record that exception is potentially thrown and check that it + * is caught. + */ + void markThrown(JCTree tree, Type exc) { + if (!chk.isUnchecked(tree.pos(), exc)) { + if (!chk.isHandled(exc, caught)) + pendingExits.append(new PendingExit(tree, exc)); + thrown = chk.incl(exc, thrown); + } + } + + /*-------------- Processing variables ----------------------*/ + + /** Do we need to track init/uninit state of this symbol? + * I.e. is symbol either a local or a blank final variable? + */ + boolean trackable(VarSymbol sym) { + return + (sym.owner.kind == MTH || + ((sym.flags() & (FINAL | HASINIT | PARAMETER)) == FINAL && + classDef.sym.isEnclosedBy((ClassSymbol)sym.owner))); + } + + /** Initialize new trackable variable by setting its address field + * to the next available sequence number and entering it under that + * index into the vars array. + */ + void newVar(VarSymbol sym) { + if (nextadr == vars.length) { + VarSymbol[] newvars = new VarSymbol[nextadr * 2]; + System.arraycopy(vars, 0, newvars, 0, nextadr); + vars = newvars; + } + sym.adr = nextadr; + vars[nextadr] = sym; + inits.excl(nextadr); + uninits.incl(nextadr); + nextadr++; + } + + /** Record an initialization of a trackable variable. + */ + void letInit(DiagnosticPosition pos, VarSymbol sym) { + if (sym.adr >= firstadr && trackable(sym)) { + if ((sym.flags() & FINAL) != 0) { + if ((sym.flags() & PARAMETER) != 0) { + if ((sym.flags() & UNION) != 0) { //multi-catch parameter + log.error(pos, "multicatch.parameter.may.not.be.assigned", + sym); + } + else { + log.error(pos, "final.parameter.may.not.be.assigned", + sym); + } + } else if (!uninits.isMember(sym.adr)) { + log.error(pos, + loopPassTwo + ? "var.might.be.assigned.in.loop" + : "var.might.already.be.assigned", + sym); + } else if (!inits.isMember(sym.adr)) { + // reachable assignment + uninits.excl(sym.adr); + uninitsTry.excl(sym.adr); + } else { + //log.rawWarning(pos, "unreachable assignment");//DEBUG + uninits.excl(sym.adr); + } + } + inits.incl(sym.adr); + } else if ((sym.flags() & FINAL) != 0) { + log.error(pos, "var.might.already.be.assigned", sym); + } + } + + /** If tree is either a simple name or of the form this.name or + * C.this.name, and tree represents a trackable variable, + * record an initialization of the variable. + */ + void letInit(JCTree tree) { + tree = TreeInfo.skipParens(tree); + if (tree.getTag() == JCTree.IDENT || tree.getTag() == JCTree.SELECT) { + Symbol sym = TreeInfo.symbol(tree); + if (sym.kind == VAR) { + letInit(tree.pos(), (VarSymbol)sym); + } + } + } + + /** Check that trackable variable is initialized. + */ + void checkInit(DiagnosticPosition pos, VarSymbol sym) { + if ((sym.adr >= firstadr || sym.owner.kind != TYP) && + trackable(sym) && + !inits.isMember(sym.adr)) { + log.error(pos, "var.might.not.have.been.initialized", + sym); + inits.incl(sym.adr); + } + } + + /*-------------------- Handling jumps ----------------------*/ + + /** Record an outward transfer of control. */ + void recordExit(JCTree tree) { + pendingExits.append(new PendingExit(tree, inits, uninits)); + markDead(); + } + + /** Resolve all breaks of this statement. */ + boolean resolveBreaks(JCTree tree, + ListBuffer oldPendingExits) { + boolean result = false; + List exits = pendingExits.toList(); + pendingExits = oldPendingExits; + for (; exits.nonEmpty(); exits = exits.tail) { + PendingExit exit = exits.head; + if (exit.tree.getTag() == JCTree.BREAK && + ((JCBreak) exit.tree).target == tree) { + inits.andSet(exit.inits); + uninits.andSet(exit.uninits); + result = true; + } else { + pendingExits.append(exit); + } + } + return result; + } + + /** Resolve all continues of this statement. */ + boolean resolveContinues(JCTree tree) { + boolean result = false; + List exits = pendingExits.toList(); + pendingExits = new ListBuffer(); + for (; exits.nonEmpty(); exits = exits.tail) { + PendingExit exit = exits.head; + if (exit.tree.getTag() == JCTree.CONTINUE && + ((JCContinue) exit.tree).target == tree) { + inits.andSet(exit.inits); + uninits.andSet(exit.uninits); + result = true; + } else { + pendingExits.append(exit); + } + } + return result; + } + + /** Record that statement is unreachable. + */ + void markDead() { + inits.inclRange(firstadr, nextadr); + uninits.inclRange(firstadr, nextadr); + alive = false; + } + + /** Split (duplicate) inits/uninits into WhenTrue/WhenFalse sets + */ + void split(boolean setToNull) { + initsWhenFalse = inits.dup(); + uninitsWhenFalse = uninits.dup(); + initsWhenTrue = inits; + uninitsWhenTrue = uninits; + if (setToNull) + inits = uninits = null; + } + + /** Merge (intersect) inits/uninits from WhenTrue/WhenFalse sets. + */ + void merge() { + inits = initsWhenFalse.andSet(initsWhenTrue); + uninits = uninitsWhenFalse.andSet(uninitsWhenTrue); + } + +/* ************************************************************************ + * Visitor methods for statements and definitions + *************************************************************************/ + + /** Analyze a definition. + */ + void scanDef(JCTree tree) { + scanStat(tree); + if (tree != null && tree.getTag() == JCTree.BLOCK && !alive) { + log.error(tree.pos(), + "initializer.must.be.able.to.complete.normally"); + } + } + + /** Analyze a statement. Check that statement is reachable. + */ + void scanStat(JCTree tree) { + if (!alive && tree != null) { + log.error(tree.pos(), "unreachable.stmt"); + if (tree.getTag() != JCTree.SKIP) alive = true; + } + scan(tree); + } + + /** Analyze list of statements. + */ + void scanStats(List trees) { + if (trees != null) + for (List l = trees; l.nonEmpty(); l = l.tail) + scanStat(l.head); + } + + /** Analyze an expression. Make sure to set (un)inits rather than + * (un)initsWhenTrue(WhenFalse) on exit. + */ + void scanExpr(JCTree tree) { + if (tree != null) { + scan(tree); + if (inits == null) merge(); + } + } + + /** Analyze a list of expressions. + */ + void scanExprs(List trees) { + if (trees != null) + for (List l = trees; l.nonEmpty(); l = l.tail) + scanExpr(l.head); + } + + /** Analyze a condition. Make sure to set (un)initsWhenTrue(WhenFalse) + * rather than (un)inits on exit. + */ + void scanCond(JCTree tree) { + if (tree.type.isFalse()) { + if (inits == null) merge(); + initsWhenTrue = inits.dup(); + initsWhenTrue.inclRange(firstadr, nextadr); + uninitsWhenTrue = uninits.dup(); + uninitsWhenTrue.inclRange(firstadr, nextadr); + initsWhenFalse = inits; + uninitsWhenFalse = uninits; + } else if (tree.type.isTrue()) { + if (inits == null) merge(); + initsWhenFalse = inits.dup(); + initsWhenFalse.inclRange(firstadr, nextadr); + uninitsWhenFalse = uninits.dup(); + uninitsWhenFalse.inclRange(firstadr, nextadr); + initsWhenTrue = inits; + uninitsWhenTrue = uninits; + } else { + scan(tree); + if (inits != null) + split(tree.type != syms.unknownType); + } + if (tree.type != syms.unknownType) + inits = uninits = null; + } + + /* ------------ Visitor methods for various sorts of trees -------------*/ + + public void visitClassDef(JCClassDecl tree) { + if (tree.sym == null) return; + + JCClassDecl classDefPrev = classDef; + List thrownPrev = thrown; + List caughtPrev = caught; + boolean alivePrev = alive; + int firstadrPrev = firstadr; + int nextadrPrev = nextadr; + ListBuffer pendingExitsPrev = pendingExits; + Lint lintPrev = lint; + + pendingExits = new ListBuffer(); + if (tree.name != names.empty) { + caught = List.nil(); + firstadr = nextadr; + } + classDef = tree; + thrown = List.nil(); + lint = lint.augment(tree.sym.attributes_field); + + try { + // define all the static fields + for (List l = tree.defs; l.nonEmpty(); l = l.tail) { + if (l.head.getTag() == JCTree.VARDEF) { + JCVariableDecl def = (JCVariableDecl)l.head; + if ((def.mods.flags & STATIC) != 0) { + VarSymbol sym = def.sym; + if (trackable(sym)) + newVar(sym); + } + } + } + + // process all the static initializers + for (List l = tree.defs; l.nonEmpty(); l = l.tail) { + if (l.head.getTag() != JCTree.METHODDEF && + (TreeInfo.flags(l.head) & STATIC) != 0) { + scanDef(l.head); + errorUncaught(); + } + } + + // add intersection of all thrown clauses of initial constructors + // to set of caught exceptions, unless class is anonymous. + if (tree.name != names.empty) { + boolean firstConstructor = true; + for (List l = tree.defs; l.nonEmpty(); l = l.tail) { + if (TreeInfo.isInitialConstructor(l.head)) { + List mthrown = + ((JCMethodDecl) l.head).sym.type.getThrownTypes(); + if (firstConstructor) { + caught = mthrown; + firstConstructor = false; + } else { + caught = chk.intersect(mthrown, caught); + } + } + } + } + + // define all the instance fields + for (List l = tree.defs; l.nonEmpty(); l = l.tail) { + if (l.head.getTag() == JCTree.VARDEF) { + JCVariableDecl def = (JCVariableDecl)l.head; + if ((def.mods.flags & STATIC) == 0) { + VarSymbol sym = def.sym; + if (trackable(sym)) + newVar(sym); + } + } + } + + // process all the instance initializers + for (List l = tree.defs; l.nonEmpty(); l = l.tail) { + if (l.head.getTag() != JCTree.METHODDEF && + (TreeInfo.flags(l.head) & STATIC) == 0) { + scanDef(l.head); + errorUncaught(); + } + } + + // in an anonymous class, add the set of thrown exceptions to + // the throws clause of the synthetic constructor and propagate + // outwards. + // Changing the throws clause on the fly is okay here because + // the anonymous constructor can't be invoked anywhere else, + // and its type hasn't been cached. + if (tree.name == names.empty) { + for (List l = tree.defs; l.nonEmpty(); l = l.tail) { + if (TreeInfo.isInitialConstructor(l.head)) { + JCMethodDecl mdef = (JCMethodDecl)l.head; + mdef.thrown = make.Types(thrown); + mdef.sym.type = types.createMethodTypeWithThrown(mdef.sym.type, thrown); + } + } + thrownPrev = chk.union(thrown, thrownPrev); + } + + // process all the methods + for (List l = tree.defs; l.nonEmpty(); l = l.tail) { + if (l.head.getTag() == JCTree.METHODDEF) { + scan(l.head); + errorUncaught(); + } + } + + thrown = thrownPrev; + } finally { + pendingExits = pendingExitsPrev; + alive = alivePrev; + nextadr = nextadrPrev; + firstadr = firstadrPrev; + caught = caughtPrev; + classDef = classDefPrev; + lint = lintPrev; + } + } + + public void visitMethodDef(JCMethodDecl tree) { + if (tree.body == null) return; + + List caughtPrev = caught; + List mthrown = tree.sym.type.getThrownTypes(); + Bits initsPrev = inits.dup(); + Bits uninitsPrev = uninits.dup(); + int nextadrPrev = nextadr; + int firstadrPrev = firstadr; + Lint lintPrev = lint; + + lint = lint.augment(tree.sym.attributes_field); + + Assert.check(pendingExits.isEmpty()); + + try { + boolean isInitialConstructor = + TreeInfo.isInitialConstructor(tree); + + if (!isInitialConstructor) + firstadr = nextadr; + for (List l = tree.params; l.nonEmpty(); l = l.tail) { + JCVariableDecl def = l.head; + scan(def); + inits.incl(def.sym.adr); + uninits.excl(def.sym.adr); + } + if (isInitialConstructor) + caught = chk.union(caught, mthrown); + else if ((tree.sym.flags() & (BLOCK | STATIC)) != BLOCK) + caught = mthrown; + // else we are in an instance initializer block; + // leave caught unchanged. + + alive = true; + scanStat(tree.body); + + if (alive && tree.sym.type.getReturnType().tag != VOID) + log.error(TreeInfo.diagEndPos(tree.body), "missing.ret.stmt"); + + if (isInitialConstructor) { + for (int i = firstadr; i < nextadr; i++) + if (vars[i].owner == classDef.sym) + checkInit(TreeInfo.diagEndPos(tree.body), vars[i]); + } + List exits = pendingExits.toList(); + pendingExits = new ListBuffer(); + while (exits.nonEmpty()) { + PendingExit exit = exits.head; + exits = exits.tail; + if (exit.thrown == null) { + Assert.check(exit.tree.getTag() == JCTree.RETURN); + if (isInitialConstructor) { + inits = exit.inits; + for (int i = firstadr; i < nextadr; i++) + checkInit(exit.tree.pos(), vars[i]); + } + } else { + // uncaught throws will be reported later + pendingExits.append(exit); + } + } + } finally { + inits = initsPrev; + uninits = uninitsPrev; + nextadr = nextadrPrev; + firstadr = firstadrPrev; + caught = caughtPrev; + lint = lintPrev; + } + } + + public void visitVarDef(JCVariableDecl tree) { + boolean track = trackable(tree.sym); + if (track && tree.sym.owner.kind == MTH) newVar(tree.sym); + if (tree.init != null) { + Lint lintPrev = lint; + lint = lint.augment(tree.sym.attributes_field); + try{ + scanExpr(tree.init); + if (track) letInit(tree.pos(), tree.sym); + } finally { + lint = lintPrev; + } + } + } + + public void visitBlock(JCBlock tree) { + int nextadrPrev = nextadr; + scanStats(tree.stats); + nextadr = nextadrPrev; + } + + public void visitDoLoop(JCDoWhileLoop tree) { + ListBuffer prevPendingExits = pendingExits; + boolean prevLoopPassTwo = loopPassTwo; + pendingExits = new ListBuffer(); + int prevErrors = log.nerrors; + do { + Bits uninitsEntry = uninits.dup(); + uninitsEntry.excludeFrom(nextadr); + scanStat(tree.body); + alive |= resolveContinues(tree); + scanCond(tree.cond); + if (log.nerrors != prevErrors || + loopPassTwo || + uninitsEntry.dup().diffSet(uninitsWhenTrue).nextBit(firstadr)==-1) + break; + inits = initsWhenTrue; + uninits = uninitsEntry.andSet(uninitsWhenTrue); + loopPassTwo = true; + alive = true; + } while (true); + loopPassTwo = prevLoopPassTwo; + inits = initsWhenFalse; + uninits = uninitsWhenFalse; + alive = alive && !tree.cond.type.isTrue(); + alive |= resolveBreaks(tree, prevPendingExits); + } + + public void visitWhileLoop(JCWhileLoop tree) { + ListBuffer prevPendingExits = pendingExits; + boolean prevLoopPassTwo = loopPassTwo; + Bits initsCond; + Bits uninitsCond; + pendingExits = new ListBuffer(); + int prevErrors = log.nerrors; + do { + Bits uninitsEntry = uninits.dup(); + uninitsEntry.excludeFrom(nextadr); + scanCond(tree.cond); + initsCond = initsWhenFalse; + uninitsCond = uninitsWhenFalse; + inits = initsWhenTrue; + uninits = uninitsWhenTrue; + alive = !tree.cond.type.isFalse(); + scanStat(tree.body); + alive |= resolveContinues(tree); + if (log.nerrors != prevErrors || + loopPassTwo || + uninitsEntry.dup().diffSet(uninits).nextBit(firstadr) == -1) + break; + uninits = uninitsEntry.andSet(uninits); + loopPassTwo = true; + alive = true; + } while (true); + loopPassTwo = prevLoopPassTwo; + inits = initsCond; + uninits = uninitsCond; + alive = resolveBreaks(tree, prevPendingExits) || + !tree.cond.type.isTrue(); + } + + public void visitForLoop(JCForLoop tree) { + ListBuffer prevPendingExits = pendingExits; + boolean prevLoopPassTwo = loopPassTwo; + int nextadrPrev = nextadr; + scanStats(tree.init); + Bits initsCond; + Bits uninitsCond; + pendingExits = new ListBuffer(); + int prevErrors = log.nerrors; + do { + Bits uninitsEntry = uninits.dup(); + uninitsEntry.excludeFrom(nextadr); + if (tree.cond != null) { + scanCond(tree.cond); + initsCond = initsWhenFalse; + uninitsCond = uninitsWhenFalse; + inits = initsWhenTrue; + uninits = uninitsWhenTrue; + alive = !tree.cond.type.isFalse(); + } else { + initsCond = inits.dup(); + initsCond.inclRange(firstadr, nextadr); + uninitsCond = uninits.dup(); + uninitsCond.inclRange(firstadr, nextadr); + alive = true; + } + scanStat(tree.body); + alive |= resolveContinues(tree); + scan(tree.step); + if (log.nerrors != prevErrors || + loopPassTwo || + uninitsEntry.dup().diffSet(uninits).nextBit(firstadr) == -1) + break; + uninits = uninitsEntry.andSet(uninits); + loopPassTwo = true; + alive = true; + } while (true); + loopPassTwo = prevLoopPassTwo; + inits = initsCond; + uninits = uninitsCond; + alive = resolveBreaks(tree, prevPendingExits) || + tree.cond != null && !tree.cond.type.isTrue(); + nextadr = nextadrPrev; + } + + public void visitForeachLoop(JCEnhancedForLoop tree) { + visitVarDef(tree.var); + + ListBuffer prevPendingExits = pendingExits; + boolean prevLoopPassTwo = loopPassTwo; + int nextadrPrev = nextadr; + scan(tree.expr); + Bits initsStart = inits.dup(); + Bits uninitsStart = uninits.dup(); + + letInit(tree.pos(), tree.var.sym); + pendingExits = new ListBuffer(); + int prevErrors = log.nerrors; + do { + Bits uninitsEntry = uninits.dup(); + uninitsEntry.excludeFrom(nextadr); + scanStat(tree.body); + alive |= resolveContinues(tree); + if (log.nerrors != prevErrors || + loopPassTwo || + uninitsEntry.dup().diffSet(uninits).nextBit(firstadr) == -1) + break; + uninits = uninitsEntry.andSet(uninits); + loopPassTwo = true; + alive = true; + } while (true); + loopPassTwo = prevLoopPassTwo; + inits = initsStart; + uninits = uninitsStart.andSet(uninits); + resolveBreaks(tree, prevPendingExits); + alive = true; + nextadr = nextadrPrev; + } + + public void visitLabelled(JCLabeledStatement tree) { + ListBuffer prevPendingExits = pendingExits; + pendingExits = new ListBuffer(); + scanStat(tree.body); + alive |= resolveBreaks(tree, prevPendingExits); + } + + public void visitSwitch(JCSwitch tree) { + ListBuffer prevPendingExits = pendingExits; + pendingExits = new ListBuffer(); + int nextadrPrev = nextadr; + scanExpr(tree.selector); + Bits initsSwitch = inits; + Bits uninitsSwitch = uninits.dup(); + boolean hasDefault = false; + for (List l = tree.cases; l.nonEmpty(); l = l.tail) { + alive = true; + inits = initsSwitch.dup(); + uninits = uninits.andSet(uninitsSwitch); + JCCase c = l.head; + if (c.pat == null) + hasDefault = true; + else + scanExpr(c.pat); + scanStats(c.stats); + addVars(c.stats, initsSwitch, uninitsSwitch); + // Warn about fall-through if lint switch fallthrough enabled. + if (!loopPassTwo && + alive && + lint.isEnabled(Lint.LintCategory.FALLTHROUGH) && + c.stats.nonEmpty() && l.tail.nonEmpty()) + log.warning(Lint.LintCategory.FALLTHROUGH, + l.tail.head.pos(), + "possible.fall-through.into.case"); + } + if (!hasDefault) { + inits.andSet(initsSwitch); + alive = true; + } + alive |= resolveBreaks(tree, prevPendingExits); + nextadr = nextadrPrev; + } + // where + /** Add any variables defined in stats to inits and uninits. */ + private static void addVars(List stats, Bits inits, + Bits uninits) { + for (;stats.nonEmpty(); stats = stats.tail) { + JCTree stat = stats.head; + if (stat.getTag() == JCTree.VARDEF) { + int adr = ((JCVariableDecl) stat).sym.adr; + inits.excl(adr); + uninits.incl(adr); + } + } + } + + public void visitTry(JCTry tree) { + List caughtPrev = caught; + List thrownPrev = thrown; + thrown = List.nil(); + for (List l = tree.catchers; l.nonEmpty(); l = l.tail) { + List subClauses = TreeInfo.isMultiCatch(l.head) ? + ((JCTypeUnion)l.head.param.vartype).alternatives : + List.of(l.head.param.vartype); + for (JCExpression ct : subClauses) { + caught = chk.incl(ct.type, caught); + } + } + ListBuffer resourceVarDecls = ListBuffer.lb(); + Bits uninitsTryPrev = uninitsTry; + ListBuffer prevPendingExits = pendingExits; + pendingExits = new ListBuffer(); + Bits initsTry = inits.dup(); + uninitsTry = uninits.dup(); + for (JCTree resource : tree.resources) { + if (resource instanceof JCVariableDecl) { + JCVariableDecl vdecl = (JCVariableDecl) resource; + visitVarDef(vdecl); + unrefdResources.enter(vdecl.sym); + resourceVarDecls.append(vdecl); + } else if (resource instanceof JCExpression) { + scanExpr((JCExpression) resource); + } else { + throw new AssertionError(tree); // parser error + } + } + for (JCTree resource : tree.resources) { + List closeableSupertypes = resource.type.isCompound() ? + types.interfaces(resource.type).prepend(types.supertype(resource.type)) : + List.of(resource.type); + for (Type sup : closeableSupertypes) { + if (types.asSuper(sup, syms.autoCloseableType.tsym) != null) { + Symbol closeMethod = rs.resolveQualifiedMethod(tree, + attrEnv, + sup, + names.close, + List.nil(), + List.nil()); + if (closeMethod.kind == MTH) { + for (Type t : ((MethodSymbol)closeMethod).getThrownTypes()) { + markThrown(resource, t); + } + } + } + } + } + scanStat(tree.body); + List thrownInTry = allowImprovedCatchAnalysis ? + chk.union(thrown, List.of(syms.runtimeExceptionType, syms.errorType)) : + thrown; + thrown = thrownPrev; + caught = caughtPrev; + boolean aliveEnd = alive; + uninitsTry.andSet(uninits); + Bits initsEnd = inits; + Bits uninitsEnd = uninits; + int nextadrCatch = nextadr; + + if (!resourceVarDecls.isEmpty() && + lint.isEnabled(Lint.LintCategory.TRY)) { + for (JCVariableDecl resVar : resourceVarDecls) { + if (unrefdResources.includes(resVar.sym)) { + log.warning(Lint.LintCategory.TRY, resVar.pos(), + "try.resource.not.referenced", resVar.sym); + unrefdResources.remove(resVar.sym); + } + } + } + + List caughtInTry = List.nil(); + for (List l = tree.catchers; l.nonEmpty(); l = l.tail) { + alive = true; + JCVariableDecl param = l.head.param; + List subClauses = TreeInfo.isMultiCatch(l.head) ? + ((JCTypeUnion)l.head.param.vartype).alternatives : + List.of(l.head.param.vartype); + List ctypes = List.nil(); + List rethrownTypes = chk.diff(thrownInTry, caughtInTry); + for (JCExpression ct : subClauses) { + Type exc = ct.type; + if (exc != syms.unknownType) { + ctypes = ctypes.append(exc); + if (types.isSameType(exc, syms.objectType)) + continue; + checkCaughtType(l.head.pos(), exc, thrownInTry, caughtInTry); + caughtInTry = chk.incl(exc, caughtInTry); + } + } + inits = initsTry.dup(); + uninits = uninitsTry.dup(); + scan(param); + inits.incl(param.sym.adr); + uninits.excl(param.sym.adr); + preciseRethrowTypes.put(param.sym, chk.intersect(ctypes, rethrownTypes)); + scanStat(l.head.body); + initsEnd.andSet(inits); + uninitsEnd.andSet(uninits); + nextadr = nextadrCatch; + preciseRethrowTypes.remove(param.sym); + aliveEnd |= alive; + } + if (tree.finalizer != null) { + List savedThrown = thrown; + thrown = List.nil(); + inits = initsTry.dup(); + uninits = uninitsTry.dup(); + ListBuffer exits = pendingExits; + pendingExits = prevPendingExits; + alive = true; + scanStat(tree.finalizer); + if (!alive) { + // discard exits and exceptions from try and finally + thrown = chk.union(thrown, thrownPrev); + if (!loopPassTwo && + lint.isEnabled(Lint.LintCategory.FINALLY)) { + log.warning(Lint.LintCategory.FINALLY, + TreeInfo.diagEndPos(tree.finalizer), + "finally.cannot.complete"); + } + } else { + thrown = chk.union(thrown, chk.diff(thrownInTry, caughtInTry)); + thrown = chk.union(thrown, savedThrown); + uninits.andSet(uninitsEnd); + // FIX: this doesn't preserve source order of exits in catch + // versus finally! + while (exits.nonEmpty()) { + PendingExit exit = exits.next(); + if (exit.inits != null) { + exit.inits.orSet(inits); + exit.uninits.andSet(uninits); + } + pendingExits.append(exit); + } + inits.orSet(initsEnd); + alive = aliveEnd; + } + } else { + thrown = chk.union(thrown, chk.diff(thrownInTry, caughtInTry)); + inits = initsEnd; + uninits = uninitsEnd; + alive = aliveEnd; + ListBuffer exits = pendingExits; + pendingExits = prevPendingExits; + while (exits.nonEmpty()) pendingExits.append(exits.next()); + } + uninitsTry.andSet(uninitsTryPrev).andSet(uninits); + } + + void checkCaughtType(DiagnosticPosition pos, Type exc, List thrownInTry, List caughtInTry) { + if (chk.subset(exc, caughtInTry)) { + log.error(pos, "except.already.caught", exc); + } else if (!chk.isUnchecked(pos, exc) && + !isExceptionOrThrowable(exc) && + !chk.intersects(exc, thrownInTry)) { + log.error(pos, "except.never.thrown.in.try", exc); + } else if (allowImprovedCatchAnalysis) { + List catchableThrownTypes = chk.intersect(List.of(exc), thrownInTry); + // 'catchableThrownTypes' cannnot possibly be empty - if 'exc' was an + // unchecked exception, the result list would not be empty, as the augmented + // thrown set includes { RuntimeException, Error }; if 'exc' was a checked + // exception, that would have been covered in the branch above + if (chk.diff(catchableThrownTypes, caughtInTry).isEmpty() && + !isExceptionOrThrowable(exc)) { + String key = catchableThrownTypes.length() == 1 ? + "unreachable.catch" : + "unreachable.catch.1"; + log.warning(pos, key, catchableThrownTypes); + } + } + } + //where + private boolean isExceptionOrThrowable(Type exc) { + return exc.tsym == syms.throwableType.tsym || + exc.tsym == syms.exceptionType.tsym; + } + + + public void visitConditional(JCConditional tree) { + scanCond(tree.cond); + Bits initsBeforeElse = initsWhenFalse; + Bits uninitsBeforeElse = uninitsWhenFalse; + inits = initsWhenTrue; + uninits = uninitsWhenTrue; + if (tree.truepart.type.tag == BOOLEAN && + tree.falsepart.type.tag == BOOLEAN) { + // if b and c are boolean valued, then + // v is (un)assigned after a?b:c when true iff + // v is (un)assigned after b when true and + // v is (un)assigned after c when true + scanCond(tree.truepart); + Bits initsAfterThenWhenTrue = initsWhenTrue.dup(); + Bits initsAfterThenWhenFalse = initsWhenFalse.dup(); + Bits uninitsAfterThenWhenTrue = uninitsWhenTrue.dup(); + Bits uninitsAfterThenWhenFalse = uninitsWhenFalse.dup(); + inits = initsBeforeElse; + uninits = uninitsBeforeElse; + scanCond(tree.falsepart); + initsWhenTrue.andSet(initsAfterThenWhenTrue); + initsWhenFalse.andSet(initsAfterThenWhenFalse); + uninitsWhenTrue.andSet(uninitsAfterThenWhenTrue); + uninitsWhenFalse.andSet(uninitsAfterThenWhenFalse); + } else { + scanExpr(tree.truepart); + Bits initsAfterThen = inits.dup(); + Bits uninitsAfterThen = uninits.dup(); + inits = initsBeforeElse; + uninits = uninitsBeforeElse; + scanExpr(tree.falsepart); + inits.andSet(initsAfterThen); + uninits.andSet(uninitsAfterThen); + } + } + + public void visitIf(JCIf tree) { + scanCond(tree.cond); + Bits initsBeforeElse = initsWhenFalse; + Bits uninitsBeforeElse = uninitsWhenFalse; + inits = initsWhenTrue; + uninits = uninitsWhenTrue; + scanStat(tree.thenpart); + if (tree.elsepart != null) { + boolean aliveAfterThen = alive; + alive = true; + Bits initsAfterThen = inits.dup(); + Bits uninitsAfterThen = uninits.dup(); + inits = initsBeforeElse; + uninits = uninitsBeforeElse; + scanStat(tree.elsepart); + inits.andSet(initsAfterThen); + uninits.andSet(uninitsAfterThen); + alive = alive | aliveAfterThen; + } else { + inits.andSet(initsBeforeElse); + uninits.andSet(uninitsBeforeElse); + alive = true; + } + } + + + + public void visitBreak(JCBreak tree) { + recordExit(tree); + } + + public void visitContinue(JCContinue tree) { + recordExit(tree); + } + + public void visitReturn(JCReturn tree) { + scanExpr(tree.expr); + // if not initial constructor, should markDead instead of recordExit + recordExit(tree); + } + + public void visitThrow(JCThrow tree) { + scanExpr(tree.expr); + Symbol sym = TreeInfo.symbol(tree.expr); + if (sym != null && + sym.kind == VAR && + (sym.flags() & (FINAL | EFFECTIVELY_FINAL)) != 0 && + preciseRethrowTypes.get(sym) != null && + allowImprovedRethrowAnalysis) { + for (Type t : preciseRethrowTypes.get(sym)) { + markThrown(tree, t); + } + } + else { + markThrown(tree, tree.expr.type); + } + markDead(); + } + + public void visitApply(JCMethodInvocation tree) { + scanExpr(tree.meth); + scanExprs(tree.args); + for (List l = tree.meth.type.getThrownTypes(); l.nonEmpty(); l = l.tail) + markThrown(tree, l.head); + } + + public void visitNewClass(JCNewClass tree) { + scanExpr(tree.encl); + scanExprs(tree.args); + // scan(tree.def); + for (List l = tree.constructorType.getThrownTypes(); + l.nonEmpty(); + l = l.tail) { + markThrown(tree, l.head); + } + List caughtPrev = caught; + try { + // If the new class expression defines an anonymous class, + // analysis of the anonymous constructor may encounter thrown + // types which are unsubstituted type variables. + // However, since the constructor's actual thrown types have + // already been marked as thrown, it is safe to simply include + // each of the constructor's formal thrown types in the set of + // 'caught/declared to be thrown' types, for the duration of + // the class def analysis. + if (tree.def != null) + for (List l = tree.constructor.type.getThrownTypes(); + l.nonEmpty(); + l = l.tail) { + caught = chk.incl(l.head, caught); + } + scan(tree.def); + } + finally { + caught = caughtPrev; + } + } + + public void visitNewArray(JCNewArray tree) { + scanExprs(tree.dims); + scanExprs(tree.elems); + } + + public void visitAssert(JCAssert tree) { + Bits initsExit = inits.dup(); + Bits uninitsExit = uninits.dup(); + scanCond(tree.cond); + uninitsExit.andSet(uninitsWhenTrue); + if (tree.detail != null) { + inits = initsWhenFalse; + uninits = uninitsWhenFalse; + scanExpr(tree.detail); + } + inits = initsExit; + uninits = uninitsExit; + } + + public void visitAssign(JCAssign tree) { + JCTree lhs = TreeInfo.skipParens(tree.lhs); + if (!(lhs instanceof JCIdent)) scanExpr(lhs); + scanExpr(tree.rhs); + letInit(lhs); + } + + public void visitAssignop(JCAssignOp tree) { + scanExpr(tree.lhs); + scanExpr(tree.rhs); + letInit(tree.lhs); + } + + public void visitUnary(JCUnary tree) { + switch (tree.getTag()) { + case JCTree.NOT: + scanCond(tree.arg); + Bits t = initsWhenFalse; + initsWhenFalse = initsWhenTrue; + initsWhenTrue = t; + t = uninitsWhenFalse; + uninitsWhenFalse = uninitsWhenTrue; + uninitsWhenTrue = t; + break; + case JCTree.PREINC: case JCTree.POSTINC: + case JCTree.PREDEC: case JCTree.POSTDEC: + scanExpr(tree.arg); + letInit(tree.arg); + break; + default: + scanExpr(tree.arg); + } + } + + public void visitBinary(JCBinary tree) { + switch (tree.getTag()) { + case JCTree.AND: + scanCond(tree.lhs); + Bits initsWhenFalseLeft = initsWhenFalse; + Bits uninitsWhenFalseLeft = uninitsWhenFalse; + inits = initsWhenTrue; + uninits = uninitsWhenTrue; + scanCond(tree.rhs); + initsWhenFalse.andSet(initsWhenFalseLeft); + uninitsWhenFalse.andSet(uninitsWhenFalseLeft); + break; + case JCTree.OR: + scanCond(tree.lhs); + Bits initsWhenTrueLeft = initsWhenTrue; + Bits uninitsWhenTrueLeft = uninitsWhenTrue; + inits = initsWhenFalse; + uninits = uninitsWhenFalse; + scanCond(tree.rhs); + initsWhenTrue.andSet(initsWhenTrueLeft); + uninitsWhenTrue.andSet(uninitsWhenTrueLeft); + break; + default: + scanExpr(tree.lhs); + scanExpr(tree.rhs); + } + } + + public void visitIdent(JCIdent tree) { + if (tree.sym.kind == VAR) { + checkInit(tree.pos(), (VarSymbol)tree.sym); + referenced(tree.sym); + } + } + + void referenced(Symbol sym) { + unrefdResources.remove(sym); + } + + public void visitTypeCast(JCTypeCast tree) { + super.visitTypeCast(tree); + if (!tree.type.isErroneous() + && lint.isEnabled(Lint.LintCategory.CAST) + && types.isSameType(tree.expr.type, tree.clazz.type) + && !is292targetTypeCast(tree)) { + log.warning(Lint.LintCategory.CAST, + tree.pos(), "redundant.cast", tree.expr.type); + } + } + //where + private boolean is292targetTypeCast(JCTypeCast tree) { + boolean is292targetTypeCast = false; + JCExpression expr = TreeInfo.skipParens(tree.expr); + if (expr.getTag() == JCTree.APPLY) { + JCMethodInvocation apply = (JCMethodInvocation)expr; + Symbol sym = TreeInfo.symbol(apply.meth); + is292targetTypeCast = sym != null && + sym.kind == MTH && + (sym.flags() & POLYMORPHIC_SIGNATURE) != 0; + } + return is292targetTypeCast; + } + + public void visitTopLevel(JCCompilationUnit tree) { + // Do nothing for TopLevel since each class is visited individually + } + +/************************************************************************** + * main method + *************************************************************************/ + + /** Perform definite assignment/unassignment analysis on a tree. + */ + public void analyzeTree(Env env, TreeMaker make) { + try { + attrEnv = env; + JCTree tree = env.tree; + this.make = make; + inits = new Bits(); + uninits = new Bits(); + uninitsTry = new Bits(); + initsWhenTrue = initsWhenFalse = + uninitsWhenTrue = uninitsWhenFalse = null; + if (vars == null) + vars = new VarSymbol[32]; + else + for (int i=0; i(); + preciseRethrowTypes = new HashMap>(); + alive = true; + this.thrown = this.caught = null; + this.classDef = null; + unrefdResources = new Scope(env.enclClass.sym); + scan(tree); + } finally { + // note that recursive invocations of this method fail hard + inits = uninits = uninitsTry = null; + initsWhenTrue = initsWhenFalse = + uninitsWhenTrue = uninitsWhenFalse = null; + if (vars != null) for (int i=0; iThis is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Infer { + protected static final Context.Key inferKey = + new Context.Key(); + + /** A value for prototypes that admit any type, including polymorphic ones. */ + public static final Type anyPoly = new Type(NONE, null); + + Symtab syms; + Types types; + Check chk; + Resolve rs; + JCDiagnostic.Factory diags; + + public static Infer instance(Context context) { + Infer instance = context.get(inferKey); + if (instance == null) + instance = new Infer(context); + return instance; + } + + protected Infer(Context context) { + context.put(inferKey, this); + syms = Symtab.instance(context); + types = Types.instance(context); + rs = Resolve.instance(context); + chk = Check.instance(context); + diags = JCDiagnostic.Factory.instance(context); + ambiguousNoInstanceException = + new NoInstanceException(true, diags); + unambiguousNoInstanceException = + new NoInstanceException(false, diags); + invalidInstanceException = + new InvalidInstanceException(diags); + + } + + public static class InferenceException extends Resolve.InapplicableMethodException { + private static final long serialVersionUID = 0; + + InferenceException(JCDiagnostic.Factory diags) { + super(diags); + } + } + + public static class NoInstanceException extends InferenceException { + private static final long serialVersionUID = 1; + + boolean isAmbiguous; // exist several incomparable best instances? + + NoInstanceException(boolean isAmbiguous, JCDiagnostic.Factory diags) { + super(diags); + this.isAmbiguous = isAmbiguous; + } + } + + public static class InvalidInstanceException extends InferenceException { + private static final long serialVersionUID = 2; + + InvalidInstanceException(JCDiagnostic.Factory diags) { + super(diags); + } + } + + private final NoInstanceException ambiguousNoInstanceException; + private final NoInstanceException unambiguousNoInstanceException; + private final InvalidInstanceException invalidInstanceException; + +/*************************************************************************** + * Auxiliary type values and classes + ***************************************************************************/ + + /** A mapping that turns type variables into undetermined type variables. + */ + Mapping fromTypeVarFun = new Mapping("fromTypeVarFun") { + public Type apply(Type t) { + if (t.tag == TYPEVAR) return new UndetVar(t); + else return t.map(this); + } + }; + + /** A mapping that returns its type argument with every UndetVar replaced + * by its `inst' field. Throws a NoInstanceException + * if this not possible because an `inst' field is null. + * Note: mutually referring undertvars will be left uninstantiated + * (that is, they will be replaced by the underlying type-variable). + */ + + Mapping getInstFun = new Mapping("getInstFun") { + public Type apply(Type t) { + switch (t.tag) { + case UNKNOWN: + throw ambiguousNoInstanceException + .setMessage("undetermined.type"); + case UNDETVAR: + UndetVar that = (UndetVar) t; + if (that.inst == null) + throw ambiguousNoInstanceException + .setMessage("type.variable.has.undetermined.type", + that.qtype); + return isConstraintCyclic(that) ? + that.qtype : + apply(that.inst); + default: + return t.map(this); + } + } + + private boolean isConstraintCyclic(UndetVar uv) { + Types.UnaryVisitor constraintScanner = + new Types.UnaryVisitor() { + + List seen = List.nil(); + + Boolean visit(List ts) { + for (Type t : ts) { + if (visit(t)) return true; + } + return false; + } + + public Boolean visitType(Type t, Void ignored) { + return false; + } + + @Override + public Boolean visitClassType(ClassType t, Void ignored) { + if (t.isCompound()) { + return visit(types.supertype(t)) || + visit(types.interfaces(t)); + } else { + return visit(t.getTypeArguments()); + } + } + @Override + public Boolean visitWildcardType(WildcardType t, Void ignored) { + return visit(t.type); + } + + @Override + public Boolean visitUndetVar(UndetVar t, Void ignored) { + if (seen.contains(t)) { + return true; + } else { + seen = seen.prepend(t); + return visit(t.inst); + } + } + }; + return constraintScanner.visit(uv); + } + }; + +/*************************************************************************** + * Mini/Maximization of UndetVars + ***************************************************************************/ + + /** Instantiate undetermined type variable to its minimal upper bound. + * Throw a NoInstanceException if this not possible. + */ + void maximizeInst(UndetVar that, Warner warn) throws NoInstanceException { + List hibounds = Type.filter(that.hibounds, errorFilter); + if (that.inst == null) { + if (hibounds.isEmpty()) + that.inst = syms.objectType; + else if (hibounds.tail.isEmpty()) + that.inst = hibounds.head; + else + that.inst = types.glb(hibounds); + } + if (that.inst == null || + that.inst.isErroneous()) + throw ambiguousNoInstanceException + .setMessage("no.unique.maximal.instance.exists", + that.qtype, hibounds); + } + //where + private boolean isSubClass(Type t, final List ts) { + t = t.baseType(); + if (t.tag == TYPEVAR) { + List bounds = types.getBounds((TypeVar)t); + for (Type s : ts) { + if (!types.isSameType(t, s.baseType())) { + for (Type bound : bounds) { + if (!isSubClass(bound, List.of(s.baseType()))) + return false; + } + } + } + } else { + for (Type s : ts) { + if (!t.tsym.isSubClass(s.baseType().tsym, types)) + return false; + } + } + return true; + } + + private Filter errorFilter = new Filter() { + @Override + public boolean accepts(Type t) { + return !t.isErroneous(); + } + }; + + /** Instantiate undetermined type variable to the lub of all its lower bounds. + * Throw a NoInstanceException if this not possible. + */ + void minimizeInst(UndetVar that, Warner warn) throws NoInstanceException { + List lobounds = Type.filter(that.lobounds, errorFilter); + if (that.inst == null) { + if (lobounds.isEmpty()) + that.inst = syms.botType; + else if (lobounds.tail.isEmpty()) + that.inst = lobounds.head.isPrimitive() ? syms.errType : lobounds.head; + else { + that.inst = types.lub(lobounds); + } + if (that.inst == null || that.inst.tag == ERROR) + throw ambiguousNoInstanceException + .setMessage("no.unique.minimal.instance.exists", + that.qtype, lobounds); + // VGJ: sort of inlined maximizeInst() below. Adding + // bounds can cause lobounds that are above hibounds. + List hibounds = Type.filter(that.hibounds, errorFilter); + if (hibounds.isEmpty()) + return; + Type hb = null; + if (hibounds.tail.isEmpty()) + hb = hibounds.head; + else for (List bs = hibounds; + bs.nonEmpty() && hb == null; + bs = bs.tail) { + if (isSubClass(bs.head, hibounds)) + hb = types.fromUnknownFun.apply(bs.head); + } + if (hb == null || + !types.isSubtypeUnchecked(hb, hibounds, warn) || + !types.isSubtypeUnchecked(that.inst, hb, warn)) + throw ambiguousNoInstanceException; + } + } + +/*************************************************************************** + * Exported Methods + ***************************************************************************/ + + /** Try to instantiate expression type `that' to given type `to'. + * If a maximal instantiation exists which makes this type + * a subtype of type `to', return the instantiated type. + * If no instantiation exists, or if several incomparable + * best instantiations exist throw a NoInstanceException. + */ + public Type instantiateExpr(ForAll that, + Type to, + Warner warn) throws InferenceException { + List undetvars = Type.map(that.tvars, fromTypeVarFun); + for (List l = undetvars; l.nonEmpty(); l = l.tail) { + UndetVar uv = (UndetVar) l.head; + TypeVar tv = (TypeVar)uv.qtype; + ListBuffer hibounds = new ListBuffer(); + for (Type t : that.getConstraints(tv, ConstraintKind.EXTENDS)) { + hibounds.append(types.subst(t, that.tvars, undetvars)); + } + + List inst = that.getConstraints(tv, ConstraintKind.EQUAL); + if (inst.nonEmpty() && inst.head.tag != BOT) { + uv.inst = inst.head; + } + uv.hibounds = hibounds.toList(); + } + Type qtype1 = types.subst(that.qtype, that.tvars, undetvars); + if (!types.isSubtype(qtype1, + qtype1.tag == UNDETVAR ? types.boxedTypeOrType(to) : to)) { + throw unambiguousNoInstanceException + .setMessage("infer.no.conforming.instance.exists", + that.tvars, that.qtype, to); + } + for (List l = undetvars; l.nonEmpty(); l = l.tail) + maximizeInst((UndetVar) l.head, warn); + // System.out.println(" = " + qtype1.map(getInstFun));//DEBUG + + // check bounds + List targs = Type.map(undetvars, getInstFun); + if (Type.containsAny(targs, that.tvars)) { + //replace uninferred type-vars + targs = types.subst(targs, + that.tvars, + instaniateAsUninferredVars(undetvars, that.tvars)); + } + return chk.checkType(warn.pos(), that.inst(targs, types), to); + } + //where + private List instaniateAsUninferredVars(List undetvars, List tvars) { + ListBuffer new_targs = ListBuffer.lb(); + //step 1 - create syntethic captured vars + for (Type t : undetvars) { + UndetVar uv = (UndetVar)t; + Type newArg = new CapturedType(t.tsym.name, t.tsym, uv.inst, syms.botType, null); + new_targs = new_targs.append(newArg); + } + //step 2 - replace synthetic vars in their bounds + for (Type t : new_targs.toList()) { + CapturedType ct = (CapturedType)t; + ct.bound = types.subst(ct.bound, tvars, new_targs.toList()); + WildcardType wt = new WildcardType(ct.bound, BoundKind.EXTENDS, syms.boundClass); + ct.wildcard = wt; + } + return new_targs.toList(); + } + + /** Instantiate method type `mt' by finding instantiations of + * `tvars' so that method can be applied to `argtypes'. + */ + public Type instantiateMethod(final Env env, + List tvars, + MethodType mt, + final Symbol msym, + final List argtypes, + final boolean allowBoxing, + final boolean useVarargs, + final Warner warn) throws InferenceException { + //-System.err.println("instantiateMethod(" + tvars + ", " + mt + ", " + argtypes + ")"); //DEBUG + List undetvars = Type.map(tvars, fromTypeVarFun); + List formals = mt.argtypes; + //need to capture exactly once - otherwise subsequent + //applicability checks might fail + final List capturedArgs = types.capture(argtypes); + List actuals = capturedArgs; + List actualsNoCapture = argtypes; + // instantiate all polymorphic argument types and + // set up lower bounds constraints for undetvars + Type varargsFormal = useVarargs ? formals.last() : null; + if (varargsFormal == null && + actuals.size() != formals.size()) { + throw unambiguousNoInstanceException + .setMessage("infer.arg.length.mismatch"); + } + while (actuals.nonEmpty() && formals.head != varargsFormal) { + Type formal = formals.head; + Type actual = actuals.head.baseType(); + Type actualNoCapture = actualsNoCapture.head.baseType(); + if (actual.tag == FORALL) + actual = instantiateArg((ForAll)actual, formal, tvars, warn); + Type undetFormal = types.subst(formal, tvars, undetvars); + boolean works = allowBoxing + ? types.isConvertible(actual, undetFormal, warn) + : types.isSubtypeUnchecked(actual, undetFormal, warn); + if (!works) { + throw unambiguousNoInstanceException + .setMessage("infer.no.conforming.assignment.exists", + tvars, actualNoCapture, formal); + } + formals = formals.tail; + actuals = actuals.tail; + actualsNoCapture = actualsNoCapture.tail; + } + + if (formals.head != varargsFormal) // not enough args + throw unambiguousNoInstanceException.setMessage("infer.arg.length.mismatch"); + + // for varargs arguments as well + if (useVarargs) { + Type elemType = types.elemtype(varargsFormal); + Type elemUndet = types.subst(elemType, tvars, undetvars); + while (actuals.nonEmpty()) { + Type actual = actuals.head.baseType(); + Type actualNoCapture = actualsNoCapture.head.baseType(); + if (actual.tag == FORALL) + actual = instantiateArg((ForAll)actual, elemType, tvars, warn); + boolean works = types.isConvertible(actual, elemUndet, warn); + if (!works) { + throw unambiguousNoInstanceException + .setMessage("infer.no.conforming.assignment.exists", + tvars, actualNoCapture, elemType); + } + actuals = actuals.tail; + actualsNoCapture = actualsNoCapture.tail; + } + } + + // minimize as yet undetermined type variables + for (Type t : undetvars) + minimizeInst((UndetVar) t, warn); + + /** Type variables instantiated to bottom */ + ListBuffer restvars = new ListBuffer(); + + /** Undet vars instantiated to bottom */ + final ListBuffer restundet = new ListBuffer(); + + /** Instantiated types or TypeVars if under-constrained */ + ListBuffer insttypes = new ListBuffer(); + + /** Instantiated types or UndetVars if under-constrained */ + ListBuffer undettypes = new ListBuffer(); + + for (Type t : undetvars) { + UndetVar uv = (UndetVar)t; + if (uv.inst.tag == BOT) { + restvars.append(uv.qtype); + restundet.append(uv); + insttypes.append(uv.qtype); + undettypes.append(uv); + uv.inst = null; + } else { + insttypes.append(uv.inst); + undettypes.append(uv.inst); + } + } + checkWithinBounds(tvars, undettypes.toList(), warn); + + mt = (MethodType)types.subst(mt, tvars, insttypes.toList()); + + if (!restvars.isEmpty()) { + // if there are uninstantiated variables, + // quantify result type with them + final List inferredTypes = insttypes.toList(); + final List all_tvars = tvars; //this is the wrong tvars + return new UninferredMethodType(mt, restvars.toList()) { + @Override + List getConstraints(TypeVar tv, ConstraintKind ck) { + for (Type t : restundet.toList()) { + UndetVar uv = (UndetVar)t; + if (uv.qtype == tv) { + switch (ck) { + case EXTENDS: return uv.hibounds.appendList(types.subst(types.getBounds(tv), all_tvars, inferredTypes)); + case SUPER: return uv.lobounds; + case EQUAL: return uv.inst != null ? List.of(uv.inst) : List.nil(); + } + } + } + return List.nil(); + } + @Override + void check(List inferred, Types types) throws NoInstanceException { + // check that actuals conform to inferred formals + checkArgumentsAcceptable(env, capturedArgs, getParameterTypes(), allowBoxing, useVarargs, warn); + // check that inferred bounds conform to their bounds + checkWithinBounds(all_tvars, + types.subst(inferredTypes, tvars, inferred), warn); + if (useVarargs) { + chk.checkVararg(env.tree.pos(), getParameterTypes(), msym); + } + }}; + } + else { + // check that actuals conform to inferred formals + checkArgumentsAcceptable(env, capturedArgs, mt.getParameterTypes(), allowBoxing, useVarargs, warn); + // return instantiated version of method type + return mt; + } + } + //where + + /** + * A delegated type representing a partially uninferred method type. + * The return type of a partially uninferred method type is a ForAll + * type - when the return type is instantiated (see Infer.instantiateExpr) + * the underlying method type is also updated. + */ + static abstract class UninferredMethodType extends DelegatedType { + + final List tvars; + + public UninferredMethodType(MethodType mtype, List tvars) { + super(METHOD, new MethodType(mtype.argtypes, null, mtype.thrown, mtype.tsym)); + this.tvars = tvars; + asMethodType().restype = new UninferredReturnType(tvars, mtype.restype); + } + + @Override + public MethodType asMethodType() { + return qtype.asMethodType(); + } + + @Override + public Type map(Mapping f) { + return qtype.map(f); + } + + void instantiateReturnType(Type restype, List inferred, Types types) throws NoInstanceException { + //update method type with newly inferred type-arguments + qtype = new MethodType(types.subst(getParameterTypes(), tvars, inferred), + restype, + types.subst(UninferredMethodType.this.getThrownTypes(), tvars, inferred), + UninferredMethodType.this.qtype.tsym); + check(inferred, types); + } + + abstract void check(List inferred, Types types) throws NoInstanceException; + + abstract List getConstraints(TypeVar tv, ConstraintKind ck); + + class UninferredReturnType extends ForAll { + public UninferredReturnType(List tvars, Type restype) { + super(tvars, restype); + } + @Override + public Type inst(List actuals, Types types) { + Type newRestype = super.inst(actuals, types); + instantiateReturnType(newRestype, actuals, types); + return newRestype; + } + @Override + public List getConstraints(TypeVar tv, ConstraintKind ck) { + return UninferredMethodType.this.getConstraints(tv, ck); + } + } + } + + private void checkArgumentsAcceptable(Env env, List actuals, List formals, + boolean allowBoxing, boolean useVarargs, Warner warn) { + try { + rs.checkRawArgumentsAcceptable(env, actuals, formals, + allowBoxing, useVarargs, warn); + } + catch (Resolve.InapplicableMethodException ex) { + // inferred method is not applicable + throw invalidInstanceException.setMessage(ex.getDiagnostic()); + } + } + + /** Try to instantiate argument type `that' to given type `to'. + * If this fails, try to insantiate `that' to `to' where + * every occurrence of a type variable in `tvars' is replaced + * by an unknown type. + */ + private Type instantiateArg(ForAll that, + Type to, + List tvars, + Warner warn) throws InferenceException { + List targs; + try { + return instantiateExpr(that, to, warn); + } catch (NoInstanceException ex) { + Type to1 = to; + for (List l = tvars; l.nonEmpty(); l = l.tail) + to1 = types.subst(to1, List.of(l.head), List.of(syms.unknownType)); + return instantiateExpr(that, to1, warn); + } + } + + /** check that type parameters are within their bounds. + */ + void checkWithinBounds(List tvars, + List arguments, + Warner warn) + throws InvalidInstanceException { + for (List tvs = tvars, args = arguments; + tvs.nonEmpty(); + tvs = tvs.tail, args = args.tail) { + if (args.head instanceof UndetVar || + tvars.head.getUpperBound().isErroneous()) continue; + List bounds = types.subst(types.getBounds((TypeVar)tvs.head), tvars, arguments); + if (!types.isSubtypeUnchecked(args.head, bounds, warn)) + throw invalidInstanceException + .setMessage("inferred.do.not.conform.to.bounds", + args.head, bounds); + } + } + + /** + * Compute a synthetic method type corresponding to the requested polymorphic + * method signature. The target return type is computed from the immediately + * enclosing scope surrounding the polymorphic-signature call. + */ + Type instantiatePolymorphicSignatureInstance(Env env, Type site, + Name name, + MethodSymbol spMethod, // sig. poly. method or null if none + List argtypes) { + final Type restype; + + //The return type for a polymorphic signature call is computed from + //the enclosing tree E, as follows: if E is a cast, then use the + //target type of the cast expression as a return type; if E is an + //expression statement, the return type is 'void' - otherwise the + //return type is simply 'Object'. A correctness check ensures that + //env.next refers to the lexically enclosing environment in which + //the polymorphic signature call environment is nested. + + switch (env.next.tree.getTag()) { + case JCTree.TYPECAST: + JCTypeCast castTree = (JCTypeCast)env.next.tree; + restype = (TreeInfo.skipParens(castTree.expr) == env.tree) ? + castTree.clazz.type : + syms.objectType; + break; + case JCTree.EXEC: + JCTree.JCExpressionStatement execTree = + (JCTree.JCExpressionStatement)env.next.tree; + restype = (TreeInfo.skipParens(execTree.expr) == env.tree) ? + syms.voidType : + syms.objectType; + break; + default: + restype = syms.objectType; + } + + List paramtypes = Type.map(argtypes, implicitArgType); + List exType = spMethod != null ? + spMethod.getThrownTypes() : + List.of(syms.throwableType); // make it throw all exceptions + + MethodType mtype = new MethodType(paramtypes, + restype, + exType, + syms.methodClass); + return mtype; + } + //where + Mapping implicitArgType = new Mapping ("implicitArgType") { + public Type apply(Type t) { + t = types.erasure(t); + if (t.tag == BOT) + // nulls type as the marker type Null (which has no instances) + // infer as java.lang.Void for now + t = types.boxedClass(syms.voidType).type; + return t; + } + }; + } diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/comp/Lower.java b/douyu-javac/src/main/java/com/sun/tools/javac/comp/Lower.java new file mode 100644 index 0000000..6f677a3 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/comp/Lower.java @@ -0,0 +1,3913 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.comp; + +import java.util.*; + +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.jvm.*; +import com.sun.tools.javac.main.RecognizedOptions.PkgInfo; +import com.sun.tools.javac.tree.*; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; +import com.sun.tools.javac.util.List; + +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.tree.JCTree.*; +import com.sun.tools.javac.code.Type.*; + +import com.sun.tools.javac.jvm.Target; + +import static com.sun.tools.javac.code.Flags.*; +import static com.sun.tools.javac.code.Kinds.*; +import static com.sun.tools.javac.code.TypeTags.*; +import static com.sun.tools.javac.jvm.ByteCodes.*; + +/** This pass translates away some syntactic sugar: inner classes, + * class literals, assertions, foreach loops, etc. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Lower extends TreeTranslator { + protected static final Context.Key lowerKey = + new Context.Key(); + + public static Lower instance(Context context) { + Lower instance = context.get(lowerKey); + if (instance == null) + instance = new Lower(context); + return instance; + } + + private Names names; + private Log log; + private Symtab syms; + private Resolve rs; + private Check chk; + private Attr attr; + private TreeMaker make; + private DiagnosticPosition make_pos; + private ClassWriter writer; + private ClassReader reader; + private ConstFold cfolder; + private Target target; + private Source source; + private boolean allowEnums; + private final Name dollarAssertionsDisabled; + private final Name classDollar; + private Types types; + private boolean debugLower; + private PkgInfo pkginfoOpt; + + protected Lower(Context context) { + context.put(lowerKey, this); + names = Names.instance(context); + log = Log.instance(context); + syms = Symtab.instance(context); + rs = Resolve.instance(context); + chk = Check.instance(context); + attr = Attr.instance(context); + make = TreeMaker.instance(context); + writer = ClassWriter.instance(context); + reader = ClassReader.instance(context); + cfolder = ConstFold.instance(context); + target = Target.instance(context); + source = Source.instance(context); + allowEnums = source.allowEnums(); + dollarAssertionsDisabled = names. + fromString(target.syntheticNameChar() + "assertionsDisabled"); + classDollar = names. + fromString("class" + target.syntheticNameChar()); + + types = Types.instance(context); + Options options = Options.instance(context); + debugLower = options.isSet("debuglower"); + pkginfoOpt = PkgInfo.get(options); + } + + /** The currently enclosing class. + */ + ClassSymbol currentClass; + + /** A queue of all translated classes. + */ + ListBuffer translated; + + /** Environment for symbol lookup, set by translateTopLevelClass. + */ + Env attrEnv; + + /** A hash table mapping syntax trees to their ending source positions. + */ + Map endPositions; + +/************************************************************************** + * Global mappings + *************************************************************************/ + + /** A hash table mapping local classes to their definitions. + */ + Map classdefs; + + /** A hash table mapping virtual accessed symbols in outer subclasses + * to the actually referred symbol in superclasses. + */ + Map actualSymbols; + + /** The current method definition. + */ + JCMethodDecl currentMethodDef; + + /** The current method symbol. + */ + MethodSymbol currentMethodSym; + + /** The currently enclosing outermost class definition. + */ + JCClassDecl outermostClassDef; + + /** The currently enclosing outermost member definition. + */ + JCTree outermostMemberDef; + + /** A navigator class for assembling a mapping from local class symbols + * to class definition trees. + * There is only one case; all other cases simply traverse down the tree. + */ + class ClassMap extends TreeScanner { + + /** All encountered class defs are entered into classdefs table. + */ + public void visitClassDef(JCClassDecl tree) { + classdefs.put(tree.sym, tree); + super.visitClassDef(tree); + } + } + ClassMap classMap = new ClassMap(); + + /** Map a class symbol to its definition. + * @param c The class symbol of which we want to determine the definition. + */ + JCClassDecl classDef(ClassSymbol c) { + // First lookup the class in the classdefs table. + JCClassDecl def = classdefs.get(c); + if (def == null && outermostMemberDef != null) { + // If this fails, traverse outermost member definition, entering all + // local classes into classdefs, and try again. + classMap.scan(outermostMemberDef); + def = classdefs.get(c); + } + if (def == null) { + // If this fails, traverse outermost class definition, entering all + // local classes into classdefs, and try again. + classMap.scan(outermostClassDef); + def = classdefs.get(c); + } + return def; + } + + /** A hash table mapping class symbols to lists of free variables. + * accessed by them. Only free variables of the method immediately containing + * a class are associated with that class. + */ + Map> freevarCache; + + /** A navigator class for collecting the free variables accessed + * from a local class. + * There is only one case; all other cases simply traverse down the tree. + */ + class FreeVarCollector extends TreeScanner { + + /** The owner of the local class. + */ + Symbol owner; + + /** The local class. + */ + ClassSymbol clazz; + + /** The list of owner's variables accessed from within the local class, + * without any duplicates. + */ + List fvs; + + FreeVarCollector(ClassSymbol clazz) { + this.clazz = clazz; + this.owner = clazz.owner; + this.fvs = List.nil(); + } + + /** Add free variable to fvs list unless it is already there. + */ + private void addFreeVar(VarSymbol v) { + for (List l = fvs; l.nonEmpty(); l = l.tail) + if (l.head == v) return; + fvs = fvs.prepend(v); + } + + /** Add all free variables of class c to fvs list + * unless they are already there. + */ + private void addFreeVars(ClassSymbol c) { + List fvs = freevarCache.get(c); + if (fvs != null) { + for (List l = fvs; l.nonEmpty(); l = l.tail) { + addFreeVar(l.head); + } + } + } + + /** If tree refers to a variable in owner of local class, add it to + * free variables list. + */ + public void visitIdent(JCIdent tree) { + result = tree; + visitSymbol(tree.sym); + } + // where + private void visitSymbol(Symbol _sym) { + Symbol sym = _sym; + if (sym.kind == VAR || sym.kind == MTH) { + while (sym != null && sym.owner != owner) + sym = proxies.lookup(proxyName(sym.name)).sym; + if (sym != null && sym.owner == owner) { + VarSymbol v = (VarSymbol)sym; + if (v.getConstValue() == null) { + addFreeVar(v); + } + } else { + if (outerThisStack.head != null && + outerThisStack.head != _sym) + visitSymbol(outerThisStack.head); + } + } + } + + /** If tree refers to a class instance creation expression + * add all free variables of the freshly created class. + */ + public void visitNewClass(JCNewClass tree) { + ClassSymbol c = (ClassSymbol)tree.constructor.owner; + addFreeVars(c); + if (tree.encl == null && + c.hasOuterInstance() && + outerThisStack.head != null) + visitSymbol(outerThisStack.head); + super.visitNewClass(tree); + } + + /** If tree refers to a qualified this or super expression + * for anything but the current class, add the outer this + * stack as a free variable. + */ + public void visitSelect(JCFieldAccess tree) { + if ((tree.name == names._this || tree.name == names._super) && + tree.selected.type.tsym != clazz && + outerThisStack.head != null) + visitSymbol(outerThisStack.head); + super.visitSelect(tree); + } + + /** If tree refers to a superclass constructor call, + * add all free variables of the superclass. + */ + public void visitApply(JCMethodInvocation tree) { + if (TreeInfo.name(tree.meth) == names._super) { + addFreeVars((ClassSymbol) TreeInfo.symbol(tree.meth).owner); + Symbol constructor = TreeInfo.symbol(tree.meth); + ClassSymbol c = (ClassSymbol)constructor.owner; + if (c.hasOuterInstance() && + tree.meth.getTag() != JCTree.SELECT && + outerThisStack.head != null) + visitSymbol(outerThisStack.head); + } + super.visitApply(tree); + } + } + + /** Return the variables accessed from within a local class, which + * are declared in the local class' owner. + * (in reverse order of first access). + */ + List freevars(ClassSymbol c) { + if ((c.owner.kind & (VAR | MTH)) != 0) { + List fvs = freevarCache.get(c); + if (fvs == null) { + FreeVarCollector collector = new FreeVarCollector(c); + collector.scan(classDef(c)); + fvs = collector.fvs; + freevarCache.put(c, fvs); + } + return fvs; + } else { + return List.nil(); + } + } + + Map enumSwitchMap = new LinkedHashMap(); + + EnumMapping mapForEnum(DiagnosticPosition pos, TypeSymbol enumClass) { + EnumMapping map = enumSwitchMap.get(enumClass); + if (map == null) + enumSwitchMap.put(enumClass, map = new EnumMapping(pos, enumClass)); + return map; + } + + /** This map gives a translation table to be used for enum + * switches. + * + *

For each enum that appears as the type of a switch + * expression, we maintain an EnumMapping to assist in the + * translation, as exemplified by the following example: + * + *

we translate + *

+     *          switch(colorExpression) {
+     *          case red: stmt1;
+     *          case green: stmt2;
+     *          }
+     *  
+ * into + *
+     *          switch(Outer$0.$EnumMap$Color[colorExpression.ordinal()]) {
+     *          case 1: stmt1;
+     *          case 2: stmt2
+     *          }
+     *  
+ * with the auxiliary table initialized as follows: + *
+     *          class Outer$0 {
+     *              synthetic final int[] $EnumMap$Color = new int[Color.values().length];
+     *              static {
+     *                  try { $EnumMap$Color[red.ordinal()] = 1; } catch (NoSuchFieldError ex) {}
+     *                  try { $EnumMap$Color[green.ordinal()] = 2; } catch (NoSuchFieldError ex) {}
+     *              }
+     *          }
+     *  
+ * class EnumMapping provides mapping data and support methods for this translation. + */ + class EnumMapping { + EnumMapping(DiagnosticPosition pos, TypeSymbol forEnum) { + this.forEnum = forEnum; + this.values = new LinkedHashMap(); + this.pos = pos; + Name varName = names + .fromString(target.syntheticNameChar() + + "SwitchMap" + + target.syntheticNameChar() + + writer.xClassName(forEnum.type).toString() + .replace('/', '.') + .replace('.', target.syntheticNameChar())); + ClassSymbol outerCacheClass = outerCacheClass(); + this.mapVar = new VarSymbol(STATIC | SYNTHETIC | FINAL, + varName, + new ArrayType(syms.intType, syms.arrayClass), + outerCacheClass); + enterSynthetic(pos, mapVar, outerCacheClass.members()); + } + + DiagnosticPosition pos = null; + + // the next value to use + int next = 1; // 0 (unused map elements) go to the default label + + // the enum for which this is a map + final TypeSymbol forEnum; + + // the field containing the map + final VarSymbol mapVar; + + // the mapped values + final Map values; + + JCLiteral forConstant(VarSymbol v) { + Integer result = values.get(v); + if (result == null) + values.put(v, result = next++); + return make.Literal(result); + } + + // generate the field initializer for the map + void translate() { + make.at(pos.getStartPosition()); + JCClassDecl owner = classDef((ClassSymbol)mapVar.owner); + + // synthetic static final int[] $SwitchMap$Color = new int[Color.values().length]; + MethodSymbol valuesMethod = lookupMethod(pos, + names.values, + forEnum.type, + List.nil()); + JCExpression size = make // Color.values().length + .Select(make.App(make.QualIdent(valuesMethod)), + syms.lengthVar); + JCExpression mapVarInit = make + .NewArray(make.Type(syms.intType), List.of(size), null) + .setType(new ArrayType(syms.intType, syms.arrayClass)); + + // try { $SwitchMap$Color[red.ordinal()] = 1; } catch (java.lang.NoSuchFieldError ex) {} + ListBuffer stmts = new ListBuffer(); + Symbol ordinalMethod = lookupMethod(pos, + names.ordinal, + forEnum.type, + List.nil()); + List catcher = List.nil() + .prepend(make.Catch(make.VarDef(new VarSymbol(PARAMETER, names.ex, + syms.noSuchFieldErrorType, + syms.noSymbol), + null), + make.Block(0, List.nil()))); + for (Map.Entry e : values.entrySet()) { + VarSymbol enumerator = e.getKey(); + Integer mappedValue = e.getValue(); + JCExpression assign = make + .Assign(make.Indexed(mapVar, + make.App(make.Select(make.QualIdent(enumerator), + ordinalMethod))), + make.Literal(mappedValue)) + .setType(syms.intType); + JCStatement exec = make.Exec(assign); + JCStatement _try = make.Try(make.Block(0, List.of(exec)), catcher, null); + stmts.append(_try); + } + + owner.defs = owner.defs + .prepend(make.Block(STATIC, stmts.toList())) + .prepend(make.VarDef(mapVar, mapVarInit)); + } + } + + +/************************************************************************** + * Tree building blocks + *************************************************************************/ + + /** Equivalent to make.at(pos.getStartPosition()) with side effect of caching + * pos as make_pos, for use in diagnostics. + **/ + TreeMaker make_at(DiagnosticPosition pos) { + make_pos = pos; + return make.at(pos); + } + + /** Make an attributed tree representing a literal. This will be an + * Ident node in the case of boolean literals, a Literal node in all + * other cases. + * @param type The literal's type. + * @param value The literal's value. + */ + JCExpression makeLit(Type type, Object value) { + return make.Literal(type.tag, value).setType(type.constType(value)); + } + + /** Make an attributed tree representing null. + */ + JCExpression makeNull() { + return makeLit(syms.botType, null); + } + + /** Make an attributed class instance creation expression. + * @param ctype The class type. + * @param args The constructor arguments. + */ + JCNewClass makeNewClass(Type ctype, List args) { + JCNewClass tree = make.NewClass(null, + null, make.QualIdent(ctype.tsym), args, null); + tree.constructor = rs.resolveConstructor( + make_pos, attrEnv, ctype, TreeInfo.types(args), null, false, false); + tree.type = ctype; + return tree; + } + + /** Make an attributed unary expression. + * @param optag The operators tree tag. + * @param arg The operator's argument. + */ + JCUnary makeUnary(int optag, JCExpression arg) { + JCUnary tree = make.Unary(optag, arg); + tree.operator = rs.resolveUnaryOperator( + make_pos, optag, attrEnv, arg.type); + tree.type = tree.operator.type.getReturnType(); + return tree; + } + + /** Make an attributed binary expression. + * @param optag The operators tree tag. + * @param lhs The operator's left argument. + * @param rhs The operator's right argument. + */ + JCBinary makeBinary(int optag, JCExpression lhs, JCExpression rhs) { + JCBinary tree = make.Binary(optag, lhs, rhs); + tree.operator = rs.resolveBinaryOperator( + make_pos, optag, attrEnv, lhs.type, rhs.type); + tree.type = tree.operator.type.getReturnType(); + return tree; + } + + /** Make an attributed assignop expression. + * @param optag The operators tree tag. + * @param lhs The operator's left argument. + * @param rhs The operator's right argument. + */ + JCAssignOp makeAssignop(int optag, JCTree lhs, JCTree rhs) { + JCAssignOp tree = make.Assignop(optag, lhs, rhs); + tree.operator = rs.resolveBinaryOperator( + make_pos, tree.getTag() - JCTree.ASGOffset, attrEnv, lhs.type, rhs.type); + tree.type = lhs.type; + return tree; + } + + /** Convert tree into string object, unless it has already a + * reference type.. + */ + JCExpression makeString(JCExpression tree) { + if (tree.type.tag >= CLASS) { + return tree; + } else { + Symbol valueOfSym = lookupMethod(tree.pos(), + names.valueOf, + syms.stringType, + List.of(tree.type)); + return make.App(make.QualIdent(valueOfSym), List.of(tree)); + } + } + + /** Create an empty anonymous class definition and enter and complete + * its symbol. Return the class definition's symbol. + * and create + * @param flags The class symbol's flags + * @param owner The class symbol's owner + */ + ClassSymbol makeEmptyClass(long flags, ClassSymbol owner) { + // Create class symbol. + ClassSymbol c = reader.defineClass(names.empty, owner); + c.flatname = chk.localClassName(c); + c.sourcefile = owner.sourcefile; + c.completer = null; + c.members_field = new Scope(c); + c.flags_field = flags; + ClassType ctype = (ClassType) c.type; + ctype.supertype_field = syms.objectType; + ctype.interfaces_field = List.nil(); + + JCClassDecl odef = classDef(owner); + + // Enter class symbol in owner scope and compiled table. + enterSynthetic(odef.pos(), c, owner.members()); + chk.compiled.put(c.flatname, c); + + // Create class definition tree. + JCClassDecl cdef = make.ClassDef( + make.Modifiers(flags), names.empty, + List.nil(), + null, List.nil(), List.nil()); + cdef.sym = c; + cdef.type = c.type; + + // Append class definition tree to owner's definitions. + odef.defs = odef.defs.prepend(cdef); + + return c; + } + +/************************************************************************** + * Symbol manipulation utilities + *************************************************************************/ + + /** Enter a synthetic symbol in a given scope, but complain if there was already one there. + * @param pos Position for error reporting. + * @param sym The symbol. + * @param s The scope. + */ + private void enterSynthetic(DiagnosticPosition pos, Symbol sym, Scope s) { + s.enter(sym); + } + + /** Create a fresh synthetic name within a given scope - the unique name is + * obtained by appending '$' chars at the end of the name until no match + * is found. + * + * @param name base name + * @param s scope in which the name has to be unique + * @return fresh synthetic name + */ + private Name makeSyntheticName(Name name, Scope s) { + do { + name = name.append( + target.syntheticNameChar(), + names.empty); + } while (lookupSynthetic(name, s) != null); + return name; + } + + /** Check whether synthetic symbols generated during lowering conflict + * with user-defined symbols. + * + * @param translatedTrees lowered class trees + */ + void checkConflicts(List translatedTrees) { + for (JCTree t : translatedTrees) { + t.accept(conflictsChecker); + } + } + + JCTree.Visitor conflictsChecker = new TreeScanner() { + + TypeSymbol currentClass; + + @Override + public void visitMethodDef(JCMethodDecl that) { + chk.checkConflicts(that.pos(), that.sym, currentClass); + super.visitMethodDef(that); + } + + @Override + public void visitVarDef(JCVariableDecl that) { + if (that.sym.owner.kind == TYP) { + chk.checkConflicts(that.pos(), that.sym, currentClass); + } + super.visitVarDef(that); + } + + @Override + public void visitClassDef(JCClassDecl that) { + TypeSymbol prevCurrentClass = currentClass; + currentClass = that.sym; + try { + super.visitClassDef(that); + } + finally { + currentClass = prevCurrentClass; + } + } + }; + + /** Look up a synthetic name in a given scope. + * @param scope The scope. + * @param name The name. + */ + private Symbol lookupSynthetic(Name name, Scope s) { + Symbol sym = s.lookup(name).sym; + return (sym==null || (sym.flags()&SYNTHETIC)==0) ? null : sym; + } + + /** Look up a method in a given scope. + */ + private MethodSymbol lookupMethod(DiagnosticPosition pos, Name name, Type qual, List args) { + return rs.resolveInternalMethod(pos, attrEnv, qual, name, args, null); + } + + /** Look up a constructor. + */ + private MethodSymbol lookupConstructor(DiagnosticPosition pos, Type qual, List args) { + return rs.resolveInternalConstructor(pos, attrEnv, qual, args, null); + } + + /** Look up a field. + */ + private VarSymbol lookupField(DiagnosticPosition pos, Type qual, Name name) { + return rs.resolveInternalField(pos, attrEnv, qual, name); + } + + /** Anon inner classes are used as access constructor tags. + * accessConstructorTag will use an existing anon class if one is available, + * and synthethise a class (with makeEmptyClass) if one is not available. + * However, there is a small possibility that an existing class will not + * be generated as expected if it is inside a conditional with a constant + * expression. If that is found to be the case, create an empty class here. + */ + private void checkAccessConstructorTags() { + for (List l = accessConstrTags; l.nonEmpty(); l = l.tail) { + ClassSymbol c = l.head; + if (isTranslatedClassAvailable(c)) + continue; + // Create class definition tree. + JCClassDecl cdef = make.ClassDef( + make.Modifiers(STATIC | SYNTHETIC), names.empty, + List.nil(), + null, List.nil(), List.nil()); + cdef.sym = c; + cdef.type = c.type; + // add it to the list of classes to be generated + translated.append(cdef); + } + } + // where + private boolean isTranslatedClassAvailable(ClassSymbol c) { + for (JCTree tree: translated) { + if (tree.getTag() == JCTree.CLASSDEF + && ((JCClassDecl) tree).sym == c) { + return true; + } + } + return false; + } + +/************************************************************************** + * Access methods + *************************************************************************/ + + /** Access codes for dereferencing, assignment, + * and pre/post increment/decrement. + * Access codes for assignment operations are determined by method accessCode + * below. + * + * All access codes for accesses to the current class are even. + * If a member of the superclass should be accessed instead (because + * access was via a qualified super), add one to the corresponding code + * for the current class, making the number odd. + * This numbering scheme is used by the backend to decide whether + * to issue an invokevirtual or invokespecial call. + * + * @see Gen.visitSelect(Select tree) + */ + private static final int + DEREFcode = 0, + ASSIGNcode = 2, + PREINCcode = 4, + PREDECcode = 6, + POSTINCcode = 8, + POSTDECcode = 10, + FIRSTASGOPcode = 12; + + /** Number of access codes + */ + private static final int NCODES = accessCode(ByteCodes.lushrl) + 2; + + /** A mapping from symbols to their access numbers. + */ + private Map accessNums; + + /** A mapping from symbols to an array of access symbols, indexed by + * access code. + */ + private Map accessSyms; + + /** A mapping from (constructor) symbols to access constructor symbols. + */ + private Map accessConstrs; + + /** A list of all class symbols used for access constructor tags. + */ + private List accessConstrTags; + + /** A queue for all accessed symbols. + */ + private ListBuffer accessed; + + /** Map bytecode of binary operation to access code of corresponding + * assignment operation. This is always an even number. + */ + private static int accessCode(int bytecode) { + if (ByteCodes.iadd <= bytecode && bytecode <= ByteCodes.lxor) + return (bytecode - iadd) * 2 + FIRSTASGOPcode; + else if (bytecode == ByteCodes.string_add) + return (ByteCodes.lxor + 1 - iadd) * 2 + FIRSTASGOPcode; + else if (ByteCodes.ishll <= bytecode && bytecode <= ByteCodes.lushrl) + return (bytecode - ishll + ByteCodes.lxor + 2 - iadd) * 2 + FIRSTASGOPcode; + else + return -1; + } + + /** return access code for identifier, + * @param tree The tree representing the identifier use. + * @param enclOp The closest enclosing operation node of tree, + * null if tree is not a subtree of an operation. + */ + private static int accessCode(JCTree tree, JCTree enclOp) { + if (enclOp == null) + return DEREFcode; + else if (enclOp.getTag() == JCTree.ASSIGN && + tree == TreeInfo.skipParens(((JCAssign) enclOp).lhs)) + return ASSIGNcode; + else if (JCTree.PREINC <= enclOp.getTag() && enclOp.getTag() <= JCTree.POSTDEC && + tree == TreeInfo.skipParens(((JCUnary) enclOp).arg)) + return (enclOp.getTag() - JCTree.PREINC) * 2 + PREINCcode; + else if (JCTree.BITOR_ASG <= enclOp.getTag() && enclOp.getTag() <= JCTree.MOD_ASG && + tree == TreeInfo.skipParens(((JCAssignOp) enclOp).lhs)) + return accessCode(((OperatorSymbol) ((JCAssignOp) enclOp).operator).opcode); + else + return DEREFcode; + } + + /** Return binary operator that corresponds to given access code. + */ + private OperatorSymbol binaryAccessOperator(int acode) { + for (Scope.Entry e = syms.predefClass.members().elems; + e != null; + e = e.sibling) { + if (e.sym instanceof OperatorSymbol) { + OperatorSymbol op = (OperatorSymbol)e.sym; + if (accessCode(op.opcode) == acode) return op; + } + } + return null; + } + + /** Return tree tag for assignment operation corresponding + * to given binary operator. + */ + private static int treeTag(OperatorSymbol operator) { + switch (operator.opcode) { + case ByteCodes.ior: case ByteCodes.lor: + return JCTree.BITOR_ASG; + case ByteCodes.ixor: case ByteCodes.lxor: + return JCTree.BITXOR_ASG; + case ByteCodes.iand: case ByteCodes.land: + return JCTree.BITAND_ASG; + case ByteCodes.ishl: case ByteCodes.lshl: + case ByteCodes.ishll: case ByteCodes.lshll: + return JCTree.SL_ASG; + case ByteCodes.ishr: case ByteCodes.lshr: + case ByteCodes.ishrl: case ByteCodes.lshrl: + return JCTree.SR_ASG; + case ByteCodes.iushr: case ByteCodes.lushr: + case ByteCodes.iushrl: case ByteCodes.lushrl: + return JCTree.USR_ASG; + case ByteCodes.iadd: case ByteCodes.ladd: + case ByteCodes.fadd: case ByteCodes.dadd: + case ByteCodes.string_add: + return JCTree.PLUS_ASG; + case ByteCodes.isub: case ByteCodes.lsub: + case ByteCodes.fsub: case ByteCodes.dsub: + return JCTree.MINUS_ASG; + case ByteCodes.imul: case ByteCodes.lmul: + case ByteCodes.fmul: case ByteCodes.dmul: + return JCTree.MUL_ASG; + case ByteCodes.idiv: case ByteCodes.ldiv: + case ByteCodes.fdiv: case ByteCodes.ddiv: + return JCTree.DIV_ASG; + case ByteCodes.imod: case ByteCodes.lmod: + case ByteCodes.fmod: case ByteCodes.dmod: + return JCTree.MOD_ASG; + default: + throw new AssertionError(); + } + } + + /** The name of the access method with number `anum' and access code `acode'. + */ + Name accessName(int anum, int acode) { + return names.fromString( + "access" + target.syntheticNameChar() + anum + acode / 10 + acode % 10); + } + + /** Return access symbol for a private or protected symbol from an inner class. + * @param sym The accessed private symbol. + * @param tree The accessing tree. + * @param enclOp The closest enclosing operation node of tree, + * null if tree is not a subtree of an operation. + * @param protAccess Is access to a protected symbol in another + * package? + * @param refSuper Is access via a (qualified) C.super? + */ + MethodSymbol accessSymbol(Symbol sym, JCTree tree, JCTree enclOp, + boolean protAccess, boolean refSuper) { + ClassSymbol accOwner = refSuper && protAccess + // For access via qualified super (T.super.x), place the + // access symbol on T. + ? (ClassSymbol)((JCFieldAccess) tree).selected.type.tsym + // Otherwise pretend that the owner of an accessed + // protected symbol is the enclosing class of the current + // class which is a subclass of the symbol's owner. + : accessClass(sym, protAccess, tree); + + Symbol vsym = sym; + if (sym.owner != accOwner) { + vsym = sym.clone(accOwner); + actualSymbols.put(vsym, sym); + } + + Integer anum // The access number of the access method. + = accessNums.get(vsym); + if (anum == null) { + anum = accessed.length(); + accessNums.put(vsym, anum); + accessSyms.put(vsym, new MethodSymbol[NCODES]); + accessed.append(vsym); + // System.out.println("accessing " + vsym + " in " + vsym.location()); + } + + int acode; // The access code of the access method. + List argtypes; // The argument types of the access method. + Type restype; // The result type of the access method. + List thrown; // The thrown exceptions of the access method. + switch (vsym.kind) { + case VAR: + acode = accessCode(tree, enclOp); + if (acode >= FIRSTASGOPcode) { + OperatorSymbol operator = binaryAccessOperator(acode); + if (operator.opcode == string_add) + argtypes = List.of(syms.objectType); + else + argtypes = operator.type.getParameterTypes().tail; + } else if (acode == ASSIGNcode) + argtypes = List.of(vsym.erasure(types)); + else + argtypes = List.nil(); + restype = vsym.erasure(types); + thrown = List.nil(); + break; + case MTH: + acode = DEREFcode; + argtypes = vsym.erasure(types).getParameterTypes(); + restype = vsym.erasure(types).getReturnType(); + thrown = vsym.type.getThrownTypes(); + break; + default: + throw new AssertionError(); + } + + // For references via qualified super, increment acode by one, + // making it odd. + if (protAccess && refSuper) acode++; + + // Instance access methods get instance as first parameter. + // For protected symbols this needs to be the instance as a member + // of the type containing the accessed symbol, not the class + // containing the access method. + if ((vsym.flags() & STATIC) == 0) { + argtypes = argtypes.prepend(vsym.owner.erasure(types)); + } + MethodSymbol[] accessors = accessSyms.get(vsym); + MethodSymbol accessor = accessors[acode]; + if (accessor == null) { + accessor = new MethodSymbol( + STATIC | SYNTHETIC, + accessName(anum.intValue(), acode), + new MethodType(argtypes, restype, thrown, syms.methodClass), + accOwner); + enterSynthetic(tree.pos(), accessor, accOwner.members()); + accessors[acode] = accessor; + } + return accessor; + } + + /** The qualifier to be used for accessing a symbol in an outer class. + * This is either C.sym or C.this.sym, depending on whether or not + * sym is static. + * @param sym The accessed symbol. + */ + JCExpression accessBase(DiagnosticPosition pos, Symbol sym) { + return (sym.flags() & STATIC) != 0 + ? access(make.at(pos.getStartPosition()).QualIdent(sym.owner)) + : makeOwnerThis(pos, sym, true); + } + + /** Do we need an access method to reference private symbol? + */ + boolean needsPrivateAccess(Symbol sym) { + if ((sym.flags() & PRIVATE) == 0 || sym.owner == currentClass) { + return false; + } else if (sym.name == names.init && (sym.owner.owner.kind & (VAR | MTH)) != 0) { + // private constructor in local class: relax protection + sym.flags_field &= ~PRIVATE; + return false; + } else { + return true; + } + } + + /** Do we need an access method to reference symbol in other package? + */ + boolean needsProtectedAccess(Symbol sym, JCTree tree) { + if ((sym.flags() & PROTECTED) == 0 || + sym.owner.owner == currentClass.owner || // fast special case + sym.packge() == currentClass.packge()) + return false; + if (!currentClass.isSubClass(sym.owner, types)) + return true; + if ((sym.flags() & STATIC) != 0 || + tree.getTag() != JCTree.SELECT || + TreeInfo.name(((JCFieldAccess) tree).selected) == names._super) + return false; + return !((JCFieldAccess) tree).selected.type.tsym.isSubClass(currentClass, types); + } + + /** The class in which an access method for given symbol goes. + * @param sym The access symbol + * @param protAccess Is access to a protected symbol in another + * package? + */ + ClassSymbol accessClass(Symbol sym, boolean protAccess, JCTree tree) { + if (protAccess) { + Symbol qualifier = null; + ClassSymbol c = currentClass; + if (tree.getTag() == JCTree.SELECT && (sym.flags() & STATIC) == 0) { + qualifier = ((JCFieldAccess) tree).selected.type.tsym; + while (!qualifier.isSubClass(c, types)) { + c = c.owner.enclClass(); + } + return c; + } else { + while (!c.isSubClass(sym.owner, types)) { + c = c.owner.enclClass(); + } + } + return c; + } else { + // the symbol is private + return sym.owner.enclClass(); + } + } + + /** Ensure that identifier is accessible, return tree accessing the identifier. + * @param sym The accessed symbol. + * @param tree The tree referring to the symbol. + * @param enclOp The closest enclosing operation node of tree, + * null if tree is not a subtree of an operation. + * @param refSuper Is access via a (qualified) C.super? + */ + JCExpression access(Symbol sym, JCExpression tree, JCExpression enclOp, boolean refSuper) { + // Access a free variable via its proxy, or its proxy's proxy + while (sym.kind == VAR && sym.owner.kind == MTH && + sym.owner.enclClass() != currentClass) { + // A constant is replaced by its constant value. + Object cv = ((VarSymbol)sym).getConstValue(); + if (cv != null) { + make.at(tree.pos); + return makeLit(sym.type, cv); + } + // Otherwise replace the variable by its proxy. + sym = proxies.lookup(proxyName(sym.name)).sym; + Assert.check(sym != null && (sym.flags_field & FINAL) != 0); + tree = make.at(tree.pos).Ident(sym); + } + JCExpression base = (tree.getTag() == JCTree.SELECT) ? ((JCFieldAccess) tree).selected : null; + switch (sym.kind) { + case TYP: + if (sym.owner.kind != PCK) { + // Convert type idents to + // or . + Name flatname = Convert.shortName(sym.flatName()); + while (base != null && + TreeInfo.symbol(base) != null && + TreeInfo.symbol(base).kind != PCK) { + base = (base.getTag() == JCTree.SELECT) + ? ((JCFieldAccess) base).selected + : null; + } + if (tree.getTag() == JCTree.IDENT) { + ((JCIdent) tree).name = flatname; + } else if (base == null) { + tree = make.at(tree.pos).Ident(sym); + ((JCIdent) tree).name = flatname; + } else { + ((JCFieldAccess) tree).selected = base; + ((JCFieldAccess) tree).name = flatname; + } + } + break; + case MTH: case VAR: + if (sym.owner.kind == TYP) { + + // Access methods are required for + // - private members, + // - protected members in a superclass of an + // enclosing class contained in another package. + // - all non-private members accessed via a qualified super. + boolean protAccess = refSuper && !needsPrivateAccess(sym) + || needsProtectedAccess(sym, tree); + boolean accReq = protAccess || needsPrivateAccess(sym); + + // A base has to be supplied for + // - simple identifiers accessing variables in outer classes. + boolean baseReq = + base == null && + sym.owner != syms.predefClass && + !sym.isMemberOf(currentClass, types); + + if (accReq || baseReq) { + make.at(tree.pos); + + // Constants are replaced by their constant value. + if (sym.kind == VAR) { + Object cv = ((VarSymbol)sym).getConstValue(); + if (cv != null) return makeLit(sym.type, cv); + } + + // Private variables and methods are replaced by calls + // to their access methods. + if (accReq) { + List args = List.nil(); + if ((sym.flags() & STATIC) == 0) { + // Instance access methods get instance + // as first parameter. + if (base == null) + base = makeOwnerThis(tree.pos(), sym, true); + args = args.prepend(base); + base = null; // so we don't duplicate code + } + Symbol access = accessSymbol(sym, tree, + enclOp, protAccess, + refSuper); + JCExpression receiver = make.Select( + base != null ? base : make.QualIdent(access.owner), + access); + return make.App(receiver, args); + + // Other accesses to members of outer classes get a + // qualifier. + } else if (baseReq) { + return make.at(tree.pos).Select( + accessBase(tree.pos(), sym), sym).setType(tree.type); + } + } + } + } + return tree; + } + + /** Ensure that identifier is accessible, return tree accessing the identifier. + * @param tree The identifier tree. + */ + JCExpression access(JCExpression tree) { + Symbol sym = TreeInfo.symbol(tree); + return sym == null ? tree : access(sym, tree, null, false); + } + + /** Return access constructor for a private constructor, + * or the constructor itself, if no access constructor is needed. + * @param pos The position to report diagnostics, if any. + * @param constr The private constructor. + */ + Symbol accessConstructor(DiagnosticPosition pos, Symbol constr) { + if (needsPrivateAccess(constr)) { + ClassSymbol accOwner = constr.owner.enclClass(); + MethodSymbol aconstr = accessConstrs.get(constr); + if (aconstr == null) { + List argtypes = constr.type.getParameterTypes(); + if ((accOwner.flags_field & ENUM) != 0) + argtypes = argtypes + .prepend(syms.intType) + .prepend(syms.stringType); + aconstr = new MethodSymbol( + SYNTHETIC, + names.init, + new MethodType( + argtypes.append( + accessConstructorTag().erasure(types)), + constr.type.getReturnType(), + constr.type.getThrownTypes(), + syms.methodClass), + accOwner); + enterSynthetic(pos, aconstr, accOwner.members()); + accessConstrs.put(constr, aconstr); + accessed.append(constr); + } + return aconstr; + } else { + return constr; + } + } + + /** Return an anonymous class nested in this toplevel class. + */ + ClassSymbol accessConstructorTag() { + ClassSymbol topClass = currentClass.outermostClass(); + Name flatname = names.fromString("" + topClass.getQualifiedName() + + target.syntheticNameChar() + + "1"); + ClassSymbol ctag = chk.compiled.get(flatname); + if (ctag == null) + ctag = makeEmptyClass(STATIC | SYNTHETIC, topClass); + // keep a record of all tags, to verify that all are generated as required + accessConstrTags = accessConstrTags.prepend(ctag); + return ctag; + } + + /** Add all required access methods for a private symbol to enclosing class. + * @param sym The symbol. + */ + void makeAccessible(Symbol sym) { + JCClassDecl cdef = classDef(sym.owner.enclClass()); + if (cdef == null) Assert.error("class def not found: " + sym + " in " + sym.owner); + if (sym.name == names.init) { + cdef.defs = cdef.defs.prepend( + accessConstructorDef(cdef.pos, sym, accessConstrs.get(sym))); + } else { + MethodSymbol[] accessors = accessSyms.get(sym); + for (int i = 0; i < NCODES; i++) { + if (accessors[i] != null) + cdef.defs = cdef.defs.prepend( + accessDef(cdef.pos, sym, accessors[i], i)); + } + } + } + + /** Construct definition of an access method. + * @param pos The source code position of the definition. + * @param vsym The private or protected symbol. + * @param accessor The access method for the symbol. + * @param acode The access code. + */ + JCTree accessDef(int pos, Symbol vsym, MethodSymbol accessor, int acode) { +// System.err.println("access " + vsym + " with " + accessor);//DEBUG + currentClass = vsym.owner.enclClass(); + make.at(pos); + JCMethodDecl md = make.MethodDef(accessor, null); + + // Find actual symbol + Symbol sym = actualSymbols.get(vsym); + if (sym == null) sym = vsym; + + JCExpression ref; // The tree referencing the private symbol. + List args; // Any additional arguments to be passed along. + if ((sym.flags() & STATIC) != 0) { + ref = make.Ident(sym); + args = make.Idents(md.params); + } else { + ref = make.Select(make.Ident(md.params.head), sym); + args = make.Idents(md.params.tail); + } + JCStatement stat; // The statement accessing the private symbol. + if (sym.kind == VAR) { + // Normalize out all odd access codes by taking floor modulo 2: + int acode1 = acode - (acode & 1); + + JCExpression expr; // The access method's return value. + switch (acode1) { + case DEREFcode: + expr = ref; + break; + case ASSIGNcode: + expr = make.Assign(ref, args.head); + break; + case PREINCcode: case POSTINCcode: case PREDECcode: case POSTDECcode: + expr = makeUnary( + ((acode1 - PREINCcode) >> 1) + JCTree.PREINC, ref); + break; + default: + expr = make.Assignop( + treeTag(binaryAccessOperator(acode1)), ref, args.head); + ((JCAssignOp) expr).operator = binaryAccessOperator(acode1); + } + stat = make.Return(expr.setType(sym.type)); + } else { + stat = make.Call(make.App(ref, args)); + } + md.body = make.Block(0, List.of(stat)); + + // Make sure all parameters, result types and thrown exceptions + // are accessible. + for (List l = md.params; l.nonEmpty(); l = l.tail) + l.head.vartype = access(l.head.vartype); + md.restype = access(md.restype); + for (List l = md.thrown; l.nonEmpty(); l = l.tail) + l.head = access(l.head); + + return md; + } + + /** Construct definition of an access constructor. + * @param pos The source code position of the definition. + * @param constr The private constructor. + * @param accessor The access method for the constructor. + */ + JCTree accessConstructorDef(int pos, Symbol constr, MethodSymbol accessor) { + make.at(pos); + JCMethodDecl md = make.MethodDef(accessor, + accessor.externalType(types), + null); + JCIdent callee = make.Ident(names._this); + callee.sym = constr; + callee.type = constr.type; + md.body = + make.Block(0, List.of( + make.Call( + make.App( + callee, + make.Idents(md.params.reverse().tail.reverse()))))); + return md; + } + +/************************************************************************** + * Free variables proxies and this$n + *************************************************************************/ + + /** A scope containing all free variable proxies for currently translated + * class, as well as its this$n symbol (if needed). + * Proxy scopes are nested in the same way classes are. + * Inside a constructor, proxies and any this$n symbol are duplicated + * in an additional innermost scope, where they represent the constructor + * parameters. + */ + Scope proxies; + + /** A scope containing all unnamed resource variables/saved + * exception variables for translated TWR blocks + */ + Scope twrVars; + + /** A stack containing the this$n field of the currently translated + * classes (if needed) in innermost first order. + * Inside a constructor, proxies and any this$n symbol are duplicated + * in an additional innermost scope, where they represent the constructor + * parameters. + */ + List outerThisStack; + + /** The name of a free variable proxy. + */ + Name proxyName(Name name) { + return names.fromString("val" + target.syntheticNameChar() + name); + } + + /** Proxy definitions for all free variables in given list, in reverse order. + * @param pos The source code position of the definition. + * @param freevars The free variables. + * @param owner The class in which the definitions go. + */ + List freevarDefs(int pos, List freevars, Symbol owner) { + long flags = FINAL | SYNTHETIC; + if (owner.kind == TYP && + target.usePrivateSyntheticFields()) + flags |= PRIVATE; + List defs = List.nil(); + for (List l = freevars; l.nonEmpty(); l = l.tail) { + VarSymbol v = l.head; + VarSymbol proxy = new VarSymbol( + flags, proxyName(v.name), v.erasure(types), owner); + proxies.enter(proxy); + JCVariableDecl vd = make.at(pos).VarDef(proxy, null); + vd.vartype = access(vd.vartype); + defs = defs.prepend(vd); + } + return defs; + } + + /** The name of a this$n field + * @param type The class referenced by the this$n field + */ + Name outerThisName(Type type, Symbol owner) { + Type t = type.getEnclosingType(); + int nestingLevel = 0; + while (t.tag == CLASS) { + t = t.getEnclosingType(); + nestingLevel++; + } + Name result = names.fromString("this" + target.syntheticNameChar() + nestingLevel); + while (owner.kind == TYP && ((ClassSymbol)owner).members().lookup(result).scope != null) + result = names.fromString(result.toString() + target.syntheticNameChar()); + return result; + } + + /** Definition for this$n field. + * @param pos The source code position of the definition. + * @param owner The class in which the definition goes. + */ + JCVariableDecl outerThisDef(int pos, Symbol owner) { + long flags = FINAL | SYNTHETIC; + if (owner.kind == TYP && + target.usePrivateSyntheticFields()) + flags |= PRIVATE; + Type target = types.erasure(owner.enclClass().type.getEnclosingType()); + VarSymbol outerThis = new VarSymbol( + flags, outerThisName(target, owner), target, owner); + outerThisStack = outerThisStack.prepend(outerThis); + JCVariableDecl vd = make.at(pos).VarDef(outerThis, null); + vd.vartype = access(vd.vartype); + return vd; + } + + /** Return a list of trees that load the free variables in given list, + * in reverse order. + * @param pos The source code position to be used for the trees. + * @param freevars The list of free variables. + */ + List loadFreevars(DiagnosticPosition pos, List freevars) { + List args = List.nil(); + for (List l = freevars; l.nonEmpty(); l = l.tail) + args = args.prepend(loadFreevar(pos, l.head)); + return args; + } +//where + JCExpression loadFreevar(DiagnosticPosition pos, VarSymbol v) { + return access(v, make.at(pos).Ident(v), null, false); + } + + /** Construct a tree simulating the expression . + * @param pos The source code position to be used for the tree. + * @param c The qualifier class. + */ + JCExpression makeThis(DiagnosticPosition pos, TypeSymbol c) { + if (currentClass == c) { + // in this case, `this' works fine + return make.at(pos).This(c.erasure(types)); + } else { + // need to go via this$n + return makeOuterThis(pos, c); + } + } + + /** + * Optionally replace a try statement with the desugaring of a + * try-with-resources statement. The canonical desugaring of + * + * try ResourceSpecification + * Block + * + * is + * + * { + * final VariableModifiers_minus_final R #resource = Expression; + * Throwable #primaryException = null; + * + * try ResourceSpecificationtail + * Block + * catch (Throwable #t) { + * #primaryException = t; + * throw #t; + * } finally { + * if (#resource != null) { + * if (#primaryException != null) { + * try { + * #resource.close(); + * } catch(Throwable #suppressedException) { + * #primaryException.addSuppressed(#suppressedException); + * } + * } else { + * #resource.close(); + * } + * } + * } + * + * @param tree The try statement to inspect. + * @return A a desugared try-with-resources tree, or the original + * try block if there are no resources to manage. + */ + JCTree makeTwrTry(JCTry tree) { + make_at(tree.pos()); + twrVars = twrVars.dup(); + JCBlock twrBlock = makeTwrBlock(tree.resources, tree.body, 0); + if (tree.catchers.isEmpty() && tree.finalizer == null) + result = translate(twrBlock); + else + result = translate(make.Try(twrBlock, tree.catchers, tree.finalizer)); + twrVars = twrVars.leave(); + return result; + } + + private JCBlock makeTwrBlock(List resources, JCBlock block, int depth) { + if (resources.isEmpty()) + return block; + + // Add resource declaration or expression to block statements + ListBuffer stats = new ListBuffer(); + JCTree resource = resources.head; + JCExpression expr = null; + if (resource instanceof JCVariableDecl) { + JCVariableDecl var = (JCVariableDecl) resource; + expr = make.Ident(var.sym).setType(resource.type); + stats.add(var); + } else { + Assert.check(resource instanceof JCExpression); + VarSymbol syntheticTwrVar = + new VarSymbol(SYNTHETIC | FINAL, + makeSyntheticName(names.fromString("twrVar" + + depth), twrVars), + (resource.type.tag == TypeTags.BOT) ? + syms.autoCloseableType : resource.type, + currentMethodSym); + twrVars.enter(syntheticTwrVar); + JCVariableDecl syntheticTwrVarDecl = + make.VarDef(syntheticTwrVar, (JCExpression)resource); + expr = (JCExpression)make.Ident(syntheticTwrVar); + stats.add(syntheticTwrVarDecl); + } + + // Add primaryException declaration + VarSymbol primaryException = + new VarSymbol(SYNTHETIC, + makeSyntheticName(names.fromString("primaryException" + + depth), twrVars), + syms.throwableType, + currentMethodSym); + twrVars.enter(primaryException); + JCVariableDecl primaryExceptionTreeDecl = make.VarDef(primaryException, makeNull()); + stats.add(primaryExceptionTreeDecl); + + // Create catch clause that saves exception and then rethrows it + VarSymbol param = + new VarSymbol(FINAL|SYNTHETIC, + names.fromString("t" + + target.syntheticNameChar()), + syms.throwableType, + currentMethodSym); + JCVariableDecl paramTree = make.VarDef(param, null); + JCStatement assign = make.Assignment(primaryException, make.Ident(param)); + JCStatement rethrowStat = make.Throw(make.Ident(param)); + JCBlock catchBlock = make.Block(0L, List.of(assign, rethrowStat)); + JCCatch catchClause = make.Catch(paramTree, catchBlock); + + int oldPos = make.pos; + make.at(TreeInfo.endPos(block)); + JCBlock finallyClause = makeTwrFinallyClause(primaryException, expr); + make.at(oldPos); + JCTry outerTry = make.Try(makeTwrBlock(resources.tail, block, depth + 1), + List.of(catchClause), + finallyClause); + stats.add(outerTry); + return make.Block(0L, stats.toList()); + } + + private JCBlock makeTwrFinallyClause(Symbol primaryException, JCExpression resource) { + // primaryException.addSuppressed(catchException); + VarSymbol catchException = + new VarSymbol(0, make.paramName(2), + syms.throwableType, + currentMethodSym); + JCStatement addSuppressionStatement = + make.Exec(makeCall(make.Ident(primaryException), + names.addSuppressed, + List.of(make.Ident(catchException)))); + + // try { resource.close(); } catch (e) { primaryException.addSuppressed(e); } + JCBlock tryBlock = + make.Block(0L, List.of(makeResourceCloseInvocation(resource))); + JCVariableDecl catchExceptionDecl = make.VarDef(catchException, null); + JCBlock catchBlock = make.Block(0L, List.of(addSuppressionStatement)); + List catchClauses = List.of(make.Catch(catchExceptionDecl, catchBlock)); + JCTry tryTree = make.Try(tryBlock, catchClauses, null); + + // if (primaryException != null) {try...} else resourceClose; + JCIf closeIfStatement = make.If(makeNonNullCheck(make.Ident(primaryException)), + tryTree, + makeResourceCloseInvocation(resource)); + + // if (#resource != null) { if (primaryException ... } + return make.Block(0L, + List.of(make.If(makeNonNullCheck(resource), + closeIfStatement, + null))); + } + + private JCStatement makeResourceCloseInvocation(JCExpression resource) { + // create resource.close() method invocation + JCExpression resourceClose = makeCall(resource, + names.close, + List.nil()); + return make.Exec(resourceClose); + } + + private JCExpression makeNonNullCheck(JCExpression expression) { + return makeBinary(JCTree.NE, expression, makeNull()); + } + + /** Construct a tree that represents the outer instance + * . Never pick the current `this'. + * @param pos The source code position to be used for the tree. + * @param c The qualifier class. + */ + JCExpression makeOuterThis(DiagnosticPosition pos, TypeSymbol c) { + List ots = outerThisStack; + if (ots.isEmpty()) { + log.error(pos, "no.encl.instance.of.type.in.scope", c); + Assert.error(); + return makeNull(); + } + VarSymbol ot = ots.head; + JCExpression tree = access(make.at(pos).Ident(ot)); + TypeSymbol otc = ot.type.tsym; + while (otc != c) { + do { + ots = ots.tail; + if (ots.isEmpty()) { + log.error(pos, + "no.encl.instance.of.type.in.scope", + c); + Assert.error(); // should have been caught in Attr + return tree; + } + ot = ots.head; + } while (ot.owner != otc); + if (otc.owner.kind != PCK && !otc.hasOuterInstance()) { + chk.earlyRefError(pos, c); + Assert.error(); // should have been caught in Attr + return makeNull(); + } + tree = access(make.at(pos).Select(tree, ot)); + otc = ot.type.tsym; + } + return tree; + } + + /** Construct a tree that represents the closest outer instance + * such that the given symbol is a member of C. + * @param pos The source code position to be used for the tree. + * @param sym The accessed symbol. + * @param preciseMatch should we accept a type that is a subtype of + * sym's owner, even if it doesn't contain sym + * due to hiding, overriding, or non-inheritance + * due to protection? + */ + JCExpression makeOwnerThis(DiagnosticPosition pos, Symbol sym, boolean preciseMatch) { + Symbol c = sym.owner; + if (preciseMatch ? sym.isMemberOf(currentClass, types) + : currentClass.isSubClass(sym.owner, types)) { + // in this case, `this' works fine + return make.at(pos).This(c.erasure(types)); + } else { + // need to go via this$n + return makeOwnerThisN(pos, sym, preciseMatch); + } + } + + /** + * Similar to makeOwnerThis but will never pick "this". + */ + JCExpression makeOwnerThisN(DiagnosticPosition pos, Symbol sym, boolean preciseMatch) { + Symbol c = sym.owner; + List ots = outerThisStack; + if (ots.isEmpty()) { + log.error(pos, "no.encl.instance.of.type.in.scope", c); + Assert.error(); + return makeNull(); + } + VarSymbol ot = ots.head; + JCExpression tree = access(make.at(pos).Ident(ot)); + TypeSymbol otc = ot.type.tsym; + while (!(preciseMatch ? sym.isMemberOf(otc, types) : otc.isSubClass(sym.owner, types))) { + do { + ots = ots.tail; + if (ots.isEmpty()) { + log.error(pos, + "no.encl.instance.of.type.in.scope", + c); + Assert.error(); + return tree; + } + ot = ots.head; + } while (ot.owner != otc); + tree = access(make.at(pos).Select(tree, ot)); + otc = ot.type.tsym; + } + return tree; + } + + /** Return tree simulating the assignment , where + * name is the name of a free variable. + */ + JCStatement initField(int pos, Name name) { + Scope.Entry e = proxies.lookup(name); + Symbol rhs = e.sym; + Assert.check(rhs.owner.kind == MTH); + Symbol lhs = e.next().sym; + Assert.check(rhs.owner.owner == lhs.owner); + make.at(pos); + return + make.Exec( + make.Assign( + make.Select(make.This(lhs.owner.erasure(types)), lhs), + make.Ident(rhs)).setType(lhs.erasure(types))); + } + + /** Return tree simulating the assignment . + */ + JCStatement initOuterThis(int pos) { + VarSymbol rhs = outerThisStack.head; + Assert.check(rhs.owner.kind == MTH); + VarSymbol lhs = outerThisStack.tail.head; + Assert.check(rhs.owner.owner == lhs.owner); + make.at(pos); + return + make.Exec( + make.Assign( + make.Select(make.This(lhs.owner.erasure(types)), lhs), + make.Ident(rhs)).setType(lhs.erasure(types))); + } + +/************************************************************************** + * Code for .class + *************************************************************************/ + + /** Return the symbol of a class to contain a cache of + * compiler-generated statics such as class$ and the + * $assertionsDisabled flag. We create an anonymous nested class + * (unless one already exists) and return its symbol. However, + * for backward compatibility in 1.4 and earlier we use the + * top-level class itself. + */ + private ClassSymbol outerCacheClass() { + ClassSymbol clazz = outermostClassDef.sym; + if ((clazz.flags() & INTERFACE) == 0 && + !target.useInnerCacheClass()) return clazz; + Scope s = clazz.members(); + for (Scope.Entry e = s.elems; e != null; e = e.sibling) + if (e.sym.kind == TYP && + e.sym.name == names.empty && + (e.sym.flags() & INTERFACE) == 0) return (ClassSymbol) e.sym; + return makeEmptyClass(STATIC | SYNTHETIC, clazz); + } + + /** Return symbol for "class$" method. If there is no method definition + * for class$, construct one as follows: + * + * class class$(String x0) { + * try { + * return Class.forName(x0); + * } catch (ClassNotFoundException x1) { + * throw new NoClassDefFoundError(x1.getMessage()); + * } + * } + */ + private MethodSymbol classDollarSym(DiagnosticPosition pos) { + ClassSymbol outerCacheClass = outerCacheClass(); + MethodSymbol classDollarSym = + (MethodSymbol)lookupSynthetic(classDollar, + outerCacheClass.members()); + if (classDollarSym == null) { + classDollarSym = new MethodSymbol( + STATIC | SYNTHETIC, + classDollar, + new MethodType( + List.of(syms.stringType), + types.erasure(syms.classType), + List.nil(), + syms.methodClass), + outerCacheClass); + enterSynthetic(pos, classDollarSym, outerCacheClass.members()); + + JCMethodDecl md = make.MethodDef(classDollarSym, null); + try { + md.body = classDollarSymBody(pos, md); + } catch (CompletionFailure ex) { + md.body = make.Block(0, List.nil()); + chk.completionError(pos, ex); + } + JCClassDecl outerCacheClassDef = classDef(outerCacheClass); + outerCacheClassDef.defs = outerCacheClassDef.defs.prepend(md); + } + return classDollarSym; + } + + /** Generate code for class$(String name). */ + JCBlock classDollarSymBody(DiagnosticPosition pos, JCMethodDecl md) { + MethodSymbol classDollarSym = md.sym; + ClassSymbol outerCacheClass = (ClassSymbol)classDollarSym.owner; + + JCBlock returnResult; + + // in 1.4.2 and above, we use + // Class.forName(String name, boolean init, ClassLoader loader); + // which requires we cache the current loader in cl$ + if (target.classLiteralsNoInit()) { + // clsym = "private static ClassLoader cl$" + VarSymbol clsym = new VarSymbol(STATIC|SYNTHETIC, + names.fromString("cl" + target.syntheticNameChar()), + syms.classLoaderType, + outerCacheClass); + enterSynthetic(pos, clsym, outerCacheClass.members()); + + // emit "private static ClassLoader cl$;" + JCVariableDecl cldef = make.VarDef(clsym, null); + JCClassDecl outerCacheClassDef = classDef(outerCacheClass); + outerCacheClassDef.defs = outerCacheClassDef.defs.prepend(cldef); + + // newcache := "new cache$1[0]" + JCNewArray newcache = make. + NewArray(make.Type(outerCacheClass.type), + List.of(make.Literal(INT, 0).setType(syms.intType)), + null); + newcache.type = new ArrayType(types.erasure(outerCacheClass.type), + syms.arrayClass); + + // forNameSym := java.lang.Class.forName( + // String s,boolean init,ClassLoader loader) + Symbol forNameSym = lookupMethod(make_pos, names.forName, + types.erasure(syms.classType), + List.of(syms.stringType, + syms.booleanType, + syms.classLoaderType)); + // clvalue := "(cl$ == null) ? + // $newcache.getClass().getComponentType().getClassLoader() : cl$" + JCExpression clvalue = + make.Conditional( + makeBinary(JCTree.EQ, make.Ident(clsym), makeNull()), + make.Assign( + make.Ident(clsym), + makeCall( + makeCall(makeCall(newcache, + names.getClass, + List.nil()), + names.getComponentType, + List.nil()), + names.getClassLoader, + List.nil())).setType(syms.classLoaderType), + make.Ident(clsym)).setType(syms.classLoaderType); + + // returnResult := "{ return Class.forName(param1, false, cl$); }" + List args = List.of(make.Ident(md.params.head.sym), + makeLit(syms.booleanType, 0), + clvalue); + returnResult = make. + Block(0, List.of(make. + Call(make. // return + App(make. + Ident(forNameSym), args)))); + } else { + // forNameSym := java.lang.Class.forName(String s) + Symbol forNameSym = lookupMethod(make_pos, + names.forName, + types.erasure(syms.classType), + List.of(syms.stringType)); + // returnResult := "{ return Class.forName(param1); }" + returnResult = make. + Block(0, List.of(make. + Call(make. // return + App(make. + QualIdent(forNameSym), + List.of(make. + Ident(md.params. + head.sym)))))); + } + + // catchParam := ClassNotFoundException e1 + VarSymbol catchParam = + new VarSymbol(0, make.paramName(1), + syms.classNotFoundExceptionType, + classDollarSym); + + JCStatement rethrow; + if (target.hasInitCause()) { + // rethrow = "throw new NoClassDefFoundError().initCause(e); + JCTree throwExpr = + makeCall(makeNewClass(syms.noClassDefFoundErrorType, + List.nil()), + names.initCause, + List.of(make.Ident(catchParam))); + rethrow = make.Throw(throwExpr); + } else { + // getMessageSym := ClassNotFoundException.getMessage() + Symbol getMessageSym = lookupMethod(make_pos, + names.getMessage, + syms.classNotFoundExceptionType, + List.nil()); + // rethrow = "throw new NoClassDefFoundError(e.getMessage());" + rethrow = make. + Throw(makeNewClass(syms.noClassDefFoundErrorType, + List.of(make.App(make.Select(make.Ident(catchParam), + getMessageSym), + List.nil())))); + } + + // rethrowStmt := "( $rethrow )" + JCBlock rethrowStmt = make.Block(0, List.of(rethrow)); + + // catchBlock := "catch ($catchParam) $rethrowStmt" + JCCatch catchBlock = make.Catch(make.VarDef(catchParam, null), + rethrowStmt); + + // tryCatch := "try $returnResult $catchBlock" + JCStatement tryCatch = make.Try(returnResult, + List.of(catchBlock), null); + + return make.Block(0, List.of(tryCatch)); + } + // where + /** Create an attributed tree of the form left.name(). */ + private JCMethodInvocation makeCall(JCExpression left, Name name, List args) { + Assert.checkNonNull(left.type); + Symbol funcsym = lookupMethod(make_pos, name, left.type, + TreeInfo.types(args)); + return make.App(make.Select(left, funcsym), args); + } + + /** The Name Of The variable to cache T.class values. + * @param sig The signature of type T. + */ + private Name cacheName(String sig) { + StringBuffer buf = new StringBuffer(); + if (sig.startsWith("[")) { + buf = buf.append("array"); + while (sig.startsWith("[")) { + buf = buf.append(target.syntheticNameChar()); + sig = sig.substring(1); + } + if (sig.startsWith("L")) { + sig = sig.substring(0, sig.length() - 1); + } + } else { + buf = buf.append("class" + target.syntheticNameChar()); + } + buf = buf.append(sig.replace('.', target.syntheticNameChar())); + return names.fromString(buf.toString()); + } + + /** The variable symbol that caches T.class values. + * If none exists yet, create a definition. + * @param sig The signature of type T. + * @param pos The position to report diagnostics, if any. + */ + private VarSymbol cacheSym(DiagnosticPosition pos, String sig) { + ClassSymbol outerCacheClass = outerCacheClass(); + Name cname = cacheName(sig); + VarSymbol cacheSym = + (VarSymbol)lookupSynthetic(cname, outerCacheClass.members()); + if (cacheSym == null) { + cacheSym = new VarSymbol( + STATIC | SYNTHETIC, cname, types.erasure(syms.classType), outerCacheClass); + enterSynthetic(pos, cacheSym, outerCacheClass.members()); + + JCVariableDecl cacheDef = make.VarDef(cacheSym, null); + JCClassDecl outerCacheClassDef = classDef(outerCacheClass); + outerCacheClassDef.defs = outerCacheClassDef.defs.prepend(cacheDef); + } + return cacheSym; + } + + /** The tree simulating a T.class expression. + * @param clazz The tree identifying type T. + */ + private JCExpression classOf(JCTree clazz) { + return classOfType(clazz.type, clazz.pos()); + } + + private JCExpression classOfType(Type type, DiagnosticPosition pos) { + switch (type.tag) { + case BYTE: case SHORT: case CHAR: case INT: case LONG: case FLOAT: + case DOUBLE: case BOOLEAN: case VOID: + // replace with .TYPE + ClassSymbol c = types.boxedClass(type); + Symbol typeSym = + rs.access( + rs.findIdentInType(attrEnv, c.type, names.TYPE, VAR), + pos, c.type, names.TYPE, true); + if (typeSym.kind == VAR) + ((VarSymbol)typeSym).getConstValue(); // ensure initializer is evaluated + return make.QualIdent(typeSym); + case CLASS: case ARRAY: + if (target.hasClassLiterals()) { + VarSymbol sym = new VarSymbol( + STATIC | PUBLIC | FINAL, names._class, + syms.classType, type.tsym); + return make_at(pos).Select(make.Type(type), sym); + } + // replace with + // where + // - is the type signature of T, + // - is the cache variable for tsig. + String sig = + writer.xClassName(type).toString().replace('/', '.'); + Symbol cs = cacheSym(pos, sig); + return make_at(pos).Conditional( + makeBinary(JCTree.EQ, make.Ident(cs), makeNull()), + make.Assign( + make.Ident(cs), + make.App( + make.Ident(classDollarSym(pos)), + List.of(make.Literal(CLASS, sig) + .setType(syms.stringType)))) + .setType(types.erasure(syms.classType)), + make.Ident(cs)).setType(types.erasure(syms.classType)); + default: + throw new AssertionError(); + } + } + +/************************************************************************** + * Code for enabling/disabling assertions. + *************************************************************************/ + + // This code is not particularly robust if the user has + // previously declared a member named '$assertionsDisabled'. + // The same faulty idiom also appears in the translation of + // class literals above. We should report an error if a + // previous declaration is not synthetic. + + private JCExpression assertFlagTest(DiagnosticPosition pos) { + // Outermost class may be either true class or an interface. + ClassSymbol outermostClass = outermostClassDef.sym; + + // note that this is a class, as an interface can't contain a statement. + ClassSymbol container = currentClass; + + VarSymbol assertDisabledSym = + (VarSymbol)lookupSynthetic(dollarAssertionsDisabled, + container.members()); + if (assertDisabledSym == null) { + assertDisabledSym = + new VarSymbol(STATIC | FINAL | SYNTHETIC, + dollarAssertionsDisabled, + syms.booleanType, + container); + enterSynthetic(pos, assertDisabledSym, container.members()); + Symbol desiredAssertionStatusSym = lookupMethod(pos, + names.desiredAssertionStatus, + types.erasure(syms.classType), + List.nil()); + JCClassDecl containerDef = classDef(container); + make_at(containerDef.pos()); + JCExpression notStatus = makeUnary(JCTree.NOT, make.App(make.Select( + classOfType(types.erasure(outermostClass.type), + containerDef.pos()), + desiredAssertionStatusSym))); + JCVariableDecl assertDisabledDef = make.VarDef(assertDisabledSym, + notStatus); + containerDef.defs = containerDef.defs.prepend(assertDisabledDef); + } + make_at(pos); + return makeUnary(JCTree.NOT, make.Ident(assertDisabledSym)); + } + + +/************************************************************************** + * Building blocks for let expressions + *************************************************************************/ + + interface TreeBuilder { + JCTree build(JCTree arg); + } + + /** Construct an expression using the builder, with the given rval + * expression as an argument to the builder. However, the rval + * expression must be computed only once, even if used multiple + * times in the result of the builder. We do that by + * constructing a "let" expression that saves the rvalue into a + * temporary variable and then uses the temporary variable in + * place of the expression built by the builder. The complete + * resulting expression is of the form + *
+     *    (let TYPE TEMP = RVAL;
+     *     in (BUILDER(TEMP)))
+     *  
+ * where TEMP is a newly declared variable + * in the let expression. + */ + JCTree abstractRval(JCTree rval, Type type, TreeBuilder builder) { + rval = TreeInfo.skipParens(rval); + switch (rval.getTag()) { + case JCTree.LITERAL: + return builder.build(rval); + case JCTree.IDENT: + JCIdent id = (JCIdent) rval; + if ((id.sym.flags() & FINAL) != 0 && id.sym.owner.kind == MTH) + return builder.build(rval); + } + VarSymbol var = + new VarSymbol(FINAL|SYNTHETIC, + names.fromString( + target.syntheticNameChar() + + "" + rval.hashCode()), + type, + currentMethodSym); + rval = convert(rval,type); + JCVariableDecl def = make.VarDef(var, (JCExpression)rval); // XXX cast + JCTree built = builder.build(make.Ident(var)); + JCTree res = make.LetExpr(def, built); + res.type = built.type; + return res; + } + + // same as above, with the type of the temporary variable computed + JCTree abstractRval(JCTree rval, TreeBuilder builder) { + return abstractRval(rval, rval.type, builder); + } + + // same as above, but for an expression that may be used as either + // an rvalue or an lvalue. This requires special handling for + // Select expressions, where we place the left-hand-side of the + // select in a temporary, and for Indexed expressions, where we + // place both the indexed expression and the index value in temps. + JCTree abstractLval(JCTree lval, final TreeBuilder builder) { + lval = TreeInfo.skipParens(lval); + switch (lval.getTag()) { + case JCTree.IDENT: + return builder.build(lval); + case JCTree.SELECT: { + final JCFieldAccess s = (JCFieldAccess)lval; + JCTree selected = TreeInfo.skipParens(s.selected); + Symbol lid = TreeInfo.symbol(s.selected); + if (lid != null && lid.kind == TYP) return builder.build(lval); + return abstractRval(s.selected, new TreeBuilder() { + public JCTree build(final JCTree selected) { + return builder.build(make.Select((JCExpression)selected, s.sym)); + } + }); + } + case JCTree.INDEXED: { + final JCArrayAccess i = (JCArrayAccess)lval; + return abstractRval(i.indexed, new TreeBuilder() { + public JCTree build(final JCTree indexed) { + return abstractRval(i.index, syms.intType, new TreeBuilder() { + public JCTree build(final JCTree index) { + JCTree newLval = make.Indexed((JCExpression)indexed, + (JCExpression)index); + newLval.setType(i.type); + return builder.build(newLval); + } + }); + } + }); + } + case JCTree.TYPECAST: { + return abstractLval(((JCTypeCast)lval).expr, builder); + } + } + throw new AssertionError(lval); + } + + // evaluate and discard the first expression, then evaluate the second. + JCTree makeComma(final JCTree expr1, final JCTree expr2) { + return abstractRval(expr1, new TreeBuilder() { + public JCTree build(final JCTree discarded) { + return expr2; + } + }); + } + +/************************************************************************** + * Translation methods + *************************************************************************/ + + /** Visitor argument: enclosing operator node. + */ + private JCExpression enclOp; + + /** Visitor method: Translate a single node. + * Attach the source position from the old tree to its replacement tree. + */ + public T translate(T tree) { + if (tree == null) { + return null; + } else { + make_at(tree.pos()); + T result = super.translate(tree); + if (endPositions != null && result != tree) { + Integer endPos = endPositions.remove(tree); + if (endPos != null) endPositions.put(result, endPos); + } + return result; + } + } + + /** Visitor method: Translate a single node, boxing or unboxing if needed. + */ + public T translate(T tree, Type type) { + return (tree == null) ? null : boxIfNeeded(translate(tree), type); + } + + /** Visitor method: Translate tree. + */ + public T translate(T tree, JCExpression enclOp) { + JCExpression prevEnclOp = this.enclOp; + this.enclOp = enclOp; + T res = translate(tree); + this.enclOp = prevEnclOp; + return res; + } + + /** Visitor method: Translate list of trees. + */ + public List translate(List trees, JCExpression enclOp) { + JCExpression prevEnclOp = this.enclOp; + this.enclOp = enclOp; + List res = translate(trees); + this.enclOp = prevEnclOp; + return res; + } + + /** Visitor method: Translate list of trees. + */ + public List translate(List trees, Type type) { + if (trees == null) return null; + for (List l = trees; l.nonEmpty(); l = l.tail) + l.head = translate(l.head, type); + return trees; + } + + public void visitTopLevel(JCCompilationUnit tree) { + if (needPackageInfoClass(tree)) { + Name name = names.package_info; + long flags = Flags.ABSTRACT | Flags.INTERFACE; + if (target.isPackageInfoSynthetic()) + // package-info is marked SYNTHETIC in JDK 1.6 and later releases + flags = flags | Flags.SYNTHETIC; + JCClassDecl packageAnnotationsClass + = make.ClassDef(make.Modifiers(flags, + tree.packageAnnotations), + name, List.nil(), + null, List.nil(), List.nil()); + ClassSymbol c = tree.packge.package_info; + c.flags_field |= flags; + c.attributes_field = tree.packge.attributes_field; + ClassType ctype = (ClassType) c.type; + ctype.supertype_field = syms.objectType; + ctype.interfaces_field = List.nil(); + packageAnnotationsClass.sym = c; + + translated.append(packageAnnotationsClass); + } + } + // where + private boolean needPackageInfoClass(JCCompilationUnit tree) { + switch (pkginfoOpt) { + case ALWAYS: + return true; + case LEGACY: + return tree.packageAnnotations.nonEmpty(); + case NONEMPTY: + for (Attribute.Compound a: tree.packge.attributes_field) { + Attribute.RetentionPolicy p = types.getRetention(a); + if (p != Attribute.RetentionPolicy.SOURCE) + return true; + } + return false; + } + throw new AssertionError(); + } + + public void visitClassDef(JCClassDecl tree) { + ClassSymbol currentClassPrev = currentClass; + MethodSymbol currentMethodSymPrev = currentMethodSym; + currentClass = tree.sym; + currentMethodSym = null; + classdefs.put(currentClass, tree); + + proxies = proxies.dup(currentClass); + List prevOuterThisStack = outerThisStack; + + // If this is an enum definition + if ((tree.mods.flags & ENUM) != 0 && + (types.supertype(currentClass.type).tsym.flags() & ENUM) == 0) + visitEnumDef(tree); + + // If this is a nested class, define a this$n field for + // it and add to proxies. + JCVariableDecl otdef = null; + if (currentClass.hasOuterInstance()) + otdef = outerThisDef(tree.pos, currentClass); + + // If this is a local class, define proxies for all its free variables. + List fvdefs = freevarDefs( + tree.pos, freevars(currentClass), currentClass); + + // Recursively translate superclass, interfaces. + tree.extending = translate(tree.extending); + tree.implementing = translate(tree.implementing); + + // Recursively translate members, taking into account that new members + // might be created during the translation and prepended to the member + // list `tree.defs'. + List seen = List.nil(); + while (tree.defs != seen) { + List unseen = tree.defs; + for (List l = unseen; l.nonEmpty() && l != seen; l = l.tail) { + JCTree outermostMemberDefPrev = outermostMemberDef; + if (outermostMemberDefPrev == null) outermostMemberDef = l.head; + l.head = translate(l.head); + outermostMemberDef = outermostMemberDefPrev; + } + seen = unseen; + } + + // Convert a protected modifier to public, mask static modifier. + if ((tree.mods.flags & PROTECTED) != 0) tree.mods.flags |= PUBLIC; + tree.mods.flags &= ClassFlags; + + // Convert name to flat representation, replacing '.' by '$'. + tree.name = Convert.shortName(currentClass.flatName()); + + // Add this$n and free variables proxy definitions to class. + for (List l = fvdefs; l.nonEmpty(); l = l.tail) { + tree.defs = tree.defs.prepend(l.head); + enterSynthetic(tree.pos(), l.head.sym, currentClass.members()); + } + if (currentClass.hasOuterInstance()) { + tree.defs = tree.defs.prepend(otdef); + enterSynthetic(tree.pos(), otdef.sym, currentClass.members()); + } + + proxies = proxies.leave(); + outerThisStack = prevOuterThisStack; + + // Append translated tree to `translated' queue. + translated.append(tree); + + currentClass = currentClassPrev; + currentMethodSym = currentMethodSymPrev; + + // Return empty block {} as a placeholder for an inner class. + result = make_at(tree.pos()).Block(0, List.nil()); + } + + /** Translate an enum class. */ + private void visitEnumDef(JCClassDecl tree) { + make_at(tree.pos()); + + // add the supertype, if needed + if (tree.extending == null) + tree.extending = make.Type(types.supertype(tree.type)); + + // classOfType adds a cache field to tree.defs unless + // target.hasClassLiterals(). + JCExpression e_class = classOfType(tree.sym.type, tree.pos()). + setType(types.erasure(syms.classType)); + + // process each enumeration constant, adding implicit constructor parameters + int nextOrdinal = 0; + ListBuffer values = new ListBuffer(); + ListBuffer enumDefs = new ListBuffer(); + ListBuffer otherDefs = new ListBuffer(); + for (List defs = tree.defs; + defs.nonEmpty(); + defs=defs.tail) { + if (defs.head.getTag() == JCTree.VARDEF && (((JCVariableDecl) defs.head).mods.flags & ENUM) != 0) { + JCVariableDecl var = (JCVariableDecl)defs.head; + visitEnumConstantDef(var, nextOrdinal++); + values.append(make.QualIdent(var.sym)); + enumDefs.append(var); + } else { + otherDefs.append(defs.head); + } + } + + // private static final T[] #VALUES = { a, b, c }; + Name valuesName = names.fromString(target.syntheticNameChar() + "VALUES"); + while (tree.sym.members().lookup(valuesName).scope != null) // avoid name clash + valuesName = names.fromString(valuesName + "" + target.syntheticNameChar()); + Type arrayType = new ArrayType(types.erasure(tree.type), syms.arrayClass); + VarSymbol valuesVar = new VarSymbol(PRIVATE|FINAL|STATIC|SYNTHETIC, + valuesName, + arrayType, + tree.type.tsym); + JCNewArray newArray = make.NewArray(make.Type(types.erasure(tree.type)), + List.nil(), + values.toList()); + newArray.type = arrayType; + enumDefs.append(make.VarDef(valuesVar, newArray)); + tree.sym.members().enter(valuesVar); + + Symbol valuesSym = lookupMethod(tree.pos(), names.values, + tree.type, List.nil()); + List valuesBody; + if (useClone()) { + // return (T[]) $VALUES.clone(); + JCTypeCast valuesResult = + make.TypeCast(valuesSym.type.getReturnType(), + make.App(make.Select(make.Ident(valuesVar), + syms.arrayCloneMethod))); + valuesBody = List.of(make.Return(valuesResult)); + } else { + // template: T[] $result = new T[$values.length]; + Name resultName = names.fromString(target.syntheticNameChar() + "result"); + while (tree.sym.members().lookup(resultName).scope != null) // avoid name clash + resultName = names.fromString(resultName + "" + target.syntheticNameChar()); + VarSymbol resultVar = new VarSymbol(FINAL|SYNTHETIC, + resultName, + arrayType, + valuesSym); + JCNewArray resultArray = make.NewArray(make.Type(types.erasure(tree.type)), + List.of(make.Select(make.Ident(valuesVar), syms.lengthVar)), + null); + resultArray.type = arrayType; + JCVariableDecl decl = make.VarDef(resultVar, resultArray); + + // template: System.arraycopy($VALUES, 0, $result, 0, $VALUES.length); + if (systemArraycopyMethod == null) { + systemArraycopyMethod = + new MethodSymbol(PUBLIC | STATIC, + names.fromString("arraycopy"), + new MethodType(List.of(syms.objectType, + syms.intType, + syms.objectType, + syms.intType, + syms.intType), + syms.voidType, + List.nil(), + syms.methodClass), + syms.systemType.tsym); + } + JCStatement copy = + make.Exec(make.App(make.Select(make.Ident(syms.systemType.tsym), + systemArraycopyMethod), + List.of(make.Ident(valuesVar), make.Literal(0), + make.Ident(resultVar), make.Literal(0), + make.Select(make.Ident(valuesVar), syms.lengthVar)))); + + // template: return $result; + JCStatement ret = make.Return(make.Ident(resultVar)); + valuesBody = List.of(decl, copy, ret); + } + + JCMethodDecl valuesDef = + make.MethodDef((MethodSymbol)valuesSym, make.Block(0, valuesBody)); + + enumDefs.append(valuesDef); + + if (debugLower) + System.err.println(tree.sym + ".valuesDef = " + valuesDef); + + /** The template for the following code is: + * + * public static E valueOf(String name) { + * return (E)Enum.valueOf(E.class, name); + * } + * + * where E is tree.sym + */ + MethodSymbol valueOfSym = lookupMethod(tree.pos(), + names.valueOf, + tree.sym.type, + List.of(syms.stringType)); + Assert.check((valueOfSym.flags() & STATIC) != 0); + VarSymbol nameArgSym = valueOfSym.params.head; + JCIdent nameVal = make.Ident(nameArgSym); + JCStatement enum_ValueOf = + make.Return(make.TypeCast(tree.sym.type, + makeCall(make.Ident(syms.enumSym), + names.valueOf, + List.of(e_class, nameVal)))); + JCMethodDecl valueOf = make.MethodDef(valueOfSym, + make.Block(0, List.of(enum_ValueOf))); + nameVal.sym = valueOf.params.head.sym; + if (debugLower) + System.err.println(tree.sym + ".valueOf = " + valueOf); + enumDefs.append(valueOf); + + enumDefs.appendList(otherDefs.toList()); + tree.defs = enumDefs.toList(); + + // Add the necessary members for the EnumCompatibleMode + if (target.compilerBootstrap(tree.sym)) { + addEnumCompatibleMembers(tree); + } + } + // where + private MethodSymbol systemArraycopyMethod; + private boolean useClone() { + try { + Scope.Entry e = syms.objectType.tsym.members().lookup(names.clone); + return (e.sym != null); + } + catch (CompletionFailure e) { + return false; + } + } + + /** Translate an enumeration constant and its initializer. */ + private void visitEnumConstantDef(JCVariableDecl var, int ordinal) { + JCNewClass varDef = (JCNewClass)var.init; + varDef.args = varDef.args. + prepend(makeLit(syms.intType, ordinal)). + prepend(makeLit(syms.stringType, var.name.toString())); + } + + public void visitMethodDef(JCMethodDecl tree) { + if (tree.name == names.init && (currentClass.flags_field&ENUM) != 0) { + // Add "String $enum$name, int $enum$ordinal" to the beginning of the + // argument list for each constructor of an enum. + JCVariableDecl nameParam = make_at(tree.pos()). + Param(names.fromString(target.syntheticNameChar() + + "enum" + target.syntheticNameChar() + "name"), + syms.stringType, tree.sym); + nameParam.mods.flags |= SYNTHETIC; nameParam.sym.flags_field |= SYNTHETIC; + + JCVariableDecl ordParam = make. + Param(names.fromString(target.syntheticNameChar() + + "enum" + target.syntheticNameChar() + + "ordinal"), + syms.intType, tree.sym); + ordParam.mods.flags |= SYNTHETIC; ordParam.sym.flags_field |= SYNTHETIC; + + tree.params = tree.params.prepend(ordParam).prepend(nameParam); + + MethodSymbol m = tree.sym; + Type olderasure = m.erasure(types); + m.erasure_field = new MethodType( + olderasure.getParameterTypes().prepend(syms.intType).prepend(syms.stringType), + olderasure.getReturnType(), + olderasure.getThrownTypes(), + syms.methodClass); + + if (target.compilerBootstrap(m.owner)) { + // Initialize synthetic name field + Symbol nameVarSym = lookupSynthetic(names.fromString("$name"), + tree.sym.owner.members()); + JCIdent nameIdent = make.Ident(nameParam.sym); + JCIdent id1 = make.Ident(nameVarSym); + JCAssign newAssign = make.Assign(id1, nameIdent); + newAssign.type = id1.type; + JCExpressionStatement nameAssign = make.Exec(newAssign); + nameAssign.type = id1.type; + tree.body.stats = tree.body.stats.prepend(nameAssign); + + // Initialize synthetic ordinal field + Symbol ordinalVarSym = lookupSynthetic(names.fromString("$ordinal"), + tree.sym.owner.members()); + JCIdent ordIdent = make.Ident(ordParam.sym); + id1 = make.Ident(ordinalVarSym); + newAssign = make.Assign(id1, ordIdent); + newAssign.type = id1.type; + JCExpressionStatement ordinalAssign = make.Exec(newAssign); + ordinalAssign.type = id1.type; + tree.body.stats = tree.body.stats.prepend(ordinalAssign); + } + } + + JCMethodDecl prevMethodDef = currentMethodDef; + MethodSymbol prevMethodSym = currentMethodSym; + try { + currentMethodDef = tree; + currentMethodSym = tree.sym; + visitMethodDefInternal(tree); + } finally { + currentMethodDef = prevMethodDef; + currentMethodSym = prevMethodSym; + } + } + //where + private void visitMethodDefInternal(JCMethodDecl tree) { + if (tree.name == names.init && + (currentClass.isInner() || + (currentClass.owner.kind & (VAR | MTH)) != 0)) { + // We are seeing a constructor of an inner class. + MethodSymbol m = tree.sym; + + // Push a new proxy scope for constructor parameters. + // and create definitions for any this$n and proxy parameters. + proxies = proxies.dup(m); + List prevOuterThisStack = outerThisStack; + List fvs = freevars(currentClass); + JCVariableDecl otdef = null; + if (currentClass.hasOuterInstance()) + otdef = outerThisDef(tree.pos, m); + List fvdefs = freevarDefs(tree.pos, fvs, m); + + // Recursively translate result type, parameters and thrown list. + tree.restype = translate(tree.restype); + tree.params = translateVarDefs(tree.params); + tree.thrown = translate(tree.thrown); + + // when compiling stubs, don't process body + if (tree.body == null) { + result = tree; + return; + } + + // Add this$n (if needed) in front of and free variables behind + // constructor parameter list. + tree.params = tree.params.appendList(fvdefs); + if (currentClass.hasOuterInstance()) + tree.params = tree.params.prepend(otdef); + + // If this is an initial constructor, i.e., it does not start with + // this(...), insert initializers for this$n and proxies + // before (pre-1.4, after) the call to superclass constructor. + JCStatement selfCall = translate(tree.body.stats.head); + + List added = List.nil(); + if (fvs.nonEmpty()) { + List addedargtypes = List.nil(); + for (List l = fvs; l.nonEmpty(); l = l.tail) { + if (TreeInfo.isInitialConstructor(tree)) + added = added.prepend( + initField(tree.body.pos, proxyName(l.head.name))); + addedargtypes = addedargtypes.prepend(l.head.erasure(types)); + } + Type olderasure = m.erasure(types); + m.erasure_field = new MethodType( + olderasure.getParameterTypes().appendList(addedargtypes), + olderasure.getReturnType(), + olderasure.getThrownTypes(), + syms.methodClass); + } + if (currentClass.hasOuterInstance() && + TreeInfo.isInitialConstructor(tree)) + { + added = added.prepend(initOuterThis(tree.body.pos)); + } + + // pop local variables from proxy stack + proxies = proxies.leave(); + + // recursively translate following local statements and + // combine with this- or super-call + List stats = translate(tree.body.stats.tail); + if (target.initializeFieldsBeforeSuper()) + tree.body.stats = stats.prepend(selfCall).prependList(added); + else + tree.body.stats = stats.prependList(added).prepend(selfCall); + + outerThisStack = prevOuterThisStack; + } else { + super.visitMethodDef(tree); + } + result = tree; + } + + public void visitTypeCast(JCTypeCast tree) { + tree.clazz = translate(tree.clazz); + if (tree.type.isPrimitive() != tree.expr.type.isPrimitive()) + tree.expr = translate(tree.expr, tree.type); + else + tree.expr = translate(tree.expr); + result = tree; + } + + public void visitNewClass(JCNewClass tree) { + ClassSymbol c = (ClassSymbol)tree.constructor.owner; + + // Box arguments, if necessary + boolean isEnum = (tree.constructor.owner.flags() & ENUM) != 0; + List argTypes = tree.constructor.type.getParameterTypes(); + if (isEnum) argTypes = argTypes.prepend(syms.intType).prepend(syms.stringType); + tree.args = boxArgs(argTypes, tree.args, tree.varargsElement); + tree.varargsElement = null; + + // If created class is local, add free variables after + // explicit constructor arguments. + if ((c.owner.kind & (VAR | MTH)) != 0) { + tree.args = tree.args.appendList(loadFreevars(tree.pos(), freevars(c))); + } + + // If an access constructor is used, append null as a last argument. + Symbol constructor = accessConstructor(tree.pos(), tree.constructor); + if (constructor != tree.constructor) { + tree.args = tree.args.append(makeNull()); + tree.constructor = constructor; + } + + // If created class has an outer instance, and new is qualified, pass + // qualifier as first argument. If new is not qualified, pass the + // correct outer instance as first argument. + if (c.hasOuterInstance()) { + JCExpression thisArg; + if (tree.encl != null) { + thisArg = attr.makeNullCheck(translate(tree.encl)); + thisArg.type = tree.encl.type; + } else if ((c.owner.kind & (MTH | VAR)) != 0) { + // local class + thisArg = makeThis(tree.pos(), c.type.getEnclosingType().tsym); + } else { + // nested class + thisArg = makeOwnerThis(tree.pos(), c, false); + } + tree.args = tree.args.prepend(thisArg); + } + tree.encl = null; + + // If we have an anonymous class, create its flat version, rather + // than the class or interface following new. + if (tree.def != null) { + translate(tree.def); + tree.clazz = access(make_at(tree.clazz.pos()).Ident(tree.def.sym)); + tree.def = null; + } else { + tree.clazz = access(c, tree.clazz, enclOp, false); + } + result = tree; + } + + // Simplify conditionals with known constant controlling expressions. + // This allows us to avoid generating supporting declarations for + // the dead code, which will not be eliminated during code generation. + // Note that Flow.isFalse and Flow.isTrue only return true + // for constant expressions in the sense of JLS 15.27, which + // are guaranteed to have no side-effects. More aggressive + // constant propagation would require that we take care to + // preserve possible side-effects in the condition expression. + + /** Visitor method for conditional expressions. + */ + public void visitConditional(JCConditional tree) { + JCTree cond = tree.cond = translate(tree.cond, syms.booleanType); + if (cond.type.isTrue()) { + result = convert(translate(tree.truepart, tree.type), tree.type); + } else if (cond.type.isFalse()) { + result = convert(translate(tree.falsepart, tree.type), tree.type); + } else { + // Condition is not a compile-time constant. + tree.truepart = translate(tree.truepart, tree.type); + tree.falsepart = translate(tree.falsepart, tree.type); + result = tree; + } + } +//where + private JCTree convert(JCTree tree, Type pt) { + if (tree.type == pt || tree.type.tag == TypeTags.BOT) + return tree; + JCTree result = make_at(tree.pos()).TypeCast(make.Type(pt), (JCExpression)tree); + result.type = (tree.type.constValue() != null) ? cfolder.coerce(tree.type, pt) + : pt; + return result; + } + + /** Visitor method for if statements. + */ + public void visitIf(JCIf tree) { + JCTree cond = tree.cond = translate(tree.cond, syms.booleanType); + if (cond.type.isTrue()) { + result = translate(tree.thenpart); + } else if (cond.type.isFalse()) { + if (tree.elsepart != null) { + result = translate(tree.elsepart); + } else { + result = make.Skip(); + } + } else { + // Condition is not a compile-time constant. + tree.thenpart = translate(tree.thenpart); + tree.elsepart = translate(tree.elsepart); + result = tree; + } + } + + /** Visitor method for assert statements. Translate them away. + */ + public void visitAssert(JCAssert tree) { + DiagnosticPosition detailPos = (tree.detail == null) ? tree.pos() : tree.detail.pos(); + tree.cond = translate(tree.cond, syms.booleanType); + if (!tree.cond.type.isTrue()) { + JCExpression cond = assertFlagTest(tree.pos()); + List exnArgs = (tree.detail == null) ? + List.nil() : List.of(translate(tree.detail)); + if (!tree.cond.type.isFalse()) { + cond = makeBinary + (JCTree.AND, + cond, + makeUnary(JCTree.NOT, tree.cond)); + } + result = + make.If(cond, + make_at(detailPos). + Throw(makeNewClass(syms.assertionErrorType, exnArgs)), + null); + } else { + result = make.Skip(); + } + } + + public void visitApply(JCMethodInvocation tree) { + Symbol meth = TreeInfo.symbol(tree.meth); + List argtypes = meth.type.getParameterTypes(); + if (allowEnums && + meth.name==names.init && + meth.owner == syms.enumSym) + argtypes = argtypes.tail.tail; + tree.args = boxArgs(argtypes, tree.args, tree.varargsElement); + tree.varargsElement = null; + Name methName = TreeInfo.name(tree.meth); + if (meth.name==names.init) { + // We are seeing a this(...) or super(...) constructor call. + // If an access constructor is used, append null as a last argument. + Symbol constructor = accessConstructor(tree.pos(), meth); + if (constructor != meth) { + tree.args = tree.args.append(makeNull()); + TreeInfo.setSymbol(tree.meth, constructor); + } + + // If we are calling a constructor of a local class, add + // free variables after explicit constructor arguments. + ClassSymbol c = (ClassSymbol)constructor.owner; + if ((c.owner.kind & (VAR | MTH)) != 0) { + tree.args = tree.args.appendList(loadFreevars(tree.pos(), freevars(c))); + } + + // If we are calling a constructor of an enum class, pass + // along the name and ordinal arguments + if ((c.flags_field&ENUM) != 0 || c.getQualifiedName() == names.java_lang_Enum) { + List params = currentMethodDef.params; + if (currentMethodSym.owner.hasOuterInstance()) + params = params.tail; // drop this$n + tree.args = tree.args + .prepend(make_at(tree.pos()).Ident(params.tail.head.sym)) // ordinal + .prepend(make.Ident(params.head.sym)); // name + } + + // If we are calling a constructor of a class with an outer + // instance, and the call + // is qualified, pass qualifier as first argument in front of + // the explicit constructor arguments. If the call + // is not qualified, pass the correct outer instance as + // first argument. + if (c.hasOuterInstance()) { + JCExpression thisArg; + if (tree.meth.getTag() == JCTree.SELECT) { + thisArg = attr. + makeNullCheck(translate(((JCFieldAccess) tree.meth).selected)); + tree.meth = make.Ident(constructor); + ((JCIdent) tree.meth).name = methName; + } else if ((c.owner.kind & (MTH | VAR)) != 0 || methName == names._this){ + // local class or this() call + thisArg = makeThis(tree.meth.pos(), c.type.getEnclosingType().tsym); + } else { + // super() call of nested class - never pick 'this' + thisArg = makeOwnerThisN(tree.meth.pos(), c, false); + } + tree.args = tree.args.prepend(thisArg); + } + } else { + // We are seeing a normal method invocation; translate this as usual. + tree.meth = translate(tree.meth); + + // If the translated method itself is an Apply tree, we are + // seeing an access method invocation. In this case, append + // the method arguments to the arguments of the access method. + if (tree.meth.getTag() == JCTree.APPLY) { + JCMethodInvocation app = (JCMethodInvocation)tree.meth; + app.args = tree.args.prependList(app.args); + result = app; + return; + } + + //M + //---------------------------------------- + if(meth.owner != null && + types.isSubtype(meth.owner.type, syms.viewManagerType)) { + if(tree.meth.getTag() == JCTree.SELECT && meth.name.toString().equals("out")) { + JCExpression selected = ((JCFieldAccess)tree.meth).selected; + + if(selected.getTag() == JCTree.IDENT) { + Symbol contextSymbol = ((JCIdent)selected).sym; + + ListBuffer stmtList = new ListBuffer(); + + make.at(tree.pos()); + + ListBuffer varList = attr.outVarList.poll(); + for (List l = varList.toList(); l.nonEmpty(); l = l.tail) { + VarSymbol var = l.head; + JCExpression value = make.Ident(var); + + if(value.type.isPrimitive()) { + value = (JCExpression)boxIfNeeded(value, types.boxedClass(value.type).type); + } + + JCMethodInvocation putCall = makeCall(make.Ident(contextSymbol), + names.fromString("put"),List.of(make.Literal(var.name.toString()),value)).setType(syms.voidType); + + stmtList.append(make.Exec(putCall)); + } + stmtList.append(make.Exec(tree)); + JCBlock newJCBlock = make.Block(0L, stmtList.toList()); + JCMethodInvocation newJCMethodInvocation = tree; + + newJCBlockList.add(newJCBlock); + newJCMethodInvocationList.add(newJCMethodInvocation); + } + } + } + } + result = tree; + } + + java.util.LinkedList newJCBlockList = + new java.util.LinkedList(); //M + + java.util.LinkedList newJCMethodInvocationList = + new java.util.LinkedList(); //M + + List boxArgs(List parameters, List _args, Type varargsElement) { + List args = _args; + if (parameters.isEmpty()) return args; + boolean anyChanges = false; + ListBuffer result = new ListBuffer(); + while (parameters.tail.nonEmpty()) { + JCExpression arg = translate(args.head, parameters.head); + anyChanges |= (arg != args.head); + result.append(arg); + args = args.tail; + parameters = parameters.tail; + } + Type parameter = parameters.head; + if (varargsElement != null) { + anyChanges = true; + ListBuffer elems = new ListBuffer(); + while (args.nonEmpty()) { + JCExpression arg = translate(args.head, varargsElement); + elems.append(arg); + args = args.tail; + } + JCNewArray boxedArgs = make.NewArray(make.Type(varargsElement), + List.nil(), + elems.toList()); + boxedArgs.type = new ArrayType(varargsElement, syms.arrayClass); + result.append(boxedArgs); + } else { + if (args.length() != 1) throw new AssertionError(args); + JCExpression arg = translate(args.head, parameter); + anyChanges |= (arg != args.head); + result.append(arg); + if (!anyChanges) return _args; + } + return result.toList(); + } + + /** Expand a boxing or unboxing conversion if needed. */ + @SuppressWarnings("unchecked") // XXX unchecked + T boxIfNeeded(T tree, Type type) { + boolean havePrimitive = tree.type.isPrimitive(); + if (havePrimitive == type.isPrimitive()) + return tree; + if (havePrimitive) { + Type unboxedTarget = types.unboxedType(type); + if (unboxedTarget.tag != NONE) { + if (!types.isSubtype(tree.type, unboxedTarget)) //e.g. Character c = 89; + tree.type = unboxedTarget.constType(tree.type.constValue()); + return (T)boxPrimitive((JCExpression)tree, type); + } else { + tree = (T)boxPrimitive((JCExpression)tree); + } + } else { + tree = (T)unbox((JCExpression)tree, type); + } + return tree; + } + + /** Box up a single primitive expression. */ + JCExpression boxPrimitive(JCExpression tree) { + return boxPrimitive(tree, types.boxedClass(tree.type).type); + } + + /** Box up a single primitive expression. */ + JCExpression boxPrimitive(JCExpression tree, Type box) { + make_at(tree.pos()); + if (target.boxWithConstructors()) { + Symbol ctor = lookupConstructor(tree.pos(), + box, + List.nil() + .prepend(tree.type)); + return make.Create(ctor, List.of(tree)); + } else { + Symbol valueOfSym = lookupMethod(tree.pos(), + names.valueOf, + box, + List.nil() + .prepend(tree.type)); + return make.App(make.QualIdent(valueOfSym), List.of(tree)); + } + } + + /** Unbox an object to a primitive value. */ + JCExpression unbox(JCExpression tree, Type primitive) { + Type unboxedType = types.unboxedType(tree.type); + if (unboxedType.tag == NONE) { + unboxedType = primitive; + if (!unboxedType.isPrimitive()) + throw new AssertionError(unboxedType); + make_at(tree.pos()); + tree = make.TypeCast(types.boxedClass(unboxedType).type, tree); + } else { + // There must be a conversion from unboxedType to primitive. + if (!types.isSubtype(unboxedType, primitive)) + throw new AssertionError(tree); + } + make_at(tree.pos()); + Symbol valueSym = lookupMethod(tree.pos(), + unboxedType.tsym.name.append(names.Value), // x.intValue() + tree.type, + List.nil()); + return make.App(make.Select(tree, valueSym)); + } + + /** Visitor method for parenthesized expressions. + * If the subexpression has changed, omit the parens. + */ + public void visitParens(JCParens tree) { + JCTree expr = translate(tree.expr); + result = ((expr == tree.expr) ? tree : expr); + } + + public void visitIndexed(JCArrayAccess tree) { + tree.indexed = translate(tree.indexed); + tree.index = translate(tree.index, syms.intType); + result = tree; + } + + public void visitAssign(JCAssign tree) { + tree.lhs = translate(tree.lhs, tree); + tree.rhs = translate(tree.rhs, tree.lhs.type); + + // If translated left hand side is an Apply, we are + // seeing an access method invocation. In this case, append + // right hand side as last argument of the access method. + if (tree.lhs.getTag() == JCTree.APPLY) { + JCMethodInvocation app = (JCMethodInvocation)tree.lhs; + app.args = List.of(tree.rhs).prependList(app.args); + result = app; + } else { + result = tree; + } + } + + public void visitAssignop(final JCAssignOp tree) { + if (!tree.lhs.type.isPrimitive() && + tree.operator.type.getReturnType().isPrimitive()) { + // boxing required; need to rewrite as x = (unbox typeof x)(x op y); + // or if x == (typeof x)z then z = (unbox typeof x)((typeof x)z op y) + // (but without recomputing x) + JCTree newTree = abstractLval(tree.lhs, new TreeBuilder() { + public JCTree build(final JCTree lhs) { + int newTag = tree.getTag() - JCTree.ASGOffset; + // Erasure (TransTypes) can change the type of + // tree.lhs. However, we can still get the + // unerased type of tree.lhs as it is stored + // in tree.type in Attr. + Symbol newOperator = rs.resolveBinaryOperator(tree.pos(), + newTag, + attrEnv, + tree.type, + tree.rhs.type); + JCExpression expr = (JCExpression)lhs; + if (expr.type != tree.type) + expr = make.TypeCast(tree.type, expr); + JCBinary opResult = make.Binary(newTag, expr, tree.rhs); + opResult.operator = newOperator; + opResult.type = newOperator.type.getReturnType(); + JCTypeCast newRhs = make.TypeCast(types.unboxedType(tree.type), + opResult); + return make.Assign((JCExpression)lhs, newRhs).setType(tree.type); + } + }); + result = translate(newTree); + return; + } + tree.lhs = translate(tree.lhs, tree); + tree.rhs = translate(tree.rhs, tree.operator.type.getParameterTypes().tail.head); + + // If translated left hand side is an Apply, we are + // seeing an access method invocation. In this case, append + // right hand side as last argument of the access method. + if (tree.lhs.getTag() == JCTree.APPLY) { + JCMethodInvocation app = (JCMethodInvocation)tree.lhs; + // if operation is a += on strings, + // make sure to convert argument to string + JCExpression rhs = (((OperatorSymbol)tree.operator).opcode == string_add) + ? makeString(tree.rhs) + : tree.rhs; + app.args = List.of(rhs).prependList(app.args); + result = app; + } else { + result = tree; + } + } + + /** Lower a tree of the form e++ or e-- where e is an object type */ + JCTree lowerBoxedPostop(final JCUnary tree) { + // translate to tmp1=lval(e); tmp2=tmp1; tmp1 OP 1; tmp2 + // or + // translate to tmp1=lval(e); tmp2=tmp1; (typeof tree)tmp1 OP 1; tmp2 + // where OP is += or -= + final boolean cast = TreeInfo.skipParens(tree.arg).getTag() == JCTree.TYPECAST; + return abstractLval(tree.arg, new TreeBuilder() { + public JCTree build(final JCTree tmp1) { + return abstractRval(tmp1, tree.arg.type, new TreeBuilder() { + public JCTree build(final JCTree tmp2) { + int opcode = (tree.getTag() == JCTree.POSTINC) + ? JCTree.PLUS_ASG : JCTree.MINUS_ASG; + JCTree lhs = cast + ? make.TypeCast(tree.arg.type, (JCExpression)tmp1) + : tmp1; + JCTree update = makeAssignop(opcode, + lhs, + make.Literal(1)); + return makeComma(update, tmp2); + } + }); + } + }); + } + + public void visitUnary(JCUnary tree) { + boolean isUpdateOperator = + JCTree.PREINC <= tree.getTag() && tree.getTag() <= JCTree.POSTDEC; + if (isUpdateOperator && !tree.arg.type.isPrimitive()) { + switch(tree.getTag()) { + case JCTree.PREINC: // ++ e + // translate to e += 1 + case JCTree.PREDEC: // -- e + // translate to e -= 1 + { + int opcode = (tree.getTag() == JCTree.PREINC) + ? JCTree.PLUS_ASG : JCTree.MINUS_ASG; + JCAssignOp newTree = makeAssignop(opcode, + tree.arg, + make.Literal(1)); + result = translate(newTree, tree.type); + return; + } + case JCTree.POSTINC: // e ++ + case JCTree.POSTDEC: // e -- + { + result = translate(lowerBoxedPostop(tree), tree.type); + return; + } + } + throw new AssertionError(tree); + } + + tree.arg = boxIfNeeded(translate(tree.arg, tree), tree.type); + + if (tree.getTag() == JCTree.NOT && tree.arg.type.constValue() != null) { + tree.type = cfolder.fold1(bool_not, tree.arg.type); + } + + // If translated left hand side is an Apply, we are + // seeing an access method invocation. In this case, return + // that access method invocation as result. + if (isUpdateOperator && tree.arg.getTag() == JCTree.APPLY) { + result = tree.arg; + } else { + result = tree; + } + } + + public void visitBinary(JCBinary tree) { + List formals = tree.operator.type.getParameterTypes(); + JCTree lhs = tree.lhs = translate(tree.lhs, formals.head); + switch (tree.getTag()) { + case JCTree.OR: + if (lhs.type.isTrue()) { + result = lhs; + return; + } + if (lhs.type.isFalse()) { + result = translate(tree.rhs, formals.tail.head); + return; + } + break; + case JCTree.AND: + if (lhs.type.isFalse()) { + result = lhs; + return; + } + if (lhs.type.isTrue()) { + result = translate(tree.rhs, formals.tail.head); + return; + } + break; + } + tree.rhs = translate(tree.rhs, formals.tail.head); + result = tree; + } + + public void visitIdent(JCIdent tree) { + result = access(tree.sym, tree, enclOp, false); + } + + /** Translate away the foreach loop. */ + public void visitForeachLoop(JCEnhancedForLoop tree) { + if (types.elemtype(tree.expr.type) == null) + visitIterableForeachLoop(tree); + else + visitArrayForeachLoop(tree); + } + // where + /** + * A statement of the form + * + *
+         *     for ( T v : arrayexpr ) stmt;
+         * 
+ * + * (where arrayexpr is of an array type) gets translated to + * + *
+         *     for ( { arraytype #arr = arrayexpr;
+         *             int #len = array.length;
+         *             int #i = 0; };
+         *           #i < #len; i$++ ) {
+         *         T v = arr$[#i];
+         *         stmt;
+         *     }
+         * 
+ * + * where #arr, #len, and #i are freshly named synthetic local variables. + */ + private void visitArrayForeachLoop(JCEnhancedForLoop tree) { + make_at(tree.expr.pos()); + VarSymbol arraycache = new VarSymbol(0, + names.fromString("arr" + target.syntheticNameChar()), + tree.expr.type, + currentMethodSym); + JCStatement arraycachedef = make.VarDef(arraycache, tree.expr); + VarSymbol lencache = new VarSymbol(0, + names.fromString("len" + target.syntheticNameChar()), + syms.intType, + currentMethodSym); + JCStatement lencachedef = make. + VarDef(lencache, make.Select(make.Ident(arraycache), syms.lengthVar)); + VarSymbol index = new VarSymbol(0, + names.fromString("i" + target.syntheticNameChar()), + syms.intType, + currentMethodSym); + + JCVariableDecl indexdef = make.VarDef(index, make.Literal(INT, 0)); + indexdef.init.type = indexdef.type = syms.intType.constType(0); + + List loopinit = List.of(arraycachedef, lencachedef, indexdef); + JCBinary cond = makeBinary(JCTree.LT, make.Ident(index), make.Ident(lencache)); + + JCExpressionStatement step = make.Exec(makeUnary(JCTree.PREINC, make.Ident(index))); + + Type elemtype = types.elemtype(tree.expr.type); + JCExpression loopvarinit = make.Indexed(make.Ident(arraycache), + make.Ident(index)).setType(elemtype); + JCVariableDecl loopvardef = (JCVariableDecl)make.VarDef(tree.var.mods, + tree.var.name, + tree.var.vartype, + loopvarinit).setType(tree.var.type); + loopvardef.sym = tree.var.sym; + JCBlock body = make. + Block(0, List.of(loopvardef, tree.body)); + + result = translate(make. + ForLoop(loopinit, + cond, + List.of(step), + body)); + patchTargets(body, tree, result); + } + /** Patch up break and continue targets. */ + private void patchTargets(JCTree body, final JCTree src, final JCTree dest) { + class Patcher extends TreeScanner { + public void visitBreak(JCBreak tree) { + if (tree.target == src) + tree.target = dest; + } + public void visitContinue(JCContinue tree) { + if (tree.target == src) + tree.target = dest; + } + public void visitClassDef(JCClassDecl tree) {} + } + new Patcher().scan(body); + } + /** + * A statement of the form + * + *
+         *     for ( T v : coll ) stmt ;
+         * 
+ * + * (where coll implements Iterable) gets translated to + * + *
+         *     for ( Iterator #i = coll.iterator(); #i.hasNext(); ) {
+         *         T v = (T) #i.next();
+         *         stmt;
+         *     }
+         * 
+ * + * where #i is a freshly named synthetic local variable. + */ + private void visitIterableForeachLoop(JCEnhancedForLoop tree) { + make_at(tree.expr.pos()); + Type iteratorTarget = syms.objectType; + Type iterableType = types.asSuper(types.upperBound(tree.expr.type), + syms.iterableType.tsym); + if (iterableType.getTypeArguments().nonEmpty()) + iteratorTarget = types.erasure(iterableType.getTypeArguments().head); + Type eType = tree.expr.type; + tree.expr.type = types.erasure(eType); + if (eType.tag == TYPEVAR && eType.getUpperBound().isCompound()) + tree.expr = make.TypeCast(types.erasure(iterableType), tree.expr); + Symbol iterator = lookupMethod(tree.expr.pos(), + names.iterator, + types.erasure(syms.iterableType), + List.nil()); + VarSymbol itvar = new VarSymbol(0, names.fromString("i" + target.syntheticNameChar()), + types.erasure(iterator.type.getReturnType()), + currentMethodSym); + JCStatement init = make. + VarDef(itvar, + make.App(make.Select(tree.expr, iterator))); + Symbol hasNext = lookupMethod(tree.expr.pos(), + names.hasNext, + itvar.type, + List.nil()); + JCMethodInvocation cond = make.App(make.Select(make.Ident(itvar), hasNext)); + Symbol next = lookupMethod(tree.expr.pos(), + names.next, + itvar.type, + List.nil()); + JCExpression vardefinit = make.App(make.Select(make.Ident(itvar), next)); + if (tree.var.type.isPrimitive()) + vardefinit = make.TypeCast(types.upperBound(iteratorTarget), vardefinit); + else + vardefinit = make.TypeCast(tree.var.type, vardefinit); + JCVariableDecl indexDef = (JCVariableDecl)make.VarDef(tree.var.mods, + tree.var.name, + tree.var.vartype, + vardefinit).setType(tree.var.type); + indexDef.sym = tree.var.sym; + JCBlock body = make.Block(0, List.of(indexDef, tree.body)); + body.endpos = TreeInfo.endPos(tree.body); + result = translate(make. + ForLoop(List.of(init), + cond, + List.nil(), + body)); + patchTargets(body, tree, result); + } + + public void visitVarDef(JCVariableDecl tree) { + MethodSymbol oldMethodSym = currentMethodSym; + tree.mods = translate(tree.mods); + tree.vartype = translate(tree.vartype); + if (currentMethodSym == null) { + // A class or instance field initializer. + currentMethodSym = + new MethodSymbol((tree.mods.flags&STATIC) | BLOCK, + names.empty, null, + currentClass); + } + if (tree.init != null) tree.init = translate(tree.init, tree.type); + result = tree; + currentMethodSym = oldMethodSym; + } + + public void visitBlock(JCBlock tree) { + MethodSymbol oldMethodSym = currentMethodSym; + if (currentMethodSym == null) { + // Block is a static or instance initializer. + currentMethodSym = + new MethodSymbol(tree.flags | BLOCK, + names.empty, null, + currentClass); + } + super.visitBlock(tree); + + //M + //------------------------------------ + if(newJCBlockList.size() > 0) { + List l = tree.stats; + int count = newJCBlockList.size(); + for(int i=0; inil()); + JCArrayAccess selector = make.Indexed(map.mapVar, + make.App(make.Select(tree.selector, + ordinalMethod))); + ListBuffer cases = new ListBuffer(); + for (JCCase c : tree.cases) { + if (c.pat != null) { + VarSymbol label = (VarSymbol)TreeInfo.symbol(c.pat); + JCLiteral pat = map.forConstant(label); + cases.append(make.Case(pat, c.stats)); + } else { + cases.append(c); + } + } + JCSwitch enumSwitch = make.Switch(selector, cases.toList()); + patchTargets(enumSwitch, tree, enumSwitch); + return enumSwitch; + } + + public JCTree visitStringSwitch(JCSwitch tree) { + List caseList = tree.getCases(); + int alternatives = caseList.size(); + + if (alternatives == 0) { // Strange but legal possibility + return make.at(tree.pos()).Exec(attr.makeNullCheck(tree.getExpression())); + } else { + /* + * The general approach used is to translate a single + * string switch statement into a series of two chained + * switch statements: the first a synthesized statement + * switching on the argument string's hash value and + * computing a string's position in the list of original + * case labels, if any, followed by a second switch on the + * computed integer value. The second switch has the same + * code structure as the original string switch statement + * except that the string case labels are replaced with + * positional integer constants starting at 0. + * + * The first switch statement can be thought of as an + * inlined map from strings to their position in the case + * label list. An alternate implementation would use an + * actual Map for this purpose, as done for enum switches. + * + * With some additional effort, it would be possible to + * use a single switch statement on the hash code of the + * argument, but care would need to be taken to preserve + * the proper control flow in the presence of hash + * collisions and other complications, such as + * fallthroughs. Switch statements with one or two + * alternatives could also be specially translated into + * if-then statements to omit the computation of the hash + * code. + * + * The generated code assumes that the hashing algorithm + * of String is the same in the compilation environment as + * in the environment the code will run in. The string + * hashing algorithm in the SE JDK has been unchanged + * since at least JDK 1.2. Since the algorithm has been + * specified since that release as well, it is very + * unlikely to be changed in the future. + * + * Different hashing algorithms, such as the length of the + * strings or a perfect hashing algorithm over the + * particular set of case labels, could potentially be + * used instead of String.hashCode. + */ + + ListBuffer stmtList = new ListBuffer(); + + // Map from String case labels to their original position in + // the list of case labels. + Map caseLabelToPosition = + new LinkedHashMap(alternatives + 1, 1.0f); + + // Map of hash codes to the string case labels having that hashCode. + Map> hashToString = + new LinkedHashMap>(alternatives + 1, 1.0f); + + int casePosition = 0; + for(JCCase oneCase : caseList) { + JCExpression expression = oneCase.getExpression(); + + if (expression != null) { // expression for a "default" case is null + String labelExpr = (String) expression.type.constValue(); + Integer mapping = caseLabelToPosition.put(labelExpr, casePosition); + Assert.checkNull(mapping); + int hashCode = labelExpr.hashCode(); + + Set stringSet = hashToString.get(hashCode); + if (stringSet == null) { + stringSet = new LinkedHashSet(1, 1.0f); + stringSet.add(labelExpr); + hashToString.put(hashCode, stringSet); + } else { + boolean added = stringSet.add(labelExpr); + Assert.check(added); + } + } + casePosition++; + } + + // Synthesize a switch statement that has the effect of + // mapping from a string to the integer position of that + // string in the list of case labels. This is done by + // switching on the hashCode of the string followed by an + // if-then-else chain comparing the input for equality + // with all the case labels having that hash value. + + /* + * s$ = top of stack; + * tmp$ = -1; + * switch($s.hashCode()) { + * case caseLabel.hashCode: + * if (s$.equals("caseLabel_1") + * tmp$ = caseLabelToPosition("caseLabel_1"); + * else if (s$.equals("caseLabel_2")) + * tmp$ = caseLabelToPosition("caseLabel_2"); + * ... + * break; + * ... + * } + */ + + VarSymbol dollar_s = new VarSymbol(FINAL|SYNTHETIC, + names.fromString("s" + tree.pos + target.syntheticNameChar()), + syms.stringType, + currentMethodSym); + stmtList.append(make.at(tree.pos()).VarDef(dollar_s, tree.getExpression()).setType(dollar_s.type)); + + VarSymbol dollar_tmp = new VarSymbol(SYNTHETIC, + names.fromString("tmp" + tree.pos + target.syntheticNameChar()), + syms.intType, + currentMethodSym); + JCVariableDecl dollar_tmp_def = + (JCVariableDecl)make.VarDef(dollar_tmp, make.Literal(INT, -1)).setType(dollar_tmp.type); + dollar_tmp_def.init.type = dollar_tmp.type = syms.intType; + stmtList.append(dollar_tmp_def); + ListBuffer caseBuffer = ListBuffer.lb(); + // hashCode will trigger nullcheck on original switch expression + JCMethodInvocation hashCodeCall = makeCall(make.Ident(dollar_s), + names.hashCode, + List.nil()).setType(syms.intType); + JCSwitch switch1 = make.Switch(hashCodeCall, + caseBuffer.toList()); + for(Map.Entry> entry : hashToString.entrySet()) { + int hashCode = entry.getKey(); + Set stringsWithHashCode = entry.getValue(); + Assert.check(stringsWithHashCode.size() >= 1); + + JCStatement elsepart = null; + for(String caseLabel : stringsWithHashCode ) { + JCMethodInvocation stringEqualsCall = makeCall(make.Ident(dollar_s), + names.equals, + List.of(make.Literal(caseLabel))); + elsepart = make.If(stringEqualsCall, + make.Exec(make.Assign(make.Ident(dollar_tmp), + make.Literal(caseLabelToPosition.get(caseLabel))). + setType(dollar_tmp.type)), + elsepart); + } + + ListBuffer lb = ListBuffer.lb(); + JCBreak breakStmt = make.Break(null); + breakStmt.target = switch1; + lb.append(elsepart).append(breakStmt); + + caseBuffer.append(make.Case(make.Literal(hashCode), lb.toList())); + } + + switch1.cases = caseBuffer.toList(); + stmtList.append(switch1); + + // Make isomorphic switch tree replacing string labels + // with corresponding integer ones from the label to + // position map. + + ListBuffer lb = ListBuffer.lb(); + JCSwitch switch2 = make.Switch(make.Ident(dollar_tmp), lb.toList()); + for(JCCase oneCase : caseList ) { + // Rewire up old unlabeled break statements to the + // replacement switch being created. + patchTargets(oneCase, tree, switch2); + + boolean isDefault = (oneCase.getExpression() == null); + JCExpression caseExpr; + if (isDefault) + caseExpr = null; + else { + caseExpr = make.Literal(caseLabelToPosition.get((String)oneCase. + getExpression(). + type.constValue())); + } + + lb.append(make.Case(caseExpr, + oneCase.getStatements())); + } + + switch2.cases = lb.toList(); + stmtList.append(switch2); + + return make.Block(0L, stmtList.toList()); + } + } + + public void visitNewArray(JCNewArray tree) { + tree.elemtype = translate(tree.elemtype); + for (List t = tree.dims; t.tail != null; t = t.tail) + if (t.head != null) t.head = translate(t.head, syms.intType); + tree.elems = translate(tree.elems, types.elemtype(tree.type)); + result = tree; + } + + public void visitSelect(JCFieldAccess tree) { + // need to special case-access of the form C.super.x + // these will always need an access method. + boolean qualifiedSuperAccess = + tree.selected.getTag() == JCTree.SELECT && + TreeInfo.name(tree.selected) == names._super; + tree.selected = translate(tree.selected); + if (tree.name == names._class) + result = classOf(tree.selected); + else if (tree.name == names._this || tree.name == names._super) + result = makeThis(tree.pos(), tree.selected.type.tsym); + else + result = access(tree.sym, tree, enclOp, qualifiedSuperAccess); + } + + public void visitLetExpr(LetExpr tree) { + tree.defs = translateVarDefs(tree.defs); + tree.expr = translate(tree.expr, tree.type); + result = tree; + } + + // There ought to be nothing to rewrite here; + // we don't generate code. + public void visitAnnotation(JCAnnotation tree) { + result = tree; + } + + @Override + public void visitTry(JCTry tree) { + if (tree.resources.isEmpty()) { + super.visitTry(tree); + } else { + result = makeTwrTry(tree); + } + } + +/************************************************************************** + * main method + *************************************************************************/ + + /** Translate a toplevel class and return a list consisting of + * the translated class and translated versions of all inner classes. + * @param env The attribution environment current at the class definition. + * We need this for resolving some additional symbols. + * @param cdef The tree representing the class definition. + */ + public List translateTopLevelClass(Env env, JCTree cdef, TreeMaker make) { + ListBuffer translated = null; + try { + attrEnv = env; + this.make = make; + endPositions = env.toplevel.endPositions; + currentClass = null; + currentMethodDef = null; + outermostClassDef = (cdef.getTag() == JCTree.CLASSDEF) ? (JCClassDecl)cdef : null; + outermostMemberDef = null; + this.translated = new ListBuffer(); + classdefs = new HashMap(); + actualSymbols = new HashMap(); + freevarCache = new HashMap>(); + proxies = new Scope(syms.noSymbol); + twrVars = new Scope(syms.noSymbol); + outerThisStack = List.nil(); + accessNums = new HashMap(); + accessSyms = new HashMap(); + accessConstrs = new HashMap(); + accessConstrTags = List.nil(); + accessed = new ListBuffer(); + translate(cdef, (JCExpression)null); + for (List l = accessed.toList(); l.nonEmpty(); l = l.tail) + makeAccessible(l.head); + for (EnumMapping map : enumSwitchMap.values()) + map.translate(); + checkConflicts(this.translated.toList()); + checkAccessConstructorTags(); + translated = this.translated; + } finally { + // note that recursive invocations of this method fail hard + attrEnv = null; + this.make = null; + endPositions = null; + currentClass = null; + currentMethodDef = null; + outermostClassDef = null; + outermostMemberDef = null; + this.translated = null; + classdefs = null; + actualSymbols = null; + freevarCache = null; + proxies = null; + outerThisStack = null; + accessNums = null; + accessSyms = null; + accessConstrs = null; + accessConstrTags = null; + accessed = null; + enumSwitchMap.clear(); + } + return translated.toList(); + } + + ////////////////////////////////////////////////////////////// + // The following contributed by Borland for bootstrapping purposes + ////////////////////////////////////////////////////////////// + private void addEnumCompatibleMembers(JCClassDecl cdef) { + make_at(null); + + // Add the special enum fields + VarSymbol ordinalFieldSym = addEnumOrdinalField(cdef); + VarSymbol nameFieldSym = addEnumNameField(cdef); + + // Add the accessor methods for name and ordinal + MethodSymbol ordinalMethodSym = addEnumFieldOrdinalMethod(cdef, ordinalFieldSym); + MethodSymbol nameMethodSym = addEnumFieldNameMethod(cdef, nameFieldSym); + + // Add the toString method + addEnumToString(cdef, nameFieldSym); + + // Add the compareTo method + addEnumCompareTo(cdef, ordinalFieldSym); + } + + private VarSymbol addEnumOrdinalField(JCClassDecl cdef) { + VarSymbol ordinal = new VarSymbol(PRIVATE|FINAL|SYNTHETIC, + names.fromString("$ordinal"), + syms.intType, + cdef.sym); + cdef.sym.members().enter(ordinal); + cdef.defs = cdef.defs.prepend(make.VarDef(ordinal, null)); + return ordinal; + } + + private VarSymbol addEnumNameField(JCClassDecl cdef) { + VarSymbol name = new VarSymbol(PRIVATE|FINAL|SYNTHETIC, + names.fromString("$name"), + syms.stringType, + cdef.sym); + cdef.sym.members().enter(name); + cdef.defs = cdef.defs.prepend(make.VarDef(name, null)); + return name; + } + + private MethodSymbol addEnumFieldOrdinalMethod(JCClassDecl cdef, VarSymbol ordinalSymbol) { + // Add the accessor methods for ordinal + Symbol ordinalSym = lookupMethod(cdef.pos(), + names.ordinal, + cdef.type, + List.nil()); + + Assert.check(ordinalSym instanceof MethodSymbol); + + JCStatement ret = make.Return(make.Ident(ordinalSymbol)); + cdef.defs = cdef.defs.append(make.MethodDef((MethodSymbol)ordinalSym, + make.Block(0L, List.of(ret)))); + + return (MethodSymbol)ordinalSym; + } + + private MethodSymbol addEnumFieldNameMethod(JCClassDecl cdef, VarSymbol nameSymbol) { + // Add the accessor methods for name + Symbol nameSym = lookupMethod(cdef.pos(), + names._name, + cdef.type, + List.nil()); + + Assert.check(nameSym instanceof MethodSymbol); + + JCStatement ret = make.Return(make.Ident(nameSymbol)); + + cdef.defs = cdef.defs.append(make.MethodDef((MethodSymbol)nameSym, + make.Block(0L, List.of(ret)))); + + return (MethodSymbol)nameSym; + } + + private MethodSymbol addEnumToString(JCClassDecl cdef, + VarSymbol nameSymbol) { + Symbol toStringSym = lookupMethod(cdef.pos(), + names.toString, + cdef.type, + List.nil()); + + JCTree toStringDecl = null; + if (toStringSym != null) + toStringDecl = TreeInfo.declarationFor(toStringSym, cdef); + + if (toStringDecl != null) + return (MethodSymbol)toStringSym; + + JCStatement ret = make.Return(make.Ident(nameSymbol)); + + JCTree resTypeTree = make.Type(syms.stringType); + + MethodType toStringType = new MethodType(List.nil(), + syms.stringType, + List.nil(), + cdef.sym); + toStringSym = new MethodSymbol(PUBLIC, + names.toString, + toStringType, + cdef.type.tsym); + toStringDecl = make.MethodDef((MethodSymbol)toStringSym, + make.Block(0L, List.of(ret))); + + cdef.defs = cdef.defs.prepend(toStringDecl); + cdef.sym.members().enter(toStringSym); + + return (MethodSymbol)toStringSym; + } + + private MethodSymbol addEnumCompareTo(JCClassDecl cdef, VarSymbol ordinalSymbol) { + Symbol compareToSym = lookupMethod(cdef.pos(), + names.compareTo, + cdef.type, + List.of(cdef.sym.type)); + + Assert.check(compareToSym instanceof MethodSymbol); + + JCMethodDecl compareToDecl = (JCMethodDecl) TreeInfo.declarationFor(compareToSym, cdef); + + ListBuffer blockStatements = new ListBuffer(); + + JCModifiers mod1 = make.Modifiers(0L); + Name oName = names.fromString("o"); + JCVariableDecl par1 = make.Param(oName, cdef.type, compareToSym); + + JCIdent paramId1 = make.Ident(names.java_lang_Object); + paramId1.type = cdef.type; + paramId1.sym = par1.sym; + + ((MethodSymbol)compareToSym).params = List.of(par1.sym); + + JCIdent par1UsageId = make.Ident(par1.sym); + JCIdent castTargetIdent = make.Ident(cdef.sym); + JCTypeCast cast = make.TypeCast(castTargetIdent, par1UsageId); + cast.setType(castTargetIdent.type); + + Name otherName = names.fromString("other"); + + VarSymbol otherVarSym = new VarSymbol(mod1.flags, + otherName, + cdef.type, + compareToSym); + JCVariableDecl otherVar = make.VarDef(otherVarSym, cast); + blockStatements.append(otherVar); + + JCIdent id1 = make.Ident(ordinalSymbol); + + JCIdent fLocUsageId = make.Ident(otherVarSym); + JCExpression sel = make.Select(fLocUsageId, ordinalSymbol); + JCBinary bin = makeBinary(JCTree.MINUS, id1, sel); + JCReturn ret = make.Return(bin); + blockStatements.append(ret); + JCMethodDecl compareToMethod = make.MethodDef((MethodSymbol)compareToSym, + make.Block(0L, + blockStatements.toList())); + compareToMethod.params = List.of(par1); + cdef.defs = cdef.defs.append(compareToMethod); + + return (MethodSymbol)compareToSym; + } + ////////////////////////////////////////////////////////////// + // The above contributed by Borland for bootstrapping purposes + ////////////////////////////////////////////////////////////// +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/comp/MemberEnter.java b/douyu-javac/src/main/java/com/sun/tools/javac/comp/MemberEnter.java new file mode 100644 index 0000000..d758398 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/comp/MemberEnter.java @@ -0,0 +1,1280 @@ +/* + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.comp; + +import java.util.*; +import java.util.Set; +import javax.tools.JavaFileObject; + +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.jvm.*; +import com.sun.tools.javac.tree.*; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.List; + +import com.sun.tools.javac.code.Type.*; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.tree.JCTree.*; + +import static com.sun.tools.javac.code.Flags.*; +import static com.sun.tools.javac.code.Kinds.*; +import static com.sun.tools.javac.code.TypeTags.*; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; + +/** This is the second phase of Enter, in which classes are completed + * by entering their members into the class scope using + * MemberEnter.complete(). See Enter for an overview. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class MemberEnter extends JCTree.Visitor implements Completer { + protected static final Context.Key memberEnterKey = + new Context.Key(); + + /** A switch to determine whether we check for package/class conflicts + */ + final static boolean checkClash = true; + + private final Names names; + private final Enter enter; + private final Log log; + private final Check chk; + private final Attr attr; + private final Symtab syms; + private final TreeMaker make; + private final ClassReader reader; + private final Todo todo; + private final Annotate annotate; + private final Types types; + private final JCDiagnostic.Factory diags; + private final Target target; + private final DeferredLintHandler deferredLintHandler; + + private final boolean skipAnnotations; + + public static MemberEnter instance(Context context) { + MemberEnter instance = context.get(memberEnterKey); + if (instance == null) + instance = new MemberEnter(context); + return instance; + } + + protected MemberEnter(Context context) { + context.put(memberEnterKey, this); + names = Names.instance(context); + enter = Enter.instance(context); + log = Log.instance(context); + chk = Check.instance(context); + attr = Attr.instance(context); + syms = Symtab.instance(context); + make = TreeMaker.instance(context); + reader = ClassReader.instance(context); + todo = Todo.instance(context); + annotate = Annotate.instance(context); + types = Types.instance(context); + diags = JCDiagnostic.Factory.instance(context); + target = Target.instance(context); + deferredLintHandler = DeferredLintHandler.instance(context); + Options options = Options.instance(context); + skipAnnotations = options.isSet("skipAnnotations"); + } + + /** A queue for classes whose members still need to be entered into the + * symbol table. + */ + ListBuffer> halfcompleted = new ListBuffer>(); + + /** Set to true only when the first of a set of classes is + * processed from the halfcompleted queue. + */ + boolean isFirst = true; + + /** A flag to disable completion from time to time during member + * enter, as we only need to look up types. This avoids + * unnecessarily deep recursion. + */ + boolean completionEnabled = true; + + /* ---------- Processing import clauses ---------------- + */ + + /** Import all classes of a class or package on demand. + * @param pos Position to be used for error reporting. + * @param tsym The class or package the members of which are imported. + * @param toScope The (import) scope in which imported classes + * are entered. + */ + private void importAll(int pos, + final TypeSymbol tsym, + Env env) { + // Check that packages imported from exist (JLS ???). + if (tsym.kind == PCK && tsym.members().elems == null && !tsym.exists()) { + // If we can't find java.lang, exit immediately. + if (((PackageSymbol)tsym).fullname.equals(names.java_lang)) { + JCDiagnostic msg = diags.fragment("fatal.err.no.java.lang"); + throw new FatalError(msg); + } else { + log.error(pos, "doesnt.exist", tsym); + } + } + env.toplevel.starImportScope.importAll(tsym.members()); + } + + /** Import all static members of a class or package on demand. + * @param pos Position to be used for error reporting. + * @param tsym The class or package the members of which are imported. + * @param toScope The (import) scope in which imported classes + * are entered. + */ + private void importStaticAll(int pos, + final TypeSymbol tsym, + Env env) { + final JavaFileObject sourcefile = env.toplevel.sourcefile; + final Scope toScope = env.toplevel.starImportScope; + final PackageSymbol packge = env.toplevel.packge; + final TypeSymbol origin = tsym; + + // enter imported types immediately + new Object() { + Set processed = new HashSet(); + void importFrom(TypeSymbol tsym) { + if (tsym == null || !processed.add(tsym)) + return; + + // also import inherited names + importFrom(types.supertype(tsym.type).tsym); + for (Type t : types.interfaces(tsym.type)) + importFrom(t.tsym); + + final Scope fromScope = tsym.members(); + for (Scope.Entry e = fromScope.elems; e != null; e = e.sibling) { + Symbol sym = e.sym; + if (sym.kind == TYP && + (sym.flags() & STATIC) != 0 && + staticImportAccessible(sym, packge) && + sym.isMemberOf(origin, types) && + !toScope.includes(sym)) + toScope.enter(sym, fromScope, origin.members()); + } + } + }.importFrom(tsym); + + // enter non-types before annotations that might use them + annotate.earlier(new Annotate.Annotator() { + Set processed = new HashSet(); + + public String toString() { + return "import static " + tsym + ".*" + " in " + sourcefile; + } + void importFrom(TypeSymbol tsym) { + if (tsym == null || !processed.add(tsym)) + return; + + // also import inherited names + importFrom(types.supertype(tsym.type).tsym); + for (Type t : types.interfaces(tsym.type)) + importFrom(t.tsym); + + final Scope fromScope = tsym.members(); + for (Scope.Entry e = fromScope.elems; e != null; e = e.sibling) { + Symbol sym = e.sym; + if (sym.isStatic() && sym.kind != TYP && + staticImportAccessible(sym, packge) && + !toScope.includes(sym) && + sym.isMemberOf(origin, types)) { + toScope.enter(sym, fromScope, origin.members()); + } + } + } + public void enterAnnotation() { + importFrom(tsym); + } + }); + } + + // is the sym accessible everywhere in packge? + boolean staticImportAccessible(Symbol sym, PackageSymbol packge) { + int flags = (int)(sym.flags() & AccessFlags); + switch (flags) { + default: + case PUBLIC: + return true; + case PRIVATE: + return false; + case 0: + case PROTECTED: + return sym.packge() == packge; + } + } + + /** Import statics types of a given name. Non-types are handled in Attr. + * @param pos Position to be used for error reporting. + * @param tsym The class from which the name is imported. + * @param name The (simple) name being imported. + * @param env The environment containing the named import + * scope to add to. + */ + private void importNamedStatic(final DiagnosticPosition pos, + final TypeSymbol tsym, + final Name name, + final Env env) { + if (tsym.kind != TYP) { + log.error(pos, "static.imp.only.classes.and.interfaces"); + return; + } + + final Scope toScope = env.toplevel.namedImportScope; + final PackageSymbol packge = env.toplevel.packge; + final TypeSymbol origin = tsym; + + // enter imported types immediately + new Object() { + Set processed = new HashSet(); + void importFrom(TypeSymbol tsym) { + if (tsym == null || !processed.add(tsym)) + return; + + // also import inherited names + importFrom(types.supertype(tsym.type).tsym); + for (Type t : types.interfaces(tsym.type)) + importFrom(t.tsym); + + for (Scope.Entry e = tsym.members().lookup(name); + e.scope != null; + e = e.next()) { + Symbol sym = e.sym; + if (sym.isStatic() && + sym.kind == TYP && + staticImportAccessible(sym, packge) && + sym.isMemberOf(origin, types) && + chk.checkUniqueStaticImport(pos, sym, toScope)) + toScope.enter(sym, sym.owner.members(), origin.members()); + } + } + }.importFrom(tsym); + + // enter non-types before annotations that might use them + annotate.earlier(new Annotate.Annotator() { + Set processed = new HashSet(); + boolean found = false; + + public String toString() { + return "import static " + tsym + "." + name; + } + void importFrom(TypeSymbol tsym) { + if (tsym == null || !processed.add(tsym)) + return; + + // also import inherited names + importFrom(types.supertype(tsym.type).tsym); + for (Type t : types.interfaces(tsym.type)) + importFrom(t.tsym); + + for (Scope.Entry e = tsym.members().lookup(name); + e.scope != null; + e = e.next()) { + Symbol sym = e.sym; + if (sym.isStatic() && + staticImportAccessible(sym, packge) && + sym.isMemberOf(origin, types)) { + found = true; + if (sym.kind == MTH || + sym.kind != TYP && chk.checkUniqueStaticImport(pos, sym, toScope)) + toScope.enter(sym, sym.owner.members(), origin.members()); + } + } + } + public void enterAnnotation() { + JavaFileObject prev = log.useSource(env.toplevel.sourcefile); + try { + importFrom(tsym); + if (!found) { + log.error(pos, "cant.resolve.location", + KindName.STATIC, + name, List.nil(), List.nil(), + Kinds.typeKindName(tsym.type), + tsym.type); + } + } finally { + log.useSource(prev); + } + } + }); + } + /** Import given class. + * @param pos Position to be used for error reporting. + * @param tsym The class to be imported. + * @param env The environment containing the named import + * scope to add to. + */ + private void importNamed(DiagnosticPosition pos, Symbol tsym, Env env) { + if (tsym.kind == TYP && + chk.checkUniqueImport(pos, tsym, env.toplevel.namedImportScope)) + env.toplevel.namedImportScope.enter(tsym, tsym.owner.members()); + } + + /** Construct method type from method signature. + * @param typarams The method's type parameters. + * @param params The method's value parameters. + * @param res The method's result type, + * null if it is a constructor. + * @param thrown The method's thrown exceptions. + * @param env The method's (local) environment. + */ + Type signature(List typarams, + List params, + JCTree res, + List thrown, + Env env) { + + // Enter and attribute type parameters. + List tvars = enter.classEnter(typarams, env); + attr.attribTypeVariables(typarams, env); + + // Enter and attribute value parameters. + ListBuffer argbuf = new ListBuffer(); + for (List l = params; l.nonEmpty(); l = l.tail) { + memberEnter(l.head, env); + argbuf.append(l.head.vartype.type); + } + + // Attribute result type, if one is given. + Type restype = res == null ? syms.voidType : attr.attribType(res, env); + + // Attribute thrown exceptions. + ListBuffer thrownbuf = new ListBuffer(); + for (List l = thrown; l.nonEmpty(); l = l.tail) { + Type exc = attr.attribType(l.head, env); + if (exc.tag != TYPEVAR) + exc = chk.checkClassType(l.head.pos(), exc); + thrownbuf.append(exc); + } + Type mtype = new MethodType(argbuf.toList(), + restype, + thrownbuf.toList(), + syms.methodClass); + return tvars.isEmpty() ? mtype : new ForAll(tvars, mtype); + } + +/* ******************************************************************** + * Visitor methods for member enter + *********************************************************************/ + + /** Visitor argument: the current environment + */ + protected Env env; + + /** Enter field and method definitions and process import + * clauses, catching any completion failure exceptions. + */ + protected void memberEnter(JCTree tree, Env env) { + Env prevEnv = this.env; + try { + this.env = env; + tree.accept(this); + } catch (CompletionFailure ex) { + chk.completionError(tree.pos(), ex); + } finally { + this.env = prevEnv; + } + } + + /** Enter members from a list of trees. + */ + void memberEnter(List trees, Env env) { + for (List l = trees; l.nonEmpty(); l = l.tail) + memberEnter(l.head, env); + } + + /** Enter members for a class. + */ + void finishClass(JCClassDecl tree, Env env) { + if ((tree.mods.flags & Flags.ENUM) != 0 && + (types.supertype(tree.sym.type).tsym.flags() & Flags.ENUM) == 0) { + addEnumMembers(tree, env); + } + memberEnter(tree.defs, env); + } + + /** Add the implicit members for an enum type + * to the symbol table. + */ + private void addEnumMembers(JCClassDecl tree, Env env) { + JCExpression valuesType = make.Type(new ArrayType(tree.sym.type, syms.arrayClass)); + + // public static T[] values() { return ???; } + JCMethodDecl values = make. + MethodDef(make.Modifiers(Flags.PUBLIC|Flags.STATIC), + names.values, + valuesType, + List.nil(), + List.nil(), + List.nil(), // thrown + null, //make.Block(0, Tree.emptyList.prepend(make.Return(make.Ident(names._null)))), + null); + memberEnter(values, env); + + // public static T valueOf(String name) { return ???; } + JCMethodDecl valueOf = make. + MethodDef(make.Modifiers(Flags.PUBLIC|Flags.STATIC), + names.valueOf, + make.Type(tree.sym.type), + List.nil(), + List.of(make.VarDef(make.Modifiers(Flags.PARAMETER), + names.fromString("name"), + make.Type(syms.stringType), null)), + List.nil(), // thrown + null, //make.Block(0, Tree.emptyList.prepend(make.Return(make.Ident(names._null)))), + null); + memberEnter(valueOf, env); + + // the remaining members are for bootstrapping only + if (!target.compilerBootstrap(tree.sym)) return; + + // public final int ordinal() { return ???; } + JCMethodDecl ordinal = make.at(tree.pos). + MethodDef(make.Modifiers(Flags.PUBLIC|Flags.FINAL), + names.ordinal, + make.Type(syms.intType), + List.nil(), + List.nil(), + List.nil(), + null, + null); + memberEnter(ordinal, env); + + // public final String name() { return ???; } + JCMethodDecl name = make. + MethodDef(make.Modifiers(Flags.PUBLIC|Flags.FINAL), + names._name, + make.Type(syms.stringType), + List.nil(), + List.nil(), + List.nil(), + null, + null); + memberEnter(name, env); + + // public int compareTo(E other) { return ???; } + MethodSymbol compareTo = new + MethodSymbol(Flags.PUBLIC, + names.compareTo, + new MethodType(List.of(tree.sym.type), + syms.intType, + List.nil(), + syms.methodClass), + tree.sym); + memberEnter(make.MethodDef(compareTo, null), env); + } + + public void visitTopLevel(JCCompilationUnit tree) { + if (tree.starImportScope.elems != null) { + // we must have already processed this toplevel + return; + } + + // check that no class exists with same fully qualified name as + // toplevel package + if (checkClash && tree.pid != null) { + Symbol p = tree.packge; + while (p.owner != syms.rootPackage) { + p.owner.complete(); // enter all class members of p + if (syms.classes.get(p.getQualifiedName()) != null) { + log.error(tree.pos, + "pkg.clashes.with.class.of.same.name", + p); + } + p = p.owner; + } + } + + // process package annotations + annotateLater(tree.packageAnnotations, env, tree.packge); + + // Import-on-demand java.lang. + importAll(tree.pos, reader.enterPackage(names.java_lang), env); + + // Process all import clauses. + memberEnter(tree.defs, env); + } + + // process the non-static imports and the static imports of types. + public void visitImport(JCImport tree) { + JCTree imp = tree.qualid; + Name name = TreeInfo.name(imp); + TypeSymbol p; + + // Create a local environment pointing to this tree to disable + // effects of other imports in Resolve.findGlobalType + Env localEnv = env.dup(tree); + + // Attribute qualifying package or class. + JCFieldAccess s = (JCFieldAccess) imp; + p = attr. + attribTree(s.selected, + localEnv, + tree.staticImport ? TYP : (TYP | PCK), + Type.noType).tsym; + if (name == names.asterisk) { + // Import on demand. + chk.checkCanonical(s.selected); + if (tree.staticImport) + importStaticAll(tree.pos, p, env); + else + importAll(tree.pos, p, env); + } else { + // Named type import. + if (tree.staticImport) { + importNamedStatic(tree.pos(), p, name, localEnv); + chk.checkCanonical(s.selected); + } else { + TypeSymbol c = attribImportType(imp, localEnv).tsym; + chk.checkCanonical(imp); + importNamed(tree.pos(), c, env); + } + } + } + + public void visitMethodDef(JCMethodDecl tree) { + Scope enclScope = enter.enterScope(env); + MethodSymbol m = new MethodSymbol(0, tree.name, null, enclScope.owner); + m.flags_field = chk.checkFlags(tree.pos(), tree.mods.flags, m, tree); + tree.sym = m; + Env localEnv = methodEnv(tree, env); + + DeferredLintHandler prevLintHandler = + chk.setDeferredLintHandler(deferredLintHandler.setPos(tree.pos())); + try { + // Compute the method type + m.type = signature(tree.typarams, tree.params, + tree.restype, tree.thrown, + localEnv); + } finally { + chk.setDeferredLintHandler(prevLintHandler); + } + + // Set m.params + ListBuffer params = new ListBuffer(); + JCVariableDecl lastParam = null; + for (List l = tree.params; l.nonEmpty(); l = l.tail) { + JCVariableDecl param = lastParam = l.head; + params.append(Assert.checkNonNull(param.sym)); + } + m.params = params.toList(); + + // mark the method varargs, if necessary + if (lastParam != null && (lastParam.mods.flags & Flags.VARARGS) != 0) + m.flags_field |= Flags.VARARGS; + + localEnv.info.scope.leave(); + if (chk.checkUnique(tree.pos(), m, enclScope)) { + enclScope.enter(m); + } + annotateLater(tree.mods.annotations, localEnv, m); + if (tree.defaultValue != null) + annotateDefaultValueLater(tree.defaultValue, localEnv, m); + } + + /** Create a fresh environment for method bodies. + * @param tree The method definition. + * @param env The environment current outside of the method definition. + */ + Env methodEnv(JCMethodDecl tree, Env env) { + Env localEnv = + env.dup(tree, env.info.dup(env.info.scope.dupUnshared())); + localEnv.enclMethod = tree; + localEnv.info.scope.owner = tree.sym; + if ((tree.mods.flags & STATIC) != 0) localEnv.info.staticLevel++; + return localEnv; + } + + public void visitVarDef(JCVariableDecl tree) { + Env localEnv = env; + if ((tree.mods.flags & STATIC) != 0 || + (env.info.scope.owner.flags() & INTERFACE) != 0) { + localEnv = env.dup(tree, env.info.dup()); + localEnv.info.staticLevel++; + } + DeferredLintHandler prevLintHandler = + chk.setDeferredLintHandler(deferredLintHandler.setPos(tree.pos())); + try { + attr.attribType(tree.vartype, localEnv); + } finally { + chk.setDeferredLintHandler(prevLintHandler); + } + + if ((tree.mods.flags & VARARGS) != 0) { + //if we are entering a varargs parameter, we need to replace its type + //(a plain array type) with the more precise VarargsType --- we need + //to do it this way because varargs is represented in the tree as a modifier + //on the parameter declaration, and not as a distinct type of array node. + ArrayType atype = (ArrayType)tree.vartype.type; + tree.vartype.type = atype.makeVarargs(); + } + Scope enclScope = enter.enterScope(env); + VarSymbol v = + new VarSymbol(0, tree.name, tree.vartype.type, enclScope.owner); + v.flags_field = chk.checkFlags(tree.pos(), tree.mods.flags, v, tree); + tree.sym = v; + if (tree.init != null) { + v.flags_field |= HASINIT; + if ((v.flags_field & FINAL) != 0 && tree.init.getTag() != JCTree.NEWCLASS) { + Env initEnv = getInitEnv(tree, env); + initEnv.info.enclVar = v; + v.setLazyConstValue(initEnv(tree, initEnv), attr, tree.init); + } + } + if (chk.checkUnique(tree.pos(), v, enclScope)) { + chk.checkTransparentVar(tree.pos(), v, enclScope); + enclScope.enter(v); + } + annotateLater(tree.mods.annotations, localEnv, v); + v.pos = tree.pos; + } + + /** Create a fresh environment for a variable's initializer. + * If the variable is a field, the owner of the environment's scope + * is be the variable itself, otherwise the owner is the method + * enclosing the variable definition. + * + * @param tree The variable definition. + * @param env The environment current outside of the variable definition. + */ + Env initEnv(JCVariableDecl tree, Env env) { + Env localEnv = env.dupto(new AttrContextEnv(tree, env.info.dup())); + if (tree.sym.owner.kind == TYP) { + localEnv.info.scope = new Scope.DelegatedScope(env.info.scope); + localEnv.info.scope.owner = tree.sym; + } + if ((tree.mods.flags & STATIC) != 0 || + (env.enclClass.sym.flags() & INTERFACE) != 0) + localEnv.info.staticLevel++; + return localEnv; + } + + /** Default member enter visitor method: do nothing + */ + public void visitTree(JCTree tree) { + } + + public void visitErroneous(JCErroneous tree) { + if (tree.errs != null) + memberEnter(tree.errs, env); + } + + public Env getMethodEnv(JCMethodDecl tree, Env env) { + Env mEnv = methodEnv(tree, env); + mEnv.info.lint = mEnv.info.lint.augment(tree.sym.attributes_field, tree.sym.flags()); + for (List l = tree.typarams; l.nonEmpty(); l = l.tail) + mEnv.info.scope.enterIfAbsent(l.head.type.tsym); + for (List l = tree.params; l.nonEmpty(); l = l.tail) + mEnv.info.scope.enterIfAbsent(l.head.sym); + return mEnv; + } + + public Env getInitEnv(JCVariableDecl tree, Env env) { + Env iEnv = initEnv(tree, env); + return iEnv; + } + +/* ******************************************************************** + * Type completion + *********************************************************************/ + + Type attribImportType(JCTree tree, Env env) { + Assert.check(completionEnabled); + try { + // To prevent deep recursion, suppress completion of some + // types. + completionEnabled = false; + return attr.attribType(tree, env); + } finally { + completionEnabled = true; + } + } + +/* ******************************************************************** + * Annotation processing + *********************************************************************/ + + /** Queue annotations for later processing. */ + void annotateLater(final List annotations, + final Env localEnv, + final Symbol s) { + if (annotations.isEmpty()) return; + if (s.kind != PCK) s.attributes_field = null; // mark it incomplete for now + annotate.later(new Annotate.Annotator() { + public String toString() { + return "annotate " + annotations + " onto " + s + " in " + s.owner; + } + public void enterAnnotation() { + Assert.check(s.kind == PCK || s.attributes_field == null); + JavaFileObject prev = log.useSource(localEnv.toplevel.sourcefile); + try { + if (s.attributes_field != null && + s.attributes_field.nonEmpty() && + annotations.nonEmpty()) + log.error(annotations.head.pos, + "already.annotated", + kindName(s), s); + enterAnnotations(annotations, localEnv, s); + } finally { + log.useSource(prev); + } + } + }); + } + + /** + * Check if a list of annotations contains a reference to + * java.lang.Deprecated. + **/ + private boolean hasDeprecatedAnnotation(List annotations) { + for (List al = annotations; al.nonEmpty(); al = al.tail) { + JCAnnotation a = al.head; + if (a.annotationType.type == syms.deprecatedType && a.args.isEmpty()) + return true; + } + return false; + } + + + /** Enter a set of annotations. */ + private void enterAnnotations(List annotations, + Env env, + Symbol s) { + ListBuffer buf = + new ListBuffer(); + Set annotated = new HashSet(); + if (!skipAnnotations) + for (List al = annotations; al.nonEmpty(); al = al.tail) { + JCAnnotation a = al.head; + Attribute.Compound c = annotate.enterAnnotation(a, + syms.annotationType, + env); + if (c == null) continue; + buf.append(c); + // Note: @Deprecated has no effect on local variables and parameters + if (!c.type.isErroneous() + && s.owner.kind != MTH + && types.isSameType(c.type, syms.deprecatedType)) + s.flags_field |= Flags.DEPRECATED; + // Internally to java.lang.invoke, a @PolymorphicSignature annotation + // acts like a classfile attribute. + if (!c.type.isErroneous() && + types.isSameType(c.type, syms.polymorphicSignatureType)) { + if (!target.hasMethodHandles()) { + // Somebody is compiling JDK7 source code to a JDK6 target. + // Make it an error, since it is unlikely but important. + log.error(env.tree.pos(), + "wrong.target.for.polymorphic.signature.definition", + target.name); + } + // Pull the flag through for better diagnostics, even on a bad target. + s.flags_field |= Flags.POLYMORPHIC_SIGNATURE; + } + if (!annotated.add(a.type.tsym)) + log.error(a.pos, "duplicate.annotation"); + } + s.attributes_field = buf.toList(); + } + + /** Queue processing of an attribute default value. */ + void annotateDefaultValueLater(final JCExpression defaultValue, + final Env localEnv, + final MethodSymbol m) { + annotate.later(new Annotate.Annotator() { + public String toString() { + return "annotate " + m.owner + "." + + m + " default " + defaultValue; + } + public void enterAnnotation() { + JavaFileObject prev = log.useSource(localEnv.toplevel.sourcefile); + try { + enterDefaultValue(defaultValue, localEnv, m); + } finally { + log.useSource(prev); + } + } + }); + } + + /** Enter a default value for an attribute method. */ + private void enterDefaultValue(final JCExpression defaultValue, + final Env localEnv, + final MethodSymbol m) { + m.defaultValue = annotate.enterAttributeValue(m.type.getReturnType(), + defaultValue, + localEnv); + } + +/* ******************************************************************** + * Source completer + *********************************************************************/ + + /** Complete entering a class. + * @param sym The symbol of the class to be completed. + */ + public void complete(Symbol sym) throws CompletionFailure { + // Suppress some (recursive) MemberEnter invocations + if (!completionEnabled) { + // Re-install same completer for next time around and return. + Assert.check((sym.flags() & Flags.COMPOUND) == 0); + sym.completer = this; + return; + } + + ClassSymbol c = (ClassSymbol)sym; + ClassType ct = (ClassType)c.type; + Env env = enter.typeEnvs.get(c); + JCClassDecl tree = (JCClassDecl)env.tree; + boolean wasFirst = isFirst; + isFirst = false; + + JavaFileObject prev = log.useSource(env.toplevel.sourcefile); + try { + // Save class environment for later member enter (2) processing. + halfcompleted.append(env); + + // Mark class as not yet attributed. + c.flags_field |= UNATTRIBUTED; + + // If this is a toplevel-class, make sure any preceding import + // clauses have been seen. + if (c.owner.kind == PCK) { + memberEnter(env.toplevel, env.enclosing(JCTree.TOPLEVEL)); + todo.append(env); + } + + if (c.owner.kind == TYP) + c.owner.complete(); + + // create an environment for evaluating the base clauses + Env baseEnv = baseEnv(tree, env); + + // Determine supertype. + Type supertype = + (tree.extending != null) + ? attr.attribBase(tree.extending, baseEnv, true, false, true) + : ((tree.mods.flags & Flags.ENUM) != 0 && !target.compilerBootstrap(c)) + ? attr.attribBase(enumBase(tree.pos, c), baseEnv, + true, false, false) + : (c.fullname == names.java_lang_Object) + ? Type.noType + : syms.objectType; + ct.supertype_field = modelMissingTypes(supertype, tree.extending, false); + + // Determine interfaces. + ListBuffer interfaces = new ListBuffer(); + ListBuffer all_interfaces = null; // lazy init + Set interfaceSet = new HashSet(); + List interfaceTrees = tree.implementing; + if ((tree.mods.flags & Flags.ENUM) != 0 && target.compilerBootstrap(c)) { + // add interface Comparable + interfaceTrees = + interfaceTrees.prepend(make.Type(new ClassType(syms.comparableType.getEnclosingType(), + List.of(c.type), + syms.comparableType.tsym))); + // add interface Serializable + interfaceTrees = + interfaceTrees.prepend(make.Type(syms.serializableType)); + } + for (JCExpression iface : interfaceTrees) { + Type i = attr.attribBase(iface, baseEnv, false, true, true); + if (i.tag == CLASS) { + interfaces.append(i); + if (all_interfaces != null) all_interfaces.append(i); + chk.checkNotRepeated(iface.pos(), types.erasure(i), interfaceSet); + } else { + if (all_interfaces == null) + all_interfaces = new ListBuffer().appendList(interfaces); + all_interfaces.append(modelMissingTypes(i, iface, true)); + } + } + if ((c.flags_field & ANNOTATION) != 0) { + ct.interfaces_field = List.of(syms.annotationType); + ct.all_interfaces_field = ct.interfaces_field; + } else { + ct.interfaces_field = interfaces.toList(); + ct.all_interfaces_field = (all_interfaces == null) + ? ct.interfaces_field : all_interfaces.toList(); + } + + if (c.fullname == names.java_lang_Object) { + if (tree.extending != null) { + chk.checkNonCyclic(tree.extending.pos(), + supertype); + ct.supertype_field = Type.noType; + } + else if (tree.implementing.nonEmpty()) { + chk.checkNonCyclic(tree.implementing.head.pos(), + ct.interfaces_field.head); + ct.interfaces_field = List.nil(); + } + } + + // Annotations. + // In general, we cannot fully process annotations yet, but we + // can attribute the annotation types and then check to see if the + // @Deprecated annotation is present. + attr.attribAnnotationTypes(tree.mods.annotations, baseEnv); + if (hasDeprecatedAnnotation(tree.mods.annotations)) + c.flags_field |= DEPRECATED; + annotateLater(tree.mods.annotations, baseEnv, c); + + chk.checkNonCyclicDecl(tree); + + attr.attribTypeVariables(tree.typarams, baseEnv); + + // Add default constructor if needed. + if ((c.flags() & INTERFACE) == 0 && + !TreeInfo.hasConstructors(tree.defs)) { + List argtypes = List.nil(); + List typarams = List.nil(); + List thrown = List.nil(); + long ctorFlags = 0; + boolean based = false; + if (c.name.isEmpty()) { + JCNewClass nc = (JCNewClass)env.next.tree; + if (nc.constructor != null) { + Type superConstrType = types.memberType(c.type, + nc.constructor); + argtypes = superConstrType.getParameterTypes(); + typarams = superConstrType.getTypeArguments(); + ctorFlags = nc.constructor.flags() & VARARGS; + if (nc.encl != null) { + argtypes = argtypes.prepend(nc.encl.type); + based = true; + } + thrown = superConstrType.getThrownTypes(); + } + } + JCTree constrDef = DefaultConstructor(make.at(tree.pos), c, + typarams, argtypes, thrown, + ctorFlags, based); + tree.defs = tree.defs.prepend(constrDef); + } + + // If this is a class, enter symbols for this and super into + // current scope. + if ((c.flags_field & INTERFACE) == 0) { + VarSymbol thisSym = + new VarSymbol(FINAL | HASINIT, names._this, c.type, c); + thisSym.pos = Position.FIRSTPOS; + env.info.scope.enter(thisSym); + if (ct.supertype_field.tag == CLASS) { + VarSymbol superSym = + new VarSymbol(FINAL | HASINIT, names._super, + ct.supertype_field, c); + superSym.pos = Position.FIRSTPOS; + env.info.scope.enter(superSym); + } + } + + // check that no package exists with same fully qualified name, + // but admit classes in the unnamed package which have the same + // name as a top-level package. + if (checkClash && + c.owner.kind == PCK && c.owner != syms.unnamedPackage && + reader.packageExists(c.fullname)) + { + log.error(tree.pos, "clash.with.pkg.of.same.name", Kinds.kindName(sym), c); + } + + } catch (CompletionFailure ex) { + chk.completionError(tree.pos(), ex); + } finally { + log.useSource(prev); + } + + // Enter all member fields and methods of a set of half completed + // classes in a second phase. + if (wasFirst) { + try { + while (halfcompleted.nonEmpty()) { + finish(halfcompleted.next()); + } + } finally { + isFirst = true; + } + + // commit pending annotations + annotate.flush(); + } + } + + private Env baseEnv(JCClassDecl tree, Env env) { + Scope baseScope = new Scope(tree.sym); + //import already entered local classes into base scope + for (Scope.Entry e = env.outer.info.scope.elems ; e != null ; e = e.sibling) { + if (e.sym.isLocal()) { + baseScope.enter(e.sym); + } + } + //import current type-parameters into base scope + if (tree.typarams != null) + for (List typarams = tree.typarams; + typarams.nonEmpty(); + typarams = typarams.tail) + baseScope.enter(typarams.head.type.tsym); + Env outer = env.outer; // the base clause can't see members of this class + Env localEnv = outer.dup(tree, outer.info.dup(baseScope)); + localEnv.baseClause = true; + localEnv.outer = outer; + localEnv.info.isSelfCall = false; + return localEnv; + } + + /** Enter member fields and methods of a class + * @param env the environment current for the class block. + */ + private void finish(Env env) { + JavaFileObject prev = log.useSource(env.toplevel.sourcefile); + try { + JCClassDecl tree = (JCClassDecl)env.tree; + finishClass(tree, env); + } finally { + log.useSource(prev); + } + } + + /** Generate a base clause for an enum type. + * @param pos The position for trees and diagnostics, if any + * @param c The class symbol of the enum + */ + private JCExpression enumBase(int pos, ClassSymbol c) { + JCExpression result = make.at(pos). + TypeApply(make.QualIdent(syms.enumSym), + List.of(make.Type(c.type))); + return result; + } + + Type modelMissingTypes(Type t, final JCExpression tree, final boolean interfaceExpected) { + if (t.tag != ERROR) + return t; + + return new ErrorType(((ErrorType) t).getOriginalType(), t.tsym) { + private Type modelType; + + @Override + public Type getModelType() { + if (modelType == null) + modelType = new Synthesizer(getOriginalType(), interfaceExpected).visit(tree); + return modelType; + } + }; + } + // where + private class Synthesizer extends JCTree.Visitor { + Type originalType; + boolean interfaceExpected; + List synthesizedSymbols = List.nil(); + Type result; + + Synthesizer(Type originalType, boolean interfaceExpected) { + this.originalType = originalType; + this.interfaceExpected = interfaceExpected; + } + + Type visit(JCTree tree) { + tree.accept(this); + return result; + } + + List visit(List trees) { + ListBuffer lb = new ListBuffer(); + for (JCTree t: trees) + lb.append(visit(t)); + return lb.toList(); + } + + @Override + public void visitTree(JCTree tree) { + result = syms.errType; + } + + @Override + public void visitIdent(JCIdent tree) { + if (tree.type.tag != ERROR) { + result = tree.type; + } else { + result = synthesizeClass(tree.name, syms.unnamedPackage).type; + } + } + + @Override + public void visitSelect(JCFieldAccess tree) { + if (tree.type.tag != ERROR) { + result = tree.type; + } else { + Type selectedType; + boolean prev = interfaceExpected; + try { + interfaceExpected = false; + selectedType = visit(tree.selected); + } finally { + interfaceExpected = prev; + } + ClassSymbol c = synthesizeClass(tree.name, selectedType.tsym); + result = c.type; + } + } + + @Override + public void visitTypeApply(JCTypeApply tree) { + if (tree.type.tag != ERROR) { + result = tree.type; + } else { + ClassType clazzType = (ClassType) visit(tree.clazz); + if (synthesizedSymbols.contains(clazzType.tsym)) + synthesizeTyparams((ClassSymbol) clazzType.tsym, tree.arguments.size()); + final List actuals = visit(tree.arguments); + result = new ErrorType(tree.type, clazzType.tsym) { + @Override + public List getTypeArguments() { + return actuals; + } + }; + } + } + + ClassSymbol synthesizeClass(Name name, Symbol owner) { + int flags = interfaceExpected ? INTERFACE : 0; + ClassSymbol c = new ClassSymbol(flags, name, owner); + c.members_field = new Scope.ErrorScope(c); + c.type = new ErrorType(originalType, c) { + @Override + public List getTypeArguments() { + return typarams_field; + } + }; + synthesizedSymbols = synthesizedSymbols.prepend(c); + return c; + } + + void synthesizeTyparams(ClassSymbol sym, int n) { + ClassType ct = (ClassType) sym.type; + Assert.check(ct.typarams_field.isEmpty()); + if (n == 1) { + TypeVar v = new TypeVar(names.fromString("T"), sym, syms.botType); + ct.typarams_field = ct.typarams_field.prepend(v); + } else { + for (int i = n; i > 0; i--) { + TypeVar v = new TypeVar(names.fromString("T" + i), sym, syms.botType); + ct.typarams_field = ct.typarams_field.prepend(v); + } + } + } + } + + +/* *************************************************************************** + * tree building + ****************************************************************************/ + + /** Generate default constructor for given class. For classes different + * from java.lang.Object, this is: + * + * c(argtype_0 x_0, ..., argtype_n x_n) throws thrown { + * super(x_0, ..., x_n) + * } + * + * or, if based == true: + * + * c(argtype_0 x_0, ..., argtype_n x_n) throws thrown { + * x_0.super(x_1, ..., x_n) + * } + * + * @param make The tree factory. + * @param c The class owning the default constructor. + * @param argtypes The parameter types of the constructor. + * @param thrown The thrown exceptions of the constructor. + * @param based Is first parameter a this$n? + */ + JCTree DefaultConstructor(TreeMaker make, + ClassSymbol c, + List typarams, + List argtypes, + List thrown, + long flags, + boolean based) { + List params = make.Params(argtypes, syms.noSymbol); + List stats = List.nil(); + if (c.type != syms.objectType) + stats = stats.prepend(SuperCall(make, typarams, params, based)); + if ((c.flags() & ENUM) != 0 && + (types.supertype(c.type).tsym == syms.enumSym || + target.compilerBootstrap(c))) { + // constructors of true enums are private + flags = (flags & ~AccessFlags) | PRIVATE | GENERATEDCONSTR; + } else + flags |= (c.flags() & AccessFlags) | GENERATEDCONSTR; + if (c.name.isEmpty()) flags |= ANONCONSTR; + JCTree result = make.MethodDef( + make.Modifiers(flags), + names.init, + null, + make.TypeParams(typarams), + params, + make.Types(thrown), + make.Block(0, stats), + null); + return result; + } + + /** Generate call to superclass constructor. This is: + * + * super(id_0, ..., id_n) + * + * or, if based == true + * + * id_0.super(id_1,...,id_n) + * + * where id_0, ..., id_n are the names of the given parameters. + * + * @param make The tree factory + * @param params The parameters that need to be passed to super + * @param typarams The type parameters that need to be passed to super + * @param based Is first parameter a this$n? + */ + JCExpressionStatement SuperCall(TreeMaker make, + List typarams, + List params, + boolean based) { + JCExpression meth; + if (based) { + meth = make.Select(make.Ident(params.head), names._super); + params = params.tail; + } else { + meth = make.Ident(names._super); + } + List typeargs = typarams.nonEmpty() ? make.Types(typarams) : null; + return make.Exec(make.Apply(typeargs, meth, make.Idents(params))); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/comp/Resolve.java b/douyu-javac/src/main/java/com/sun/tools/javac/comp/Resolve.java new file mode 100644 index 0000000..9e33361 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/comp/Resolve.java @@ -0,0 +1,2376 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.comp; + +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.jvm.*; +import com.sun.tools.javac.tree.*; +import com.sun.tools.javac.api.Formattable.LocalizedString; +import static com.sun.tools.javac.comp.Resolve.MethodResolutionPhase.*; + +import com.sun.tools.javac.code.Type.*; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.tree.JCTree.*; + +import static com.sun.tools.javac.code.Flags.*; +import static com.sun.tools.javac.code.Kinds.*; +import static com.sun.tools.javac.code.TypeTags.*; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType; +import javax.lang.model.element.ElementVisitor; + +import java.util.Map; +import java.util.Set; +import java.util.HashMap; +import java.util.HashSet; + +/** Helper class for name resolution, used mostly by the attribution phase. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Resolve { + protected static final Context.Key resolveKey = + new Context.Key(); + + Names names; + Log log; + Symtab syms; + Check chk; + Infer infer; + ClassReader reader; + TreeInfo treeinfo; + Types types; + JCDiagnostic.Factory diags; + public final boolean boxingEnabled; // = source.allowBoxing(); + public final boolean varargsEnabled; // = source.allowVarargs(); + public final boolean allowMethodHandles; + private final boolean debugResolve; + + Scope polymorphicSignatureScope; + + public static Resolve instance(Context context) { + Resolve instance = context.get(resolveKey); + if (instance == null) + instance = new Resolve(context); + return instance; + } + + protected Resolve(Context context) { + context.put(resolveKey, this); + syms = Symtab.instance(context); + + varNotFound = new + SymbolNotFoundError(ABSENT_VAR); + wrongMethod = new + InapplicableSymbolError(syms.errSymbol); + wrongMethods = new + InapplicableSymbolsError(syms.errSymbol); + methodNotFound = new + SymbolNotFoundError(ABSENT_MTH); + typeNotFound = new + SymbolNotFoundError(ABSENT_TYP); + + names = Names.instance(context); + log = Log.instance(context); + chk = Check.instance(context); + infer = Infer.instance(context); + reader = ClassReader.instance(context); + treeinfo = TreeInfo.instance(context); + types = Types.instance(context); + diags = JCDiagnostic.Factory.instance(context); + Source source = Source.instance(context); + boxingEnabled = source.allowBoxing(); + varargsEnabled = source.allowVarargs(); + Options options = Options.instance(context); + debugResolve = options.isSet("debugresolve"); + Target target = Target.instance(context); + allowMethodHandles = target.hasMethodHandles(); + polymorphicSignatureScope = new Scope(syms.noSymbol); + + inapplicableMethodException = new InapplicableMethodException(diags); + } + + /** error symbols, which are returned when resolution fails + */ + final SymbolNotFoundError varNotFound; + final InapplicableSymbolError wrongMethod; + final InapplicableSymbolsError wrongMethods; + final SymbolNotFoundError methodNotFound; + final SymbolNotFoundError typeNotFound; + +/* ************************************************************************ + * Identifier resolution + *************************************************************************/ + + /** An environment is "static" if its static level is greater than + * the one of its outer environment + */ + static boolean isStatic(Env env) { + return env.info.staticLevel > env.outer.info.staticLevel; + } + + /** An environment is an "initializer" if it is a constructor or + * an instance initializer. + */ + static boolean isInitializer(Env env) { + Symbol owner = env.info.scope.owner; + return owner.isConstructor() || + owner.owner.kind == TYP && + (owner.kind == VAR || + owner.kind == MTH && (owner.flags() & BLOCK) != 0) && + (owner.flags() & STATIC) == 0; + } + + /** Is class accessible in given evironment? + * @param env The current environment. + * @param c The class whose accessibility is checked. + */ + public boolean isAccessible(Env env, TypeSymbol c) { + return isAccessible(env, c, false); + } + + public boolean isAccessible(Env env, TypeSymbol c, boolean checkInner) { + boolean isAccessible = false; + switch ((short)(c.flags() & AccessFlags)) { + case PRIVATE: + isAccessible = + env.enclClass.sym.outermostClass() == + c.owner.outermostClass(); + break; + case 0: + isAccessible = + env.toplevel.packge == c.owner // fast special case + || + env.toplevel.packge == c.packge() + || + // Hack: this case is added since synthesized default constructors + // of anonymous classes should be allowed to access + // classes which would be inaccessible otherwise. + env.enclMethod != null && + (env.enclMethod.mods.flags & ANONCONSTR) != 0; + break; + default: // error recovery + case PUBLIC: + isAccessible = true; + break; + case PROTECTED: + isAccessible = + env.toplevel.packge == c.owner // fast special case + || + env.toplevel.packge == c.packge() + || + isInnerSubClass(env.enclClass.sym, c.owner); + break; + } + return (checkInner == false || c.type.getEnclosingType() == Type.noType) ? + isAccessible : + isAccessible && isAccessible(env, c.type.getEnclosingType(), checkInner); + } + //where + /** Is given class a subclass of given base class, or an inner class + * of a subclass? + * Return null if no such class exists. + * @param c The class which is the subclass or is contained in it. + * @param base The base class + */ + private boolean isInnerSubClass(ClassSymbol c, Symbol base) { + while (c != null && !c.isSubClass(base, types)) { + c = c.owner.enclClass(); + } + return c != null; + } + + boolean isAccessible(Env env, Type t) { + return isAccessible(env, t, false); + } + + boolean isAccessible(Env env, Type t, boolean checkInner) { + return (t.tag == ARRAY) + ? isAccessible(env, types.elemtype(t)) + : isAccessible(env, t.tsym, checkInner); + } + + /** Is symbol accessible as a member of given type in given evironment? + * @param env The current environment. + * @param site The type of which the tested symbol is regarded + * as a member. + * @param sym The symbol. + */ + public boolean isAccessible(Env env, Type site, Symbol sym) { + return isAccessible(env, site, sym, false); + } + public boolean isAccessible(Env env, Type site, Symbol sym, boolean checkInner) { + if (sym.name == names.init && sym.owner != site.tsym) return false; + switch ((short)(sym.flags() & AccessFlags)) { + case PRIVATE: + return + (env.enclClass.sym == sym.owner // fast special case + || + env.enclClass.sym.outermostClass() == + sym.owner.outermostClass()) + && + sym.isInheritedIn(site.tsym, types); + case 0: + return + (env.toplevel.packge == sym.owner.owner // fast special case + || + env.toplevel.packge == sym.packge()) + && + isAccessible(env, site, checkInner) + && + sym.isInheritedIn(site.tsym, types) + && + notOverriddenIn(site, sym); + case PROTECTED: + return + (env.toplevel.packge == sym.owner.owner // fast special case + || + env.toplevel.packge == sym.packge() + || + isProtectedAccessible(sym, env.enclClass.sym, site) + || + // OK to select instance method or field from 'super' or type name + // (but type names should be disallowed elsewhere!) + env.info.selectSuper && (sym.flags() & STATIC) == 0 && sym.kind != TYP) + && + isAccessible(env, site, checkInner) + && + notOverriddenIn(site, sym); + default: // this case includes erroneous combinations as well + return isAccessible(env, site, checkInner) && notOverriddenIn(site, sym); + } + } + //where + /* `sym' is accessible only if not overridden by + * another symbol which is a member of `site' + * (because, if it is overridden, `sym' is not strictly + * speaking a member of `site'). A polymorphic signature method + * cannot be overridden (e.g. MH.invokeExact(Object[])). + */ + private boolean notOverriddenIn(Type site, Symbol sym) { + if (sym.kind != MTH || sym.isConstructor() || sym.isStatic()) + return true; + else { + Symbol s2 = ((MethodSymbol)sym).implementation(site.tsym, types, true); + return (s2 == null || s2 == sym || sym.owner == s2.owner || + s2.isPolymorphicSignatureGeneric() || + !types.isSubSignature(types.memberType(site, s2), types.memberType(site, sym))); + } + } + //where + /** Is given protected symbol accessible if it is selected from given site + * and the selection takes place in given class? + * @param sym The symbol with protected access + * @param c The class where the access takes place + * @site The type of the qualifier + */ + private + boolean isProtectedAccessible(Symbol sym, ClassSymbol c, Type site) { + while (c != null && + !(c.isSubClass(sym.owner, types) && + (c.flags() & INTERFACE) == 0 && + // In JLS 2e 6.6.2.1, the subclass restriction applies + // only to instance fields and methods -- types are excluded + // regardless of whether they are declared 'static' or not. + ((sym.flags() & STATIC) != 0 || sym.kind == TYP || site.tsym.isSubClass(c, types)))) + c = c.owner.enclClass(); + return c != null; + } + + /** Try to instantiate the type of a method so that it fits + * given type arguments and argument types. If succesful, return + * the method's instantiated type, else return null. + * The instantiation will take into account an additional leading + * formal parameter if the method is an instance method seen as a member + * of un underdetermined site In this case, we treat site as an additional + * parameter and the parameters of the class containing the method as + * additional type variables that get instantiated. + * + * @param env The current environment + * @param site The type of which the method is a member. + * @param m The method symbol. + * @param argtypes The invocation's given value arguments. + * @param typeargtypes The invocation's given type arguments. + * @param allowBoxing Allow boxing conversions of arguments. + * @param useVarargs Box trailing arguments into an array for varargs. + */ + Type rawInstantiate(Env env, + Type site, + Symbol m, + List argtypes, + List typeargtypes, + boolean allowBoxing, + boolean useVarargs, + Warner warn) + throws Infer.InferenceException { + boolean polymorphicSignature = m.isPolymorphicSignatureGeneric() && allowMethodHandles; + if (useVarargs && (m.flags() & VARARGS) == 0) + throw inapplicableMethodException.setMessage(); + Type mt = types.memberType(site, m); + + // tvars is the list of formal type variables for which type arguments + // need to inferred. + List tvars = null; + if (env.info.tvars != null) { + tvars = types.newInstances(env.info.tvars); + mt = types.subst(mt, env.info.tvars, tvars); + } + if (typeargtypes == null) typeargtypes = List.nil(); + if (mt.tag != FORALL && typeargtypes.nonEmpty()) { + // This is not a polymorphic method, but typeargs are supplied + // which is fine, see JLS 15.12.2.1 + } else if (mt.tag == FORALL && typeargtypes.nonEmpty()) { + ForAll pmt = (ForAll) mt; + if (typeargtypes.length() != pmt.tvars.length()) + throw inapplicableMethodException.setMessage("arg.length.mismatch"); // not enough args + // Check type arguments are within bounds + List formals = pmt.tvars; + List actuals = typeargtypes; + while (formals.nonEmpty() && actuals.nonEmpty()) { + List bounds = types.subst(types.getBounds((TypeVar)formals.head), + pmt.tvars, typeargtypes); + for (; bounds.nonEmpty(); bounds = bounds.tail) + if (!types.isSubtypeUnchecked(actuals.head, bounds.head, warn)) + throw inapplicableMethodException.setMessage("explicit.param.do.not.conform.to.bounds",actuals.head, bounds); + formals = formals.tail; + actuals = actuals.tail; + } + mt = types.subst(pmt.qtype, pmt.tvars, typeargtypes); + } else if (mt.tag == FORALL) { + ForAll pmt = (ForAll) mt; + List tvars1 = types.newInstances(pmt.tvars); + tvars = tvars.appendList(tvars1); + mt = types.subst(pmt.qtype, pmt.tvars, tvars1); + } + + // find out whether we need to go the slow route via infer + boolean instNeeded = tvars.tail != null || /*inlined: tvars.nonEmpty()*/ + polymorphicSignature; + for (List l = argtypes; + l.tail != null/*inlined: l.nonEmpty()*/ && !instNeeded; + l = l.tail) { + if (l.head.tag == FORALL) instNeeded = true; + } + + if (instNeeded) + return polymorphicSignature ? + infer.instantiatePolymorphicSignatureInstance(env, site, m.name, (MethodSymbol)m, argtypes) : + infer.instantiateMethod(env, + tvars, + (MethodType)mt, + m, + argtypes, + allowBoxing, + useVarargs, + warn); + + checkRawArgumentsAcceptable(env, argtypes, mt.getParameterTypes(), + allowBoxing, useVarargs, warn); + return mt; + } + + /** Same but returns null instead throwing a NoInstanceException + */ + Type instantiate(Env env, + Type site, + Symbol m, + List argtypes, + List typeargtypes, + boolean allowBoxing, + boolean useVarargs, + Warner warn) { + try { + return rawInstantiate(env, site, m, argtypes, typeargtypes, + allowBoxing, useVarargs, warn); + } catch (InapplicableMethodException ex) { + return null; + } + } + + /** Check if a parameter list accepts a list of args. + */ + boolean argumentsAcceptable(Env env, + List argtypes, + List formals, + boolean allowBoxing, + boolean useVarargs, + Warner warn) { + try { + checkRawArgumentsAcceptable(env, argtypes, formals, allowBoxing, useVarargs, warn); + return true; + } catch (InapplicableMethodException ex) { + return false; + } + } + void checkRawArgumentsAcceptable(Env env, + List argtypes, + List formals, + boolean allowBoxing, + boolean useVarargs, + Warner warn) { + Type varargsFormal = useVarargs ? formals.last() : null; + if (varargsFormal == null && + argtypes.size() != formals.size()) { + throw inapplicableMethodException.setMessage("arg.length.mismatch"); // not enough args + } + + while (argtypes.nonEmpty() && formals.head != varargsFormal) { + boolean works = allowBoxing + ? types.isConvertible(argtypes.head, formals.head, warn) + : types.isSubtypeUnchecked(argtypes.head, formals.head, warn); + if (!works) + throw inapplicableMethodException.setMessage("no.conforming.assignment.exists", + argtypes.head, + formals.head); + argtypes = argtypes.tail; + formals = formals.tail; + } + + if (formals.head != varargsFormal) + throw inapplicableMethodException.setMessage("arg.length.mismatch"); // not enough args + + if (useVarargs) { + Type elt = types.elemtype(varargsFormal); + while (argtypes.nonEmpty()) { + if (!types.isConvertible(argtypes.head, elt, warn)) + throw inapplicableMethodException.setMessage("varargs.argument.mismatch", + argtypes.head, + elt); + argtypes = argtypes.tail; + } + //check varargs element type accessibility + if (!isAccessible(env, elt)) { + Symbol location = env.enclClass.sym; + throw inapplicableMethodException.setMessage("inaccessible.varargs.type", + elt, + Kinds.kindName(location), + location); + } + } + return; + } + // where + public static class InapplicableMethodException extends RuntimeException { + private static final long serialVersionUID = 0; + + JCDiagnostic diagnostic; + JCDiagnostic.Factory diags; + + InapplicableMethodException(JCDiagnostic.Factory diags) { + this.diagnostic = null; + this.diags = diags; + } + InapplicableMethodException setMessage() { + this.diagnostic = null; + return this; + } + InapplicableMethodException setMessage(String key) { + this.diagnostic = key != null ? diags.fragment(key) : null; + return this; + } + InapplicableMethodException setMessage(String key, Object... args) { + this.diagnostic = key != null ? diags.fragment(key, args) : null; + return this; + } + InapplicableMethodException setMessage(JCDiagnostic diag) { + this.diagnostic = diag; + return this; + } + + public JCDiagnostic getDiagnostic() { + return diagnostic; + } + } + private final InapplicableMethodException inapplicableMethodException; + +/* *************************************************************************** + * Symbol lookup + * the following naming conventions for arguments are used + * + * env is the environment where the symbol was mentioned + * site is the type of which the symbol is a member + * name is the symbol's name + * if no arguments are given + * argtypes are the value arguments, if we search for a method + * + * If no symbol was found, a ResolveError detailing the problem is returned. + ****************************************************************************/ + + /** Find field. Synthetic fields are always skipped. + * @param env The current environment. + * @param site The original type from where the selection takes place. + * @param name The name of the field. + * @param c The class to search for the field. This is always + * a superclass or implemented interface of site's class. + */ + Symbol findField(Env env, + Type site, + Name name, + TypeSymbol c) { + while (c.type.tag == TYPEVAR) + c = c.type.getUpperBound().tsym; + Symbol bestSoFar = varNotFound; + Symbol sym; + Scope.Entry e = c.members().lookup(name); + while (e.scope != null) { + if (e.sym.kind == VAR && (e.sym.flags_field & SYNTHETIC) == 0) { + return isAccessible(env, site, e.sym) + ? e.sym : new AccessError(env, site, e.sym); + } + e = e.next(); + } + Type st = types.supertype(c.type); + if (st != null && (st.tag == CLASS || st.tag == TYPEVAR)) { + sym = findField(env, site, name, st.tsym); + if (sym.kind < bestSoFar.kind) bestSoFar = sym; + } + for (List l = types.interfaces(c.type); + bestSoFar.kind != AMBIGUOUS && l.nonEmpty(); + l = l.tail) { + sym = findField(env, site, name, l.head.tsym); + if (bestSoFar.kind < AMBIGUOUS && sym.kind < AMBIGUOUS && + sym.owner != bestSoFar.owner) + bestSoFar = new AmbiguityError(bestSoFar, sym); + else if (sym.kind < bestSoFar.kind) + bestSoFar = sym; + } + return bestSoFar; + } + + /** Resolve a field identifier, throw a fatal error if not found. + * @param pos The position to use for error reporting. + * @param env The environment current at the method invocation. + * @param site The type of the qualifying expression, in which + * identifier is searched. + * @param name The identifier's name. + */ + public VarSymbol resolveInternalField(DiagnosticPosition pos, Env env, + Type site, Name name) { + Symbol sym = findField(env, site, name, site.tsym); + if (sym.kind == VAR) return (VarSymbol)sym; + else throw new FatalError( + diags.fragment("fatal.err.cant.locate.field", + name)); + } + + /** Find unqualified variable or field with given name. + * Synthetic fields always skipped. + * @param env The current environment. + * @param name The name of the variable or field. + */ + Symbol findVar(Env env, Name name) { + Symbol bestSoFar = varNotFound; + Symbol sym; + Env env1 = env; + boolean staticOnly = false; + while (env1.outer != null) { + if (isStatic(env1)) staticOnly = true; + Scope.Entry e = env1.info.scope.lookup(name); + while (e.scope != null && + (e.sym.kind != VAR || + (e.sym.flags_field & SYNTHETIC) != 0)) + e = e.next(); + sym = (e.scope != null) + ? e.sym + : findField( + env1, env1.enclClass.sym.type, name, env1.enclClass.sym); + if (sym.exists()) { + if (staticOnly && + sym.kind == VAR && + sym.owner.kind == TYP && + (sym.flags() & STATIC) == 0) + return new StaticError(sym); + else + return sym; + } else if (sym.kind < bestSoFar.kind) { + bestSoFar = sym; + } + + if ((env1.enclClass.sym.flags() & STATIC) != 0) staticOnly = true; + env1 = env1.outer; + } + + sym = findField(env, syms.predefClass.type, name, syms.predefClass); + if (sym.exists()) + return sym; + if (bestSoFar.exists()) + return bestSoFar; + + Scope.Entry e = env.toplevel.namedImportScope.lookup(name); + for (; e.scope != null; e = e.next()) { + sym = e.sym; + Type origin = e.getOrigin().owner.type; + if (sym.kind == VAR) { + if (e.sym.owner.type != origin) + sym = sym.clone(e.getOrigin().owner); + return isAccessible(env, origin, sym) + ? sym : new AccessError(env, origin, sym); + } + } + + Symbol origin = null; + e = env.toplevel.starImportScope.lookup(name); + for (; e.scope != null; e = e.next()) { + sym = e.sym; + if (sym.kind != VAR) + continue; + // invariant: sym.kind == VAR + if (bestSoFar.kind < AMBIGUOUS && sym.owner != bestSoFar.owner) + return new AmbiguityError(bestSoFar, sym); + else if (bestSoFar.kind >= VAR) { + origin = e.getOrigin().owner; + bestSoFar = isAccessible(env, origin.type, sym) + ? sym : new AccessError(env, origin.type, sym); + } + } + if (bestSoFar.kind == VAR && bestSoFar.owner.type != origin.type) + return bestSoFar.clone(origin); + else + return bestSoFar; + } + + Warner noteWarner = new Warner(); + + /** Select the best method for a call site among two choices. + * @param env The current environment. + * @param site The original type from where the + * selection takes place. + * @param argtypes The invocation's value arguments, + * @param typeargtypes The invocation's type arguments, + * @param sym Proposed new best match. + * @param bestSoFar Previously found best match. + * @param allowBoxing Allow boxing conversions of arguments. + * @param useVarargs Box trailing arguments into an array for varargs. + */ + @SuppressWarnings("fallthrough") + Symbol selectBest(Env env, + Type site, + List argtypes, + List typeargtypes, + Symbol sym, + Symbol bestSoFar, + boolean allowBoxing, + boolean useVarargs, + boolean operator) { + if (sym.kind == ERR) return bestSoFar; + if (!sym.isInheritedIn(site.tsym, types)) return bestSoFar; + Assert.check(sym.kind < AMBIGUOUS); + try { + rawInstantiate(env, site, sym, argtypes, typeargtypes, + allowBoxing, useVarargs, Warner.noWarnings); + } catch (InapplicableMethodException ex) { + switch (bestSoFar.kind) { + case ABSENT_MTH: + return wrongMethod.setWrongSym(sym, ex.getDiagnostic()); + case WRONG_MTH: + wrongMethods.addCandidate(currentStep, wrongMethod.sym, wrongMethod.explanation); + case WRONG_MTHS: + return wrongMethods.addCandidate(currentStep, sym, ex.getDiagnostic()); + default: + return bestSoFar; + } + } + if (!isAccessible(env, site, sym)) { + return (bestSoFar.kind == ABSENT_MTH) + ? new AccessError(env, site, sym) + : bestSoFar; + } + return (bestSoFar.kind > AMBIGUOUS) + ? sym + : mostSpecific(sym, bestSoFar, env, site, + allowBoxing && operator, useVarargs); + } + + /* Return the most specific of the two methods for a call, + * given that both are accessible and applicable. + * @param m1 A new candidate for most specific. + * @param m2 The previous most specific candidate. + * @param env The current environment. + * @param site The original type from where the selection + * takes place. + * @param allowBoxing Allow boxing conversions of arguments. + * @param useVarargs Box trailing arguments into an array for varargs. + */ + Symbol mostSpecific(Symbol m1, + Symbol m2, + Env env, + final Type site, + boolean allowBoxing, + boolean useVarargs) { + switch (m2.kind) { + case MTH: + if (m1 == m2) return m1; + boolean m1SignatureMoreSpecific = signatureMoreSpecific(env, site, m1, m2, allowBoxing, useVarargs); + boolean m2SignatureMoreSpecific = signatureMoreSpecific(env, site, m2, m1, allowBoxing, useVarargs); + if (m1SignatureMoreSpecific && m2SignatureMoreSpecific) { + Type mt1 = types.memberType(site, m1); + Type mt2 = types.memberType(site, m2); + if (!types.overrideEquivalent(mt1, mt2)) + return ambiguityError(m1, m2); + + // same signature; select (a) the non-bridge method, or + // (b) the one that overrides the other, or (c) the concrete + // one, or (d) merge both abstract signatures + if ((m1.flags() & BRIDGE) != (m2.flags() & BRIDGE)) + return ((m1.flags() & BRIDGE) != 0) ? m2 : m1; + + // if one overrides or hides the other, use it + TypeSymbol m1Owner = (TypeSymbol)m1.owner; + TypeSymbol m2Owner = (TypeSymbol)m2.owner; + if (types.asSuper(m1Owner.type, m2Owner) != null && + ((m1.owner.flags_field & INTERFACE) == 0 || + (m2.owner.flags_field & INTERFACE) != 0) && + m1.overrides(m2, m1Owner, types, false)) + return m1; + if (types.asSuper(m2Owner.type, m1Owner) != null && + ((m2.owner.flags_field & INTERFACE) == 0 || + (m1.owner.flags_field & INTERFACE) != 0) && + m2.overrides(m1, m2Owner, types, false)) + return m2; + boolean m1Abstract = (m1.flags() & ABSTRACT) != 0; + boolean m2Abstract = (m2.flags() & ABSTRACT) != 0; + if (m1Abstract && !m2Abstract) return m2; + if (m2Abstract && !m1Abstract) return m1; + // both abstract or both concrete + if (!m1Abstract && !m2Abstract) + return ambiguityError(m1, m2); + // check that both signatures have the same erasure + if (!types.isSameTypes(m1.erasure(types).getParameterTypes(), + m2.erasure(types).getParameterTypes())) + return ambiguityError(m1, m2); + // both abstract, neither overridden; merge throws clause and result type + Symbol mostSpecific; + if (types.returnTypeSubstitutable(mt1, mt2)) + mostSpecific = m1; + else if (types.returnTypeSubstitutable(mt2, mt1)) + mostSpecific = m2; + else { + // Theoretically, this can't happen, but it is possible + // due to error recovery or mixing incompatible class files + return ambiguityError(m1, m2); + } + List allThrown = chk.intersect(mt1.getThrownTypes(), mt2.getThrownTypes()); + Type newSig = types.createMethodTypeWithThrown(mostSpecific.type, allThrown); + MethodSymbol result = new MethodSymbol( + mostSpecific.flags(), + mostSpecific.name, + newSig, + mostSpecific.owner) { + @Override + public MethodSymbol implementation(TypeSymbol origin, Types types, boolean checkResult) { + if (origin == site.tsym) + return this; + else + return super.implementation(origin, types, checkResult); + } + }; + return result; + } + if (m1SignatureMoreSpecific) return m1; + if (m2SignatureMoreSpecific) return m2; + return ambiguityError(m1, m2); + case AMBIGUOUS: + AmbiguityError e = (AmbiguityError)m2; + Symbol err1 = mostSpecific(m1, e.sym, env, site, allowBoxing, useVarargs); + Symbol err2 = mostSpecific(m1, e.sym2, env, site, allowBoxing, useVarargs); + if (err1 == err2) return err1; + if (err1 == e.sym && err2 == e.sym2) return m2; + if (err1 instanceof AmbiguityError && + err2 instanceof AmbiguityError && + ((AmbiguityError)err1).sym == ((AmbiguityError)err2).sym) + return ambiguityError(m1, m2); + else + return ambiguityError(err1, err2); + default: + throw new AssertionError(); + } + } + //where + private boolean signatureMoreSpecific(Env env, Type site, Symbol m1, Symbol m2, boolean allowBoxing, boolean useVarargs) { + noteWarner.clear(); + Type mtype1 = types.memberType(site, adjustVarargs(m1, m2, useVarargs)); + Type mtype2 = instantiate(env, site, adjustVarargs(m2, m1, useVarargs), + types.lowerBoundArgtypes(mtype1), null, + allowBoxing, false, noteWarner); + return mtype2 != null && + !noteWarner.hasLint(Lint.LintCategory.UNCHECKED); + } + //where + private Symbol adjustVarargs(Symbol to, Symbol from, boolean useVarargs) { + List fromArgs = from.type.getParameterTypes(); + List toArgs = to.type.getParameterTypes(); + if (useVarargs && + (from.flags() & VARARGS) != 0 && + (to.flags() & VARARGS) != 0) { + Type varargsTypeFrom = fromArgs.last(); + Type varargsTypeTo = toArgs.last(); + ListBuffer args = ListBuffer.lb(); + if (toArgs.length() < fromArgs.length()) { + //if we are checking a varargs method 'from' against another varargs + //method 'to' (where arity of 'to' < arity of 'from') then expand signature + //of 'to' to 'fit' arity of 'from' (this means adding fake formals to 'to' + //until 'to' signature has the same arity as 'from') + while (fromArgs.head != varargsTypeFrom) { + args.append(toArgs.head == varargsTypeTo ? types.elemtype(varargsTypeTo) : toArgs.head); + fromArgs = fromArgs.tail; + toArgs = toArgs.head == varargsTypeTo ? + toArgs : + toArgs.tail; + } + } else { + //formal argument list is same as original list where last + //argument (array type) is removed + args.appendList(toArgs.reverse().tail.reverse()); + } + //append varargs element type as last synthetic formal + args.append(types.elemtype(varargsTypeTo)); + Type mtype = types.createMethodTypeWithParameters(to.type, args.toList()); + return new MethodSymbol(to.flags_field & ~VARARGS, to.name, mtype, to.owner); + } else { + return to; + } + } + //where + Symbol ambiguityError(Symbol m1, Symbol m2) { + if (((m1.flags() | m2.flags()) & CLASH) != 0) { + return (m1.flags() & CLASH) == 0 ? m1 : m2; + } else { + return new AmbiguityError(m1, m2); + } + } + + /** Find best qualified method matching given name, type and value + * arguments. + * @param env The current environment. + * @param site The original type from where the selection + * takes place. + * @param name The method's name. + * @param argtypes The method's value arguments. + * @param typeargtypes The method's type arguments + * @param allowBoxing Allow boxing conversions of arguments. + * @param useVarargs Box trailing arguments into an array for varargs. + */ + Symbol findMethod(Env env, + Type site, + Name name, + List argtypes, + List typeargtypes, + boolean allowBoxing, + boolean useVarargs, + boolean operator) { + Symbol bestSoFar = methodNotFound; + return findMethod(env, + site, + name, + argtypes, + typeargtypes, + site.tsym.type, + true, + bestSoFar, + allowBoxing, + useVarargs, + operator, + new HashSet()); + } + // where + private Symbol findMethod(Env env, + Type site, + Name name, + List argtypes, + List typeargtypes, + Type intype, + boolean abstractok, + Symbol bestSoFar, + boolean allowBoxing, + boolean useVarargs, + boolean operator, + Set seen) { + for (Type ct = intype; ct.tag == CLASS || ct.tag == TYPEVAR; ct = types.supertype(ct)) { + while (ct.tag == TYPEVAR) + ct = ct.getUpperBound(); + ClassSymbol c = (ClassSymbol)ct.tsym; + if (!seen.add(c)) return bestSoFar; + if ((c.flags() & (ABSTRACT | INTERFACE | ENUM)) == 0) + abstractok = false; + for (Scope.Entry e = c.members().lookup(name); + e.scope != null; + e = e.next()) { + //- System.out.println(" e " + e.sym); + if (e.sym.kind == MTH && + (e.sym.flags_field & SYNTHETIC) == 0) { + bestSoFar = selectBest(env, site, argtypes, typeargtypes, + e.sym, bestSoFar, + allowBoxing, + useVarargs, + operator); + } + } + if (name == names.init) + break; + //- System.out.println(" - " + bestSoFar); + if (abstractok) { + Symbol concrete = methodNotFound; + if ((bestSoFar.flags() & ABSTRACT) == 0) + concrete = bestSoFar; + for (List l = types.interfaces(c.type); + l.nonEmpty(); + l = l.tail) { + bestSoFar = findMethod(env, site, name, argtypes, + typeargtypes, + l.head, abstractok, bestSoFar, + allowBoxing, useVarargs, operator, seen); + } + if (concrete != bestSoFar && + concrete.kind < ERR && bestSoFar.kind < ERR && + types.isSubSignature(concrete.type, bestSoFar.type)) + bestSoFar = concrete; + } + } + return bestSoFar; + } + + /** Find unqualified method matching given name, type and value arguments. + * @param env The current environment. + * @param name The method's name. + * @param argtypes The method's value arguments. + * @param typeargtypes The method's type arguments. + * @param allowBoxing Allow boxing conversions of arguments. + * @param useVarargs Box trailing arguments into an array for varargs. + */ + Symbol findFun(Env env, Name name, + List argtypes, List typeargtypes, + boolean allowBoxing, boolean useVarargs) { + Symbol bestSoFar = methodNotFound; + Symbol sym; + Env env1 = env; + boolean staticOnly = false; + while (env1.outer != null) { + if (isStatic(env1)) staticOnly = true; + sym = findMethod( + env1, env1.enclClass.sym.type, name, argtypes, typeargtypes, + allowBoxing, useVarargs, false); + if (sym.exists()) { + if (staticOnly && + sym.kind == MTH && + sym.owner.kind == TYP && + (sym.flags() & STATIC) == 0) return new StaticError(sym); + else return sym; + } else if (sym.kind < bestSoFar.kind) { + bestSoFar = sym; + } + if ((env1.enclClass.sym.flags() & STATIC) != 0) staticOnly = true; + env1 = env1.outer; + } + + sym = findMethod(env, syms.predefClass.type, name, argtypes, + typeargtypes, allowBoxing, useVarargs, false); + if (sym.exists()) + return sym; + + Scope.Entry e = env.toplevel.namedImportScope.lookup(name); + for (; e.scope != null; e = e.next()) { + sym = e.sym; + Type origin = e.getOrigin().owner.type; + if (sym.kind == MTH) { + if (e.sym.owner.type != origin) + sym = sym.clone(e.getOrigin().owner); + if (!isAccessible(env, origin, sym)) + sym = new AccessError(env, origin, sym); + bestSoFar = selectBest(env, origin, + argtypes, typeargtypes, + sym, bestSoFar, + allowBoxing, useVarargs, false); + } + } + if (bestSoFar.exists()) + return bestSoFar; + + e = env.toplevel.starImportScope.lookup(name); + for (; e.scope != null; e = e.next()) { + sym = e.sym; + Type origin = e.getOrigin().owner.type; + if (sym.kind == MTH) { + if (e.sym.owner.type != origin) + sym = sym.clone(e.getOrigin().owner); + if (!isAccessible(env, origin, sym)) + sym = new AccessError(env, origin, sym); + bestSoFar = selectBest(env, origin, + argtypes, typeargtypes, + sym, bestSoFar, + allowBoxing, useVarargs, false); + } + } + return bestSoFar; + } + + /** Load toplevel or member class with given fully qualified name and + * verify that it is accessible. + * @param env The current environment. + * @param name The fully qualified name of the class to be loaded. + */ + Symbol loadClass(Env env, Name name) { + try { + ClassSymbol c = reader.loadClass(name); + return isAccessible(env, c) ? c : new AccessError(c); + } catch (ClassReader.BadClassFile err) { + throw err; + } catch (CompletionFailure ex) { + return typeNotFound; + } + } + + /** Find qualified member type. + * @param env The current environment. + * @param site The original type from where the selection takes + * place. + * @param name The type's name. + * @param c The class to search for the member type. This is + * always a superclass or implemented interface of + * site's class. + */ + Symbol findMemberType(Env env, + Type site, + Name name, + TypeSymbol c) { + Symbol bestSoFar = typeNotFound; + Symbol sym; + Scope.Entry e = c.members().lookup(name); + while (e.scope != null) { + if (e.sym.kind == TYP) { + return isAccessible(env, site, e.sym) + ? e.sym + : new AccessError(env, site, e.sym); + } + e = e.next(); + } + Type st = types.supertype(c.type); + if (st != null && st.tag == CLASS) { + sym = findMemberType(env, site, name, st.tsym); + if (sym.kind < bestSoFar.kind) bestSoFar = sym; + } + for (List l = types.interfaces(c.type); + bestSoFar.kind != AMBIGUOUS && l.nonEmpty(); + l = l.tail) { + sym = findMemberType(env, site, name, l.head.tsym); + if (bestSoFar.kind < AMBIGUOUS && sym.kind < AMBIGUOUS && + sym.owner != bestSoFar.owner) + bestSoFar = new AmbiguityError(bestSoFar, sym); + else if (sym.kind < bestSoFar.kind) + bestSoFar = sym; + } + return bestSoFar; + } + + /** Find a global type in given scope and load corresponding class. + * @param env The current environment. + * @param scope The scope in which to look for the type. + * @param name The type's name. + */ + Symbol findGlobalType(Env env, Scope scope, Name name) { + Symbol bestSoFar = typeNotFound; + for (Scope.Entry e = scope.lookup(name); e.scope != null; e = e.next()) { + Symbol sym = loadClass(env, e.sym.flatName()); + if (bestSoFar.kind == TYP && sym.kind == TYP && + bestSoFar != sym) + return new AmbiguityError(bestSoFar, sym); + else if (sym.kind < bestSoFar.kind) + bestSoFar = sym; + } + return bestSoFar; + } + + /** Find an unqualified type symbol. + * @param env The current environment. + * @param name The type's name. + */ + Symbol findType(Env env, Name name) { + Symbol bestSoFar = typeNotFound; + Symbol sym; + boolean staticOnly = false; + for (Env env1 = env; env1.outer != null; env1 = env1.outer) { + if (isStatic(env1)) staticOnly = true; + for (Scope.Entry e = env1.info.scope.lookup(name); + e.scope != null; + e = e.next()) { + if (e.sym.kind == TYP) { + if (staticOnly && + e.sym.type.tag == TYPEVAR && + e.sym.owner.kind == TYP) return new StaticError(e.sym); + return e.sym; + } + } + + sym = findMemberType(env1, env1.enclClass.sym.type, name, + env1.enclClass.sym); + if (staticOnly && sym.kind == TYP && + sym.type.tag == CLASS && + sym.type.getEnclosingType().tag == CLASS && + env1.enclClass.sym.type.isParameterized() && + sym.type.getEnclosingType().isParameterized()) + return new StaticError(sym); + else if (sym.exists()) return sym; + else if (sym.kind < bestSoFar.kind) bestSoFar = sym; + + JCClassDecl encl = env1.baseClause ? (JCClassDecl)env1.tree : env1.enclClass; + if ((encl.sym.flags() & STATIC) != 0) + staticOnly = true; + } + + if (env.tree.getTag() != JCTree.IMPORT) { + sym = findGlobalType(env, env.toplevel.namedImportScope, name); + if (sym.exists()) return sym; + else if (sym.kind < bestSoFar.kind) bestSoFar = sym; + + sym = findGlobalType(env, env.toplevel.packge.members(), name); + if (sym.exists()) return sym; + else if (sym.kind < bestSoFar.kind) bestSoFar = sym; + + sym = findGlobalType(env, env.toplevel.starImportScope, name); + if (sym.exists()) return sym; + else if (sym.kind < bestSoFar.kind) bestSoFar = sym; + } + + return bestSoFar; + } + + /** Find an unqualified identifier which matches a specified kind set. + * @param env The current environment. + * @param name The indentifier's name. + * @param kind Indicates the possible symbol kinds + * (a subset of VAL, TYP, PCK). + */ + Symbol findIdent(Env env, Name name, int kind) { + Symbol bestSoFar = typeNotFound; + Symbol sym; + + if ((kind & VAR) != 0) { + sym = findVar(env, name); + if (sym.exists()) return sym; + else if (sym.kind < bestSoFar.kind) bestSoFar = sym; + } + + if ((kind & TYP) != 0) { + sym = findType(env, name); + if (sym.exists()) return sym; + else if (sym.kind < bestSoFar.kind) bestSoFar = sym; + } + + if ((kind & PCK) != 0) return reader.enterPackage(name); + else return bestSoFar; + } + + /** Find an identifier in a package which matches a specified kind set. + * @param env The current environment. + * @param name The identifier's name. + * @param kind Indicates the possible symbol kinds + * (a nonempty subset of TYP, PCK). + */ + Symbol findIdentInPackage(Env env, TypeSymbol pck, + Name name, int kind) { + Name fullname = TypeSymbol.formFullName(name, pck); + Symbol bestSoFar = typeNotFound; + PackageSymbol pack = null; + if ((kind & PCK) != 0) { + pack = reader.enterPackage(fullname); + if (pack.exists()) return pack; + } + if ((kind & TYP) != 0) { + Symbol sym = loadClass(env, fullname); + if (sym.exists()) { + // don't allow programs to use flatnames + if (name == sym.name) return sym; + } + else if (sym.kind < bestSoFar.kind) bestSoFar = sym; + } + return (pack != null) ? pack : bestSoFar; + } + + /** Find an identifier among the members of a given type `site'. + * @param env The current environment. + * @param site The type containing the symbol to be found. + * @param name The identifier's name. + * @param kind Indicates the possible symbol kinds + * (a subset of VAL, TYP). + */ + Symbol findIdentInType(Env env, Type site, + Name name, int kind) { + Symbol bestSoFar = typeNotFound; + Symbol sym; + if ((kind & VAR) != 0) { + sym = findField(env, site, name, site.tsym); + if (sym.exists()) return sym; + else if (sym.kind < bestSoFar.kind) bestSoFar = sym; + } + + if ((kind & TYP) != 0) { + sym = findMemberType(env, site, name, site.tsym); + if (sym.exists()) return sym; + else if (sym.kind < bestSoFar.kind) bestSoFar = sym; + } + return bestSoFar; + } + +/* *************************************************************************** + * Access checking + * The following methods convert ResolveErrors to ErrorSymbols, issuing + * an error message in the process + ****************************************************************************/ + + /** If `sym' is a bad symbol: report error and return errSymbol + * else pass through unchanged, + * additional arguments duplicate what has been used in trying to find the + * symbol (--> flyweight pattern). This improves performance since we + * expect misses to happen frequently. + * + * @param sym The symbol that was found, or a ResolveError. + * @param pos The position to use for error reporting. + * @param site The original type from where the selection took place. + * @param name The symbol's name. + * @param argtypes The invocation's value arguments, + * if we looked for a method. + * @param typeargtypes The invocation's type arguments, + * if we looked for a method. + */ + Symbol access(Symbol sym, + DiagnosticPosition pos, + Symbol location, + Type site, + Name name, + boolean qualified, + List argtypes, + List typeargtypes) { + if (sym.kind >= AMBIGUOUS) { + ResolveError errSym = (ResolveError)sym; + if (!site.isErroneous() && + !Type.isErroneous(argtypes) && + (typeargtypes==null || !Type.isErroneous(typeargtypes))) + logResolveError(errSym, pos, location, site, name, argtypes, typeargtypes); + sym = errSym.access(name, qualified ? site.tsym : syms.noSymbol); + } + return sym; + } + + /** Same as original access(), but without location. + */ + Symbol access(Symbol sym, + DiagnosticPosition pos, + Type site, + Name name, + boolean qualified, + List argtypes, + List typeargtypes) { + return access(sym, pos, site.tsym, site, name, qualified, argtypes, typeargtypes); + } + + /** Same as original access(), but without type arguments and arguments. + */ + Symbol access(Symbol sym, + DiagnosticPosition pos, + Symbol location, + Type site, + Name name, + boolean qualified) { + if (sym.kind >= AMBIGUOUS) + return access(sym, pos, location, site, name, qualified, List.nil(), null); + else + return sym; + } + + /** Same as original access(), but without location, type arguments and arguments. + */ + Symbol access(Symbol sym, + DiagnosticPosition pos, + Type site, + Name name, + boolean qualified) { + return access(sym, pos, site.tsym, site, name, qualified); + } + + /** Check that sym is not an abstract method. + */ + void checkNonAbstract(DiagnosticPosition pos, Symbol sym) { + if ((sym.flags() & ABSTRACT) != 0) + log.error(pos, "abstract.cant.be.accessed.directly", + kindName(sym), sym, sym.location()); + } + +/* *************************************************************************** + * Debugging + ****************************************************************************/ + + /** print all scopes starting with scope s and proceeding outwards. + * used for debugging. + */ + public void printscopes(Scope s) { + while (s != null) { + if (s.owner != null) + System.err.print(s.owner + ": "); + for (Scope.Entry e = s.elems; e != null; e = e.sibling) { + if ((e.sym.flags() & ABSTRACT) != 0) + System.err.print("abstract "); + System.err.print(e.sym + " "); + } + System.err.println(); + s = s.next; + } + } + + void printscopes(Env env) { + while (env.outer != null) { + System.err.println("------------------------------"); + printscopes(env.info.scope); + env = env.outer; + } + } + + public void printscopes(Type t) { + while (t.tag == CLASS) { + printscopes(t.tsym.members()); + t = types.supertype(t); + } + } + +/* *************************************************************************** + * Name resolution + * Naming conventions are as for symbol lookup + * Unlike the find... methods these methods will report access errors + ****************************************************************************/ + + /** Resolve an unqualified (non-method) identifier. + * @param pos The position to use for error reporting. + * @param env The environment current at the identifier use. + * @param name The identifier's name. + * @param kind The set of admissible symbol kinds for the identifier. + */ + Symbol resolveIdent(DiagnosticPosition pos, Env env, + Name name, int kind) { + return access( + findIdent(env, name, kind), + pos, env.enclClass.sym.type, name, false); + } + + /** Resolve an unqualified method identifier. + * @param pos The position to use for error reporting. + * @param env The environment current at the method invocation. + * @param name The identifier's name. + * @param argtypes The types of the invocation's value arguments. + * @param typeargtypes The types of the invocation's type arguments. + */ + Symbol resolveMethod(DiagnosticPosition pos, + Env env, + Name name, + List argtypes, + List typeargtypes) { + Symbol sym = startResolution(); + List steps = methodResolutionSteps; + while (steps.nonEmpty() && + steps.head.isApplicable(boxingEnabled, varargsEnabled) && + sym.kind >= ERRONEOUS) { + currentStep = steps.head; + sym = findFun(env, name, argtypes, typeargtypes, + steps.head.isBoxingRequired, + env.info.varArgs = steps.head.isVarargsRequired); + methodResolutionCache.put(steps.head, sym); + steps = steps.tail; + } + if (sym.kind >= AMBIGUOUS) {//if nothing is found return the 'first' error + MethodResolutionPhase errPhase = + firstErroneousResolutionPhase(); + sym = access(methodResolutionCache.get(errPhase), + pos, env.enclClass.sym.type, name, false, argtypes, typeargtypes); + env.info.varArgs = errPhase.isVarargsRequired; + } + return sym; + } + + private Symbol startResolution() { + wrongMethod.clear(); + wrongMethods.clear(); + return methodNotFound; + } + + /** Resolve a qualified method identifier + * @param pos The position to use for error reporting. + * @param env The environment current at the method invocation. + * @param site The type of the qualifying expression, in which + * identifier is searched. + * @param name The identifier's name. + * @param argtypes The types of the invocation's value arguments. + * @param typeargtypes The types of the invocation's type arguments. + */ + Symbol resolveQualifiedMethod(DiagnosticPosition pos, Env env, + Type site, Name name, List argtypes, + List typeargtypes) { + return resolveQualifiedMethod(pos, env, site.tsym, site, name, argtypes, typeargtypes); + } + Symbol resolveQualifiedMethod(DiagnosticPosition pos, Env env, + Symbol location, Type site, Name name, List argtypes, + List typeargtypes) { + Symbol sym = startResolution(); + List steps = methodResolutionSteps; + while (steps.nonEmpty() && + steps.head.isApplicable(boxingEnabled, varargsEnabled) && + sym.kind >= ERRONEOUS) { + currentStep = steps.head; + sym = findMethod(env, site, name, argtypes, typeargtypes, + steps.head.isBoxingRequired(), + env.info.varArgs = steps.head.isVarargsRequired(), false); + methodResolutionCache.put(steps.head, sym); + steps = steps.tail; + } + if (sym.kind >= AMBIGUOUS) { + if (site.tsym.isPolymorphicSignatureGeneric()) { + //polymorphic receiver - synthesize new method symbol + env.info.varArgs = false; + sym = findPolymorphicSignatureInstance(env, + site, name, null, argtypes); + } + else { + //if nothing is found return the 'first' error + MethodResolutionPhase errPhase = + firstErroneousResolutionPhase(); + sym = access(methodResolutionCache.get(errPhase), + pos, location, site, name, true, argtypes, typeargtypes); + env.info.varArgs = errPhase.isVarargsRequired; + } + } else if (allowMethodHandles && sym.isPolymorphicSignatureGeneric()) { + //non-instantiated polymorphic signature - synthesize new method symbol + env.info.varArgs = false; + sym = findPolymorphicSignatureInstance(env, + site, name, (MethodSymbol)sym, argtypes); + } + return sym; + } + + /** Find or create an implicit method of exactly the given type (after erasure). + * Searches in a side table, not the main scope of the site. + * This emulates the lookup process required by JSR 292 in JVM. + * @param env Attribution environment + * @param site The original type from where the selection takes place. + * @param name The method's name. + * @param spMethod A template for the implicit method, or null. + * @param argtypes The required argument types. + * @param typeargtypes The required type arguments. + */ + Symbol findPolymorphicSignatureInstance(Env env, Type site, + Name name, + MethodSymbol spMethod, // sig. poly. method or null if none + List argtypes) { + Type mtype = infer.instantiatePolymorphicSignatureInstance(env, + site, name, spMethod, argtypes); + long flags = ABSTRACT | HYPOTHETICAL | POLYMORPHIC_SIGNATURE | + (spMethod != null ? + spMethod.flags() & Flags.AccessFlags : + Flags.PUBLIC | Flags.STATIC); + Symbol m = null; + for (Scope.Entry e = polymorphicSignatureScope.lookup(name); + e.scope != null; + e = e.next()) { + Symbol sym = e.sym; + if (types.isSameType(mtype, sym.type) && + (sym.flags() & Flags.STATIC) == (flags & Flags.STATIC) && + types.isSameType(sym.owner.type, site)) { + m = sym; + break; + } + } + if (m == null) { + // create the desired method + m = new MethodSymbol(flags, name, mtype, site.tsym); + polymorphicSignatureScope.enter(m); + } + return m; + } + + /** Resolve a qualified method identifier, throw a fatal error if not + * found. + * @param pos The position to use for error reporting. + * @param env The environment current at the method invocation. + * @param site The type of the qualifying expression, in which + * identifier is searched. + * @param name The identifier's name. + * @param argtypes The types of the invocation's value arguments. + * @param typeargtypes The types of the invocation's type arguments. + */ + public MethodSymbol resolveInternalMethod(DiagnosticPosition pos, Env env, + Type site, Name name, + List argtypes, + List typeargtypes) { + Symbol sym = resolveQualifiedMethod( + pos, env, site.tsym, site, name, argtypes, typeargtypes); + if (sym.kind == MTH) return (MethodSymbol)sym; + else throw new FatalError( + diags.fragment("fatal.err.cant.locate.meth", + name)); + } + + /** Resolve constructor. + * @param pos The position to use for error reporting. + * @param env The environment current at the constructor invocation. + * @param site The type of class for which a constructor is searched. + * @param argtypes The types of the constructor invocation's value + * arguments. + * @param typeargtypes The types of the constructor invocation's type + * arguments. + */ + Symbol resolveConstructor(DiagnosticPosition pos, + Env env, + Type site, + List argtypes, + List typeargtypes) { + Symbol sym = startResolution(); + List steps = methodResolutionSteps; + while (steps.nonEmpty() && + steps.head.isApplicable(boxingEnabled, varargsEnabled) && + sym.kind >= ERRONEOUS) { + currentStep = steps.head; + sym = resolveConstructor(pos, env, site, argtypes, typeargtypes, + steps.head.isBoxingRequired(), + env.info.varArgs = steps.head.isVarargsRequired()); + methodResolutionCache.put(steps.head, sym); + steps = steps.tail; + } + if (sym.kind >= AMBIGUOUS) {//if nothing is found return the 'first' error + MethodResolutionPhase errPhase = firstErroneousResolutionPhase(); + sym = access(methodResolutionCache.get(errPhase), + pos, site, names.init, true, argtypes, typeargtypes); + env.info.varArgs = errPhase.isVarargsRequired(); + } + return sym; + } + + /** Resolve constructor using diamond inference. + * @param pos The position to use for error reporting. + * @param env The environment current at the constructor invocation. + * @param site The type of class for which a constructor is searched. + * The scope of this class has been touched in attribution. + * @param argtypes The types of the constructor invocation's value + * arguments. + * @param typeargtypes The types of the constructor invocation's type + * arguments. + */ + Symbol resolveDiamond(DiagnosticPosition pos, + Env env, + Type site, + List argtypes, + List typeargtypes) { + Symbol sym = startResolution(); + List steps = methodResolutionSteps; + while (steps.nonEmpty() && + steps.head.isApplicable(boxingEnabled, varargsEnabled) && + sym.kind >= ERRONEOUS) { + currentStep = steps.head; + sym = resolveConstructor(pos, env, site, argtypes, typeargtypes, + steps.head.isBoxingRequired(), + env.info.varArgs = steps.head.isVarargsRequired()); + methodResolutionCache.put(steps.head, sym); + steps = steps.tail; + } + if (sym.kind >= AMBIGUOUS) { + final JCDiagnostic details = sym.kind == WRONG_MTH ? + ((InapplicableSymbolError)sym).explanation : + null; + Symbol errSym = new ResolveError(WRONG_MTH, "diamond error") { + @Override + JCDiagnostic getDiagnostic(DiagnosticType dkind, DiagnosticPosition pos, + Symbol location, Type site, Name name, List argtypes, List typeargtypes) { + String key = details == null ? + "cant.apply.diamond" : + "cant.apply.diamond.1"; + return diags.create(dkind, log.currentSource(), pos, key, + diags.fragment("diamond", site.tsym), details); + } + }; + MethodResolutionPhase errPhase = firstErroneousResolutionPhase(); + sym = access(errSym, pos, site, names.init, true, argtypes, typeargtypes); + env.info.varArgs = errPhase.isVarargsRequired(); + } + return sym; + } + + /** Resolve constructor. + * @param pos The position to use for error reporting. + * @param env The environment current at the constructor invocation. + * @param site The type of class for which a constructor is searched. + * @param argtypes The types of the constructor invocation's value + * arguments. + * @param typeargtypes The types of the constructor invocation's type + * arguments. + * @param allowBoxing Allow boxing and varargs conversions. + * @param useVarargs Box trailing arguments into an array for varargs. + */ + Symbol resolveConstructor(DiagnosticPosition pos, Env env, + Type site, List argtypes, + List typeargtypes, + boolean allowBoxing, + boolean useVarargs) { + Symbol sym = findMethod(env, site, + names.init, argtypes, + typeargtypes, allowBoxing, + useVarargs, false); + chk.checkDeprecated(pos, env.info.scope.owner, sym); + return sym; + } + + /** Resolve a constructor, throw a fatal error if not found. + * @param pos The position to use for error reporting. + * @param env The environment current at the method invocation. + * @param site The type to be constructed. + * @param argtypes The types of the invocation's value arguments. + * @param typeargtypes The types of the invocation's type arguments. + */ + public MethodSymbol resolveInternalConstructor(DiagnosticPosition pos, Env env, + Type site, + List argtypes, + List typeargtypes) { + Symbol sym = resolveConstructor( + pos, env, site, argtypes, typeargtypes); + if (sym.kind == MTH) return (MethodSymbol)sym; + else throw new FatalError( + diags.fragment("fatal.err.cant.locate.ctor", site)); + } + + /** Resolve operator. + * @param pos The position to use for error reporting. + * @param optag The tag of the operation tree. + * @param env The environment current at the operation. + * @param argtypes The types of the operands. + */ + Symbol resolveOperator(DiagnosticPosition pos, int optag, + Env env, List argtypes) { + Name name = treeinfo.operatorName(optag); + Symbol sym = findMethod(env, syms.predefClass.type, name, argtypes, + null, false, false, true); + if (boxingEnabled && sym.kind >= WRONG_MTHS) + sym = findMethod(env, syms.predefClass.type, name, argtypes, + null, true, false, true); + return access(sym, pos, env.enclClass.sym.type, name, + false, argtypes, null); + } + + /** Resolve operator. + * @param pos The position to use for error reporting. + * @param optag The tag of the operation tree. + * @param env The environment current at the operation. + * @param arg The type of the operand. + */ + Symbol resolveUnaryOperator(DiagnosticPosition pos, int optag, Env env, Type arg) { + return resolveOperator(pos, optag, env, List.of(arg)); + } + + /** Resolve binary operator. + * @param pos The position to use for error reporting. + * @param optag The tag of the operation tree. + * @param env The environment current at the operation. + * @param left The types of the left operand. + * @param right The types of the right operand. + */ + Symbol resolveBinaryOperator(DiagnosticPosition pos, + int optag, + Env env, + Type left, + Type right) { + return resolveOperator(pos, optag, env, List.of(left, right)); + } + + /** + * Resolve `c.name' where name == this or name == super. + * @param pos The position to use for error reporting. + * @param env The environment current at the expression. + * @param c The qualifier. + * @param name The identifier's name. + */ + Symbol resolveSelf(DiagnosticPosition pos, + Env env, + TypeSymbol c, + Name name) { + Env env1 = env; + boolean staticOnly = false; + while (env1.outer != null) { + if (isStatic(env1)) staticOnly = true; + if (env1.enclClass.sym == c) { + Symbol sym = env1.info.scope.lookup(name).sym; + if (sym != null) { + if (staticOnly) sym = new StaticError(sym); + return access(sym, pos, env.enclClass.sym.type, + name, true); + } + } + if ((env1.enclClass.sym.flags() & STATIC) != 0) staticOnly = true; + env1 = env1.outer; + } + log.error(pos, "not.encl.class", c); + return syms.errSymbol; + } + + /** + * Resolve `c.this' for an enclosing class c that contains the + * named member. + * @param pos The position to use for error reporting. + * @param env The environment current at the expression. + * @param member The member that must be contained in the result. + */ + Symbol resolveSelfContaining(DiagnosticPosition pos, + Env env, + Symbol member, + boolean isSuperCall) { + Name name = names._this; + Env env1 = isSuperCall ? env.outer : env; + boolean staticOnly = false; + if (env1 != null) { + while (env1 != null && env1.outer != null) { + if (isStatic(env1)) staticOnly = true; + if (env1.enclClass.sym.isSubClass(member.owner, types)) { + Symbol sym = env1.info.scope.lookup(name).sym; + if (sym != null) { + if (staticOnly) sym = new StaticError(sym); + return access(sym, pos, env.enclClass.sym.type, + name, true); + } + } + if ((env1.enclClass.sym.flags() & STATIC) != 0) + staticOnly = true; + env1 = env1.outer; + } + } + log.error(pos, "encl.class.required", member); + return syms.errSymbol; + } + + /** + * Resolve an appropriate implicit this instance for t's container. + * JLS 8.8.5.1 and 15.9.2 + */ + Type resolveImplicitThis(DiagnosticPosition pos, Env env, Type t) { + return resolveImplicitThis(pos, env, t, false); + } + + Type resolveImplicitThis(DiagnosticPosition pos, Env env, Type t, boolean isSuperCall) { + Type thisType = (((t.tsym.owner.kind & (MTH|VAR)) != 0) + ? resolveSelf(pos, env, t.getEnclosingType().tsym, names._this) + : resolveSelfContaining(pos, env, t.tsym, isSuperCall)).type; + if (env.info.isSelfCall && thisType.tsym == env.enclClass.sym) + log.error(pos, "cant.ref.before.ctor.called", "this"); + return thisType; + } + +/* *************************************************************************** + * ResolveError classes, indicating error situations when accessing symbols + ****************************************************************************/ + + public void logAccessError(Env env, JCTree tree, Type type) { + AccessError error = new AccessError(env, type.getEnclosingType(), type.tsym); + logResolveError(error, tree.pos(), type.getEnclosingType().tsym, type.getEnclosingType(), null, null, null); + } + //where + private void logResolveError(ResolveError error, + DiagnosticPosition pos, + Symbol location, + Type site, + Name name, + List argtypes, + List typeargtypes) { + JCDiagnostic d = error.getDiagnostic(JCDiagnostic.DiagnosticType.ERROR, + pos, location, site, name, argtypes, typeargtypes); + if (d != null) { + d.setFlag(DiagnosticFlag.RESOLVE_ERROR); + log.report(d); + } + } + + private final LocalizedString noArgs = new LocalizedString("compiler.misc.no.args"); + + public Object methodArguments(List argtypes) { + return argtypes.isEmpty() ? noArgs : argtypes; + } + + /** + * Root class for resolution errors. Subclass of ResolveError + * represent a different kinds of resolution error - as such they must + * specify how they map into concrete compiler diagnostics. + */ + private abstract class ResolveError extends Symbol { + + /** The name of the kind of error, for debugging only. */ + final String debugName; + + ResolveError(int kind, String debugName) { + super(kind, 0, null, null, null); + this.debugName = debugName; + } + + @Override + public R accept(ElementVisitor v, P p) { + throw new AssertionError(); + } + + @Override + public String toString() { + return debugName; + } + + @Override + public boolean exists() { + return false; + } + + /** + * Create an external representation for this erroneous symbol to be + * used during attribution - by default this returns the symbol of a + * brand new error type which stores the original type found + * during resolution. + * + * @param name the name used during resolution + * @param location the location from which the symbol is accessed + */ + protected Symbol access(Name name, TypeSymbol location) { + return types.createErrorType(name, location, syms.errSymbol.type).tsym; + } + + /** + * Create a diagnostic representing this resolution error. + * + * @param dkind The kind of the diagnostic to be created (e.g error). + * @param pos The position to be used for error reporting. + * @param site The original type from where the selection took place. + * @param name The name of the symbol to be resolved. + * @param argtypes The invocation's value arguments, + * if we looked for a method. + * @param typeargtypes The invocation's type arguments, + * if we looked for a method. + */ + abstract JCDiagnostic getDiagnostic(JCDiagnostic.DiagnosticType dkind, + DiagnosticPosition pos, + Symbol location, + Type site, + Name name, + List argtypes, + List typeargtypes); + + /** + * A name designates an operator if it consists + * of a non-empty sequence of operator symbols +-~!/*%&|^<>= + */ + boolean isOperator(Name name) { + int i = 0; + while (i < name.getByteLength() && + "+-~!*/%&|^<>=".indexOf(name.getByteAt(i)) >= 0) i++; + return i > 0 && i == name.getByteLength(); + } + } + + /** + * This class is the root class of all resolution errors caused by + * an invalid symbol being found during resolution. + */ + abstract class InvalidSymbolError extends ResolveError { + + /** The invalid symbol found during resolution */ + Symbol sym; + + InvalidSymbolError(int kind, Symbol sym, String debugName) { + super(kind, debugName); + this.sym = sym; + } + + @Override + public boolean exists() { + return true; + } + + @Override + public String toString() { + return super.toString() + " wrongSym=" + sym; + } + + @Override + public Symbol access(Name name, TypeSymbol location) { + if (sym.kind >= AMBIGUOUS) + return ((ResolveError)sym).access(name, location); + else if ((sym.kind & ERRONEOUS) == 0 && (sym.kind & TYP) != 0) + return types.createErrorType(name, location, sym.type).tsym; + else + return sym; + } + } + + /** + * InvalidSymbolError error class indicating that a symbol matching a + * given name does not exists in a given site. + */ + class SymbolNotFoundError extends ResolveError { + + SymbolNotFoundError(int kind) { + super(kind, "symbol not found error"); + } + + @Override + JCDiagnostic getDiagnostic(JCDiagnostic.DiagnosticType dkind, + DiagnosticPosition pos, + Symbol location, + Type site, + Name name, + List argtypes, + List typeargtypes) { + argtypes = argtypes == null ? List.nil() : argtypes; + typeargtypes = typeargtypes == null ? List.nil() : typeargtypes; + if (name == names.error) + return null; + + if (isOperator(name)) { + boolean isUnaryOp = argtypes.size() == 1; + String key = argtypes.size() == 1 ? + "operator.cant.be.applied" : + "operator.cant.be.applied.1"; + Type first = argtypes.head; + Type second = !isUnaryOp ? argtypes.tail.head : null; + return diags.create(dkind, log.currentSource(), pos, + key, name, first, second); + } + boolean hasLocation = false; + if (location == null) { + location = site.tsym; + } + if (!location.name.isEmpty()) { + if (location.kind == PCK && !site.tsym.exists()) { + return diags.create(dkind, log.currentSource(), pos, + "doesnt.exist", location); + } + hasLocation = !location.name.equals(names._this) && + !location.name.equals(names._super); + } + boolean isConstructor = kind == ABSENT_MTH && + name == names.table.names.init; + KindName kindname = isConstructor ? KindName.CONSTRUCTOR : absentKind(kind); + Name idname = isConstructor ? site.tsym.name : name; + String errKey = getErrorKey(kindname, typeargtypes.nonEmpty(), hasLocation); + if (hasLocation) { + return diags.create(dkind, log.currentSource(), pos, + errKey, kindname, idname, //symbol kindname, name + typeargtypes, argtypes, //type parameters and arguments (if any) + getLocationDiag(location, site)); //location kindname, type + } + else { + return diags.create(dkind, log.currentSource(), pos, + errKey, kindname, idname, //symbol kindname, name + typeargtypes, argtypes); //type parameters and arguments (if any) + } + } + //where + private String getErrorKey(KindName kindname, boolean hasTypeArgs, boolean hasLocation) { + String key = "cant.resolve"; + String suffix = hasLocation ? ".location" : ""; + switch (kindname) { + case METHOD: + case CONSTRUCTOR: { + suffix += ".args"; + suffix += hasTypeArgs ? ".params" : ""; + } + } + return key + suffix; + } + private JCDiagnostic getLocationDiag(Symbol location, Type site) { + if (location.kind == VAR) { + return diags.fragment("location.1", + kindName(location), + location, + location.type); + } else { + return diags.fragment("location", + typeKindName(site), + site, + null); + } + } + } + + /** + * InvalidSymbolError error class indicating that a given symbol + * (either a method, a constructor or an operand) is not applicable + * given an actual arguments/type argument list. + */ + class InapplicableSymbolError extends InvalidSymbolError { + + /** An auxiliary explanation set in case of instantiation errors. */ + JCDiagnostic explanation; + + InapplicableSymbolError(Symbol sym) { + super(WRONG_MTH, sym, "inapplicable symbol error"); + } + + /** Update sym and explanation and return this. + */ + InapplicableSymbolError setWrongSym(Symbol sym, JCDiagnostic explanation) { + this.sym = sym; + if (this.sym == sym && explanation != null) + this.explanation = explanation; //update the details + return this; + } + + /** Update sym and return this. + */ + InapplicableSymbolError setWrongSym(Symbol sym) { + this.sym = sym; + return this; + } + + @Override + public String toString() { + return super.toString() + " explanation=" + explanation; + } + + @Override + JCDiagnostic getDiagnostic(JCDiagnostic.DiagnosticType dkind, + DiagnosticPosition pos, + Symbol location, + Type site, + Name name, + List argtypes, + List typeargtypes) { + if (name == names.error) + return null; + + if (isOperator(name)) { + boolean isUnaryOp = argtypes.size() == 1; + String key = argtypes.size() == 1 ? + "operator.cant.be.applied" : + "operator.cant.be.applied.1"; + Type first = argtypes.head; + Type second = !isUnaryOp ? argtypes.tail.head : null; + return diags.create(dkind, log.currentSource(), pos, + key, name, first, second); + } + else { + Symbol ws = sym.asMemberOf(site, types); + return diags.create(dkind, log.currentSource(), pos, + "cant.apply.symbol" + (explanation != null ? ".1" : ""), + kindName(ws), + ws.name == names.init ? ws.owner.name : ws.name, + methodArguments(ws.type.getParameterTypes()), + methodArguments(argtypes), + kindName(ws.owner), + ws.owner.type, + explanation); + } + } + + void clear() { + explanation = null; + } + + @Override + public Symbol access(Name name, TypeSymbol location) { + return types.createErrorType(name, location, syms.errSymbol.type).tsym; + } + } + + /** + * ResolveError error class indicating that a set of symbols + * (either methods, constructors or operands) is not applicable + * given an actual arguments/type argument list. + */ + class InapplicableSymbolsError extends ResolveError { + + private List candidates = List.nil(); + + InapplicableSymbolsError(Symbol sym) { + super(WRONG_MTHS, "inapplicable symbols"); + } + + @Override + JCDiagnostic getDiagnostic(JCDiagnostic.DiagnosticType dkind, + DiagnosticPosition pos, + Symbol location, + Type site, + Name name, + List argtypes, + List typeargtypes) { + if (candidates.nonEmpty()) { + JCDiagnostic err = diags.create(dkind, + log.currentSource(), + pos, + "cant.apply.symbols", + name == names.init ? KindName.CONSTRUCTOR : absentKind(kind), + getName(), + argtypes); + return new JCDiagnostic.MultilineDiagnostic(err, candidateDetails(site)); + } else { + return new SymbolNotFoundError(ABSENT_MTH).getDiagnostic(dkind, pos, + location, site, name, argtypes, typeargtypes); + } + } + + //where + List candidateDetails(Type site) { + List details = List.nil(); + for (Candidate c : candidates) + details = details.prepend(c.getDiagnostic(site)); + return details.reverse(); + } + + Symbol addCandidate(MethodResolutionPhase currentStep, Symbol sym, JCDiagnostic details) { + Candidate c = new Candidate(currentStep, sym, details); + if (c.isValid() && !candidates.contains(c)) + candidates = candidates.append(c); + return this; + } + + void clear() { + candidates = List.nil(); + } + + private Name getName() { + Symbol sym = candidates.head.sym; + return sym.name == names.init ? + sym.owner.name : + sym.name; + } + + private class Candidate { + + final MethodResolutionPhase step; + final Symbol sym; + final JCDiagnostic details; + + private Candidate(MethodResolutionPhase step, Symbol sym, JCDiagnostic details) { + this.step = step; + this.sym = sym; + this.details = details; + } + + JCDiagnostic getDiagnostic(Type site) { + return diags.fragment("inapplicable.method", + Kinds.kindName(sym), + sym.location(site, types), + sym.asMemberOf(site, types), + details); + } + + @Override + public boolean equals(Object o) { + if (o instanceof Candidate) { + Symbol s1 = this.sym; + Symbol s2 = ((Candidate)o).sym; + if ((s1 != s2 && + (s1.overrides(s2, s1.owner.type.tsym, types, false) || + (s2.overrides(s1, s2.owner.type.tsym, types, false)))) || + ((s1.isConstructor() || s2.isConstructor()) && s1.owner != s2.owner)) + return true; + } + return false; + } + + boolean isValid() { + return (((sym.flags() & VARARGS) != 0 && step == VARARITY) || + (sym.flags() & VARARGS) == 0 && step == (boxingEnabled ? BOX : BASIC)); + } + } + } + + /** + * An InvalidSymbolError error class indicating that a symbol is not + * accessible from a given site + */ + class AccessError extends InvalidSymbolError { + + private Env env; + private Type site; + + AccessError(Symbol sym) { + this(null, null, sym); + } + + AccessError(Env env, Type site, Symbol sym) { + super(HIDDEN, sym, "access error"); + this.env = env; + this.site = site; + if (debugResolve) + log.error("proc.messager", sym + " @ " + site + " is inaccessible."); + } + + @Override + public boolean exists() { + return false; + } + + @Override + JCDiagnostic getDiagnostic(JCDiagnostic.DiagnosticType dkind, + DiagnosticPosition pos, + Symbol location, + Type site, + Name name, + List argtypes, + List typeargtypes) { + if (sym.owner.type.tag == ERROR) + return null; + + if (sym.name == names.init && sym.owner != site.tsym) { + return new SymbolNotFoundError(ABSENT_MTH).getDiagnostic(dkind, + pos, location, site, name, argtypes, typeargtypes); + } + else if ((sym.flags() & PUBLIC) != 0 + || (env != null && this.site != null + && !isAccessible(env, this.site))) { + return diags.create(dkind, log.currentSource(), + pos, "not.def.access.class.intf.cant.access", + sym, sym.location()); + } + else if ((sym.flags() & (PRIVATE | PROTECTED)) != 0) { + return diags.create(dkind, log.currentSource(), + pos, "report.access", sym, + asFlagSet(sym.flags() & (PRIVATE | PROTECTED)), + sym.location()); + } + else { + return diags.create(dkind, log.currentSource(), + pos, "not.def.public.cant.access", sym, sym.location()); + } + } + } + + /** + * InvalidSymbolError error class indicating that an instance member + * has erroneously been accessed from a static context. + */ + class StaticError extends InvalidSymbolError { + + StaticError(Symbol sym) { + super(STATICERR, sym, "static error"); + } + + @Override + JCDiagnostic getDiagnostic(JCDiagnostic.DiagnosticType dkind, + DiagnosticPosition pos, + Symbol location, + Type site, + Name name, + List argtypes, + List typeargtypes) { + Symbol errSym = ((sym.kind == TYP && sym.type.tag == CLASS) + ? types.erasure(sym.type).tsym + : sym); + return diags.create(dkind, log.currentSource(), pos, + "non-static.cant.be.ref", kindName(sym), errSym); + } + } + + /** + * InvalidSymbolError error class indicating that a pair of symbols + * (either methods, constructors or operands) are ambiguous + * given an actual arguments/type argument list. + */ + class AmbiguityError extends InvalidSymbolError { + + /** The other maximally specific symbol */ + Symbol sym2; + + AmbiguityError(Symbol sym1, Symbol sym2) { + super(AMBIGUOUS, sym1, "ambiguity error"); + this.sym2 = sym2; + } + + @Override + JCDiagnostic getDiagnostic(JCDiagnostic.DiagnosticType dkind, + DiagnosticPosition pos, + Symbol location, + Type site, + Name name, + List argtypes, + List typeargtypes) { + AmbiguityError pair = this; + while (true) { + if (pair.sym.kind == AMBIGUOUS) + pair = (AmbiguityError)pair.sym; + else if (pair.sym2.kind == AMBIGUOUS) + pair = (AmbiguityError)pair.sym2; + else break; + } + Name sname = pair.sym.name; + if (sname == names.init) sname = pair.sym.owner.name; + return diags.create(dkind, log.currentSource(), + pos, "ref.ambiguous", sname, + kindName(pair.sym), + pair.sym, + pair.sym.location(site, types), + kindName(pair.sym2), + pair.sym2, + pair.sym2.location(site, types)); + } + } + + enum MethodResolutionPhase { + BASIC(false, false), + BOX(true, false), + VARARITY(true, true); + + boolean isBoxingRequired; + boolean isVarargsRequired; + + MethodResolutionPhase(boolean isBoxingRequired, boolean isVarargsRequired) { + this.isBoxingRequired = isBoxingRequired; + this.isVarargsRequired = isVarargsRequired; + } + + public boolean isBoxingRequired() { + return isBoxingRequired; + } + + public boolean isVarargsRequired() { + return isVarargsRequired; + } + + public boolean isApplicable(boolean boxingEnabled, boolean varargsEnabled) { + return (varargsEnabled || !isVarargsRequired) && + (boxingEnabled || !isBoxingRequired); + } + } + + private Map methodResolutionCache = + new HashMap(MethodResolutionPhase.values().length); + + final List methodResolutionSteps = List.of(BASIC, BOX, VARARITY); + + private MethodResolutionPhase currentStep = null; + + private MethodResolutionPhase firstErroneousResolutionPhase() { + MethodResolutionPhase bestSoFar = BASIC; + Symbol sym = methodNotFound; + List steps = methodResolutionSteps; + while (steps.nonEmpty() && + steps.head.isApplicable(boxingEnabled, varargsEnabled) && + sym.kind >= WRONG_MTHS) { + sym = methodResolutionCache.get(steps.head); + bestSoFar = steps.head; + steps = steps.tail; + } + return bestSoFar; + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/comp/Todo.java b/douyu-javac/src/main/java/com/sun/tools/javac/comp/Todo.java new file mode 100644 index 0000000..ffea219 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/comp/Todo.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.comp; + +import java.util.AbstractQueue; +import com.sun.tools.javac.util.Context; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; +import javax.tools.JavaFileObject; + +/** A queue of all as yet unattributed classes. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Todo extends AbstractQueue> { + /** The context key for the todo list. */ + protected static final Context.Key todoKey = + new Context.Key(); + + /** Get the Todo instance for this context. */ + public static Todo instance(Context context) { + Todo instance = context.get(todoKey); + if (instance == null) + instance = new Todo(context); + return instance; + } + + /** Create a new todo list. */ + protected Todo(Context context) { + context.put(todoKey, this); + } + + public void append(Env env) { + add(env); + } + + @Override + public Iterator> iterator() { + return contents.iterator(); + } + + @Override + public int size() { + return contents.size(); + } + + public boolean offer(Env e) { + if (contents.add(e)) { + if (contentsByFile != null) + addByFile(e); + return true; + } else { + return false; + } + } + + public Env poll() { + if (size() == 0) + return null; + Env env = contents.remove(0); + if (contentsByFile != null) + removeByFile(env); + return env; + } + + public Env peek() { + return (size() == 0 ? null : contents.get(0)); + } + + public Queue>> groupByFile() { + if (contentsByFile == null) { + contentsByFile = new LinkedList>>(); + for (Env env: contents) { + addByFile(env); + } + } + return contentsByFile; + } + + private void addByFile(Env env) { + JavaFileObject file = env.toplevel.sourcefile; + if (fileMap == null) + fileMap = new HashMap(); + FileQueue fq = fileMap.get(file); + if (fq == null) { + fq = new FileQueue(); + fileMap.put(file, fq); + contentsByFile.add(fq); + } + fq.fileContents.add(env); + } + + private void removeByFile(Env env) { + JavaFileObject file = env.toplevel.sourcefile; + FileQueue fq = fileMap.get(file); + if (fq == null) + return; + if (fq.fileContents.remove(env)) { + if (fq.isEmpty()) { + fileMap.remove(file); + contentsByFile.remove(fq); + } + } + } + + LinkedList> contents = new LinkedList>(); + LinkedList>> contentsByFile; + Map fileMap; + + class FileQueue extends AbstractQueue> { + @Override + public Iterator> iterator() { + return fileContents.iterator(); + } + + @Override + public int size() { + return fileContents.size(); + } + + public boolean offer(Env e) { + if (fileContents.offer(e)) { + contents.add(e); + return true; + } + return false; + } + + public Env poll() { + if (fileContents.size() == 0) + return null; + Env env = fileContents.remove(0); + contents.remove(env); + return env; + } + + public Env peek() { + return (fileContents.size() == 0 ? null : fileContents.get(0)); + } + + LinkedList> fileContents = new LinkedList>(); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/comp/TransTypes.java b/douyu-javac/src/main/java/com/sun/tools/javac/comp/TransTypes.java new file mode 100644 index 0000000..b69b498 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/comp/TransTypes.java @@ -0,0 +1,900 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.comp; + +import java.util.*; + +import javax.lang.model.element.ElementKind; + +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.tree.*; +import com.sun.tools.javac.tree.JCTree.*; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; +import com.sun.tools.javac.util.List; + +import static com.sun.tools.javac.code.Flags.*; +import static com.sun.tools.javac.code.Kinds.*; +import static com.sun.tools.javac.code.TypeTags.*; + +/** This pass translates Generic Java to conventional Java. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class TransTypes extends TreeTranslator { + /** The context key for the TransTypes phase. */ + protected static final Context.Key transTypesKey = + new Context.Key(); + + /** Get the instance for this context. */ + public static TransTypes instance(Context context) { + TransTypes instance = context.get(transTypesKey); + if (instance == null) + instance = new TransTypes(context); + return instance; + } + + private Names names; + private Log log; + private Symtab syms; + private TreeMaker make; + private Enter enter; + private boolean allowEnums; + private Types types; + private final Resolve resolve; + + /** + * Flag to indicate whether or not to generate bridge methods. + * For pre-Tiger source there is no need for bridge methods, so it + * can be skipped to get better performance for -source 1.4 etc. + */ + private final boolean addBridges; + + protected TransTypes(Context context) { + context.put(transTypesKey, this); + names = Names.instance(context); + log = Log.instance(context); + syms = Symtab.instance(context); + enter = Enter.instance(context); + overridden = new HashMap(); + Source source = Source.instance(context); + allowEnums = source.allowEnums(); + addBridges = source.addBridges(); + types = Types.instance(context); + make = TreeMaker.instance(context); + resolve = Resolve.instance(context); + } + + /** A hashtable mapping bridge methods to the methods they override after + * type erasure. + */ + Map overridden; + + /** Construct an attributed tree for a cast of expression to target type, + * unless it already has precisely that type. + * @param tree The expression tree. + * @param target The target type. + */ + JCExpression cast(JCExpression tree, Type target) { + int oldpos = make.pos; + make.at(tree.pos); + if (!types.isSameType(tree.type, target)) { + if (!resolve.isAccessible(env, target.tsym)) + resolve.logAccessError(env, tree, target); + tree = make.TypeCast(make.Type(target), tree).setType(target); + } + make.pos = oldpos; + return tree; + } + + /** Construct an attributed tree to coerce an expression to some erased + * target type, unless the expression is already assignable to that type. + * If target type is a constant type, use its base type instead. + * @param tree The expression tree. + * @param target The target type. + */ + JCExpression coerce(JCExpression tree, Type target) { + Type btarget = target.baseType(); + if (tree.type.isPrimitive() == target.isPrimitive()) { + return types.isAssignable(tree.type, btarget, Warner.noWarnings) + ? tree + : cast(tree, btarget); + } + return tree; + } + + /** Given an erased reference type, assume this type as the tree's type. + * Then, coerce to some given target type unless target type is null. + * This operation is used in situations like the following: + * + * class Cell { A value; } + * ... + * Cell cell; + * Integer x = cell.value; + * + * Since the erasure of Cell.value is Object, but the type + * of cell.value in the assignment is Integer, we need to + * adjust the original type of cell.value to Object, and insert + * a cast to Integer. That is, the last assignment becomes: + * + * Integer x = (Integer)cell.value; + * + * @param tree The expression tree whose type might need adjustment. + * @param erasedType The expression's type after erasure. + * @param target The target type, which is usually the erasure of the + * expression's original type. + */ + JCExpression retype(JCExpression tree, Type erasedType, Type target) { +// System.err.println("retype " + tree + " to " + erasedType);//DEBUG + if (erasedType.tag > lastBaseTag) { + if (target != null && target.isPrimitive()) + target = erasure(tree.type); + tree.type = erasedType; + if (target != null) return coerce(tree, target); + } + return tree; + } + + /** Translate method argument list, casting each argument + * to its corresponding type in a list of target types. + * @param _args The method argument list. + * @param parameters The list of target types. + * @param varargsElement The erasure of the varargs element type, + * or null if translating a non-varargs invocation + */ + List translateArgs(List _args, + List parameters, + Type varargsElement) { + if (parameters.isEmpty()) return _args; + List args = _args; + while (parameters.tail.nonEmpty()) { + args.head = translate(args.head, parameters.head); + args = args.tail; + parameters = parameters.tail; + } + Type parameter = parameters.head; + Assert.check(varargsElement != null || args.length() == 1); + if (varargsElement != null) { + while (args.nonEmpty()) { + args.head = translate(args.head, varargsElement); + args = args.tail; + } + } else { + args.head = translate(args.head, parameter); + } + return _args; + } + + /** Add a bridge definition and enter corresponding method symbol in + * local scope of origin. + * + * @param pos The source code position to be used for the definition. + * @param meth The method for which a bridge needs to be added + * @param impl That method's implementation (possibly the method itself) + * @param origin The class to which the bridge will be added + * @param hypothetical + * True if the bridge method is not strictly necessary in the + * binary, but is represented in the symbol table to detect + * erasure clashes. + * @param bridges The list buffer to which the bridge will be added + */ + void addBridge(DiagnosticPosition pos, + MethodSymbol meth, + MethodSymbol impl, + ClassSymbol origin, + boolean hypothetical, + ListBuffer bridges) { + make.at(pos); + Type origType = types.memberType(origin.type, meth); + Type origErasure = erasure(origType); + + // Create a bridge method symbol and a bridge definition without a body. + Type bridgeType = meth.erasure(types); + long flags = impl.flags() & AccessFlags | SYNTHETIC | BRIDGE; + if (hypothetical) flags |= HYPOTHETICAL; + MethodSymbol bridge = new MethodSymbol(flags, + meth.name, + bridgeType, + origin); + if (!hypothetical) { + JCMethodDecl md = make.MethodDef(bridge, null); + + // The bridge calls this.impl(..), if we have an implementation + // in the current class, super.impl(...) otherwise. + JCExpression receiver = (impl.owner == origin) + ? make.This(origin.erasure(types)) + : make.Super(types.supertype(origin.type).tsym.erasure(types), origin); + + // The type returned from the original method. + Type calltype = erasure(impl.type.getReturnType()); + + // Construct a call of this.impl(params), or super.impl(params), + // casting params and possibly results as needed. + JCExpression call = + make.Apply( + null, + make.Select(receiver, impl).setType(calltype), + translateArgs(make.Idents(md.params), origErasure.getParameterTypes(), null)) + .setType(calltype); + JCStatement stat = (origErasure.getReturnType().tag == VOID) + ? make.Exec(call) + : make.Return(coerce(call, bridgeType.getReturnType())); + md.body = make.Block(0, List.of(stat)); + + // Add bridge to `bridges' buffer + bridges.append(md); + } + + // Add bridge to scope of enclosing class and `overridden' table. + origin.members().enter(bridge); + overridden.put(bridge, meth); + } + + /** Add bridge if given symbol is a non-private, non-static member + * of the given class, which is either defined in the class or non-final + * inherited, and one of the two following conditions holds: + * 1. The method's type changes in the given class, as compared to the + * class where the symbol was defined, (in this case + * we have extended a parameterized class with non-trivial parameters). + * 2. The method has an implementation with a different erased return type. + * (in this case we have used co-variant returns). + * If a bridge already exists in some other class, no new bridge is added. + * Instead, it is checked that the bridge symbol overrides the method symbol. + * (Spec ???). + * todo: what about bridges for privates??? + * + * @param pos The source code position to be used for the definition. + * @param sym The symbol for which a bridge might have to be added. + * @param origin The class in which the bridge would go. + * @param bridges The list buffer to which the bridge would be added. + */ + void addBridgeIfNeeded(DiagnosticPosition pos, + Symbol sym, + ClassSymbol origin, + ListBuffer bridges) { + if (sym.kind == MTH && + sym.name != names.init && + (sym.flags() & (PRIVATE | STATIC)) == 0 && + (sym.flags() & (SYNTHETIC | OVERRIDE_BRIDGE)) != SYNTHETIC && + sym.isMemberOf(origin, types)) + { + MethodSymbol meth = (MethodSymbol)sym; + MethodSymbol bridge = meth.binaryImplementation(origin, types); + MethodSymbol impl = meth.implementation(origin, types, true, overrideBridgeFilter); + if (bridge == null || + bridge == meth || + (impl != null && !bridge.owner.isSubClass(impl.owner, types))) { + // No bridge was added yet. + if (impl != null && isBridgeNeeded(meth, impl, origin.type)) { + addBridge(pos, meth, impl, origin, bridge==impl, bridges); + } else if (impl == meth + && impl.owner != origin + && (impl.flags() & FINAL) == 0 + && (meth.flags() & (ABSTRACT|PUBLIC)) == PUBLIC + && (origin.flags() & PUBLIC) > (impl.owner.flags() & PUBLIC)) { + // this is to work around a horrible but permanent + // reflection design error. + addBridge(pos, meth, impl, origin, false, bridges); + } + } else if ((bridge.flags() & (SYNTHETIC | OVERRIDE_BRIDGE)) == SYNTHETIC) { + MethodSymbol other = overridden.get(bridge); + if (other != null && other != meth) { + if (impl == null || !impl.overrides(other, origin, types, true)) { + // Bridge for other symbol pair was added + log.error(pos, "name.clash.same.erasure.no.override", + other, other.location(origin.type, types), + meth, meth.location(origin.type, types)); + } + } + } else if (!bridge.overrides(meth, origin, types, true)) { + // Accidental binary override without source override. + if (bridge.owner == origin || + types.asSuper(bridge.owner.type, meth.owner) == null) + // Don't diagnose the problem if it would already + // have been reported in the superclass + log.error(pos, "name.clash.same.erasure.no.override", + bridge, bridge.location(origin.type, types), + meth, meth.location(origin.type, types)); + } + } + } + // where + Filter overrideBridgeFilter = new Filter() { + public boolean accepts(Symbol s) { + return (s.flags() & (SYNTHETIC | OVERRIDE_BRIDGE)) != SYNTHETIC; + } + }; + /** + * @param method The symbol for which a bridge might have to be added + * @param impl The implementation of method + * @param dest The type in which the bridge would go + */ + private boolean isBridgeNeeded(MethodSymbol method, + MethodSymbol impl, + Type dest) { + if (impl != method) { + // If either method or impl have different erasures as + // members of dest, a bridge is needed. + Type method_erasure = method.erasure(types); + if (!isSameMemberWhenErased(dest, method, method_erasure)) + return true; + Type impl_erasure = impl.erasure(types); + if (!isSameMemberWhenErased(dest, impl, impl_erasure)) + return true; + + // If the erasure of the return type is different, a + // bridge is needed. + return !types.isSameType(impl_erasure.getReturnType(), + method_erasure.getReturnType()); + } else { + // method and impl are the same... + if ((method.flags() & ABSTRACT) != 0) { + // ...and abstract so a bridge is not needed. + // Concrete subclasses will bridge as needed. + return false; + } + + // The erasure of the return type is always the same + // for the same symbol. Reducing the three tests in + // the other branch to just one: + return !isSameMemberWhenErased(dest, method, method.erasure(types)); + } + } + /** + * Lookup the method as a member of the type. Compare the + * erasures. + * @param type the class where to look for the method + * @param method the method to look for in class + * @param erasure the erasure of method + */ + private boolean isSameMemberWhenErased(Type type, + MethodSymbol method, + Type erasure) { + return types.isSameType(erasure(types.memberType(type, method)), + erasure); + } + + void addBridges(DiagnosticPosition pos, + TypeSymbol i, + ClassSymbol origin, + ListBuffer bridges) { + for (Scope.Entry e = i.members().elems; e != null; e = e.sibling) + addBridgeIfNeeded(pos, e.sym, origin, bridges); + for (List l = types.interfaces(i.type); l.nonEmpty(); l = l.tail) + addBridges(pos, l.head.tsym, origin, bridges); + } + + /** Add all necessary bridges to some class appending them to list buffer. + * @param pos The source code position to be used for the bridges. + * @param origin The class in which the bridges go. + * @param bridges The list buffer to which the bridges are added. + */ + void addBridges(DiagnosticPosition pos, ClassSymbol origin, ListBuffer bridges) { + Type st = types.supertype(origin.type); + while (st.tag == CLASS) { +// if (isSpecialization(st)) + addBridges(pos, st.tsym, origin, bridges); + st = types.supertype(st); + } + for (List l = types.interfaces(origin.type); l.nonEmpty(); l = l.tail) +// if (isSpecialization(l.head)) + addBridges(pos, l.head.tsym, origin, bridges); + } + +/* ************************************************************************ + * Visitor methods + *************************************************************************/ + + /** Visitor argument: proto-type. + */ + private Type pt; + + /** Visitor method: perform a type translation on tree. + */ + public T translate(T tree, Type pt) { + Type prevPt = this.pt; + try { + this.pt = pt; + return translate(tree); + } finally { + this.pt = prevPt; + } + } + + /** Visitor method: perform a type translation on list of trees. + */ + public List translate(List trees, Type pt) { + Type prevPt = this.pt; + List res; + try { + this.pt = pt; + res = translate(trees); + } finally { + this.pt = prevPt; + } + return res; + } + + public void visitClassDef(JCClassDecl tree) { + translateClass(tree.sym); + result = tree; + } + + JCMethodDecl currentMethod = null; + public void visitMethodDef(JCMethodDecl tree) { + JCMethodDecl previousMethod = currentMethod; + try { + currentMethod = tree; + tree.restype = translate(tree.restype, null); + tree.typarams = List.nil(); + tree.params = translateVarDefs(tree.params); + tree.thrown = translate(tree.thrown, null); + tree.body = translate(tree.body, tree.sym.erasure(types).getReturnType()); + tree.type = erasure(tree.type); + result = tree; + } finally { + currentMethod = previousMethod; + } + + // Check that we do not introduce a name clash by erasing types. + for (Scope.Entry e = tree.sym.owner.members().lookup(tree.name); + e.sym != null; + e = e.next()) { + if (e.sym != tree.sym && + types.isSameType(erasure(e.sym.type), tree.type)) { + log.error(tree.pos(), + "name.clash.same.erasure", tree.sym, + e.sym); + return; + } + } + } + + public void visitVarDef(JCVariableDecl tree) { + tree.vartype = translate(tree.vartype, null); + tree.init = translate(tree.init, tree.sym.erasure(types)); + tree.type = erasure(tree.type); + result = tree; + } + + public void visitDoLoop(JCDoWhileLoop tree) { + tree.body = translate(tree.body); + tree.cond = translate(tree.cond, syms.booleanType); + result = tree; + } + + public void visitWhileLoop(JCWhileLoop tree) { + tree.cond = translate(tree.cond, syms.booleanType); + tree.body = translate(tree.body); + result = tree; + } + + public void visitForLoop(JCForLoop tree) { + tree.init = translate(tree.init, null); + if (tree.cond != null) + tree.cond = translate(tree.cond, syms.booleanType); + tree.step = translate(tree.step, null); + tree.body = translate(tree.body); + result = tree; + } + + public void visitForeachLoop(JCEnhancedForLoop tree) { + tree.var = translate(tree.var, null); + Type iterableType = tree.expr.type; + tree.expr = translate(tree.expr, erasure(tree.expr.type)); + if (types.elemtype(tree.expr.type) == null) + tree.expr.type = iterableType; // preserve type for Lower + tree.body = translate(tree.body); + result = tree; + } + + public void visitSwitch(JCSwitch tree) { + Type selsuper = types.supertype(tree.selector.type); + boolean enumSwitch = selsuper != null && + selsuper.tsym == syms.enumSym; + Type target = enumSwitch ? erasure(tree.selector.type) : syms.intType; + tree.selector = translate(tree.selector, target); + tree.cases = translateCases(tree.cases); + result = tree; + } + + public void visitCase(JCCase tree) { + tree.pat = translate(tree.pat, null); + tree.stats = translate(tree.stats); + result = tree; + } + + public void visitSynchronized(JCSynchronized tree) { + tree.lock = translate(tree.lock, erasure(tree.lock.type)); + tree.body = translate(tree.body); + result = tree; + } + + public void visitTry(JCTry tree) { + tree.resources = translate(tree.resources, syms.autoCloseableType); + tree.body = translate(tree.body); + tree.catchers = translateCatchers(tree.catchers); + tree.finalizer = translate(tree.finalizer); + result = tree; + } + + public void visitConditional(JCConditional tree) { + tree.cond = translate(tree.cond, syms.booleanType); + tree.truepart = translate(tree.truepart, erasure(tree.type)); + tree.falsepart = translate(tree.falsepart, erasure(tree.type)); + tree.type = erasure(tree.type); + result = retype(tree, tree.type, pt); + } + + public void visitIf(JCIf tree) { + tree.cond = translate(tree.cond, syms.booleanType); + tree.thenpart = translate(tree.thenpart); + tree.elsepart = translate(tree.elsepart); + result = tree; + } + + public void visitExec(JCExpressionStatement tree) { + tree.expr = translate(tree.expr, null); + result = tree; + } + + public void visitReturn(JCReturn tree) { + tree.expr = translate(tree.expr, currentMethod.sym.erasure(types).getReturnType()); + result = tree; + } + + public void visitThrow(JCThrow tree) { + tree.expr = translate(tree.expr, erasure(tree.expr.type)); + result = tree; + } + + public void visitAssert(JCAssert tree) { + tree.cond = translate(tree.cond, syms.booleanType); + if (tree.detail != null) + tree.detail = translate(tree.detail, erasure(tree.detail.type)); + result = tree; + } + + public void visitApply(JCMethodInvocation tree) { + tree.meth = translate(tree.meth, null); + Symbol meth = TreeInfo.symbol(tree.meth); + Type mt = meth.erasure(types); + List argtypes = mt.getParameterTypes(); + if (allowEnums && + meth.name==names.init && + meth.owner == syms.enumSym) + argtypes = argtypes.tail.tail; + if (tree.varargsElement != null) + tree.varargsElement = types.erasure(tree.varargsElement); + else + Assert.check(tree.args.length() == argtypes.length()); + tree.args = translateArgs(tree.args, argtypes, tree.varargsElement); + + // Insert casts of method invocation results as needed. + result = retype(tree, mt.getReturnType(), pt); + } + + public void visitNewClass(JCNewClass tree) { + if (tree.encl != null) + tree.encl = translate(tree.encl, erasure(tree.encl.type)); + tree.clazz = translate(tree.clazz, null); + if (tree.varargsElement != null) + tree.varargsElement = types.erasure(tree.varargsElement); + tree.args = translateArgs( + tree.args, tree.constructor.erasure(types).getParameterTypes(), tree.varargsElement); + tree.def = translate(tree.def, null); + tree.type = erasure(tree.type); + result = tree; + } + + public void visitNewArray(JCNewArray tree) { + tree.elemtype = translate(tree.elemtype, null); + translate(tree.dims, syms.intType); + if (tree.type != null) { + tree.elems = translate(tree.elems, erasure(types.elemtype(tree.type))); + tree.type = erasure(tree.type); + } else { + tree.elems = translate(tree.elems, null); + } + + result = tree; + } + + public void visitParens(JCParens tree) { + tree.expr = translate(tree.expr, pt); + tree.type = erasure(tree.type); + result = tree; + } + + public void visitAssign(JCAssign tree) { + tree.lhs = translate(tree.lhs, null); + tree.rhs = translate(tree.rhs, erasure(tree.lhs.type)); + tree.type = erasure(tree.type); + result = tree; + } + + public void visitAssignop(JCAssignOp tree) { + tree.lhs = translate(tree.lhs, null); + tree.rhs = translate(tree.rhs, tree.operator.type.getParameterTypes().tail.head); + tree.type = erasure(tree.type); + result = tree; + } + + public void visitUnary(JCUnary tree) { + tree.arg = translate(tree.arg, tree.operator.type.getParameterTypes().head); + result = tree; + } + + public void visitBinary(JCBinary tree) { + tree.lhs = translate(tree.lhs, tree.operator.type.getParameterTypes().head); + tree.rhs = translate(tree.rhs, tree.operator.type.getParameterTypes().tail.head); + result = tree; + } + + public void visitTypeCast(JCTypeCast tree) { + tree.clazz = translate(tree.clazz, null); + tree.type = erasure(tree.type); + tree.expr = translate(tree.expr, tree.type); + result = tree; + } + + public void visitTypeTest(JCInstanceOf tree) { + tree.expr = translate(tree.expr, null); + tree.clazz = translate(tree.clazz, null); + result = tree; + } + + public void visitIndexed(JCArrayAccess tree) { + tree.indexed = translate(tree.indexed, erasure(tree.indexed.type)); + tree.index = translate(tree.index, syms.intType); + + // Insert casts of indexed expressions as needed. + result = retype(tree, types.elemtype(tree.indexed.type), pt); + } + + // There ought to be nothing to rewrite here; + // we don't generate code. + public void visitAnnotation(JCAnnotation tree) { + result = tree; + } + + public void visitIdent(JCIdent tree) { + Type et = tree.sym.erasure(types); + + // Map type variables to their bounds. + if (tree.sym.kind == TYP && tree.sym.type.tag == TYPEVAR) { + result = make.at(tree.pos).Type(et); + } else + // Map constants expressions to themselves. + if (tree.type.constValue() != null) { + result = tree; + } + // Insert casts of variable uses as needed. + else if (tree.sym.kind == VAR) { + result = retype(tree, et, pt); + } + else { + tree.type = erasure(tree.type); + result = tree; + } + } + + public void visitSelect(JCFieldAccess tree) { + Type t = tree.selected.type; + while (t.tag == TYPEVAR) + t = t.getUpperBound(); + if (t.isCompound()) { + if ((tree.sym.flags() & IPROXY) != 0) { + tree.sym = ((MethodSymbol)tree.sym). + implemented((TypeSymbol)tree.sym.owner, types); + } + tree.selected = coerce( + translate(tree.selected, erasure(tree.selected.type)), + erasure(tree.sym.owner.type)); + } else + tree.selected = translate(tree.selected, erasure(t)); + + // Map constants expressions to themselves. + if (tree.type.constValue() != null) { + result = tree; + } + // Insert casts of variable uses as needed. + else if (tree.sym.kind == VAR) { + result = retype(tree, tree.sym.erasure(types), pt); + } + else { + tree.type = erasure(tree.type); + result = tree; + } + } + + public void visitTypeArray(JCArrayTypeTree tree) { + tree.elemtype = translate(tree.elemtype, null); + tree.type = erasure(tree.type); + result = tree; + } + + /** Visitor method for parameterized types. + */ + public void visitTypeApply(JCTypeApply tree) { + JCTree clazz = translate(tree.clazz, null); + result = clazz; + } + +/************************************************************************** + * utility methods + *************************************************************************/ + + private Type erasure(Type t) { + return types.erasure(t); + } + + private boolean boundsRestricted(ClassSymbol c) { + Type st = types.supertype(c.type); + if (st.isParameterized()) { + List actuals = st.allparams(); + List formals = st.tsym.type.allparams(); + while (!actuals.isEmpty() && !formals.isEmpty()) { + Type actual = actuals.head; + Type formal = formals.head; + + if (!types.isSameType(types.erasure(actual), + types.erasure(formal))) + return true; + + actuals = actuals.tail; + formals = formals.tail; + } + } + return false; + } + + private List addOverrideBridgesIfNeeded(DiagnosticPosition pos, + final ClassSymbol c) { + ListBuffer buf = ListBuffer.lb(); + if (c.isInterface() || !boundsRestricted(c)) + return buf.toList(); + Type t = types.supertype(c.type); + Scope s = t.tsym.members(); + if (s.elems != null) { + for (Symbol sym : s.getElements(new NeedsOverridBridgeFilter(c))) { + + MethodSymbol m = (MethodSymbol)sym; + MethodSymbol member = (MethodSymbol)m.asMemberOf(c.type, types); + MethodSymbol impl = m.implementation(c, types, false); + + if ((impl == null || impl.owner != c) && + !types.isSameType(member.erasure(types), m.erasure(types))) { + addOverrideBridges(pos, m, member, c, buf); + } + } + } + return buf.toList(); + } + // where + class NeedsOverridBridgeFilter implements Filter { + + ClassSymbol c; + + NeedsOverridBridgeFilter(ClassSymbol c) { + this.c = c; + } + public boolean accepts(Symbol s) { + return s.kind == MTH && + !s.isConstructor() && + s.isInheritedIn(c, types) && + (s.flags() & FINAL) == 0 && + (s.flags() & (SYNTHETIC | OVERRIDE_BRIDGE)) != SYNTHETIC; + } + } + + private void addOverrideBridges(DiagnosticPosition pos, + MethodSymbol impl, + MethodSymbol member, + ClassSymbol c, + ListBuffer bridges) { + Type implErasure = impl.erasure(types); + long flags = (impl.flags() & AccessFlags) | SYNTHETIC | BRIDGE | OVERRIDE_BRIDGE; + member = new MethodSymbol(flags, member.name, member.type, c); + JCMethodDecl md = make.MethodDef(member, null); + JCExpression receiver = make.Super(types.supertype(c.type).tsym.erasure(types), c); + Type calltype = erasure(impl.type.getReturnType()); + JCExpression call = + make.Apply(null, + make.Select(receiver, impl).setType(calltype), + translateArgs(make.Idents(md.params), + implErasure.getParameterTypes(), null)) + .setType(calltype); + JCStatement stat = (member.getReturnType().tag == VOID) + ? make.Exec(call) + : make.Return(coerce(call, member.erasure(types).getReturnType())); + md.body = make.Block(0, List.of(stat)); + c.members().enter(member); + bridges.append(md); + } + +/************************************************************************** + * main method + *************************************************************************/ + + private Env env; + + void translateClass(ClassSymbol c) { + Type st = types.supertype(c.type); + + // process superclass before derived + if (st.tag == CLASS) + translateClass((ClassSymbol)st.tsym); + + Env myEnv = enter.typeEnvs.remove(c); + if (myEnv == null) + return; + Env oldEnv = env; + try { + env = myEnv; + // class has not been translated yet + + TreeMaker savedMake = make; + Type savedPt = pt; + make = make.forToplevel(env.toplevel); + pt = null; + try { + JCClassDecl tree = (JCClassDecl) env.tree; + tree.typarams = List.nil(); + super.visitClassDef(tree); + make.at(tree.pos); + if (addBridges) { + ListBuffer bridges = new ListBuffer(); + if (false) //see CR: 6996415 + bridges.appendList(addOverrideBridgesIfNeeded(tree, c)); + if ((tree.sym.flags() & INTERFACE) == 0) + addBridges(tree.pos(), tree.sym, bridges); + tree.defs = bridges.toList().prependList(tree.defs); + } + tree.type = erasure(tree.type); + } finally { + make = savedMake; + pt = savedPt; + } + } finally { + env = oldEnv; + } + } + + /** Translate a toplevel class definition. + * @param cdef The definition to be translated. + */ + public JCTree translateTopLevelClass(JCTree cdef, TreeMaker make) { + // note that this method does NOT support recursion. + this.make = make; + pt = null; + return translate(cdef, null); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/file/BaseFileObject.java b/douyu-javac/src/main/java/com/sun/tools/javac/file/BaseFileObject.java new file mode 100644 index 0000000..556f14c --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/file/BaseFileObject.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2005, 2009, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.file; + +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.CharsetDecoder; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.NestingKind; +import javax.tools.FileObject; +import javax.tools.JavaFileObject; + +import static javax.tools.JavaFileObject.Kind.*; + +import com.sun.tools.javac.util.BaseFileManager; + +/** + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. +*/ +public abstract class BaseFileObject implements JavaFileObject { + protected BaseFileObject(JavacFileManager fileManager) { + this.fileManager = fileManager; + } + + /** Return a short name for the object, such as for use in raw diagnostics + */ + public abstract String getShortName(); + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + getName() + "]"; + } + + public NestingKind getNestingKind() { return null; } + + public Modifier getAccessLevel() { return null; } + + public Reader openReader(boolean ignoreEncodingErrors) throws IOException { + return new InputStreamReader(openInputStream(), getDecoder(ignoreEncodingErrors)); + } + + protected CharsetDecoder getDecoder(boolean ignoreEncodingErrors) { + throw new UnsupportedOperationException(); + } + + protected abstract String inferBinaryName(Iterable path); + + protected static JavaFileObject.Kind getKind(String filename) { + return BaseFileManager.getKind(filename); + } + + protected static String removeExtension(String fileName) { + int lastDot = fileName.lastIndexOf("."); + return (lastDot == -1 ? fileName : fileName.substring(0, lastDot)); + } + + protected static URI createJarUri(File jarFile, String entryName) { + URI jarURI = jarFile.toURI().normalize(); + String separator = entryName.startsWith("/") ? "!" : "!/"; + try { + // The jar URI convention appears to be not to re-encode the jarURI + return new URI("jar:" + jarURI + separator + entryName); + } catch (URISyntaxException e) { + throw new CannotCreateUriError(jarURI + separator + entryName, e); + } + } + + /** Used when URLSyntaxException is thrown unexpectedly during + * implementations of (Base)FileObject.toURI(). */ + protected static class CannotCreateUriError extends Error { + private static final long serialVersionUID = 9101708840997613546L; + public CannotCreateUriError(String value, Throwable cause) { + super(value, cause); + } + } + + /** Return the last component of a presumed hierarchical URI. + * From the scheme specific part of the URI, it returns the substring + * after the last "/" if any, or everything if no "/" is found. + */ + public static String getSimpleName(FileObject fo) { + URI uri = fo.toUri(); + String s = uri.getSchemeSpecificPart(); + return s.substring(s.lastIndexOf("/") + 1); // safe when / not found + + } + + // force subtypes to define equals + @Override + public abstract boolean equals(Object other); + + // force subtypes to define hashCode + @Override + public abstract int hashCode(); + + /** The file manager that created this JavaFileObject. */ + protected final JavacFileManager fileManager; +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/file/CacheFSInfo.java b/douyu-javac/src/main/java/com/sun/tools/javac/file/CacheFSInfo.java new file mode 100644 index 0000000..7c44a33 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/file/CacheFSInfo.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.file; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.sun.tools.javac.util.Context; + +/** + * Caching implementation of FSInfo. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class CacheFSInfo extends FSInfo { + + /** + * Register a Context.Factory to create a CacheFSInfo. + */ + public static void preRegister(Context context) { + context.put(FSInfo.class, new Context.Factory() { + public FSInfo make(Context c) { + FSInfo instance = new CacheFSInfo(); + c.put(FSInfo.class, instance); + return instance; + } + }); + } + + public void clearCache() { + cache.clear(); + } + + @Override + public File getCanonicalFile(File file) { + Entry e = getEntry(file); + return e.canonicalFile; + } + + @Override + public boolean exists(File file) { + Entry e = getEntry(file); + return e.exists; + } + + @Override + public boolean isDirectory(File file) { + Entry e = getEntry(file); + return e.isDirectory; + } + + @Override + public boolean isFile(File file) { + Entry e = getEntry(file); + return e.isFile; + } + + @Override + public List getJarClassPath(File file) throws IOException { + // don't bother to lock the cache, because it is thread-safe, and + // because the worst that can happen would be to create two identical + // jar class paths together and have one overwrite the other. + Entry e = getEntry(file); + if (e.jarClassPath == null) + e.jarClassPath = super.getJarClassPath(file); + return e.jarClassPath; + } + + private Entry getEntry(File file) { + // don't bother to lock the cache, because it is thread-safe, and + // because the worst that can happen would be to create two identical + // entries together and have one overwrite the other. + Entry e = cache.get(file); + if (e == null) { + e = new Entry(); + e.canonicalFile = super.getCanonicalFile(file); + e.exists = super.exists(file); + e.isDirectory = super.isDirectory(file); + e.isFile = super.isFile(file); + cache.put(file, e); + } + return e; + } + + // could also be a Map> ? + private Map cache = new ConcurrentHashMap(); + + private static class Entry { + File canonicalFile; + boolean exists; + boolean isFile; + boolean isDirectory; + List jarClassPath; + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/file/FSInfo.java b/douyu-javac/src/main/java/com/sun/tools/javac/file/FSInfo.java new file mode 100644 index 0000000..c8726a5 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/file/FSInfo.java @@ -0,0 +1,94 @@ + +package com.sun.tools.javac.file; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.StringTokenizer; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +import com.sun.tools.javac.util.Context; + +/** + * Get meta-info about files. Default direct (non-caching) implementation. + * @see CacheFSInfo + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class FSInfo { + + /** Get the FSInfo instance for this context. + * @param context the context + * @return the Paths instance for this context + */ + public static FSInfo instance(Context context) { + FSInfo instance = context.get(FSInfo.class); + if (instance == null) + instance = new FSInfo(); + return instance; + } + + protected FSInfo() { + } + + protected FSInfo(Context context) { + context.put(FSInfo.class, this); + } + + public File getCanonicalFile(File file) { + try { + return file.getCanonicalFile(); + } catch (IOException e) { + return file.getAbsoluteFile(); + } + } + + public boolean exists(File file) { + return file.exists(); + } + + public boolean isDirectory(File file) { + return file.isDirectory(); + } + + public boolean isFile(File file) { + return file.isFile(); + } + + public List getJarClassPath(File file) throws IOException { + String parent = file.getParent(); + JarFile jarFile = new JarFile(file); + try { + Manifest man = jarFile.getManifest(); + if (man == null) + return Collections.emptyList(); + + Attributes attr = man.getMainAttributes(); + if (attr == null) + return Collections.emptyList(); + + String path = attr.getValue(Attributes.Name.CLASS_PATH); + if (path == null) + return Collections.emptyList(); + + List list = new ArrayList(); + + for (StringTokenizer st = new StringTokenizer(path); st.hasMoreTokens(); ) { + String elt = st.nextToken(); + File f = (parent == null ? new File(elt) : new File(parent, elt)); + list.add(f); + } + + return list; + } finally { + jarFile.close(); + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/file/JavacFileManager.java b/douyu-javac/src/main/java/com/sun/tools/javac/file/JavacFileManager.java new file mode 100644 index 0000000..ff10fd3 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/file/JavacFileManager.java @@ -0,0 +1,945 @@ +/* + * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.file; + +import java.util.Comparator; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.zip.ZipFile; + +import javax.lang.model.SourceVersion; +import javax.tools.FileObject; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; + +import com.sun.tools.javac.file.RelativePath.RelativeFile; +import com.sun.tools.javac.file.RelativePath.RelativeDirectory; +import com.sun.tools.javac.main.OptionName; +import com.sun.tools.javac.util.BaseFileManager; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; + +import static javax.tools.StandardLocation.*; +import static com.sun.tools.javac.main.OptionName.*; + +/** + * This class provides access to the source, class and other files + * used by the compiler and related tools. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class JavacFileManager extends BaseFileManager implements StandardJavaFileManager { + + public static char[] toArray(CharBuffer buffer) { + if (buffer.hasArray()) + return ((CharBuffer)buffer.compact().flip()).array(); + else + return buffer.toString().toCharArray(); + } + + /** Encapsulates knowledge of paths + */ + private Paths paths; + + private FSInfo fsInfo; + + private boolean contextUseOptimizedZip; + private ZipFileIndexCache zipFileIndexCache; + + private final File uninited = new File("U N I N I T E D"); + + private final Set sourceOrClass = + EnumSet.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS); + + /** The standard output directory, primarily used for classes. + * Initialized by the "-d" option. + * If classOutDir = null, files are written into same directory as the sources + * they were generated from. + */ + private File classOutDir = uninited; + + /** The output directory, used when generating sources while processing annotations. + * Initialized by the "-s" option. + */ + private File sourceOutDir = uninited; + + protected boolean mmappedIO; + protected boolean ignoreSymbolFile; + + protected enum SortFiles implements Comparator { + FORWARD { + public int compare(File f1, File f2) { + return f1.getName().compareTo(f2.getName()); + } + }, + REVERSE { + public int compare(File f1, File f2) { + return -f1.getName().compareTo(f2.getName()); + } + }; + }; + protected SortFiles sortFiles; + + /** + * Register a Context.Factory to create a JavacFileManager. + */ + public static void preRegister(Context context) { + context.put(JavaFileManager.class, new Context.Factory() { + public JavaFileManager make(Context c) { + return new JavacFileManager(c, true, null); + } + }); + } + + /** + * Create a JavacFileManager using a given context, optionally registering + * it as the JavaFileManager for that context. + */ + public JavacFileManager(Context context, boolean register, Charset charset) { + super(charset); + if (register) + context.put(JavaFileManager.class, this); + setContext(context); + } + + /** + * Set the context for JavacFileManager. + */ + @Override + public void setContext(Context context) { + super.setContext(context); + if (paths == null) { + paths = Paths.instance(context); + } else { + // Reuse the Paths object as it stores the locations that + // have been set with setLocation, etc. + paths.setContext(context); + } + + fsInfo = FSInfo.instance(context); + + contextUseOptimizedZip = options.getBoolean("useOptimizedZip", true); + if (contextUseOptimizedZip) + zipFileIndexCache = ZipFileIndexCache.getSharedInstance(); + + mmappedIO = options.isSet("mmappedIO"); + ignoreSymbolFile = options.isSet("ignore.symbol.file"); + + String sf = options.get("sortFiles"); + if (sf != null) { + sortFiles = (sf.equals("reverse") ? SortFiles.REVERSE : SortFiles.FORWARD); + } + } + + @Override + public boolean isDefaultBootClassPath() { + return paths.isDefaultBootClassPath(); + } + + public JavaFileObject getFileForInput(String name) { + return getRegularFile(new File(name)); + } + + public JavaFileObject getRegularFile(File file) { + return new RegularFileObject(this, file); + } + + public JavaFileObject getFileForOutput(String classname, + JavaFileObject.Kind kind, + JavaFileObject sibling) + throws IOException + { + return getJavaFileForOutput(CLASS_OUTPUT, classname, kind, sibling); + } + + public Iterable getJavaFileObjectsFromStrings(Iterable names) { + ListBuffer files = new ListBuffer(); + for (String name : names) + files.append(new File(nullCheck(name))); + return getJavaFileObjectsFromFiles(files.toList()); + } + + public Iterable getJavaFileObjects(String... names) { + return getJavaFileObjectsFromStrings(Arrays.asList(nullCheck(names))); + } + + private static boolean isValidName(String name) { + // Arguably, isValidName should reject keywords (such as in SourceVersion.isName() ), + // but the set of keywords depends on the source level, and we don't want + // impls of JavaFileManager to have to be dependent on the source level. + // Therefore we simply check that the argument is a sequence of identifiers + // separated by ".". + for (String s : name.split("\\.", -1)) { + if (!SourceVersion.isIdentifier(s)) + return false; + } + return true; + } + + private static void validateClassName(String className) { + if (!isValidName(className)) + throw new IllegalArgumentException("Invalid class name: " + className); + } + + private static void validatePackageName(String packageName) { + if (packageName.length() > 0 && !isValidName(packageName)) + throw new IllegalArgumentException("Invalid packageName name: " + packageName); + } + + public static void testName(String name, + boolean isValidPackageName, + boolean isValidClassName) + { + try { + validatePackageName(name); + if (!isValidPackageName) + throw new AssertionError("Invalid package name accepted: " + name); + printAscii("Valid package name: \"%s\"", name); + } catch (IllegalArgumentException e) { + if (isValidPackageName) + throw new AssertionError("Valid package name rejected: " + name); + printAscii("Invalid package name: \"%s\"", name); + } + try { + validateClassName(name); + if (!isValidClassName) + throw new AssertionError("Invalid class name accepted: " + name); + printAscii("Valid class name: \"%s\"", name); + } catch (IllegalArgumentException e) { + if (isValidClassName) + throw new AssertionError("Valid class name rejected: " + name); + printAscii("Invalid class name: \"%s\"", name); + } + } + + private static void printAscii(String format, Object... args) { + String message; + try { + final String ascii = "US-ASCII"; + message = new String(String.format(null, format, args).getBytes(ascii), ascii); + } catch (java.io.UnsupportedEncodingException ex) { + throw new AssertionError(ex); + } + System.out.println(message); + } + + + /** + * Insert all files in subdirectory subdirectory of directory directory + * which match fileKinds into resultList + */ + private void listDirectory(File directory, + RelativeDirectory subdirectory, + Set fileKinds, + boolean recurse, + ListBuffer resultList) { + File d = subdirectory.getFile(directory); + if (!caseMapCheck(d, subdirectory)) + return; + + File[] files = d.listFiles(); + if (files == null) + return; + + if (sortFiles != null) + Arrays.sort(files, sortFiles); + + for (File f: files) { + String fname = f.getName(); + if (f.isDirectory()) { + if (recurse && SourceVersion.isIdentifier(fname)) { + listDirectory(directory, + new RelativeDirectory(subdirectory, fname), + fileKinds, + recurse, + resultList); + } + } else { + if (isValidFile(fname, fileKinds)) { + JavaFileObject fe = + new RegularFileObject(this, fname, new File(d, fname)); + resultList.append(fe); + } + } + } + } + + /** + * Insert all files in subdirectory subdirectory of archive archive + * which match fileKinds into resultList + */ + private void listArchive(Archive archive, + RelativeDirectory subdirectory, + Set fileKinds, + boolean recurse, + ListBuffer resultList) { + // Get the files directly in the subdir + List files = archive.getFiles(subdirectory); + if (files != null) { + for (; !files.isEmpty(); files = files.tail) { + String file = files.head; + if (isValidFile(file, fileKinds)) { + resultList.append(archive.getFileObject(subdirectory, file)); + } + } + } + if (recurse) { + for (RelativeDirectory s: archive.getSubdirectories()) { + if (subdirectory.contains(s)) { + // Because the archive map is a flat list of directories, + // the enclosing loop will pick up all child subdirectories. + // Therefore, there is no need to recurse deeper. + listArchive(archive, s, fileKinds, false, resultList); + } + } + } + } + + /** + * container is a directory, a zip file, or a non-existant path. + * Insert all files in subdirectory subdirectory of container which + * match fileKinds into resultList + */ + private void listContainer(File container, + RelativeDirectory subdirectory, + Set fileKinds, + boolean recurse, + ListBuffer resultList) { + Archive archive = archives.get(container); + if (archive == null) { + // archives are not created for directories. + if (fsInfo.isDirectory(container)) { + listDirectory(container, + subdirectory, + fileKinds, + recurse, + resultList); + return; + } + + // Not a directory; either a file or non-existant, create the archive + try { + archive = openArchive(container); + } catch (IOException ex) { + log.error("error.reading.file", + container, getMessage(ex)); + return; + } + } + listArchive(archive, + subdirectory, + fileKinds, + recurse, + resultList); + } + + private boolean isValidFile(String s, Set fileKinds) { + JavaFileObject.Kind kind = getKind(s); + return fileKinds.contains(kind); + } + + private static final boolean fileSystemIsCaseSensitive = + File.separatorChar == '/'; + + /** Hack to make Windows case sensitive. Test whether given path + * ends in a string of characters with the same case as given name. + * Ignore file separators in both path and name. + */ + private boolean caseMapCheck(File f, RelativePath name) { + if (fileSystemIsCaseSensitive) return true; + // Note that getCanonicalPath() returns the case-sensitive + // spelled file name. + String path; + try { + path = f.getCanonicalPath(); + } catch (IOException ex) { + return false; + } + char[] pcs = path.toCharArray(); + char[] ncs = name.path.toCharArray(); + int i = pcs.length - 1; + int j = ncs.length - 1; + while (i >= 0 && j >= 0) { + while (i >= 0 && pcs[i] == File.separatorChar) i--; + while (j >= 0 && ncs[j] == '/') j--; + if (i >= 0 && j >= 0) { + if (pcs[i] != ncs[j]) return false; + i--; + j--; + } + } + return j < 0; + } + + /** + * An archive provides a flat directory structure of a ZipFile by + * mapping directory names to lists of files (basenames). + */ + public interface Archive { + void close() throws IOException; + + boolean contains(RelativePath name); + + JavaFileObject getFileObject(RelativeDirectory subdirectory, String file); + + List getFiles(RelativeDirectory subdirectory); + + Set getSubdirectories(); + } + + public class MissingArchive implements Archive { + final File zipFileName; + public MissingArchive(File name) { + zipFileName = name; + } + public boolean contains(RelativePath name) { + return false; + } + + public void close() { + } + + public JavaFileObject getFileObject(RelativeDirectory subdirectory, String file) { + return null; + } + + public List getFiles(RelativeDirectory subdirectory) { + return List.nil(); + } + + public Set getSubdirectories() { + return Collections.emptySet(); + } + + @Override + public String toString() { + return "MissingArchive[" + zipFileName + "]"; + } + } + + /** A directory of zip files already opened. + */ + Map archives = new HashMap(); + + private static final String[] symbolFileLocation = { "lib", "ct.sym" }; + private static final RelativeDirectory symbolFilePrefix + = new RelativeDirectory("META-INF/sym/rt.jar/"); + + /* + * This method looks for a ZipFormatException and takes appropriate + * evasive action. If there is a failure in the fast mode then we + * fail over to the platform zip, and allow it to deal with a potentially + * non compliant zip file. + */ + protected Archive openArchive(File zipFilename) throws IOException { + try { + return openArchive(zipFilename, contextUseOptimizedZip); + } catch (IOException ioe) { + if (ioe instanceof ZipFileIndex.ZipFormatException) { + return openArchive(zipFilename, false); + } else { + throw ioe; + } + } + } + + /** Open a new zip file directory, and cache it. + */ + private Archive openArchive(File zipFileName, boolean useOptimizedZip) throws IOException { + File origZipFileName = zipFileName; + if (!ignoreSymbolFile && paths.isDefaultBootClassPathRtJar(zipFileName)) { + File file = zipFileName.getParentFile().getParentFile(); // ${java.home} + if (new File(file.getName()).equals(new File("jre"))) + file = file.getParentFile(); + // file == ${jdk.home} + for (String name : symbolFileLocation) + file = new File(file, name); + // file == ${jdk.home}/lib/ct.sym + if (file.exists()) + zipFileName = file; + } + + Archive archive; + try { + + ZipFile zdir = null; + + boolean usePreindexedCache = false; + String preindexCacheLocation = null; + + if (!useOptimizedZip) { + zdir = new ZipFile(zipFileName); + } else { + usePreindexedCache = options.isSet("usezipindex"); + preindexCacheLocation = options.get("java.io.tmpdir"); + String optCacheLoc = options.get("cachezipindexdir"); + + if (optCacheLoc != null && optCacheLoc.length() != 0) { + if (optCacheLoc.startsWith("\"")) { + if (optCacheLoc.endsWith("\"")) { + optCacheLoc = optCacheLoc.substring(1, optCacheLoc.length() - 1); + } + else { + optCacheLoc = optCacheLoc.substring(1); + } + } + + File cacheDir = new File(optCacheLoc); + if (cacheDir.exists() && cacheDir.canWrite()) { + preindexCacheLocation = optCacheLoc; + if (!preindexCacheLocation.endsWith("/") && + !preindexCacheLocation.endsWith(File.separator)) { + preindexCacheLocation += File.separator; + } + } + } + } + + if (origZipFileName == zipFileName) { + if (!useOptimizedZip) { + archive = new ZipArchive(this, zdir); + } else { + archive = new ZipFileIndexArchive(this, + zipFileIndexCache.getZipFileIndex(zipFileName, + null, + usePreindexedCache, + preindexCacheLocation, + options.isSet("writezipindexfiles"))); + } + } else { + if (!useOptimizedZip) { + archive = new SymbolArchive(this, origZipFileName, zdir, symbolFilePrefix); + } else { + archive = new ZipFileIndexArchive(this, + zipFileIndexCache.getZipFileIndex(zipFileName, + symbolFilePrefix, + usePreindexedCache, + preindexCacheLocation, + options.isSet("writezipindexfiles"))); + } + } + } catch (FileNotFoundException ex) { + archive = new MissingArchive(zipFileName); + } catch (ZipFileIndex.ZipFormatException zfe) { + throw zfe; + } catch (IOException ex) { + if (zipFileName.exists()) + log.error("error.reading.file", zipFileName, getMessage(ex)); + archive = new MissingArchive(zipFileName); + } + + archives.put(origZipFileName, archive); + return archive; + } + + /** Flush any output resources. + */ + public void flush() { + contentCache.clear(); + } + + /** + * Close the JavaFileManager, releasing resources. + */ + public void close() { + for (Iterator i = archives.values().iterator(); i.hasNext(); ) { + Archive a = i.next(); + i.remove(); + try { + a.close(); + } catch (IOException e) { + } + } + } + + private String defaultEncodingName; + private String getDefaultEncodingName() { + if (defaultEncodingName == null) { + defaultEncodingName = + new OutputStreamWriter(new ByteArrayOutputStream()).getEncoding(); + } + return defaultEncodingName; + } + + public ClassLoader getClassLoader(Location location) { + nullCheck(location); + Iterable path = getLocation(location); + if (path == null) + return null; + ListBuffer lb = new ListBuffer(); + for (File f: path) { + try { + lb.append(f.toURI().toURL()); + } catch (MalformedURLException e) { + throw new AssertionError(e); + } + } + + return getClassLoader(lb.toArray(new URL[lb.size()])); + } + + public Iterable list(Location location, + String packageName, + Set kinds, + boolean recurse) + throws IOException + { + // validatePackageName(packageName); + nullCheck(packageName); + nullCheck(kinds); + + Iterable path = getLocation(location); + if (path == null) + return List.nil(); + RelativeDirectory subdirectory = RelativeDirectory.forPackage(packageName); + ListBuffer results = new ListBuffer(); + + for (File directory : path) + listContainer(directory, subdirectory, kinds, recurse, results); + return results.toList(); + } + + public String inferBinaryName(Location location, JavaFileObject file) { + file.getClass(); // null check + location.getClass(); // null check + // Need to match the path semantics of list(location, ...) + Iterable path = getLocation(location); + if (path == null) { + return null; + } + + if (file instanceof BaseFileObject) { + return ((BaseFileObject) file).inferBinaryName(path); + } else + throw new IllegalArgumentException(file.getClass().getName()); + } + + public boolean isSameFile(FileObject a, FileObject b) { + nullCheck(a); + nullCheck(b); + if (!(a instanceof BaseFileObject)) + throw new IllegalArgumentException("Not supported: " + a); + if (!(b instanceof BaseFileObject)) + throw new IllegalArgumentException("Not supported: " + b); + return a.equals(b); + } + + public boolean hasLocation(Location location) { + return getLocation(location) != null; + } + + public JavaFileObject getJavaFileForInput(Location location, + String className, + JavaFileObject.Kind kind) + throws IOException + { + nullCheck(location); + // validateClassName(className); + nullCheck(className); + nullCheck(kind); + if (!sourceOrClass.contains(kind)) + throw new IllegalArgumentException("Invalid kind: " + kind); + return getFileForInput(location, RelativeFile.forClass(className, kind)); + } + + public FileObject getFileForInput(Location location, + String packageName, + String relativeName) + throws IOException + { + nullCheck(location); + // validatePackageName(packageName); + nullCheck(packageName); + if (!isRelativeUri(relativeName)) + throw new IllegalArgumentException("Invalid relative name: " + relativeName); + RelativeFile name = packageName.length() == 0 + ? new RelativeFile(relativeName) + : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName); + return getFileForInput(location, name); + } + + private JavaFileObject getFileForInput(Location location, RelativeFile name) throws IOException { + Iterable path = getLocation(location); + if (path == null) + return null; + + for (File dir: path) { + Archive a = archives.get(dir); + if (a == null) { + if (fsInfo.isDirectory(dir)) { + File f = name.getFile(dir); + if (f.exists()) + return new RegularFileObject(this, f); + continue; + } + // Not a directory, create the archive + a = openArchive(dir); + } + // Process the archive + if (a.contains(name)) { + return a.getFileObject(name.dirname(), name.basename()); + } + } + return null; + } + + public JavaFileObject getJavaFileForOutput(Location location, + String className, + JavaFileObject.Kind kind, + FileObject sibling) + throws IOException + { + nullCheck(location); + // validateClassName(className); + nullCheck(className); + nullCheck(kind); + if (!sourceOrClass.contains(kind)) + throw new IllegalArgumentException("Invalid kind: " + kind); + return getFileForOutput(location, RelativeFile.forClass(className, kind), sibling); + } + + public FileObject getFileForOutput(Location location, + String packageName, + String relativeName, + FileObject sibling) + throws IOException + { + nullCheck(location); + // validatePackageName(packageName); + nullCheck(packageName); + if (!isRelativeUri(relativeName)) + throw new IllegalArgumentException("Invalid relative name: " + relativeName); + RelativeFile name = packageName.length() == 0 + ? new RelativeFile(relativeName) + : new RelativeFile(RelativeDirectory.forPackage(packageName), relativeName); + return getFileForOutput(location, name, sibling); + } + + private JavaFileObject getFileForOutput(Location location, + RelativeFile fileName, + FileObject sibling) + throws IOException + { + File dir; + if (location == CLASS_OUTPUT) { + if (getClassOutDir() != null) { + dir = getClassOutDir(); + } else { + File siblingDir = null; + if (sibling != null && sibling instanceof RegularFileObject) { + siblingDir = ((RegularFileObject)sibling).file.getParentFile(); + } + return new RegularFileObject(this, new File(siblingDir, fileName.basename())); + } + } else if (location == SOURCE_OUTPUT) { + dir = (getSourceOutDir() != null ? getSourceOutDir() : getClassOutDir()); + } else { + Iterable path = paths.getPathForLocation(location); + dir = null; + for (File f: path) { + dir = f; + break; + } + } + + File file = fileName.getFile(dir); // null-safe + return new RegularFileObject(this, file); + + } + + public Iterable getJavaFileObjectsFromFiles( + Iterable files) + { + ArrayList result; + if (files instanceof Collection) + result = new ArrayList(((Collection)files).size()); + else + result = new ArrayList(); + for (File f: files) + result.add(new RegularFileObject(this, nullCheck(f))); + return result; + } + + public Iterable getJavaFileObjects(File... files) { + return getJavaFileObjectsFromFiles(Arrays.asList(nullCheck(files))); + } + + public void setLocation(Location location, + Iterable path) + throws IOException + { + nullCheck(location); + paths.lazy(); + + final File dir = location.isOutputLocation() ? getOutputDirectory(path) : null; + + if (location == CLASS_OUTPUT) + classOutDir = getOutputLocation(dir, D); + else if (location == SOURCE_OUTPUT) + sourceOutDir = getOutputLocation(dir, S); + else + paths.setPathForLocation(location, path); + } + // where + private File getOutputDirectory(Iterable path) throws IOException { + if (path == null) + return null; + Iterator pathIter = path.iterator(); + if (!pathIter.hasNext()) + throw new IllegalArgumentException("empty path for directory"); + File dir = pathIter.next(); + if (pathIter.hasNext()) + throw new IllegalArgumentException("path too long for directory"); + if (!dir.exists()) + throw new FileNotFoundException(dir + ": does not exist"); + else if (!dir.isDirectory()) + throw new IOException(dir + ": not a directory"); + return dir; + } + + private File getOutputLocation(File dir, OptionName defaultOptionName) { + if (dir != null) + return dir; + String arg = options.get(defaultOptionName); + if (arg == null) + return null; + return new File(arg); + } + + public Iterable getLocation(Location location) { + nullCheck(location); + paths.lazy(); + if (location == CLASS_OUTPUT) { + return (getClassOutDir() == null ? null : List.of(getClassOutDir())); + } else if (location == SOURCE_OUTPUT) { + return (getSourceOutDir() == null ? null : List.of(getSourceOutDir())); + } else + return paths.getPathForLocation(location); + } + + private File getClassOutDir() { + if (classOutDir == uninited) + classOutDir = getOutputLocation(null, D); + return classOutDir; + } + + private File getSourceOutDir() { + if (sourceOutDir == uninited) + sourceOutDir = getOutputLocation(null, S); + return sourceOutDir; + } + + /** + * Enforces the specification of a "relative" URI as used in + * {@linkplain #getFileForInput(Location,String,URI) + * getFileForInput}. This method must follow the rules defined in + * that method, do not make any changes without consulting the + * specification. + */ + protected static boolean isRelativeUri(URI uri) { + if (uri.isAbsolute()) + return false; + String path = uri.normalize().getPath(); + if (path.length() == 0 /* isEmpty() is mustang API */) + return false; + if (!path.equals(uri.getPath())) // implicitly checks for embedded . and .. + return false; + if (path.startsWith("/") || path.startsWith("./") || path.startsWith("../")) + return false; + return true; + } + + // Convenience method + protected static boolean isRelativeUri(String u) { + try { + return isRelativeUri(new URI(u)); + } catch (URISyntaxException e) { + return false; + } + } + + /** + * Converts a relative file name to a relative URI. This is + * different from File.toURI as this method does not canonicalize + * the file before creating the URI. Furthermore, no schema is + * used. + * @param file a relative file name + * @return a relative URI + * @throws IllegalArgumentException if the file name is not + * relative according to the definition given in {@link + * javax.tools.JavaFileManager#getFileForInput} + */ + public static String getRelativeName(File file) { + if (!file.isAbsolute()) { + String result = file.getPath().replace(File.separatorChar, '/'); + if (isRelativeUri(result)) + return result; + } + throw new IllegalArgumentException("Invalid relative path: " + file); + } + + /** + * Get a detail message from an IOException. + * Most, but not all, instances of IOException provide a non-null result + * for getLocalizedMessage(). But some instances return null: in these + * cases, fallover to getMessage(), and if even that is null, return the + * name of the exception itself. + * @param e an IOException + * @return a string to include in a compiler diagnostic + */ + public static String getMessage(IOException e) { + String s = e.getLocalizedMessage(); + if (s != null) + return s; + s = e.getMessage(); + if (s != null) + return s; + return e.toString(); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/file/Paths.java b/douyu-javac/src/main/java/com/sun/tools/javac/file/Paths.java new file mode 100644 index 0000000..3e0ec5a --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/file/Paths.java @@ -0,0 +1,558 @@ +/* + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.file; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.StringTokenizer; +import java.util.zip.ZipFile; +import javax.tools.JavaFileManager.Location; + +import com.sun.tools.javac.code.Lint; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Log; +import com.sun.tools.javac.util.Options; + +import static javax.tools.StandardLocation.*; +import static com.sun.tools.javac.main.OptionName.*; + +/** This class converts command line arguments, environment variables + * and system properties (in File.pathSeparator-separated String form) + * into a boot class path, user class path, and source path (in + * Collection form). + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Paths { + + /** The context key for the todo list */ + protected static final Context.Key pathsKey = + new Context.Key(); + + /** Get the Paths instance for this context. + * @param context the context + * @return the Paths instance for this context + */ + public static Paths instance(Context context) { + Paths instance = context.get(pathsKey); + if (instance == null) + instance = new Paths(context); + return instance; + } + + /** The log to use for warning output */ + private Log log; + + /** Collection of command-line options */ + private Options options; + + /** Handler for -Xlint options */ + private Lint lint; + + /** Access to (possibly cached) file info */ + private FSInfo fsInfo; + + protected Paths(Context context) { + context.put(pathsKey, this); + pathsForLocation = new HashMap(16); + setContext(context); + } + + void setContext(Context context) { + log = Log.instance(context); + options = Options.instance(context); + lint = Lint.instance(context); + fsInfo = FSInfo.instance(context); + } + + /** Whether to warn about non-existent path elements */ + private boolean warn; + + private Map pathsForLocation; + + private boolean inited = false; // TODO? caching bad? + + /** + * rt.jar as found on the default bootclass path. If the user specified a + * bootclasspath, null is used. + */ + private File defaultBootClassPathRtJar = null; + + /** + * Is bootclasspath the default? + */ + private boolean isDefaultBootClassPath; + + Path getPathForLocation(Location location) { + Path path = pathsForLocation.get(location); + if (path == null) + setPathForLocation(location, null); + return pathsForLocation.get(location); + } + + void setPathForLocation(Location location, Iterable path) { + // TODO? if (inited) throw new IllegalStateException + // TODO: otherwise reset sourceSearchPath, classSearchPath as needed + Path p; + if (path == null) { + if (location == CLASS_PATH) + p = computeUserClassPath(); + else if (location == PLATFORM_CLASS_PATH) + p = computeBootClassPath(); // sets isDefaultBootClassPath + else if (location == ANNOTATION_PROCESSOR_PATH) + p = computeAnnotationProcessorPath(); + else if (location == SOURCE_PATH) + p = computeSourcePath(); + else + // no defaults for other paths + p = null; + } else { + if (location == PLATFORM_CLASS_PATH) { + defaultBootClassPathRtJar = null; + isDefaultBootClassPath = false; + } + p = new Path(); + for (File f: path) + p.addFile(f, warn); // TODO: is use of warn appropriate? + } + pathsForLocation.put(location, p); + } + + public boolean isDefaultBootClassPath() { + lazy(); + return isDefaultBootClassPath; + } + + protected void lazy() { + if (!inited) { + warn = lint.isEnabled(Lint.LintCategory.PATH); + + pathsForLocation.put(PLATFORM_CLASS_PATH, computeBootClassPath()); + pathsForLocation.put(CLASS_PATH, computeUserClassPath()); + pathsForLocation.put(SOURCE_PATH, computeSourcePath()); + + inited = true; + } + } + + public Collection bootClassPath() { + lazy(); + return Collections.unmodifiableCollection(getPathForLocation(PLATFORM_CLASS_PATH)); + } + public Collection userClassPath() { + lazy(); + return Collections.unmodifiableCollection(getPathForLocation(CLASS_PATH)); + } + public Collection sourcePath() { + lazy(); + Path p = getPathForLocation(SOURCE_PATH); + return p == null || p.size() == 0 + ? null + : Collections.unmodifiableCollection(p); + } + + boolean isDefaultBootClassPathRtJar(File file) { + return file.equals(defaultBootClassPathRtJar); + } + + /** + * Split a path into its elements. Empty path elements will be ignored. + * @param path The path to be split + * @return The elements of the path + */ + private static Iterable getPathEntries(String path) { + return getPathEntries(path, null); + } + + /** + * Split a path into its elements. If emptyPathDefault is not null, all + * empty elements in the path, including empty elements at either end of + * the path, will be replaced with the value of emptyPathDefault. + * @param path The path to be split + * @param emptyPathDefault The value to substitute for empty path elements, + * or null, to ignore empty path elements + * @return The elements of the path + */ + private static Iterable getPathEntries(String path, File emptyPathDefault) { + ListBuffer entries = new ListBuffer(); + int start = 0; + while (start <= path.length()) { + int sep = path.indexOf(File.pathSeparatorChar, start); + if (sep == -1) + sep = path.length(); + if (start < sep) + entries.add(new File(path.substring(start, sep))); + else if (emptyPathDefault != null) + entries.add(emptyPathDefault); + start = sep + 1; + } + return entries; + } + + private class Path extends LinkedHashSet { + private static final long serialVersionUID = 0; + + private boolean expandJarClassPaths = false; + private Set canonicalValues = new HashSet(); + + public Path expandJarClassPaths(boolean x) { + expandJarClassPaths = x; + return this; + } + + /** What to use when path element is the empty string */ + private File emptyPathDefault = null; + + public Path emptyPathDefault(File x) { + emptyPathDefault = x; + return this; + } + + public Path() { super(); } + + public Path addDirectories(String dirs, boolean warn) { + boolean prev = expandJarClassPaths; + expandJarClassPaths = true; + try { + if (dirs != null) + for (File dir : getPathEntries(dirs)) + addDirectory(dir, warn); + return this; + } finally { + expandJarClassPaths = prev; + } + } + + public Path addDirectories(String dirs) { + return addDirectories(dirs, warn); + } + + private void addDirectory(File dir, boolean warn) { + if (!dir.isDirectory()) { + if (warn) + log.warning(Lint.LintCategory.PATH, + "dir.path.element.not.found", dir); + return; + } + + File[] files = dir.listFiles(); + if (files == null) + return; + + for (File direntry : files) { + if (isArchive(direntry)) + addFile(direntry, warn); + } + } + + public Path addFiles(String files, boolean warn) { + if (files != null) { + for (File file : getPathEntries(files, emptyPathDefault)) + addFile(file, warn); + } + return this; + } + + public Path addFiles(String files) { + return addFiles(files, warn); + } + + public void addFile(File file, boolean warn) { + if (contains(file)) { + // discard duplicates + return; + } + + if (! fsInfo.exists(file)) { + /* No such file or directory exists */ + if (warn) { + log.warning(Lint.LintCategory.PATH, + "path.element.not.found", file); + } + super.add(file); + return; + } + + File canonFile = fsInfo.getCanonicalFile(file); + if (canonicalValues.contains(canonFile)) { + /* Discard duplicates and avoid infinite recursion */ + return; + } + + if (fsInfo.isFile(file)) { + /* File is an ordinary file. */ + if (!isArchive(file)) { + /* Not a recognized extension; open it to see if + it looks like a valid zip file. */ + try { + ZipFile z = new ZipFile(file); + z.close(); + if (warn) { + log.warning(Lint.LintCategory.PATH, + "unexpected.archive.file", file); + } + } catch (IOException e) { + // FIXME: include e.getLocalizedMessage in warning + if (warn) { + log.warning(Lint.LintCategory.PATH, + "invalid.archive.file", file); + } + return; + } + } + } + + /* Now what we have left is either a directory or a file name + conforming to archive naming convention */ + super.add(file); + canonicalValues.add(canonFile); + + if (expandJarClassPaths && fsInfo.isFile(file)) + addJarClassPath(file, warn); + } + + // Adds referenced classpath elements from a jar's Class-Path + // Manifest entry. In some future release, we may want to + // update this code to recognize URLs rather than simple + // filenames, but if we do, we should redo all path-related code. + private void addJarClassPath(File jarFile, boolean warn) { + try { + for (File f: fsInfo.getJarClassPath(jarFile)) { + addFile(f, warn); + } + } catch (IOException e) { + log.error("error.reading.file", jarFile, JavacFileManager.getMessage(e)); + } + } + } + + private Path computeBootClassPath() { + defaultBootClassPathRtJar = null; + Path path = new Path(); + + String bootclasspathOpt = options.get(BOOTCLASSPATH); + String endorseddirsOpt = options.get(ENDORSEDDIRS); + String extdirsOpt = options.get(EXTDIRS); + String xbootclasspathPrependOpt = options.get(XBOOTCLASSPATH_PREPEND); + String xbootclasspathAppendOpt = options.get(XBOOTCLASSPATH_APPEND); + + path.addFiles(xbootclasspathPrependOpt); + + if (endorseddirsOpt != null) + path.addDirectories(endorseddirsOpt); + else + path.addDirectories(System.getProperty("java.endorsed.dirs"), false); + + if (bootclasspathOpt != null) { + path.addFiles(bootclasspathOpt); + } else { + // Standard system classes for this compiler's release. + String files = System.getProperty("sun.boot.class.path"); + path.addFiles(files, false); + File rt_jar = new File("rt.jar"); + for (File file : getPathEntries(files)) { + if (new File(file.getName()).equals(rt_jar)) + defaultBootClassPathRtJar = file; + } + } + + path.addFiles(xbootclasspathAppendOpt); + + // Strictly speaking, standard extensions are not bootstrap + // classes, but we treat them identically, so we'll pretend + // that they are. + if (extdirsOpt != null) + path.addDirectories(extdirsOpt); + else + path.addDirectories(System.getProperty("java.ext.dirs"), false); + + isDefaultBootClassPath = + (xbootclasspathPrependOpt == null) && + (bootclasspathOpt == null) && + (xbootclasspathAppendOpt == null); + + return path; + } + + private Path computeUserClassPath() { + String cp = options.get(CLASSPATH); + + // CLASSPATH environment variable when run from `javac'. + if (cp == null) cp = System.getProperty("env.class.path"); + + // If invoked via a java VM (not the javac launcher), use the + // platform class path + if (cp == null && System.getProperty("application.home") == null) + cp = System.getProperty("java.class.path"); + + // Default to current working directory. + if (cp == null) cp = "."; + + return new Path() + .expandJarClassPaths(true) // Only search user jars for Class-Paths + .emptyPathDefault(new File(".")) // Empty path elt ==> current directory + .addFiles(cp); + } + + private Path computeSourcePath() { + String sourcePathArg = options.get(SOURCEPATH); + if (sourcePathArg == null) + return null; + + return new Path().addFiles(sourcePathArg); + } + + private Path computeAnnotationProcessorPath() { + String processorPathArg = options.get(PROCESSORPATH); + if (processorPathArg == null) + return null; + + return new Path().addFiles(processorPathArg); + } + + /** The actual effective locations searched for sources */ + private Path sourceSearchPath; + + public Collection sourceSearchPath() { + if (sourceSearchPath == null) { + lazy(); + Path sourcePath = getPathForLocation(SOURCE_PATH); + Path userClassPath = getPathForLocation(CLASS_PATH); + sourceSearchPath = sourcePath != null ? sourcePath : userClassPath; + } + return Collections.unmodifiableCollection(sourceSearchPath); + } + + /** The actual effective locations searched for classes */ + private Path classSearchPath; + + public Collection classSearchPath() { + if (classSearchPath == null) { + lazy(); + Path bootClassPath = getPathForLocation(PLATFORM_CLASS_PATH); + Path userClassPath = getPathForLocation(CLASS_PATH); + classSearchPath = new Path(); + classSearchPath.addAll(bootClassPath); + classSearchPath.addAll(userClassPath); + } + return Collections.unmodifiableCollection(classSearchPath); + } + + /** The actual effective locations for non-source, non-class files */ + private Path otherSearchPath; + + Collection otherSearchPath() { + if (otherSearchPath == null) { + lazy(); + Path userClassPath = getPathForLocation(CLASS_PATH); + Path sourcePath = getPathForLocation(SOURCE_PATH); + if (sourcePath == null) + otherSearchPath = userClassPath; + else { + otherSearchPath = new Path(); + otherSearchPath.addAll(userClassPath); + otherSearchPath.addAll(sourcePath); + } + } + return Collections.unmodifiableCollection(otherSearchPath); + } + + /** Is this the name of an archive file? */ + private boolean isArchive(File file) { + String n = file.getName().toLowerCase(); + return fsInfo.isFile(file) + && (n.endsWith(".jar") || n.endsWith(".zip")); + } + + /** + * Utility method for converting a search path string to an array + * of directory and JAR file URLs. + * + * Note that this method is called by apt and the DocletInvoker. + * + * @param path the search path string + * @return the resulting array of directory and JAR file URLs + */ + public static URL[] pathToURLs(String path) { + StringTokenizer st = new StringTokenizer(path, File.pathSeparator); + URL[] urls = new URL[st.countTokens()]; + int count = 0; + while (st.hasMoreTokens()) { + URL url = fileToURL(new File(st.nextToken())); + if (url != null) { + urls[count++] = url; + } + } + if (urls.length != count) { + URL[] tmp = new URL[count]; + System.arraycopy(urls, 0, tmp, 0, count); + urls = tmp; + } + return urls; + } + + /** + * Returns the directory or JAR file URL corresponding to the specified + * local file name. + * + * @param file the File object + * @return the resulting directory or JAR file URL, or null if unknown + */ + private static URL fileToURL(File file) { + String name; + try { + name = file.getCanonicalPath(); + } catch (IOException e) { + name = file.getAbsolutePath(); + } + name = name.replace(File.separatorChar, '/'); + if (!name.startsWith("/")) { + name = "/" + name; + } + // If the file does not exist, then assume that it's a directory + if (!file.isFile()) { + name = name + "/"; + } + try { + return new URL("file", "", name); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(file.toString()); + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/file/RegularFileObject.java b/douyu-javac/src/main/java/com/sun/tools/javac/file/RegularFileObject.java new file mode 100644 index 0000000..4f6cf7e --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/file/RegularFileObject.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2005, 2009, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.file; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharsetDecoder; +import javax.tools.JavaFileObject; + +/** + * A subclass of JavaFileObject representing regular files. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +class RegularFileObject extends BaseFileObject { + + /** Have the parent directories been created? + */ + private boolean hasParents = false; + private String name; + final File file; + private Reference absFileRef; + + public RegularFileObject(JavacFileManager fileManager, File f) { + this(fileManager, f.getName(), f); + } + + public RegularFileObject(JavacFileManager fileManager, String name, File f) { + super(fileManager); + if (f.isDirectory()) { + throw new IllegalArgumentException("directories not supported"); + } + this.name = name; + this.file = f; + } + + @Override + public URI toUri() { + return file.toURI().normalize(); + } + + @Override + public String getName() { + return file.getPath(); + } + + @Override + public String getShortName() { + return name; + } + + @Override + public JavaFileObject.Kind getKind() { + return getKind(name); + } + + @Override + public InputStream openInputStream() throws IOException { + return new FileInputStream(file); + } + + @Override + public OutputStream openOutputStream() throws IOException { + ensureParentDirectoriesExist(); + return new FileOutputStream(file); + } + + @Override + public CharBuffer getCharContent(boolean ignoreEncodingErrors) throws IOException { + CharBuffer cb = fileManager.getCachedContent(this); + if (cb == null) { + InputStream in = new FileInputStream(file); + try { + ByteBuffer bb = fileManager.makeByteBuffer(in); + JavaFileObject prev = fileManager.log.useSource(this); + try { + cb = fileManager.decode(bb, ignoreEncodingErrors); + } finally { + fileManager.log.useSource(prev); + } + fileManager.recycleByteBuffer(bb); + if (!ignoreEncodingErrors) { + fileManager.cache(this, cb); + } + } finally { + in.close(); + } + } + return cb; + } + + @Override + public Writer openWriter() throws IOException { + ensureParentDirectoriesExist(); + return new OutputStreamWriter(new FileOutputStream(file), fileManager.getEncodingName()); + } + + @Override + public long getLastModified() { + return file.lastModified(); + } + + @Override + public boolean delete() { + return file.delete(); + } + + @Override + protected CharsetDecoder getDecoder(boolean ignoreEncodingErrors) { + return fileManager.getDecoder(fileManager.getEncodingName(), ignoreEncodingErrors); + } + + @Override + protected String inferBinaryName(Iterable path) { + String fPath = file.getPath(); + //System.err.println("RegularFileObject " + file + " " +r.getPath()); + for (File dir: path) { + //System.err.println("dir: " + dir); + String dPath = dir.getPath(); + if (dPath.length() == 0) + dPath = System.getProperty("user.dir"); + if (!dPath.endsWith(File.separator)) + dPath += File.separator; + if (fPath.regionMatches(true, 0, dPath, 0, dPath.length()) + && new File(fPath.substring(0, dPath.length())).equals(new File(dPath))) { + String relativeName = fPath.substring(dPath.length()); + return removeExtension(relativeName).replace(File.separatorChar, '.'); + } + } + return null; + } + + @Override + public boolean isNameCompatible(String cn, JavaFileObject.Kind kind) { + cn.getClass(); + // null check + if (kind == Kind.OTHER && getKind() != kind) { + return false; + } + String n = cn + kind.extension; + if (name.equals(n)) { + return true; + } + if (name.equalsIgnoreCase(n)) { + try { + // allow for Windows + return file.getCanonicalFile().getName().equals(n); + } catch (IOException e) { + } + } + return false; + } + + private void ensureParentDirectoriesExist() throws IOException { + if (!hasParents) { + File parent = file.getParentFile(); + if (parent != null && !parent.exists()) { + if (!parent.mkdirs()) { + if (!parent.exists() || !parent.isDirectory()) { + throw new IOException("could not create parent directories"); + } + } + } + hasParents = true; + } + } + + /** + * Check if two file objects are equal. + * Two RegularFileObjects are equal if the absolute paths of the underlying + * files are equal. + */ + @Override + public boolean equals(Object other) { + if (this == other) + return true; + + if (!(other instanceof RegularFileObject)) + return false; + + RegularFileObject o = (RegularFileObject) other; + return getAbsoluteFile().equals(o.getAbsoluteFile()); + } + + @Override + public int hashCode() { + return getAbsoluteFile().hashCode(); + } + + private File getAbsoluteFile() { + File absFile = (absFileRef == null ? null : absFileRef.get()); + if (absFile == null) { + absFile = file.getAbsoluteFile(); + absFileRef = new SoftReference(absFile); + } + return absFile; + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/file/RelativePath.java b/douyu-javac/src/main/java/com/sun/tools/javac/file/RelativePath.java new file mode 100644 index 0000000..564ff17 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/file/RelativePath.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.file; + +import java.io.File; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import javax.tools.JavaFileObject; + +/** + * Used to represent a platform-neutral path within a platform-specific + * container, such as a directory or zip file. + * Internally, the file separator is always '/'. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public abstract class RelativePath implements Comparable { + /** + * @param p must use '/' as an internal separator + */ + protected RelativePath(String p) { + path = p; + } + + public abstract RelativeDirectory dirname(); + + public abstract String basename(); + + public File getFile(File directory) { + if (path.length() == 0) + return directory; + return new File(directory, path.replace('/', File.separatorChar)); + } + + public int compareTo(RelativePath other) { + return path.compareTo(other.path); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof RelativePath)) + return false; + return path.equals(((RelativePath) other).path); + } + + @Override + public int hashCode() { + return path.hashCode(); + } + + @Override + public String toString() { + return "RelPath[" + path + "]"; + } + + public String getPath() { + return path; + } + + protected final String path; + + /** + * Used to represent a platform-neutral subdirectory within a platform-specific + * container, such as a directory or zip file. + * Internally, the file separator is always '/', and if the path is not empty, + * it always ends in a '/' as well. + */ + public static class RelativeDirectory extends RelativePath { + + static RelativeDirectory forPackage(CharSequence packageName) { + return new RelativeDirectory(packageName.toString().replace('.', '/')); + } + + /** + * @param p must use '/' as an internal separator + */ + public RelativeDirectory(String p) { + super(p.length() == 0 || p.endsWith("/") ? p : p + "/"); + } + + /** + * @param p must use '/' as an internal separator + */ + public RelativeDirectory(RelativeDirectory d, String p) { + this(d.path + p); + } + + @Override + public RelativeDirectory dirname() { + int l = path.length(); + if (l == 0) + return this; + int sep = path.lastIndexOf('/', l - 2); + return new RelativeDirectory(path.substring(0, sep + 1)); + } + + @Override + public String basename() { + int l = path.length(); + if (l == 0) + return path; + int sep = path.lastIndexOf('/', l - 2); + return path.substring(sep + 1, l - 1); + } + + /** + * Return true if this subdirectory "contains" the other path. + * A subdirectory path does not contain itself. + **/ + boolean contains(RelativePath other) { + return other.path.length() > path.length() && other.path.startsWith(path); + } + + @Override + public String toString() { + return "RelativeDirectory[" + path + "]"; + } + } + + /** + * Used to represent a platform-neutral file within a platform-specific + * container, such as a directory or zip file. + * Internally, the file separator is always '/'. It never ends in '/'. + */ + public static class RelativeFile extends RelativePath { + static RelativeFile forClass(CharSequence className, JavaFileObject.Kind kind) { + return new RelativeFile(className.toString().replace('.', '/') + kind.extension); + } + + public RelativeFile(String p) { + super(p); + if (p.endsWith("/")) + throw new IllegalArgumentException(p); + } + + /** + * @param p must use '/' as an internal separator + */ + public RelativeFile(RelativeDirectory d, String p) { + this(d.path + p); + } + + RelativeFile(RelativeDirectory d, RelativePath p) { + this(d, p.path); + } + + @Override + public RelativeDirectory dirname() { + int sep = path.lastIndexOf('/'); + return new RelativeDirectory(path.substring(0, sep + 1)); + } + + @Override + public String basename() { + int sep = path.lastIndexOf('/'); + return path.substring(sep + 1); + } + + ZipEntry getZipEntry(ZipFile zip) { + return zip.getEntry(path); + } + + @Override + public String toString() { + return "RelativeFile[" + path + "]"; + } + + } + +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/file/SymbolArchive.java b/douyu-javac/src/main/java/com/sun/tools/javac/file/SymbolArchive.java new file mode 100644 index 0000000..581527d --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/file/SymbolArchive.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2005, 2009, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.file; + +import java.io.File; +import java.io.IOException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import javax.tools.JavaFileObject; + +import com.sun.tools.javac.file.RelativePath.RelativeDirectory; +import com.sun.tools.javac.file.RelativePath.RelativeFile; +import com.sun.tools.javac.util.List; + +/** + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. +*/ +public class SymbolArchive extends ZipArchive { + + final File origFile; + final RelativeDirectory prefix; + + public SymbolArchive(JavacFileManager fileManager, File orig, ZipFile zdir, RelativeDirectory prefix) throws IOException { + super(fileManager, zdir, false); + this.origFile = orig; + this.prefix = prefix; + initMap(); + } + + @Override + void addZipEntry(ZipEntry entry) { + String name = entry.getName(); + if (!name.startsWith(prefix.path)) { + return; + } + name = name.substring(prefix.path.length()); + int i = name.lastIndexOf('/'); + RelativeDirectory dirname = new RelativeDirectory(name.substring(0, i+1)); + String basename = name.substring(i + 1); + if (basename.length() == 0) { + return; + } + List list = map.get(dirname); + if (list == null) + list = List.nil(); + list = list.prepend(basename); + map.put(dirname, list); + } + + @Override + public JavaFileObject getFileObject(RelativeDirectory subdirectory, String file) { + RelativeDirectory prefix_subdir = new RelativeDirectory(prefix, subdirectory.path); + ZipEntry ze = new RelativeFile(prefix_subdir, file).getZipEntry(zfile); + return new SymbolFileObject(this, file, ze); + } + + @Override + public String toString() { + return "SymbolArchive[" + zfile.getName() + "]"; + } + + /** + * A subclass of JavaFileObject representing zip entries in a symbol file. + */ + public static class SymbolFileObject extends ZipFileObject { + protected SymbolFileObject(SymbolArchive zarch, String name, ZipEntry entry) { + super(zarch, name, entry); + } + + @Override + protected String inferBinaryName(Iterable path) { + String entryName = entry.getName(); + String prefix = ((SymbolArchive) zarch).prefix.path; + if (entryName.startsWith(prefix)) + entryName = entryName.substring(prefix.length()); + return removeExtension(entryName).replace('/', '.'); + } + } + + +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/file/ZipArchive.java b/douyu-javac/src/main/java/com/sun/tools/javac/file/ZipArchive.java new file mode 100644 index 0000000..1a89c20 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/file/ZipArchive.java @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2005, 2009, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.file; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Writer; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharsetDecoder; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import javax.tools.JavaFileObject; + +import com.sun.tools.javac.file.JavacFileManager.Archive; +import com.sun.tools.javac.file.RelativePath.RelativeDirectory; +import com.sun.tools.javac.file.RelativePath.RelativeFile; +import com.sun.tools.javac.util.List; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; + +/** + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class ZipArchive implements Archive { + + public ZipArchive(JavacFileManager fm, ZipFile zfile) throws IOException { + this(fm, zfile, true); + } + + protected ZipArchive(JavacFileManager fm, ZipFile zfile, boolean initMap) throws IOException { + this.fileManager = fm; + this.zfile = zfile; + this.map = new HashMap>(); + if (initMap) + initMap(); + } + + protected void initMap() throws IOException { + for (Enumeration e = zfile.entries(); e.hasMoreElements(); ) { + ZipEntry entry; + try { + entry = e.nextElement(); + } catch (InternalError ex) { + IOException io = new IOException(); + io.initCause(ex); // convenience constructors added in Mustang :-( + throw io; + } + addZipEntry(entry); + } + } + + void addZipEntry(ZipEntry entry) { + String name = entry.getName(); + int i = name.lastIndexOf('/'); + RelativeDirectory dirname = new RelativeDirectory(name.substring(0, i+1)); + String basename = name.substring(i+1); + if (basename.length() == 0) + return; + List list = map.get(dirname); + if (list == null) + list = List.nil(); + list = list.prepend(basename); + map.put(dirname, list); + } + + public boolean contains(RelativePath name) { + RelativeDirectory dirname = name.dirname(); + String basename = name.basename(); + if (basename.length() == 0) + return false; + List list = map.get(dirname); + return (list != null && list.contains(basename)); + } + + public List getFiles(RelativeDirectory subdirectory) { + return map.get(subdirectory); + } + + public JavaFileObject getFileObject(RelativeDirectory subdirectory, String file) { + ZipEntry ze = new RelativeFile(subdirectory, file).getZipEntry(zfile); + return new ZipFileObject(this, file, ze); + } + + public Set getSubdirectories() { + return map.keySet(); + } + + public void close() throws IOException { + zfile.close(); + } + + @Override + public String toString() { + return "ZipArchive[" + zfile.getName() + "]"; + } + + private File getAbsoluteFile() { + File absFile = (absFileRef == null ? null : absFileRef.get()); + if (absFile == null) { + absFile = new File(zfile.getName()).getAbsoluteFile(); + absFileRef = new SoftReference(absFile); + } + return absFile; + } + + /** + * The file manager that created this archive. + */ + protected JavacFileManager fileManager; + /** + * The index for the contents of this archive. + */ + protected final Map> map; + /** + * The zip file for the archive. + */ + protected final ZipFile zfile; + /** + * A reference to the absolute filename for the zip file for the archive. + */ + protected Reference absFileRef; + + /** + * A subclass of JavaFileObject representing zip entries. + */ + public static class ZipFileObject extends BaseFileObject { + + private String name; + ZipArchive zarch; + ZipEntry entry; + + protected ZipFileObject(ZipArchive zarch, String name, ZipEntry entry) { + super(zarch.fileManager); + this.zarch = zarch; + this.name = name; + this.entry = entry; + } + + public URI toUri() { + File zipFile = new File(zarch.zfile.getName()); + return createJarUri(zipFile, entry.getName()); + } + + @Override + public String getName() { + return zarch.zfile.getName() + "(" + entry.getName() + ")"; + } + + @Override + public String getShortName() { + return new File(zarch.zfile.getName()).getName() + "(" + entry + ")"; + } + + @Override + public JavaFileObject.Kind getKind() { + return getKind(entry.getName()); + } + + @Override + public InputStream openInputStream() throws IOException { + return zarch.zfile.getInputStream(entry); + } + + @Override + public OutputStream openOutputStream() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public CharBuffer getCharContent(boolean ignoreEncodingErrors) throws IOException { + CharBuffer cb = fileManager.getCachedContent(this); + if (cb == null) { + InputStream in = zarch.zfile.getInputStream(entry); + try { + ByteBuffer bb = fileManager.makeByteBuffer(in); + JavaFileObject prev = fileManager.log.useSource(this); + try { + cb = fileManager.decode(bb, ignoreEncodingErrors); + } finally { + fileManager.log.useSource(prev); + } + fileManager.recycleByteBuffer(bb); + if (!ignoreEncodingErrors) { + fileManager.cache(this, cb); + } + } finally { + in.close(); + } + } + return cb; + } + + @Override + public Writer openWriter() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public long getLastModified() { + return entry.getTime(); + } + + @Override + public boolean delete() { + throw new UnsupportedOperationException(); + } + + @Override + protected CharsetDecoder getDecoder(boolean ignoreEncodingErrors) { + return fileManager.getDecoder(fileManager.getEncodingName(), ignoreEncodingErrors); + } + + @Override + protected String inferBinaryName(Iterable path) { + String entryName = entry.getName(); + return removeExtension(entryName).replace('/', '.'); + } + + @Override + public boolean isNameCompatible(String cn, JavaFileObject.Kind k) { + cn.getClass(); + // null check + if (k == Kind.OTHER && getKind() != k) { + return false; + } + return name.equals(cn + k.extension); + } + + /** + * Check if two file objects are equal. + * Two ZipFileObjects are equal if the absolute paths of the underlying + * zip files are equal and if the paths within those zip files are equal. + */ + @Override + public boolean equals(Object other) { + if (this == other) + return true; + + if (!(other instanceof ZipFileObject)) + return false; + + ZipFileObject o = (ZipFileObject) other; + return zarch.getAbsoluteFile().equals(o.zarch.getAbsoluteFile()) + && name.equals(o.name); + } + + @Override + public int hashCode() { + return zarch.getAbsoluteFile().hashCode() + name.hashCode(); + } + } + +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/file/ZipFileIndex.java b/douyu-javac/src/main/java/com/sun/tools/javac/file/ZipFileIndex.java new file mode 100644 index 0000000..1bee909 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/file/ZipFileIndex.java @@ -0,0 +1,1172 @@ +/* + * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.file; + + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; +import java.util.zip.ZipException; + +import com.sun.tools.javac.file.RelativePath.RelativeDirectory; +import com.sun.tools.javac.file.RelativePath.RelativeFile; + +/** + * This class implements the building of index of a zip archive and access to + * its context. It also uses a prebuilt index if available. + * It supports invocations where it will serialize an optimized zip index file + * to disk. + * + * In order to use a secondary index file, set "usezipindex" in the Options + * object when JavacFileManager is invoked. (You can pass "-XDusezipindex" on + * the command line.) + * + * Location where to look for/generate optimized zip index files can be + * provided using "-XDcachezipindexdir=". If this flag is not + * provided, the default location is the value of the "java.io.tmpdir" system + * property. + * + * If "-XDwritezipindexfiles" is specified, there will be new optimized index + * file created for each archive, used by the compiler for compilation, at the + * location specified by the "cachezipindexdir" option. + * + * If system property nonBatchMode option is specified the compiler will use + * timestamp checking to reindex the zip files if it is needed. In batch mode + * the timestamps are not checked and the compiler uses the cached indexes. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class ZipFileIndex { + private static final String MIN_CHAR = String.valueOf(Character.MIN_VALUE); + private static final String MAX_CHAR = String.valueOf(Character.MAX_VALUE); + + public final static long NOT_MODIFIED = Long.MIN_VALUE; + + + private static boolean NON_BATCH_MODE = System.getProperty("nonBatchMode") != null;// TODO: Use -XD compiler switch for this. + + private Map directories = + Collections.emptyMap(); + private Set allDirs = + Collections.emptySet(); + + // ZipFileIndex data entries + final File zipFile; + private Reference absFileRef; + long zipFileLastModified = NOT_MODIFIED; + private RandomAccessFile zipRandomFile; + private Entry[] entries; + + private boolean readFromIndex = false; + private File zipIndexFile = null; + private boolean triedToReadIndex = false; + final RelativeDirectory symbolFilePrefix; + private final int symbolFilePrefixLength; + private boolean hasPopulatedData = false; + long lastReferenceTimeStamp = NOT_MODIFIED; + + private final boolean usePreindexedCache; + private final String preindexedCacheLocation; + + private boolean writeIndex = false; + + private Map> relativeDirectoryCache = + new HashMap>(); + + + public synchronized boolean isOpen() { + return (zipRandomFile != null); + } + + ZipFileIndex(File zipFile, RelativeDirectory symbolFilePrefix, boolean writeIndex, + boolean useCache, String cacheLocation) throws IOException { + this.zipFile = zipFile; + this.symbolFilePrefix = symbolFilePrefix; + this.symbolFilePrefixLength = (symbolFilePrefix == null ? 0 : + symbolFilePrefix.getPath().getBytes("UTF-8").length); + this.writeIndex = writeIndex; + this.usePreindexedCache = useCache; + this.preindexedCacheLocation = cacheLocation; + + if (zipFile != null) { + this.zipFileLastModified = zipFile.lastModified(); + } + + // Validate integrity of the zip file + checkIndex(); + } + + @Override + public String toString() { + return "ZipFileIndex[" + zipFile + "]"; + } + + // Just in case... + @Override + protected void finalize() throws Throwable { + closeFile(); + super.finalize(); + } + + private boolean isUpToDate() { + if (zipFile != null + && ((!NON_BATCH_MODE) || zipFileLastModified == zipFile.lastModified()) + && hasPopulatedData) { + return true; + } + + return false; + } + + /** + * Here we need to make sure that the ZipFileIndex is valid. Check the timestamp of the file and + * if its the same as the one at the time the index was build we don't need to reopen anything. + */ + private void checkIndex() throws IOException { + boolean isUpToDate = true; + if (!isUpToDate()) { + closeFile(); + isUpToDate = false; + } + + if (zipRandomFile != null || isUpToDate) { + lastReferenceTimeStamp = System.currentTimeMillis(); + return; + } + + hasPopulatedData = true; + + if (readIndex()) { + lastReferenceTimeStamp = System.currentTimeMillis(); + return; + } + + directories = Collections.emptyMap(); + allDirs = Collections.emptySet(); + + try { + openFile(); + long totalLength = zipRandomFile.length(); + ZipDirectory directory = new ZipDirectory(zipRandomFile, 0L, totalLength, this); + directory.buildIndex(); + } finally { + if (zipRandomFile != null) { + closeFile(); + } + } + + lastReferenceTimeStamp = System.currentTimeMillis(); + } + + private void openFile() throws FileNotFoundException { + if (zipRandomFile == null && zipFile != null) { + zipRandomFile = new RandomAccessFile(zipFile, "r"); + } + } + + private void cleanupState() { + // Make sure there is a valid but empty index if the file doesn't exist + entries = Entry.EMPTY_ARRAY; + directories = Collections.emptyMap(); + zipFileLastModified = NOT_MODIFIED; + allDirs = Collections.emptySet(); + } + + public synchronized void close() { + writeIndex(); + closeFile(); + } + + private void closeFile() { + if (zipRandomFile != null) { + try { + zipRandomFile.close(); + } catch (IOException ex) { + } + zipRandomFile = null; + } + } + + /** + * Returns the ZipFileIndexEntry for a path, if there is one. + */ + synchronized Entry getZipIndexEntry(RelativePath path) { + try { + checkIndex(); + DirectoryEntry de = directories.get(path.dirname()); + String lookFor = path.basename(); + return (de == null) ? null : de.getEntry(lookFor); + } + catch (IOException e) { + return null; + } + } + + /** + * Returns a javac List of filenames within a directory in the ZipFileIndex. + */ + public synchronized com.sun.tools.javac.util.List getFiles(RelativeDirectory path) { + try { + checkIndex(); + + DirectoryEntry de = directories.get(path); + com.sun.tools.javac.util.List ret = de == null ? null : de.getFiles(); + + if (ret == null) { + return com.sun.tools.javac.util.List.nil(); + } + return ret; + } + catch (IOException e) { + return com.sun.tools.javac.util.List.nil(); + } + } + + public synchronized List getDirectories(RelativeDirectory path) { + try { + checkIndex(); + + DirectoryEntry de = directories.get(path); + com.sun.tools.javac.util.List ret = de == null ? null : de.getDirectories(); + + if (ret == null) { + return com.sun.tools.javac.util.List.nil(); + } + + return ret; + } + catch (IOException e) { + return com.sun.tools.javac.util.List.nil(); + } + } + + public synchronized Set getAllDirectories() { + try { + checkIndex(); + if (allDirs == Collections.EMPTY_SET) { + allDirs = new HashSet(directories.keySet()); + } + + return allDirs; + } + catch (IOException e) { + return Collections.emptySet(); + } + } + + /** + * Tests if a specific path exists in the zip. This method will return true + * for file entries and directories. + * + * @param path A path within the zip. + * @return True if the path is a file or dir, false otherwise. + */ + public synchronized boolean contains(RelativePath path) { + try { + checkIndex(); + return getZipIndexEntry(path) != null; + } + catch (IOException e) { + return false; + } + } + + public synchronized boolean isDirectory(RelativePath path) throws IOException { + // The top level in a zip file is always a directory. + if (path.getPath().length() == 0) { + lastReferenceTimeStamp = System.currentTimeMillis(); + return true; + } + + checkIndex(); + return directories.get(path) != null; + } + + public synchronized long getLastModified(RelativeFile path) throws IOException { + Entry entry = getZipIndexEntry(path); + if (entry == null) + throw new FileNotFoundException(); + return entry.getLastModified(); + } + + public synchronized int length(RelativeFile path) throws IOException { + Entry entry = getZipIndexEntry(path); + if (entry == null) + throw new FileNotFoundException(); + + if (entry.isDir) { + return 0; + } + + byte[] header = getHeader(entry); + // entry is not compressed? + if (get2ByteLittleEndian(header, 8) == 0) { + return entry.compressedSize; + } else { + return entry.size; + } + } + + public synchronized byte[] read(RelativeFile path) throws IOException { + Entry entry = getZipIndexEntry(path); + if (entry == null) + throw new FileNotFoundException("Path not found in ZIP: " + path.path); + return read(entry); + } + + synchronized byte[] read(Entry entry) throws IOException { + openFile(); + byte[] result = readBytes(entry); + closeFile(); + return result; + } + + public synchronized int read(RelativeFile path, byte[] buffer) throws IOException { + Entry entry = getZipIndexEntry(path); + if (entry == null) + throw new FileNotFoundException(); + return read(entry, buffer); + } + + synchronized int read(Entry entry, byte[] buffer) + throws IOException { + int result = readBytes(entry, buffer); + return result; + } + + private byte[] readBytes(Entry entry) throws IOException { + byte[] header = getHeader(entry); + int csize = entry.compressedSize; + byte[] cbuf = new byte[csize]; + zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28)); + zipRandomFile.readFully(cbuf, 0, csize); + + // is this compressed - offset 8 in the ZipEntry header + if (get2ByteLittleEndian(header, 8) == 0) + return cbuf; + + int size = entry.size; + byte[] buf = new byte[size]; + if (inflate(cbuf, buf) != size) + throw new ZipException("corrupted zip file"); + + return buf; + } + + /** + * + */ + private int readBytes(Entry entry, byte[] buffer) throws IOException { + byte[] header = getHeader(entry); + + // entry is not compressed? + if (get2ByteLittleEndian(header, 8) == 0) { + zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28)); + int offset = 0; + int size = buffer.length; + while (offset < size) { + int count = zipRandomFile.read(buffer, offset, size - offset); + if (count == -1) + break; + offset += count; + } + return entry.size; + } + + int csize = entry.compressedSize; + byte[] cbuf = new byte[csize]; + zipRandomFile.skipBytes(get2ByteLittleEndian(header, 26) + get2ByteLittleEndian(header, 28)); + zipRandomFile.readFully(cbuf, 0, csize); + + int count = inflate(cbuf, buffer); + if (count == -1) + throw new ZipException("corrupted zip file"); + + return entry.size; + } + + //---------------------------------------------------------------------------- + // Zip utilities + //---------------------------------------------------------------------------- + + private byte[] getHeader(Entry entry) throws IOException { + zipRandomFile.seek(entry.offset); + byte[] header = new byte[30]; + zipRandomFile.readFully(header); + if (get4ByteLittleEndian(header, 0) != 0x04034b50) + throw new ZipException("corrupted zip file"); + if ((get2ByteLittleEndian(header, 6) & 1) != 0) + throw new ZipException("encrypted zip file"); // offset 6 in the header of the ZipFileEntry + return header; + } + + /* + * Inflate using the java.util.zip.Inflater class + */ + private SoftReference inflaterRef; + private int inflate(byte[] src, byte[] dest) { + Inflater inflater = (inflaterRef == null ? null : inflaterRef.get()); + + // construct the inflater object or reuse an existing one + if (inflater == null) + inflaterRef = new SoftReference(inflater = new Inflater(true)); + + inflater.reset(); + inflater.setInput(src); + try { + return inflater.inflate(dest); + } catch (DataFormatException ex) { + return -1; + } + } + + /** + * return the two bytes buf[pos], buf[pos+1] as an unsigned integer in little + * endian format. + */ + private static int get2ByteLittleEndian(byte[] buf, int pos) { + return (buf[pos] & 0xFF) + ((buf[pos+1] & 0xFF) << 8); + } + + /** + * return the 4 bytes buf[i..i+3] as an integer in little endian format. + */ + private static int get4ByteLittleEndian(byte[] buf, int pos) { + return (buf[pos] & 0xFF) + ((buf[pos + 1] & 0xFF) << 8) + + ((buf[pos + 2] & 0xFF) << 16) + ((buf[pos + 3] & 0xFF) << 24); + } + + /* ---------------------------------------------------------------------------- + * ZipDirectory + * ----------------------------------------------------------------------------*/ + + private class ZipDirectory { + private RelativeDirectory lastDir; + private int lastStart; + private int lastLen; + + byte[] zipDir; + RandomAccessFile zipRandomFile = null; + ZipFileIndex zipFileIndex = null; + + public ZipDirectory(RandomAccessFile zipRandomFile, long start, long end, ZipFileIndex index) throws IOException { + this.zipRandomFile = zipRandomFile; + this.zipFileIndex = index; + hasValidHeader(); + findCENRecord(start, end); + } + + /* + * the zip entry signature should be at offset 0, otherwise allow the + * calling logic to take evasive action by throwing ZipFormatException. + */ + private boolean hasValidHeader() throws IOException { + final long pos = zipRandomFile.getFilePointer(); + try { + if (zipRandomFile.read() == 'P') { + if (zipRandomFile.read() == 'K') { + if (zipRandomFile.read() == 0x03) { + if (zipRandomFile.read() == 0x04) { + return true; + } + } + } + } + } finally { + zipRandomFile.seek(pos); + } + throw new ZipFormatException("invalid zip magic"); + } + + /* + * Reads zip file central directory. + * For more details see readCEN in zip_util.c from the JDK sources. + * This is a Java port of that function. + */ + private void findCENRecord(long start, long end) throws IOException { + long totalLength = end - start; + int endbuflen = 1024; + byte[] endbuf = new byte[endbuflen]; + long endbufend = end - start; + + // There is a variable-length field after the dir offset record. We need to do consequential search. + while (endbufend >= 22) { + if (endbufend < endbuflen) + endbuflen = (int)endbufend; + long endbufpos = endbufend - endbuflen; + zipRandomFile.seek(start + endbufpos); + zipRandomFile.readFully(endbuf, 0, endbuflen); + int i = endbuflen - 22; + while (i >= 0 && + !(endbuf[i] == 0x50 && + endbuf[i + 1] == 0x4b && + endbuf[i + 2] == 0x05 && + endbuf[i + 3] == 0x06 && + endbufpos + i + 22 + + get2ByteLittleEndian(endbuf, i + 20) == totalLength)) { + i--; + } + + if (i >= 0) { + zipDir = new byte[get4ByteLittleEndian(endbuf, i + 12) + 2]; + zipDir[0] = endbuf[i + 10]; + zipDir[1] = endbuf[i + 11]; + int sz = get4ByteLittleEndian(endbuf, i + 16); + // a negative offset or the entries field indicates a + // potential zip64 archive + if (sz < 0 || get2ByteLittleEndian(zipDir, 0) == 0xffff) { + throw new ZipFormatException("detected a zip64 archive"); + } + zipRandomFile.seek(start + sz); + zipRandomFile.readFully(zipDir, 2, zipDir.length - 2); + return; + } else { + endbufend = endbufpos + 21; + } + } + throw new ZipException("cannot read zip file"); + } + + private void buildIndex() throws IOException { + int entryCount = get2ByteLittleEndian(zipDir, 0); + + // Add each of the files + if (entryCount > 0) { + directories = new HashMap(); + ArrayList entryList = new ArrayList(); + int pos = 2; + for (int i = 0; i < entryCount; i++) { + pos = readEntry(pos, entryList, directories); + } + + // Add the accumulated dirs into the same list + for (RelativeDirectory d: directories.keySet()) { + // use shared RelativeDirectory objects for parent dirs + RelativeDirectory parent = getRelativeDirectory(d.dirname().getPath()); + String file = d.basename(); + Entry zipFileIndexEntry = new Entry(parent, file); + zipFileIndexEntry.isDir = true; + entryList.add(zipFileIndexEntry); + } + + entries = entryList.toArray(new Entry[entryList.size()]); + Arrays.sort(entries); + } else { + cleanupState(); + } + } + + private int readEntry(int pos, List entryList, + Map directories) throws IOException { + if (get4ByteLittleEndian(zipDir, pos) != 0x02014b50) { + throw new ZipException("cannot read zip file entry"); + } + + int dirStart = pos + 46; + int fileStart = dirStart; + int fileEnd = fileStart + get2ByteLittleEndian(zipDir, pos + 28); + + if (zipFileIndex.symbolFilePrefixLength != 0 && + ((fileEnd - fileStart) >= symbolFilePrefixLength)) { + dirStart += zipFileIndex.symbolFilePrefixLength; + fileStart += zipFileIndex.symbolFilePrefixLength; + } + // Force any '\' to '/'. Keep the position of the last separator. + for (int index = fileStart; index < fileEnd; index++) { + byte nextByte = zipDir[index]; + if (nextByte == (byte)'\\') { + zipDir[index] = (byte)'/'; + fileStart = index + 1; + } else if (nextByte == (byte)'/') { + fileStart = index + 1; + } + } + + RelativeDirectory directory = null; + if (fileStart == dirStart) + directory = getRelativeDirectory(""); + else if (lastDir != null && lastLen == fileStart - dirStart - 1) { + int index = lastLen - 1; + while (zipDir[lastStart + index] == zipDir[dirStart + index]) { + if (index == 0) { + directory = lastDir; + break; + } + index--; + } + } + + // Sub directories + if (directory == null) { + lastStart = dirStart; + lastLen = fileStart - dirStart - 1; + + directory = getRelativeDirectory(new String(zipDir, dirStart, lastLen, "UTF-8")); + lastDir = directory; + + // Enter also all the parent directories + RelativeDirectory tempDirectory = directory; + + while (directories.get(tempDirectory) == null) { + directories.put(tempDirectory, new DirectoryEntry(tempDirectory, zipFileIndex)); + if (tempDirectory.path.indexOf("/") == tempDirectory.path.length() - 1) + break; + else { + // use shared RelativeDirectory objects for parent dirs + tempDirectory = getRelativeDirectory(tempDirectory.dirname().getPath()); + } + } + } + else { + if (directories.get(directory) == null) { + directories.put(directory, new DirectoryEntry(directory, zipFileIndex)); + } + } + + // For each dir create also a file + if (fileStart != fileEnd) { + Entry entry = new Entry(directory, + new String(zipDir, fileStart, fileEnd - fileStart, "UTF-8")); + + entry.setNativeTime(get4ByteLittleEndian(zipDir, pos + 12)); + entry.compressedSize = get4ByteLittleEndian(zipDir, pos + 20); + entry.size = get4ByteLittleEndian(zipDir, pos + 24); + entry.offset = get4ByteLittleEndian(zipDir, pos + 42); + entryList.add(entry); + } + + return pos + 46 + + get2ByteLittleEndian(zipDir, pos + 28) + + get2ByteLittleEndian(zipDir, pos + 30) + + get2ByteLittleEndian(zipDir, pos + 32); + } + } + + /** + * Returns the last modified timestamp of a zip file. + * @return long + */ + public long getZipFileLastModified() throws IOException { + synchronized (this) { + checkIndex(); + return zipFileLastModified; + } + } + + /** ------------------------------------------------------------------------ + * DirectoryEntry class + * -------------------------------------------------------------------------*/ + + static class DirectoryEntry { + private boolean filesInited; + private boolean directoriesInited; + private boolean zipFileEntriesInited; + private boolean entriesInited; + + private long writtenOffsetOffset = 0; + + private RelativeDirectory dirName; + + private com.sun.tools.javac.util.List zipFileEntriesFiles = com.sun.tools.javac.util.List.nil(); + private com.sun.tools.javac.util.List zipFileEntriesDirectories = com.sun.tools.javac.util.List.nil(); + private com.sun.tools.javac.util.List zipFileEntries = com.sun.tools.javac.util.List.nil(); + + private List entries = new ArrayList(); + + private ZipFileIndex zipFileIndex; + + private int numEntries; + + DirectoryEntry(RelativeDirectory dirName, ZipFileIndex index) { + filesInited = false; + directoriesInited = false; + entriesInited = false; + + this.dirName = dirName; + this.zipFileIndex = index; + } + + private com.sun.tools.javac.util.List getFiles() { + if (!filesInited) { + initEntries(); + for (Entry e : entries) { + if (!e.isDir) { + zipFileEntriesFiles = zipFileEntriesFiles.append(e.name); + } + } + filesInited = true; + } + return zipFileEntriesFiles; + } + + private com.sun.tools.javac.util.List getDirectories() { + if (!directoriesInited) { + initEntries(); + for (Entry e : entries) { + if (e.isDir) { + zipFileEntriesDirectories = zipFileEntriesDirectories.append(e.name); + } + } + directoriesInited = true; + } + return zipFileEntriesDirectories; + } + + private com.sun.tools.javac.util.List getEntries() { + if (!zipFileEntriesInited) { + initEntries(); + zipFileEntries = com.sun.tools.javac.util.List.nil(); + for (Entry zfie : entries) { + zipFileEntries = zipFileEntries.append(zfie); + } + zipFileEntriesInited = true; + } + return zipFileEntries; + } + + private Entry getEntry(String rootName) { + initEntries(); + int index = Collections.binarySearch(entries, new Entry(dirName, rootName)); + if (index < 0) { + return null; + } + + return entries.get(index); + } + + private void initEntries() { + if (entriesInited) { + return; + } + + if (!zipFileIndex.readFromIndex) { + int from = -Arrays.binarySearch(zipFileIndex.entries, + new Entry(dirName, ZipFileIndex.MIN_CHAR)) - 1; + int to = -Arrays.binarySearch(zipFileIndex.entries, + new Entry(dirName, MAX_CHAR)) - 1; + + for (int i = from; i < to; i++) { + entries.add(zipFileIndex.entries[i]); + } + } else { + File indexFile = zipFileIndex.getIndexFile(); + if (indexFile != null) { + RandomAccessFile raf = null; + try { + raf = new RandomAccessFile(indexFile, "r"); + raf.seek(writtenOffsetOffset); + + for (int nFiles = 0; nFiles < numEntries; nFiles++) { + // Read the name bytes + int zfieNameBytesLen = raf.readInt(); + byte [] zfieNameBytes = new byte[zfieNameBytesLen]; + raf.read(zfieNameBytes); + String eName = new String(zfieNameBytes, "UTF-8"); + + // Read isDir + boolean eIsDir = raf.readByte() == (byte)0 ? false : true; + + // Read offset of bytes in the real Jar/Zip file + int eOffset = raf.readInt(); + + // Read size of the file in the real Jar/Zip file + int eSize = raf.readInt(); + + // Read compressed size of the file in the real Jar/Zip file + int eCsize = raf.readInt(); + + // Read java time stamp of the file in the real Jar/Zip file + long eJavaTimestamp = raf.readLong(); + + Entry rfie = new Entry(dirName, eName); + rfie.isDir = eIsDir; + rfie.offset = eOffset; + rfie.size = eSize; + rfie.compressedSize = eCsize; + rfie.javatime = eJavaTimestamp; + entries.add(rfie); + } + } catch (Throwable t) { + // Do nothing + } finally { + try { + if (raf != null) { + raf.close(); + } + } catch (Throwable t) { + // Do nothing + } + } + } + } + + entriesInited = true; + } + + List getEntriesAsCollection() { + initEntries(); + + return entries; + } + } + + private boolean readIndex() { + if (triedToReadIndex || !usePreindexedCache) { + return false; + } + + boolean ret = false; + synchronized (this) { + triedToReadIndex = true; + RandomAccessFile raf = null; + try { + File indexFileName = getIndexFile(); + raf = new RandomAccessFile(indexFileName, "r"); + + long fileStamp = raf.readLong(); + if (zipFile.lastModified() != fileStamp) { + ret = false; + } else { + directories = new HashMap(); + int numDirs = raf.readInt(); + for (int nDirs = 0; nDirs < numDirs; nDirs++) { + int dirNameBytesLen = raf.readInt(); + byte [] dirNameBytes = new byte[dirNameBytesLen]; + raf.read(dirNameBytes); + + RelativeDirectory dirNameStr = getRelativeDirectory(new String(dirNameBytes, "UTF-8")); + DirectoryEntry de = new DirectoryEntry(dirNameStr, this); + de.numEntries = raf.readInt(); + de.writtenOffsetOffset = raf.readLong(); + directories.put(dirNameStr, de); + } + ret = true; + zipFileLastModified = fileStamp; + } + } catch (Throwable t) { + // Do nothing + } finally { + if (raf != null) { + try { + raf.close(); + } catch (Throwable tt) { + // Do nothing + } + } + } + if (ret == true) { + readFromIndex = true; + } + } + + return ret; + } + + private boolean writeIndex() { + boolean ret = false; + if (readFromIndex || !usePreindexedCache) { + return true; + } + + if (!writeIndex) { + return true; + } + + File indexFile = getIndexFile(); + if (indexFile == null) { + return false; + } + + RandomAccessFile raf = null; + long writtenSoFar = 0; + try { + raf = new RandomAccessFile(indexFile, "rw"); + + raf.writeLong(zipFileLastModified); + writtenSoFar += 8; + + List directoriesToWrite = new ArrayList(); + Map offsets = new HashMap(); + raf.writeInt(directories.keySet().size()); + writtenSoFar += 4; + + for (RelativeDirectory dirName: directories.keySet()) { + DirectoryEntry dirEntry = directories.get(dirName); + + directoriesToWrite.add(dirEntry); + + // Write the dir name bytes + byte [] dirNameBytes = dirName.getPath().getBytes("UTF-8"); + int dirNameBytesLen = dirNameBytes.length; + raf.writeInt(dirNameBytesLen); + writtenSoFar += 4; + + raf.write(dirNameBytes); + writtenSoFar += dirNameBytesLen; + + // Write the number of files in the dir + List dirEntries = dirEntry.getEntriesAsCollection(); + raf.writeInt(dirEntries.size()); + writtenSoFar += 4; + + offsets.put(dirName, new Long(writtenSoFar)); + + // Write the offset of the file's data in the dir + dirEntry.writtenOffsetOffset = 0L; + raf.writeLong(0L); + writtenSoFar += 8; + } + + for (DirectoryEntry de : directoriesToWrite) { + // Fix up the offset in the directory table + long currFP = raf.getFilePointer(); + + long offsetOffset = offsets.get(de.dirName).longValue(); + raf.seek(offsetOffset); + raf.writeLong(writtenSoFar); + + raf.seek(currFP); + + // Now write each of the files in the DirectoryEntry + List list = de.getEntriesAsCollection(); + for (Entry zfie : list) { + // Write the name bytes + byte [] zfieNameBytes = zfie.name.getBytes("UTF-8"); + int zfieNameBytesLen = zfieNameBytes.length; + raf.writeInt(zfieNameBytesLen); + writtenSoFar += 4; + raf.write(zfieNameBytes); + writtenSoFar += zfieNameBytesLen; + + // Write isDir + raf.writeByte(zfie.isDir ? (byte)1 : (byte)0); + writtenSoFar += 1; + + // Write offset of bytes in the real Jar/Zip file + raf.writeInt(zfie.offset); + writtenSoFar += 4; + + // Write size of the file in the real Jar/Zip file + raf.writeInt(zfie.size); + writtenSoFar += 4; + + // Write compressed size of the file in the real Jar/Zip file + raf.writeInt(zfie.compressedSize); + writtenSoFar += 4; + + // Write java time stamp of the file in the real Jar/Zip file + raf.writeLong(zfie.getLastModified()); + writtenSoFar += 8; + } + } + } catch (Throwable t) { + // Do nothing + } finally { + try { + if (raf != null) { + raf.close(); + } + } catch(IOException ioe) { + // Do nothing + } + } + + return ret; + } + + public boolean writeZipIndex() { + synchronized (this) { + return writeIndex(); + } + } + + private File getIndexFile() { + if (zipIndexFile == null) { + if (zipFile == null) { + return null; + } + + zipIndexFile = new File((preindexedCacheLocation == null ? "" : preindexedCacheLocation) + + zipFile.getName() + ".index"); + } + + return zipIndexFile; + } + + public File getZipFile() { + return zipFile; + } + + File getAbsoluteFile() { + File absFile = (absFileRef == null ? null : absFileRef.get()); + if (absFile == null) { + absFile = zipFile.getAbsoluteFile(); + absFileRef = new SoftReference(absFile); + } + return absFile; + } + + private RelativeDirectory getRelativeDirectory(String path) { + RelativeDirectory rd; + SoftReference ref = relativeDirectoryCache.get(path); + if (ref != null) { + rd = ref.get(); + if (rd != null) + return rd; + } + rd = new RelativeDirectory(path); + relativeDirectoryCache.put(path, new SoftReference(rd)); + return rd; + } + + static class Entry implements Comparable { + public static final Entry[] EMPTY_ARRAY = {}; + + // Directory related + RelativeDirectory dir; + boolean isDir; + + // File related + String name; + + int offset; + int size; + int compressedSize; + long javatime; + + private int nativetime; + + public Entry(RelativePath path) { + this(path.dirname(), path.basename()); + } + + public Entry(RelativeDirectory directory, String name) { + this.dir = directory; + this.name = name; + } + + public String getName() { + return new RelativeFile(dir, name).getPath(); + } + + public String getFileName() { + return name; + } + + public long getLastModified() { + if (javatime == 0) { + javatime = dosToJavaTime(nativetime); + } + return javatime; + } + + // based on dosToJavaTime in java.util.Zip, but avoiding the + // use of deprecated Date constructor + private static long dosToJavaTime(int dtime) { + Calendar c = Calendar.getInstance(); + c.set(Calendar.YEAR, ((dtime >> 25) & 0x7f) + 1980); + c.set(Calendar.MONTH, ((dtime >> 21) & 0x0f) - 1); + c.set(Calendar.DATE, ((dtime >> 16) & 0x1f)); + c.set(Calendar.HOUR_OF_DAY, ((dtime >> 11) & 0x1f)); + c.set(Calendar.MINUTE, ((dtime >> 5) & 0x3f)); + c.set(Calendar.SECOND, ((dtime << 1) & 0x3e)); + c.set(Calendar.MILLISECOND, 0); + return c.getTimeInMillis(); + } + + void setNativeTime(int natTime) { + nativetime = natTime; + } + + public boolean isDirectory() { + return isDir; + } + + public int compareTo(Entry other) { + RelativeDirectory otherD = other.dir; + if (dir != otherD) { + int c = dir.compareTo(otherD); + if (c != 0) + return c; + } + return name.compareTo(other.name); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Entry)) + return false; + Entry other = (Entry) o; + return dir.equals(other.dir) && name.equals(other.name); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 97 * hash + (this.dir != null ? this.dir.hashCode() : 0); + hash = 97 * hash + (this.name != null ? this.name.hashCode() : 0); + return hash; + } + + @Override + public String toString() { + return isDir ? ("Dir:" + dir + " : " + name) : + (dir + ":" + name); + } + } + + /* + * Exception primarily used to implement a failover, used exclusively here. + */ + + static final class ZipFormatException extends IOException { + private static final long serialVersionUID = 8000196834066748623L; + protected ZipFormatException(String message) { + super(message); + } + + protected ZipFormatException(String message, Throwable cause) { + super(message, cause); + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/file/ZipFileIndexArchive.java b/douyu-javac/src/main/java/com/sun/tools/javac/file/ZipFileIndexArchive.java new file mode 100644 index 0000000..4a46601 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/file/ZipFileIndexArchive.java @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.file; + +import java.io.IOException; +import java.util.Set; +import javax.tools.JavaFileObject; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Writer; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharsetDecoder; + +import com.sun.tools.javac.file.JavacFileManager.Archive; +import com.sun.tools.javac.file.RelativePath.RelativeDirectory; +import com.sun.tools.javac.file.RelativePath.RelativeFile; +import com.sun.tools.javac.util.Assert; +import com.sun.tools.javac.util.List; + +/** + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class ZipFileIndexArchive implements Archive { + + private final ZipFileIndex zfIndex; + private JavacFileManager fileManager; + + public ZipFileIndexArchive(JavacFileManager fileManager, ZipFileIndex zdir) throws IOException { + super(); + this.fileManager = fileManager; + this.zfIndex = zdir; + } + + public boolean contains(RelativePath name) { + return zfIndex.contains(name); + } + + public List getFiles(RelativeDirectory subdirectory) { + return zfIndex.getFiles(subdirectory); + } + + public JavaFileObject getFileObject(RelativeDirectory subdirectory, String file) { + RelativeFile fullZipFileName = new RelativeFile(subdirectory, file); + ZipFileIndex.Entry entry = zfIndex.getZipIndexEntry(fullZipFileName); + JavaFileObject ret = new ZipFileIndexFileObject(fileManager, zfIndex, entry, zfIndex.getZipFile()); + return ret; + } + + public Set getSubdirectories() { + return zfIndex.getAllDirectories(); + } + + public void close() throws IOException { + zfIndex.close(); + } + + @Override + public String toString() { + return "ZipFileIndexArchive[" + zfIndex + "]"; + } + + /** + * A subclass of JavaFileObject representing zip entries using the com.sun.tools.javac.file.ZipFileIndex implementation. + */ + public static class ZipFileIndexFileObject extends BaseFileObject { + + /** The entry's name. + */ + private String name; + + /** The zipfile containing the entry. + */ + ZipFileIndex zfIndex; + + /** The underlying zip entry object. + */ + ZipFileIndex.Entry entry; + + /** The InputStream for this zip entry (file.) + */ + InputStream inputStream = null; + + /** The name of the zip file where this entry resides. + */ + File zipName; + + + ZipFileIndexFileObject(JavacFileManager fileManager, ZipFileIndex zfIndex, ZipFileIndex.Entry entry, File zipFileName) { + super(fileManager); + this.name = entry.getFileName(); + this.zfIndex = zfIndex; + this.entry = entry; + this.zipName = zipFileName; + } + + @Override + public URI toUri() { + return createJarUri(zipName, getPrefixedEntryName()); + } + + @Override + public String getName() { + return zipName + "(" + getPrefixedEntryName() + ")"; + } + + @Override + public String getShortName() { + return zipName.getName() + "(" + entry.getName() + ")"; + } + + @Override + public JavaFileObject.Kind getKind() { + return getKind(entry.getName()); + } + + @Override + public InputStream openInputStream() throws IOException { + if (inputStream == null) { + Assert.checkNonNull(entry); // see constructor + inputStream = new ByteArrayInputStream(zfIndex.read(entry)); + } + return inputStream; + } + + @Override + public OutputStream openOutputStream() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public CharBuffer getCharContent(boolean ignoreEncodingErrors) throws IOException { + CharBuffer cb = fileManager.getCachedContent(this); + if (cb == null) { + InputStream in = new ByteArrayInputStream(zfIndex.read(entry)); + try { + ByteBuffer bb = fileManager.makeByteBuffer(in); + JavaFileObject prev = fileManager.log.useSource(this); + try { + cb = fileManager.decode(bb, ignoreEncodingErrors); + } finally { + fileManager.log.useSource(prev); + } + fileManager.recycleByteBuffer(bb); // save for next time + if (!ignoreEncodingErrors) + fileManager.cache(this, cb); + } finally { + in.close(); + } + } + return cb; + } + + @Override + public Writer openWriter() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public long getLastModified() { + return entry.getLastModified(); + } + + @Override + public boolean delete() { + throw new UnsupportedOperationException(); + } + + @Override + protected CharsetDecoder getDecoder(boolean ignoreEncodingErrors) { + return fileManager.getDecoder(fileManager.getEncodingName(), ignoreEncodingErrors); + } + + @Override + protected String inferBinaryName(Iterable path) { + String entryName = entry.getName(); + if (zfIndex.symbolFilePrefix != null) { + String prefix = zfIndex.symbolFilePrefix.path; + if (entryName.startsWith(prefix)) + entryName = entryName.substring(prefix.length()); + } + return removeExtension(entryName).replace('/', '.'); + } + + @Override + public boolean isNameCompatible(String cn, JavaFileObject.Kind k) { + cn.getClass(); // null check + if (k == Kind.OTHER && getKind() != k) + return false; + return name.equals(cn + k.extension); + } + + /** + * Check if two file objects are equal. + * Two ZipFileIndexFileObjects are equal if the absolute paths of the underlying + * zip files are equal and if the paths within those zip files are equal. + */ + @Override + public boolean equals(Object other) { + if (this == other) + return true; + + if (!(other instanceof ZipFileIndexFileObject)) + return false; + + ZipFileIndexFileObject o = (ZipFileIndexFileObject) other; + return zfIndex.getAbsoluteFile().equals(o.zfIndex.getAbsoluteFile()) + && name.equals(o.name); + } + + @Override + public int hashCode() { + return zfIndex.getAbsoluteFile().hashCode() + name.hashCode(); + } + + private String getPrefixedEntryName() { + if (zfIndex.symbolFilePrefix != null) + return zfIndex.symbolFilePrefix.path + entry.getName(); + else + return entry.getName(); + } + } + +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/file/ZipFileIndexCache.java b/douyu-javac/src/main/java/com/sun/tools/javac/file/ZipFileIndexCache.java new file mode 100644 index 0000000..3c878d7 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/file/ZipFileIndexCache.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.file; + +import com.sun.tools.javac.file.RelativePath.RelativeDirectory; +import com.sun.tools.javac.util.Context; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + + +/** A cache for ZipFileIndex objects. */ +public class ZipFileIndexCache { + + private final Map map = + new HashMap(); + + /** Get a shared instance of the cache. */ + private static ZipFileIndexCache sharedInstance; + public synchronized static ZipFileIndexCache getSharedInstance() { + if (sharedInstance == null) + sharedInstance = new ZipFileIndexCache(); + return sharedInstance; + } + + /** Get a context-specific instance of a cache. */ + public static ZipFileIndexCache instance(Context context) { + ZipFileIndexCache instance = context.get(ZipFileIndexCache.class); + if (instance == null) + context.put(ZipFileIndexCache.class, instance = new ZipFileIndexCache()); + return instance; + } + + /** + * Returns a list of all ZipFileIndex entries + * + * @return A list of ZipFileIndex entries, or an empty list + */ + public List getZipFileIndexes() { + return getZipFileIndexes(false); + } + + /** + * Returns a list of all ZipFileIndex entries + * + * @param openedOnly If true it returns a list of only opened ZipFileIndex entries, otherwise + * all ZipFileEntry(s) are included into the list. + * @return A list of ZipFileIndex entries, or an empty list + */ + public synchronized List getZipFileIndexes(boolean openedOnly) { + List zipFileIndexes = new ArrayList(); + + zipFileIndexes.addAll(map.values()); + + if (openedOnly) { + for(ZipFileIndex elem : zipFileIndexes) { + if (!elem.isOpen()) { + zipFileIndexes.remove(elem); + } + } + } + + return zipFileIndexes; + } + + public synchronized ZipFileIndex getZipFileIndex(File zipFile, + RelativeDirectory symbolFilePrefix, + boolean useCache, String cacheLocation, + boolean writeIndex) throws IOException { + ZipFileIndex zi = getExistingZipIndex(zipFile); + + if (zi == null || (zi != null && zipFile.lastModified() != zi.zipFileLastModified)) { + zi = new ZipFileIndex(zipFile, symbolFilePrefix, writeIndex, + useCache, cacheLocation); + map.put(zipFile, zi); + } + return zi; + } + + public synchronized ZipFileIndex getExistingZipIndex(File zipFile) { + return map.get(zipFile); + } + + public synchronized void clearCache() { + map.clear(); + } + + public synchronized void clearCache(long timeNotUsed) { + Iterator cachedFileIterator = map.keySet().iterator(); + while (cachedFileIterator.hasNext()) { + File cachedFile = cachedFileIterator.next(); + ZipFileIndex cachedZipIndex = map.get(cachedFile); + if (cachedZipIndex != null) { + long timeToTest = cachedZipIndex.lastReferenceTimeStamp + timeNotUsed; + if (timeToTest < cachedZipIndex.lastReferenceTimeStamp || // Overflow... + System.currentTimeMillis() > timeToTest) { + map.remove(cachedFile); + } + } + } + } + + public synchronized void removeFromCache(File file) { + map.remove(file); + } + + /** Sets already opened list of ZipFileIndexes from an outside client + * of the compiler. This functionality should be used in a non-batch clients of the compiler. + */ + public synchronized void setOpenedIndexes(Listindexes) throws IllegalStateException { + if (map.isEmpty()) { + String msg = + "Setting opened indexes should be called only when the ZipFileCache is empty. " + + "Call JavacFileManager.flush() before calling this method."; + throw new IllegalStateException(msg); + } + + for (ZipFileIndex zfi : indexes) { + map.put(zfi.zipFile, zfi); + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/jvm/ByteCodes.java b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/ByteCodes.java new file mode 100644 index 0000000..81085cd --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/ByteCodes.java @@ -0,0 +1,310 @@ +/* + * Copyright (c) 1999, 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.jvm; + + +/** Bytecode instruction codes, as well as typecodes used as + * instruction modifiers. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public interface ByteCodes { + + /** Byte code instruction codes. + */ + int illegal = -1, + nop = 0, + aconst_null = 1, + iconst_m1 = 2, + iconst_0 = 3, + iconst_1 = 4, + iconst_2 = 5, + iconst_3 = 6, + iconst_4 = 7, + iconst_5 = 8, + lconst_0 = 9, + lconst_1 = 10, + fconst_0 = 11, + fconst_1 = 12, + fconst_2 = 13, + dconst_0 = 14, + dconst_1 = 15, + bipush = 16, + sipush = 17, + ldc1 = 18, + ldc2 = 19, + ldc2w = 20, + iload = 21, + lload = 22, + fload = 23, + dload = 24, + aload = 25, + iload_0 = 26, + iload_1 = 27, + iload_2 = 28, + iload_3 = 29, + lload_0 = 30, + lload_1 = 31, + lload_2 = 32, + lload_3 = 33, + fload_0 = 34, + fload_1 = 35, + fload_2 = 36, + fload_3 = 37, + dload_0 = 38, + dload_1 = 39, + dload_2 = 40, + dload_3 = 41, + aload_0 = 42, + aload_1 = 43, + aload_2 = 44, + aload_3 = 45, + iaload = 46, + laload = 47, + faload = 48, + daload = 49, + aaload = 50, + baload = 51, + caload = 52, + saload = 53, + istore = 54, + lstore = 55, + fstore = 56, + dstore = 57, + astore = 58, + istore_0 = 59, + istore_1 = 60, + istore_2 = 61, + istore_3 = 62, + lstore_0 = 63, + lstore_1 = 64, + lstore_2 = 65, + lstore_3 = 66, + fstore_0 = 67, + fstore_1 = 68, + fstore_2 = 69, + fstore_3 = 70, + dstore_0 = 71, + dstore_1 = 72, + dstore_2 = 73, + dstore_3 = 74, + astore_0 = 75, + astore_1 = 76, + astore_2 = 77, + astore_3 = 78, + iastore = 79, + lastore = 80, + fastore = 81, + dastore = 82, + aastore = 83, + bastore = 84, + castore = 85, + sastore = 86, + pop = 87, + pop2 = 88, + dup = 89, + dup_x1 = 90, + dup_x2 = 91, + dup2 = 92, + dup2_x1 = 93, + dup2_x2 = 94, + swap = 95, + iadd = 96, + ladd = 97, + fadd = 98, + dadd = 99, + isub = 100, + lsub = 101, + fsub = 102, + dsub = 103, + imul = 104, + lmul = 105, + fmul = 106, + dmul = 107, + idiv = 108, + ldiv = 109, + fdiv = 110, + ddiv = 111, + imod = 112, + lmod = 113, + fmod = 114, + dmod = 115, + ineg = 116, + lneg = 117, + fneg = 118, + dneg = 119, + ishl = 120, + lshl = 121, + ishr = 122, + lshr = 123, + iushr = 124, + lushr = 125, + iand = 126, + land = 127, + ior = 128, + lor = 129, + ixor = 130, + lxor = 131, + iinc = 132, + i2l = 133, + i2f = 134, + i2d = 135, + l2i = 136, + l2f = 137, + l2d = 138, + f2i = 139, + f2l = 140, + f2d = 141, + d2i = 142, + d2l = 143, + d2f = 144, + int2byte = 145, + int2char = 146, + int2short = 147, + lcmp = 148, + fcmpl = 149, + fcmpg = 150, + dcmpl = 151, + dcmpg = 152, + ifeq = 153, + ifne = 154, + iflt = 155, + ifge = 156, + ifgt = 157, + ifle = 158, + if_icmpeq = 159, + if_icmpne = 160, + if_icmplt = 161, + if_icmpge = 162, + if_icmpgt = 163, + if_icmple = 164, + if_acmpeq = 165, + if_acmpne = 166, + goto_ = 167, + jsr = 168, + ret = 169, + tableswitch = 170, + lookupswitch = 171, + ireturn = 172, + lreturn = 173, + freturn = 174, + dreturn = 175, + areturn = 176, + return_ = 177, + getstatic = 178, + putstatic = 179, + getfield = 180, + putfield = 181, + invokevirtual = 182, + invokespecial = 183, + invokestatic = 184, + invokeinterface = 185, + invokedynamic = 186, + new_ = 187, + newarray = 188, + anewarray = 189, + arraylength = 190, + athrow = 191, + checkcast = 192, + instanceof_ = 193, + monitorenter = 194, + monitorexit = 195, + wide = 196, + multianewarray = 197, + if_acmp_null = 198, + if_acmp_nonnull = 199, + goto_w = 200, + jsr_w = 201, + breakpoint = 202, + ByteCodeCount = 203; + + /** Virtual instruction codes; used for constant folding. + */ + int string_add = 256, // string + + bool_not = 257, // boolean ! + bool_and = 258, // boolean && + bool_or = 259; // boolean || + + /** Virtual opcodes; used for shifts with long shiftcount + */ + int ishll = 270, // int shift left with long count + lshll = 271, // long shift left with long count + ishrl = 272, // int shift right with long count + lshrl = 273, // long shift right with long count + iushrl = 274, // int unsigned shift right with long count + lushrl = 275; // long unsigned shift right with long count + + /** Virtual opcode for null reference checks + */ + int nullchk = 276; // return operand if non-null, + // otherwise throw NullPointerException. + + /** Virtual opcode for disallowed operations. + */ + int error = 277; + + /** All conditional jumps come in pairs. To streamline the + * treatment of jumps, we also introduce a negation of an + * unconditional jump. That opcode happens to be jsr. + */ + int dontgoto = jsr; + + /** Shift and mask constants for shifting prefix instructions. + * a pair of instruction codes such as LCMP ; IFEQ is encoded + * in Symtab as (LCMP << preShift) + IFEQ. + */ + int preShift = 9; + int preMask = (1 << preShift) - 1; + + /** Type codes. + */ + int INTcode = 0, + LONGcode = 1, + FLOATcode = 2, + DOUBLEcode = 3, + OBJECTcode = 4, + BYTEcode = 5, + CHARcode = 6, + SHORTcode = 7, + VOIDcode = 8, + TypeCodeCount = 9; + + static final String[] typecodeNames = { + "int", + "long", + "float", + "double", + "object", + "byte", + "char", + "short", + "void", + "oops" + }; +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/jvm/CRTFlags.java b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/CRTFlags.java new file mode 100644 index 0000000..db09b60 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/CRTFlags.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2001, 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.jvm; + + +/** The CharacterRangeTable flags indicating type of an entry. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public interface CRTFlags { + + /** CRTEntry flags. + */ + public static final int CRT_STATEMENT = 0x0001; + public static final int CRT_BLOCK = 0x0002; + public static final int CRT_ASSIGNMENT = 0x0004; + public static final int CRT_FLOW_CONTROLLER = 0x0008; + public static final int CRT_FLOW_TARGET = 0x0010; + public static final int CRT_INVOKE = 0x0020; + public static final int CRT_CREATE = 0x0040; + public static final int CRT_BRANCH_TRUE = 0x0080; + public static final int CRT_BRANCH_FALSE = 0x0100; + + /** The mask for valid flags + */ + public static final int CRT_VALID_FLAGS = CRT_STATEMENT | CRT_BLOCK | CRT_ASSIGNMENT | + CRT_FLOW_CONTROLLER | CRT_FLOW_TARGET | CRT_INVOKE | + CRT_CREATE | CRT_BRANCH_TRUE | CRT_BRANCH_FALSE; +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/jvm/CRTable.java b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/CRTable.java new file mode 100644 index 0000000..a2c1edc --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/CRTable.java @@ -0,0 +1,617 @@ +/* + * Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.jvm; + +import java.util.*; + +import com.sun.tools.javac.tree.*; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.tree.JCTree.*; + +/** This class contains the CharacterRangeTable for some method + * and the hashtable for mapping trees or lists of trees to their + * ending positions. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class CRTable +implements CRTFlags { + + private final boolean crtDebug = false; + + /** The list of CRTable entries. + */ + private ListBuffer entries = new ListBuffer(); + + /** The hashtable for source positions. + */ + private Map positions = new HashMap(); + + /** The hashtable for ending positions stored in the parser. + */ + private Map endPositions; + + /** The tree of the method this table is intended for. + * We should traverse this tree to get source ranges. + */ + JCTree.JCMethodDecl methodTree; + + /** Constructor + */ + public CRTable(JCTree.JCMethodDecl tree, Map endPositions) { + this.methodTree = tree; + this.endPositions = endPositions; + } + + /** Create a new CRTEntry and add it to the entries. + * @param tree The tree or the list of trees for which + * we are storing the code pointers. + * @param flags The set of flags designating type of the entry. + * @param startPc The starting code position. + * @param endPc The ending code position. + */ + public void put(Object tree, int flags, int startPc, int endPc) { + entries.append(new CRTEntry(tree, flags, startPc, endPc)); + } + + /** Compute source positions and write CRT to the databuf. + * @param databuf The buffer to write bytecodes to. + */ + public int writeCRT(ByteBuffer databuf, Position.LineMap lineMap, Log log) { + + int crtEntries = 0; + + // compute source positions for the method + new SourceComputer().csp(methodTree); + + for (List l = entries.toList(); l.nonEmpty(); l = l.tail) { + + CRTEntry entry = l.head; + + // eliminate entries that do not produce bytecodes: + // for example, empty blocks and statements + if (entry.startPc == entry.endPc) + continue; + + SourceRange pos = positions.get(entry.tree); + Assert.checkNonNull(pos, "CRT: tree source positions are undefined"); + if ((pos.startPos == Position.NOPOS) || (pos.endPos == Position.NOPOS)) + continue; + + if (crtDebug) { + System.out.println("Tree: " + entry.tree + ", type:" + getTypes(entry.flags)); + System.out.print("Start: pos = " + pos.startPos + ", pc = " + entry.startPc); + } + + // encode startPos into line/column representation + int startPos = encodePosition(pos.startPos, lineMap, log); + if (startPos == Position.NOPOS) + continue; + + if (crtDebug) { + System.out.print("End: pos = " + pos.endPos + ", pc = " + (entry.endPc - 1)); + } + + // encode endPos into line/column representation + int endPos = encodePosition(pos.endPos, lineMap, log); + if (endPos == Position.NOPOS) + continue; + + // write attribute + databuf.appendChar(entry.startPc); + // 'endPc - 1' because endPc actually points to start of the next command + databuf.appendChar(entry.endPc - 1); + databuf.appendInt(startPos); + databuf.appendInt(endPos); + databuf.appendChar(entry.flags); + + crtEntries++; + } + + return crtEntries; + } + + /** Return the number of the entries. + */ + public int length() { + return entries.length(); + } + + /** Return string describing flags enabled. + */ + private String getTypes(int flags) { + String types = ""; + if ((flags & CRT_STATEMENT) != 0) types += " CRT_STATEMENT"; + if ((flags & CRT_BLOCK) != 0) types += " CRT_BLOCK"; + if ((flags & CRT_ASSIGNMENT) != 0) types += " CRT_ASSIGNMENT"; + if ((flags & CRT_FLOW_CONTROLLER) != 0) types += " CRT_FLOW_CONTROLLER"; + if ((flags & CRT_FLOW_TARGET) != 0) types += " CRT_FLOW_TARGET"; + if ((flags & CRT_INVOKE) != 0) types += " CRT_INVOKE"; + if ((flags & CRT_CREATE) != 0) types += " CRT_CREATE"; + if ((flags & CRT_BRANCH_TRUE) != 0) types += " CRT_BRANCH_TRUE"; + if ((flags & CRT_BRANCH_FALSE) != 0) types += " CRT_BRANCH_FALSE"; + return types; + } + + /** Source file positions in CRT are integers in the format: + * line-number << LINESHIFT + column-number + */ + private int encodePosition(int pos, Position.LineMap lineMap, Log log) { + int line = lineMap.getLineNumber(pos); + int col = lineMap.getColumnNumber(pos); + int new_pos = Position.encodePosition(line, col); + if (crtDebug) { + System.out.println(", line = " + line + ", column = " + col + + ", new_pos = " + new_pos); + } + if (new_pos == Position.NOPOS) + log.warning(pos, "position.overflow", line); + + return new_pos; + } + +/* ************************************************************************ + * Traversal methods + *************************************************************************/ + + /** + * This class contains methods to compute source positions for trees. + * Extends Tree.Visitor to traverse the abstract syntax tree. + */ + class SourceComputer extends JCTree.Visitor { + + /** The result of the tree traversal methods. + */ + SourceRange result; + + /** Visitor method: compute source positions for a single node. + */ + public SourceRange csp(JCTree tree) { + if (tree == null) return null; + tree.accept(this); + if (result != null) { + positions.put(tree, result); + } + return result; + } + + /** Visitor method: compute source positions for a list of nodes. + */ + public SourceRange csp(List trees) { + if ((trees == null) || !(trees.nonEmpty())) return null; + SourceRange list_sr = new SourceRange(); + for (List l = trees; l.nonEmpty(); l = l.tail) { + list_sr.mergeWith(csp(l.head)); + } + positions.put(trees, list_sr); + return list_sr; + } + + /** Visitor method: compute source positions for + * a list of case blocks of switch statements. + */ + public SourceRange cspCases(List trees) { + if ((trees == null) || !(trees.nonEmpty())) return null; + SourceRange list_sr = new SourceRange(); + for (List l = trees; l.nonEmpty(); l = l.tail) { + list_sr.mergeWith(csp(l.head)); + } + positions.put(trees, list_sr); + return list_sr; + } + + /** Visitor method: compute source positions for + * a list of catch clauses in try statements. + */ + public SourceRange cspCatchers(List trees) { + if ((trees == null) || !(trees.nonEmpty())) return null; + SourceRange list_sr = new SourceRange(); + for (List l = trees; l.nonEmpty(); l = l.tail) { + list_sr.mergeWith(csp(l.head)); + } + positions.put(trees, list_sr); + return list_sr; + } + + public void visitMethodDef(JCMethodDecl tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.body)); + result = sr; + } + + public void visitVarDef(JCVariableDecl tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + csp(tree.vartype); + sr.mergeWith(csp(tree.init)); + result = sr; + } + + public void visitSkip(JCSkip tree) { + // endPos is the same as startPos for the empty statement + SourceRange sr = new SourceRange(startPos(tree), startPos(tree)); + result = sr; + } + + public void visitBlock(JCBlock tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + csp(tree.stats); // doesn't compare because block's ending position is defined + result = sr; + } + + public void visitDoLoop(JCDoWhileLoop tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.body)); + sr.mergeWith(csp(tree.cond)); + result = sr; + } + + public void visitWhileLoop(JCWhileLoop tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.cond)); + sr.mergeWith(csp(tree.body)); + result = sr; + } + + public void visitForLoop(JCForLoop tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.init)); + sr.mergeWith(csp(tree.cond)); + sr.mergeWith(csp(tree.step)); + sr.mergeWith(csp(tree.body)); + result = sr; + } + + public void visitForeachLoop(JCEnhancedForLoop tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.var)); + sr.mergeWith(csp(tree.expr)); + sr.mergeWith(csp(tree.body)); + result = sr; + } + + public void visitLabelled(JCLabeledStatement tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.body)); + result = sr; + } + + public void visitSwitch(JCSwitch tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.selector)); + sr.mergeWith(cspCases(tree.cases)); + result = sr; + } + + public void visitCase(JCCase tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.pat)); + sr.mergeWith(csp(tree.stats)); + result = sr; + } + + public void visitSynchronized(JCSynchronized tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.lock)); + sr.mergeWith(csp(tree.body)); + result = sr; + } + + public void visitTry(JCTry tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.resources)); + sr.mergeWith(csp(tree.body)); + sr.mergeWith(cspCatchers(tree.catchers)); + sr.mergeWith(csp(tree.finalizer)); + result = sr; + } + + public void visitCatch(JCCatch tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.param)); + sr.mergeWith(csp(tree.body)); + result = sr; + } + + public void visitConditional(JCConditional tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.cond)); + sr.mergeWith(csp(tree.truepart)); + sr.mergeWith(csp(tree.falsepart)); + result = sr; + } + + public void visitIf(JCIf tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.cond)); + sr.mergeWith(csp(tree.thenpart)); + sr.mergeWith(csp(tree.elsepart)); + result = sr; + } + + public void visitExec(JCExpressionStatement tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.expr)); + result = sr; + } + + public void visitBreak(JCBreak tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + result = sr; + } + + public void visitContinue(JCContinue tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + result = sr; + } + + public void visitReturn(JCReturn tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.expr)); + result = sr; + } + + public void visitThrow(JCThrow tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.expr)); + result = sr; + } + + public void visitAssert(JCAssert tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.cond)); + sr.mergeWith(csp(tree.detail)); + result = sr; + } + + public void visitApply(JCMethodInvocation tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.meth)); + sr.mergeWith(csp(tree.args)); + result = sr; + } + + public void visitNewClass(JCNewClass tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.encl)); + sr.mergeWith(csp(tree.clazz)); + sr.mergeWith(csp(tree.args)); + sr.mergeWith(csp(tree.def)); + result = sr; + } + + public void visitNewArray(JCNewArray tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.elemtype)); + sr.mergeWith(csp(tree.dims)); + sr.mergeWith(csp(tree.elems)); + result = sr; + } + + public void visitParens(JCParens tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.expr)); + result = sr; + } + + public void visitAssign(JCAssign tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.lhs)); + sr.mergeWith(csp(tree.rhs)); + result = sr; + } + + public void visitAssignop(JCAssignOp tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.lhs)); + sr.mergeWith(csp(tree.rhs)); + result = sr; + } + + public void visitUnary(JCUnary tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.arg)); + result = sr; + } + + public void visitBinary(JCBinary tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.lhs)); + sr.mergeWith(csp(tree.rhs)); + result = sr; + } + + public void visitTypeCast(JCTypeCast tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.clazz)); + sr.mergeWith(csp(tree.expr)); + result = sr; + } + + public void visitTypeTest(JCInstanceOf tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.expr)); + sr.mergeWith(csp(tree.clazz)); + result = sr; + } + + public void visitIndexed(JCArrayAccess tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.indexed)); + sr.mergeWith(csp(tree.index)); + result = sr; + } + + public void visitSelect(JCFieldAccess tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.selected)); + result = sr; + } + + public void visitIdent(JCIdent tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + result = sr; + } + + public void visitLiteral(JCLiteral tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + result = sr; + } + + public void visitTypeIdent(JCPrimitiveTypeTree tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + result = sr; + } + + public void visitTypeArray(JCArrayTypeTree tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.elemtype)); + result = sr; + } + + public void visitTypeApply(JCTypeApply tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.clazz)); + sr.mergeWith(csp(tree.arguments)); + result = sr; + } + + public void visitTypeParameter(JCTypeParameter tree) { + SourceRange sr = new SourceRange(startPos(tree), endPos(tree)); + sr.mergeWith(csp(tree.bounds)); + result = sr; + } + + public void visitWildcard(JCWildcard tree) { + result = null; + } + + public void visitErroneous(JCErroneous tree) { + result = null; + } + + public void visitTree(JCTree tree) { + Assert.error(); + } + + /** The start position of given tree. + */ + public int startPos(JCTree tree) { + if (tree == null) return Position.NOPOS; + return tree.pos; + } + + /** The end position of given tree, if it has + * defined endpos, NOPOS otherwise. + */ + public int endPos(JCTree tree) { + if (tree == null) return Position.NOPOS; + if (tree.getTag() == JCTree.BLOCK) + return ((JCBlock) tree).endpos; + Integer endpos = endPositions.get(tree); + if (endpos != null) + return endpos.intValue(); + return Position.NOPOS; + } + } + + /** This class contains a CharacterRangeTableEntry. + */ + static class CRTEntry { + + /** A tree or a list of trees to obtain source positions. + */ + Object tree; + + /** The flags described in the CharacterRangeTable spec. + */ + int flags; + + /** The starting code position of this entry. + */ + int startPc; + + /** The ending code position of this entry. + */ + int endPc; + + /** Constructor */ + CRTEntry(Object tree, int flags, int startPc, int endPc) { + this.tree = tree; + this.flags = flags; + this.startPc = startPc; + this.endPc = endPc; + } + } + + + /** This class contains source positions + * for some tree or list of trees. + */ + static class SourceRange { + + /** The starting source position. + */ + int startPos; + + /** The ending source position. + */ + int endPos; + + /** Constructor */ + SourceRange() { + startPos = Position.NOPOS; + endPos = Position.NOPOS; + } + + /** Constructor */ + SourceRange(int startPos, int endPos) { + this.startPos = startPos; + this.endPos = endPos; + } + + /** Compare the starting and the ending positions + * of the source range and combines them assigning + * the widest range to this. + */ + SourceRange mergeWith(SourceRange sr) { + if (sr == null) return this; + if (startPos == Position.NOPOS) + startPos = sr.startPos; + else if (sr.startPos != Position.NOPOS) + startPos = (startPos < sr.startPos ? startPos : sr.startPos); + if (endPos == Position.NOPOS) + endPos = sr.endPos; + else if (sr.endPos != Position.NOPOS) + endPos = (endPos > sr.endPos ? endPos : sr.endPos); + return this; + } + } + +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/jvm/ClassFile.java b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/ClassFile.java new file mode 100644 index 0000000..966a84d --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/ClassFile.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.jvm; + +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.util.Name; + + +/** A JVM class file. + * + *

Generic Java classfiles have one additional attribute for classes, + * methods and fields: + *

+ *   "Signature" (u4 attr-length, u2 signature-index)
+ *  
+ * + *

A signature gives the full Java type of a method or field. When + * used as a class attribute, it indicates type parameters, followed + * by supertype, followed by all interfaces. + *

+ *     methodOrFieldSignature ::= type
+ *     classSignature         ::= [ typeparams ] supertype { interfacetype }
+ *  
+ *

The type syntax in signatures is extended as follows: + *

+ *     type       ::= ... | classtype | methodtype | typevar
+ *     classtype  ::= classsig { '.' classsig }
+ *     classig    ::= 'L' name [typeargs] ';'
+ *     methodtype ::= [ typeparams ] '(' { type } ')' type
+ *     typevar    ::= 'T' name ';'
+ *     typeargs   ::= '<' type { type } '>'
+ *     typeparams ::= '<' typeparam { typeparam } '>'
+ *     typeparam  ::= name ':' type
+ *  
+ *

This class defines constants used in class files as well + * as routines to convert between internal ``.'' and external ``/'' + * separators in class names. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. */ +public class ClassFile { + + public final static int JAVA_MAGIC = 0xCAFEBABE; + + // see Target + public final static int CONSTANT_Utf8 = 1; + public final static int CONSTANT_Unicode = 2; + public final static int CONSTANT_Integer = 3; + public final static int CONSTANT_Float = 4; + public final static int CONSTANT_Long = 5; + public final static int CONSTANT_Double = 6; + public final static int CONSTANT_Class = 7; + public final static int CONSTANT_String = 8; + public final static int CONSTANT_Fieldref = 9; + public final static int CONSTANT_Methodref = 10; + public final static int CONSTANT_InterfaceMethodref = 11; + public final static int CONSTANT_NameandType = 12; + public final static int CONSTANT_MethodHandle = 15; + public final static int CONSTANT_MethodType = 16; + public final static int CONSTANT_InvokeDynamic = 18; + + public final static int MAX_PARAMETERS = 0xff; + public final static int MAX_DIMENSIONS = 0xff; + public final static int MAX_CODE = 0xffff; + public final static int MAX_LOCALS = 0xffff; + public final static int MAX_STACK = 0xffff; + + public enum Version { + V45_3(45, 3), // base level for all attributes + V49(49, 0), // JDK 1.5: enum, generics, annotations + V50(50, 0), // JDK 1.6: stackmaps + V51(51, 0); // JDK 1.7 + Version(int major, int minor) { + this.major = major; + this.minor = minor; + } + public final int major, minor; + } + + +/************************************************************************ + * String Translation Routines + ***********************************************************************/ + + /** Return internal representation of buf[offset..offset+len-1], + * converting '/' to '.'. + */ + public static byte[] internalize(byte[] buf, int offset, int len) { + byte[] translated = new byte[len]; + for (int j = 0; j < len; j++) { + byte b = buf[offset + j]; + if (b == '/') translated[j] = (byte) '.'; + else translated[j] = b; + } + return translated; + } + + /** Return internal representation of given name, + * converting '/' to '.'. + */ + public static byte[] internalize(Name name) { + return internalize(name.getByteArray(), name.getByteOffset(), name.getByteLength()); + } + + /** Return external representation of buf[offset..offset+len-1], + * converting '.' to '/'. + */ + public static byte[] externalize(byte[] buf, int offset, int len) { + byte[] translated = new byte[len]; + for (int j = 0; j < len; j++) { + byte b = buf[offset + j]; + if (b == '.') translated[j] = (byte) '/'; + else translated[j] = b; + } + return translated; + } + + /** Return external representation of given name, + * converting '/' to '.'. + */ + public static byte[] externalize(Name name) { + return externalize(name.getByteArray(), name.getByteOffset(), name.getByteLength()); + } + +/************************************************************************ + * Name-and-type + ***********************************************************************/ + + /** A class for the name-and-type signature of a method or field. + */ + public static class NameAndType { + Name name; + Type type; + + NameAndType(Name name, Type type) { + this.name = name; + this.type = type; + } + + public boolean equals(Object other) { + return + other instanceof NameAndType && + name == ((NameAndType) other).name && + type.equals(((NameAndType) other).type); + } + + public int hashCode() { + return name.hashCode() * type.hashCode(); + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/jvm/ClassReader.java b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/ClassReader.java new file mode 100644 index 0000000..2b61afa --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/ClassReader.java @@ -0,0 +1,2662 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.jvm; + +import java.io.*; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.CharBuffer; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.lang.model.SourceVersion; +import javax.tools.JavaFileObject; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileManager.Location; +import javax.tools.StandardJavaFileManager; + +import static javax.tools.StandardLocation.*; + +import com.sun.tools.javac.comp.Annotate; +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Lint.LintCategory; +import com.sun.tools.javac.code.Type.*; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.code.Symtab; +import com.sun.tools.javac.file.BaseFileObject; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; + +import static com.sun.tools.javac.code.Flags.*; +import static com.sun.tools.javac.code.Kinds.*; +import static com.sun.tools.javac.code.TypeTags.*; +import static com.sun.tools.javac.jvm.ClassFile.*; +import static com.sun.tools.javac.jvm.ClassFile.Version.*; + +import static com.sun.tools.javac.main.OptionName.*; + +/** This class provides operations to read a classfile into an internal + * representation. The internal representation is anchored in a + * ClassSymbol which contains in its scope symbol representations + * for all other definitions in the classfile. Top-level Classes themselves + * appear as members of the scopes of PackageSymbols. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class ClassReader implements Completer { + /** The context key for the class reader. */ + protected static final Context.Key classReaderKey = + new Context.Key(); + + public static final int INITIAL_BUFFER_SIZE = 0x0fff0; + + Annotate annotate; + + /** Switch: verbose output. + */ + boolean verbose; + + /** Switch: check class file for correct minor version, unrecognized + * attributes. + */ + boolean checkClassFile; + + /** Switch: read constant pool and code sections. This switch is initially + * set to false but can be turned on from outside. + */ + public boolean readAllOfClassFile = false; + + /** Switch: read GJ signature information. + */ + boolean allowGenerics; + + /** Switch: read varargs attribute. + */ + boolean allowVarargs; + + /** Switch: allow annotations. + */ + boolean allowAnnotations; + + /** Switch: allow simplified varargs. + */ + boolean allowSimplifiedVarargs; + + /** Lint option: warn about classfile issues + */ + boolean lintClassfile; + + + /** Switch: preserve parameter names from the variable table. + */ + public boolean saveParameterNames; + + /** + * Switch: cache completion failures unless -XDdev is used + */ + private boolean cacheCompletionFailure; + + /** + * Switch: prefer source files instead of newer when both source + * and class are available + **/ + public boolean preferSource; + + /** The log to use for verbose output + */ + final Log log; + + /** The symbol table. */ + Symtab syms; + + Types types; + + /** The name table. */ + final Names names; + + /** Force a completion failure on this name + */ + final Name completionFailureName; + + /** Access to files + */ + private final JavaFileManager fileManager; + + /** Factory for diagnostics + */ + JCDiagnostic.Factory diagFactory; + + /** Can be reassigned from outside: + * the completer to be used for ".java" files. If this remains unassigned + * ".java" files will not be loaded. + */ + public SourceCompleter sourceCompleter = null; + + /** A hashtable containing the encountered top-level and member classes, + * indexed by flat names. The table does not contain local classes. + */ + private Map classes; + + /** A hashtable containing the encountered packages. + */ + private Map packages; + + /** The current scope where type variables are entered. + */ + protected Scope typevars; + + /** The path name of the class file currently being read. + */ + protected JavaFileObject currentClassFile = null; + + /** The class or method currently being read. + */ + protected Symbol currentOwner = null; + + /** The buffer containing the currently read class file. + */ + byte[] buf = new byte[INITIAL_BUFFER_SIZE]; + + /** The current input pointer. + */ + int bp; + + /** The objects of the constant pool. + */ + Object[] poolObj; + + /** For every constant pool entry, an index into buf where the + * defining section of the entry is found. + */ + int[] poolIdx; + + /** The major version number of the class file being read. */ + int majorVersion; + /** The minor version number of the class file being read. */ + int minorVersion; + + /** A table to hold the constant pool indices for method parameter + * names, as given in LocalVariableTable attributes. + */ + int[] parameterNameIndices; + + /** + * Whether or not any parameter names have been found. + */ + boolean haveParameterNameIndices; + + /** + * The set of attribute names for which warnings have been generated for the current class + */ + Set warnedAttrs = new HashSet(); + + /** Get the ClassReader instance for this invocation. */ + public static ClassReader instance(Context context) { + ClassReader instance = context.get(classReaderKey); + if (instance == null) + instance = new ClassReader(context, true); + return instance; + } + + /** Initialize classes and packages, treating this as the definitive classreader. */ + public void init(Symtab syms) { + init(syms, true); + } + + /** Initialize classes and packages, optionally treating this as + * the definitive classreader. + */ + private void init(Symtab syms, boolean definitive) { + if (classes != null) return; + + if (definitive) { + Assert.check(packages == null || packages == syms.packages); + packages = syms.packages; + Assert.check(classes == null || classes == syms.classes); + classes = syms.classes; + } else { + packages = new HashMap(); + classes = new HashMap(); + } + + packages.put(names.empty, syms.rootPackage); + syms.rootPackage.completer = this; + syms.unnamedPackage.completer = this; + } + + /** Construct a new class reader, optionally treated as the + * definitive classreader for this invocation. + */ + protected ClassReader(Context context, boolean definitive) { + if (definitive) context.put(classReaderKey, this); + + names = Names.instance(context); + syms = Symtab.instance(context); + types = Types.instance(context); + fileManager = context.get(JavaFileManager.class); + if (fileManager == null) + throw new AssertionError("FileManager initialization error"); + diagFactory = JCDiagnostic.Factory.instance(context); + + init(syms, definitive); + log = Log.instance(context); + + Options options = Options.instance(context); + annotate = Annotate.instance(context); + verbose = options.isSet(VERBOSE); + checkClassFile = options.isSet("-checkclassfile"); + Source source = Source.instance(context); + allowGenerics = source.allowGenerics(); + allowVarargs = source.allowVarargs(); + allowAnnotations = source.allowAnnotations(); + allowSimplifiedVarargs = source.allowSimplifiedVarargs(); + saveParameterNames = options.isSet("save-parameter-names"); + cacheCompletionFailure = options.isUnset("dev"); + preferSource = "source".equals(options.get("-Xprefer")); + + completionFailureName = + options.isSet("failcomplete") + ? names.fromString(options.get("failcomplete")) + : null; + + typevars = new Scope(syms.noSymbol); + + lintClassfile = Lint.instance(context).isEnabled(LintCategory.CLASSFILE); + + initAttributeReaders(); + } + + /** Add member to class unless it is synthetic. + */ + private void enterMember(ClassSymbol c, Symbol sym) { + if ((sym.flags_field & (SYNTHETIC|BRIDGE)) != SYNTHETIC) + c.members_field.enter(sym); + } + +/************************************************************************ + * Error Diagnoses + ***********************************************************************/ + + + public class BadClassFile extends CompletionFailure { + private static final long serialVersionUID = 0; + + public BadClassFile(TypeSymbol sym, JavaFileObject file, JCDiagnostic diag) { + super(sym, createBadClassFileDiagnostic(file, diag)); + } + } + // where + private JCDiagnostic createBadClassFileDiagnostic(JavaFileObject file, JCDiagnostic diag) { + String key = (file.getKind() == JavaFileObject.Kind.SOURCE + ? "bad.source.file.header" : "bad.class.file.header"); + return diagFactory.fragment(key, file, diag); + } + + public BadClassFile badClassFile(String key, Object... args) { + return new BadClassFile ( + currentOwner.enclClass(), + currentClassFile, + diagFactory.fragment(key, args)); + } + +/************************************************************************ + * Buffer Access + ***********************************************************************/ + + /** Read a character. + */ + char nextChar() { + return (char)(((buf[bp++] & 0xFF) << 8) + (buf[bp++] & 0xFF)); + } + + /** Read a byte. + */ + byte nextByte() { + return buf[bp++]; + } + + /** Read an integer. + */ + int nextInt() { + return + ((buf[bp++] & 0xFF) << 24) + + ((buf[bp++] & 0xFF) << 16) + + ((buf[bp++] & 0xFF) << 8) + + (buf[bp++] & 0xFF); + } + + /** Extract a character at position bp from buf. + */ + char getChar(int bp) { + return + (char)(((buf[bp] & 0xFF) << 8) + (buf[bp+1] & 0xFF)); + } + + /** Extract an integer at position bp from buf. + */ + int getInt(int bp) { + return + ((buf[bp] & 0xFF) << 24) + + ((buf[bp+1] & 0xFF) << 16) + + ((buf[bp+2] & 0xFF) << 8) + + (buf[bp+3] & 0xFF); + } + + + /** Extract a long integer at position bp from buf. + */ + long getLong(int bp) { + DataInputStream bufin = + new DataInputStream(new ByteArrayInputStream(buf, bp, 8)); + try { + return bufin.readLong(); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + /** Extract a float at position bp from buf. + */ + float getFloat(int bp) { + DataInputStream bufin = + new DataInputStream(new ByteArrayInputStream(buf, bp, 4)); + try { + return bufin.readFloat(); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + /** Extract a double at position bp from buf. + */ + double getDouble(int bp) { + DataInputStream bufin = + new DataInputStream(new ByteArrayInputStream(buf, bp, 8)); + try { + return bufin.readDouble(); + } catch (IOException e) { + throw new AssertionError(e); + } + } + +/************************************************************************ + * Constant Pool Access + ***********************************************************************/ + + /** Index all constant pool entries, writing their start addresses into + * poolIdx. + */ + void indexPool() { + poolIdx = new int[nextChar()]; + poolObj = new Object[poolIdx.length]; + int i = 1; + while (i < poolIdx.length) { + poolIdx[i++] = bp; + byte tag = buf[bp++]; + switch (tag) { + case CONSTANT_Utf8: case CONSTANT_Unicode: { + int len = nextChar(); + bp = bp + len; + break; + } + case CONSTANT_Class: + case CONSTANT_String: + case CONSTANT_MethodType: + bp = bp + 2; + break; + case CONSTANT_MethodHandle: + bp = bp + 3; + break; + case CONSTANT_Fieldref: + case CONSTANT_Methodref: + case CONSTANT_InterfaceMethodref: + case CONSTANT_NameandType: + case CONSTANT_Integer: + case CONSTANT_Float: + case CONSTANT_InvokeDynamic: + bp = bp + 4; + break; + case CONSTANT_Long: + case CONSTANT_Double: + bp = bp + 8; + i++; + break; + default: + throw badClassFile("bad.const.pool.tag.at", + Byte.toString(tag), + Integer.toString(bp -1)); + } + } + } + + /** Read constant pool entry at start address i, use pool as a cache. + */ + Object readPool(int i) { + Object result = poolObj[i]; + if (result != null) return result; + + int index = poolIdx[i]; + if (index == 0) return null; + + byte tag = buf[index]; + switch (tag) { + case CONSTANT_Utf8: + poolObj[i] = names.fromUtf(buf, index + 3, getChar(index + 1)); + break; + case CONSTANT_Unicode: + throw badClassFile("unicode.str.not.supported"); + case CONSTANT_Class: + poolObj[i] = readClassOrType(getChar(index + 1)); + break; + case CONSTANT_String: + // FIXME: (footprint) do not use toString here + poolObj[i] = readName(getChar(index + 1)).toString(); + break; + case CONSTANT_Fieldref: { + ClassSymbol owner = readClassSymbol(getChar(index + 1)); + NameAndType nt = (NameAndType)readPool(getChar(index + 3)); + poolObj[i] = new VarSymbol(0, nt.name, nt.type, owner); + break; + } + case CONSTANT_Methodref: + case CONSTANT_InterfaceMethodref: { + ClassSymbol owner = readClassSymbol(getChar(index + 1)); + NameAndType nt = (NameAndType)readPool(getChar(index + 3)); + poolObj[i] = new MethodSymbol(0, nt.name, nt.type, owner); + break; + } + case CONSTANT_NameandType: + poolObj[i] = new NameAndType( + readName(getChar(index + 1)), + readType(getChar(index + 3))); + break; + case CONSTANT_Integer: + poolObj[i] = getInt(index + 1); + break; + case CONSTANT_Float: + poolObj[i] = new Float(getFloat(index + 1)); + break; + case CONSTANT_Long: + poolObj[i] = new Long(getLong(index + 1)); + break; + case CONSTANT_Double: + poolObj[i] = new Double(getDouble(index + 1)); + break; + case CONSTANT_MethodHandle: + skipBytes(4); + break; + case CONSTANT_MethodType: + skipBytes(3); + break; + case CONSTANT_InvokeDynamic: + skipBytes(5); + break; + default: + throw badClassFile("bad.const.pool.tag", Byte.toString(tag)); + } + return poolObj[i]; + } + + /** Read signature and convert to type. + */ + Type readType(int i) { + int index = poolIdx[i]; + return sigToType(buf, index + 3, getChar(index + 1)); + } + + /** If name is an array type or class signature, return the + * corresponding type; otherwise return a ClassSymbol with given name. + */ + Object readClassOrType(int i) { + int index = poolIdx[i]; + int len = getChar(index + 1); + int start = index + 3; + Assert.check(buf[start] == '[' || buf[start + len - 1] != ';'); + // by the above assertion, the following test can be + // simplified to (buf[start] == '[') + return (buf[start] == '[' || buf[start + len - 1] == ';') + ? (Object)sigToType(buf, start, len) + : (Object)enterClass(names.fromUtf(internalize(buf, start, + len))); + } + + /** Read signature and convert to type parameters. + */ + List readTypeParams(int i) { + int index = poolIdx[i]; + return sigToTypeParams(buf, index + 3, getChar(index + 1)); + } + + /** Read class entry. + */ + ClassSymbol readClassSymbol(int i) { + return (ClassSymbol) (readPool(i)); + } + + /** Read name. + */ + Name readName(int i) { + return (Name) (readPool(i)); + } + +/************************************************************************ + * Reading Types + ***********************************************************************/ + + /** The unread portion of the currently read type is + * signature[sigp..siglimit-1]. + */ + byte[] signature; + int sigp; + int siglimit; + boolean sigEnterPhase = false; + + /** Convert signature to type, where signature is a byte array segment. + */ + Type sigToType(byte[] sig, int offset, int len) { + signature = sig; + sigp = offset; + siglimit = offset + len; + return sigToType(); + } + + /** Convert signature to type, where signature is implicit. + */ + Type sigToType() { + switch ((char) signature[sigp]) { + case 'T': + sigp++; + int start = sigp; + while (signature[sigp] != ';') sigp++; + sigp++; + return sigEnterPhase + ? Type.noType + : findTypeVar(names.fromUtf(signature, start, sigp - 1 - start)); + case '+': { + sigp++; + Type t = sigToType(); + return new WildcardType(t, BoundKind.EXTENDS, + syms.boundClass); + } + case '*': + sigp++; + return new WildcardType(syms.objectType, BoundKind.UNBOUND, + syms.boundClass); + case '-': { + sigp++; + Type t = sigToType(); + return new WildcardType(t, BoundKind.SUPER, + syms.boundClass); + } + case 'B': + sigp++; + return syms.byteType; + case 'C': + sigp++; + return syms.charType; + case 'D': + sigp++; + return syms.doubleType; + case 'F': + sigp++; + return syms.floatType; + case 'I': + sigp++; + return syms.intType; + case 'J': + sigp++; + return syms.longType; + case 'L': + { + // int oldsigp = sigp; + Type t = classSigToType(); + if (sigp < siglimit && signature[sigp] == '.') + throw badClassFile("deprecated inner class signature syntax " + + "(please recompile from source)"); + /* + System.err.println(" decoded " + + new String(signature, oldsigp, sigp-oldsigp) + + " => " + t + " outer " + t.outer()); + */ + return t; + } + case 'S': + sigp++; + return syms.shortType; + case 'V': + sigp++; + return syms.voidType; + case 'Z': + sigp++; + return syms.booleanType; + case '[': + sigp++; + return new ArrayType(sigToType(), syms.arrayClass); + case '(': + sigp++; + List argtypes = sigToTypes(')'); + Type restype = sigToType(); + List thrown = List.nil(); + while (signature[sigp] == '^') { + sigp++; + thrown = thrown.prepend(sigToType()); + } + return new MethodType(argtypes, + restype, + thrown.reverse(), + syms.methodClass); + case '<': + typevars = typevars.dup(currentOwner); + Type poly = new ForAll(sigToTypeParams(), sigToType()); + typevars = typevars.leave(); + return poly; + default: + throw badClassFile("bad.signature", + Convert.utf2string(signature, sigp, 10)); + } + } + + byte[] signatureBuffer = new byte[0]; + int sbp = 0; + /** Convert class signature to type, where signature is implicit. + */ + Type classSigToType() { + if (signature[sigp] != 'L') + throw badClassFile("bad.class.signature", + Convert.utf2string(signature, sigp, 10)); + sigp++; + Type outer = Type.noType; + int startSbp = sbp; + + while (true) { + final byte c = signature[sigp++]; + switch (c) { + + case ';': { // end + ClassSymbol t = enterClass(names.fromUtf(signatureBuffer, + startSbp, + sbp - startSbp)); + if (outer == Type.noType) + outer = t.erasure(types); + else + outer = new ClassType(outer, List.nil(), t); + sbp = startSbp; + return outer; + } + + case '<': // generic arguments + ClassSymbol t = enterClass(names.fromUtf(signatureBuffer, + startSbp, + sbp - startSbp)); + outer = new ClassType(outer, sigToTypes('>'), t) { + boolean completed = false; + @Override + public Type getEnclosingType() { + if (!completed) { + completed = true; + tsym.complete(); + Type enclosingType = tsym.type.getEnclosingType(); + if (enclosingType != Type.noType) { + List typeArgs = + super.getEnclosingType().allparams(); + List typeParams = + enclosingType.allparams(); + if (typeParams.length() != typeArgs.length()) { + // no "rare" types + super.setEnclosingType(types.erasure(enclosingType)); + } else { + super.setEnclosingType(types.subst(enclosingType, + typeParams, + typeArgs)); + } + } else { + super.setEnclosingType(Type.noType); + } + } + return super.getEnclosingType(); + } + @Override + public void setEnclosingType(Type outer) { + throw new UnsupportedOperationException(); + } + }; + switch (signature[sigp++]) { + case ';': + if (sigp < signature.length && signature[sigp] == '.') { + // support old-style GJC signatures + // The signature produced was + // Lfoo/Outer;.Lfoo/Outer$Inner; + // rather than say + // Lfoo/Outer.Inner; + // so we skip past ".Lfoo/Outer$" + sigp += (sbp - startSbp) + // "foo/Outer" + 3; // ".L" and "$" + signatureBuffer[sbp++] = (byte)'$'; + break; + } else { + sbp = startSbp; + return outer; + } + case '.': + signatureBuffer[sbp++] = (byte)'$'; + break; + default: + throw new AssertionError(signature[sigp-1]); + } + continue; + + case '.': + signatureBuffer[sbp++] = (byte)'$'; + continue; + case '/': + signatureBuffer[sbp++] = (byte)'.'; + continue; + default: + signatureBuffer[sbp++] = c; + continue; + } + } + } + + /** Convert (implicit) signature to list of types + * until `terminator' is encountered. + */ + List sigToTypes(char terminator) { + List head = List.of(null); + List tail = head; + while (signature[sigp] != terminator) + tail = tail.setTail(List.of(sigToType())); + sigp++; + return head.tail; + } + + /** Convert signature to type parameters, where signature is a byte + * array segment. + */ + List sigToTypeParams(byte[] sig, int offset, int len) { + signature = sig; + sigp = offset; + siglimit = offset + len; + return sigToTypeParams(); + } + + /** Convert signature to type parameters, where signature is implicit. + */ + List sigToTypeParams() { + List tvars = List.nil(); + if (signature[sigp] == '<') { + sigp++; + int start = sigp; + sigEnterPhase = true; + while (signature[sigp] != '>') + tvars = tvars.prepend(sigToTypeParam()); + sigEnterPhase = false; + sigp = start; + while (signature[sigp] != '>') + sigToTypeParam(); + sigp++; + } + return tvars.reverse(); + } + + /** Convert (implicit) signature to type parameter. + */ + Type sigToTypeParam() { + int start = sigp; + while (signature[sigp] != ':') sigp++; + Name name = names.fromUtf(signature, start, sigp - start); + TypeVar tvar; + if (sigEnterPhase) { + tvar = new TypeVar(name, currentOwner, syms.botType); + typevars.enter(tvar.tsym); + } else { + tvar = (TypeVar)findTypeVar(name); + } + List bounds = List.nil(); + Type st = null; + if (signature[sigp] == ':' && signature[sigp+1] == ':') { + sigp++; + st = syms.objectType; + } + while (signature[sigp] == ':') { + sigp++; + bounds = bounds.prepend(sigToType()); + } + if (!sigEnterPhase) { + types.setBounds(tvar, bounds.reverse(), st); + } + return tvar; + } + + /** Find type variable with given name in `typevars' scope. + */ + Type findTypeVar(Name name) { + Scope.Entry e = typevars.lookup(name); + if (e.scope != null) { + return e.sym.type; + } else { + if (readingClassAttr) { + // While reading the class attribute, the supertypes + // might refer to a type variable from an enclosing element + // (method or class). + // If the type variable is defined in the enclosing class, + // we can actually find it in + // currentOwner.owner.type.getTypeArguments() + // However, until we have read the enclosing method attribute + // we don't know for sure if this owner is correct. It could + // be a method and there is no way to tell before reading the + // enclosing method attribute. + TypeVar t = new TypeVar(name, currentOwner, syms.botType); + missingTypeVariables = missingTypeVariables.prepend(t); + // System.err.println("Missing type var " + name); + return t; + } + throw badClassFile("undecl.type.var", name); + } + } + +/************************************************************************ + * Reading Attributes + ***********************************************************************/ + + protected enum AttributeKind { CLASS, MEMBER }; + protected abstract class AttributeReader { + AttributeReader(Name name, ClassFile.Version version, Set kinds) { + this.name = name; + this.version = version; + this.kinds = kinds; + } + + boolean accepts(AttributeKind kind) { + if (kinds.contains(kind)) { + if (majorVersion > version.major || (majorVersion == version.major && minorVersion >= version.minor)) + return true; + + if (lintClassfile && !warnedAttrs.contains(name)) { + JavaFileObject prev = log.useSource(currentClassFile); + try { + log.warning(LintCategory.CLASSFILE, (DiagnosticPosition) null, "future.attr", + name, version.major, version.minor, majorVersion, minorVersion); + } finally { + log.useSource(prev); + } + warnedAttrs.add(name); + } + } + return false; + } + + abstract void read(Symbol sym, int attrLen); + + final Name name; + final ClassFile.Version version; + final Set kinds; + } + + protected Set CLASS_ATTRIBUTE = + EnumSet.of(AttributeKind.CLASS); + protected Set MEMBER_ATTRIBUTE = + EnumSet.of(AttributeKind.MEMBER); + protected Set CLASS_OR_MEMBER_ATTRIBUTE = + EnumSet.of(AttributeKind.CLASS, AttributeKind.MEMBER); + + protected Map attributeReaders = new HashMap(); + + private void initAttributeReaders() { + AttributeReader[] readers = { + // v45.3 attributes + + new AttributeReader(names.Code, V45_3, MEMBER_ATTRIBUTE) { + void read(Symbol sym, int attrLen) { + if (readAllOfClassFile || saveParameterNames) + ((MethodSymbol)sym).code = readCode(sym); + else + bp = bp + attrLen; + } + }, + + new AttributeReader(names.ConstantValue, V45_3, MEMBER_ATTRIBUTE) { + void read(Symbol sym, int attrLen) { + Object v = readPool(nextChar()); + // Ignore ConstantValue attribute if field not final. + if ((sym.flags() & FINAL) != 0) + ((VarSymbol) sym).setData(v); + } + }, + + new AttributeReader(names.Deprecated, V45_3, CLASS_OR_MEMBER_ATTRIBUTE) { + void read(Symbol sym, int attrLen) { + sym.flags_field |= DEPRECATED; + } + }, + + new AttributeReader(names.Exceptions, V45_3, CLASS_OR_MEMBER_ATTRIBUTE) { + void read(Symbol sym, int attrLen) { + int nexceptions = nextChar(); + List thrown = List.nil(); + for (int j = 0; j < nexceptions; j++) + thrown = thrown.prepend(readClassSymbol(nextChar()).type); + if (sym.type.getThrownTypes().isEmpty()) + sym.type.asMethodType().thrown = thrown.reverse(); + } + }, + + new AttributeReader(names.InnerClasses, V45_3, CLASS_ATTRIBUTE) { + void read(Symbol sym, int attrLen) { + ClassSymbol c = (ClassSymbol) sym; + readInnerClasses(c); + } + }, + + new AttributeReader(names.LocalVariableTable, V45_3, CLASS_OR_MEMBER_ATTRIBUTE) { + void read(Symbol sym, int attrLen) { + int newbp = bp + attrLen; + if (saveParameterNames) { + // Pick up parameter names from the variable table. + // Parameter names are not explicitly identified as such, + // but all parameter name entries in the LocalVariableTable + // have a start_pc of 0. Therefore, we record the name + // indicies of all slots with a start_pc of zero in the + // parameterNameIndicies array. + // Note that this implicitly honors the JVMS spec that + // there may be more than one LocalVariableTable, and that + // there is no specified ordering for the entries. + int numEntries = nextChar(); + for (int i = 0; i < numEntries; i++) { + int start_pc = nextChar(); + int length = nextChar(); + int nameIndex = nextChar(); + int sigIndex = nextChar(); + int register = nextChar(); + if (start_pc == 0) { + // ensure array large enough + if (register >= parameterNameIndices.length) { + int newSize = Math.max(register, parameterNameIndices.length + 8); + parameterNameIndices = + Arrays.copyOf(parameterNameIndices, newSize); + } + parameterNameIndices[register] = nameIndex; + haveParameterNameIndices = true; + } + } + } + bp = newbp; + } + }, + + new AttributeReader(names.SourceFile, V45_3, CLASS_ATTRIBUTE) { + void read(Symbol sym, int attrLen) { + ClassSymbol c = (ClassSymbol) sym; + Name n = readName(nextChar()); + c.sourcefile = new SourceFileObject(n, c.flatname); + } + }, + + new AttributeReader(names.Synthetic, V45_3, CLASS_OR_MEMBER_ATTRIBUTE) { + void read(Symbol sym, int attrLen) { + // bridge methods are visible when generics not enabled + if (allowGenerics || (sym.flags_field & BRIDGE) == 0) + sym.flags_field |= SYNTHETIC; + } + }, + + // standard v49 attributes + + new AttributeReader(names.EnclosingMethod, V49, CLASS_ATTRIBUTE) { + void read(Symbol sym, int attrLen) { + int newbp = bp + attrLen; + readEnclosingMethodAttr(sym); + bp = newbp; + } + }, + + new AttributeReader(names.Signature, V49, CLASS_OR_MEMBER_ATTRIBUTE) { + @Override + boolean accepts(AttributeKind kind) { + return super.accepts(kind) && allowGenerics; + } + + void read(Symbol sym, int attrLen) { + if (sym.kind == TYP) { + ClassSymbol c = (ClassSymbol) sym; + readingClassAttr = true; + try { + ClassType ct1 = (ClassType)c.type; + Assert.check(c == currentOwner); + ct1.typarams_field = readTypeParams(nextChar()); + ct1.supertype_field = sigToType(); + ListBuffer is = new ListBuffer(); + while (sigp != siglimit) is.append(sigToType()); + ct1.interfaces_field = is.toList(); + } finally { + readingClassAttr = false; + } + } else { + List thrown = sym.type.getThrownTypes(); + sym.type = readType(nextChar()); + //- System.err.println(" # " + sym.type); + if (sym.kind == MTH && sym.type.getThrownTypes().isEmpty()) + sym.type.asMethodType().thrown = thrown; + + } + } + }, + + // v49 annotation attributes + + new AttributeReader(names.AnnotationDefault, V49, CLASS_OR_MEMBER_ATTRIBUTE) { + void read(Symbol sym, int attrLen) { + attachAnnotationDefault(sym); + } + }, + + new AttributeReader(names.RuntimeInvisibleAnnotations, V49, CLASS_OR_MEMBER_ATTRIBUTE) { + void read(Symbol sym, int attrLen) { + attachAnnotations(sym); + } + }, + + new AttributeReader(names.RuntimeInvisibleParameterAnnotations, V49, CLASS_OR_MEMBER_ATTRIBUTE) { + void read(Symbol sym, int attrLen) { + attachParameterAnnotations(sym); + } + }, + + new AttributeReader(names.RuntimeVisibleAnnotations, V49, CLASS_OR_MEMBER_ATTRIBUTE) { + void read(Symbol sym, int attrLen) { + attachAnnotations(sym); + } + }, + + new AttributeReader(names.RuntimeVisibleParameterAnnotations, V49, CLASS_OR_MEMBER_ATTRIBUTE) { + void read(Symbol sym, int attrLen) { + attachParameterAnnotations(sym); + } + }, + + // additional "legacy" v49 attributes, superceded by flags + + new AttributeReader(names.Annotation, V49, CLASS_OR_MEMBER_ATTRIBUTE) { + void read(Symbol sym, int attrLen) { + if (allowAnnotations) + sym.flags_field |= ANNOTATION; + } + }, + + new AttributeReader(names.Bridge, V49, MEMBER_ATTRIBUTE) { + void read(Symbol sym, int attrLen) { + sym.flags_field |= BRIDGE; + if (!allowGenerics) + sym.flags_field &= ~SYNTHETIC; + } + }, + + new AttributeReader(names.Enum, V49, CLASS_OR_MEMBER_ATTRIBUTE) { + void read(Symbol sym, int attrLen) { + sym.flags_field |= ENUM; + } + }, + + new AttributeReader(names.Varargs, V49, CLASS_OR_MEMBER_ATTRIBUTE) { + void read(Symbol sym, int attrLen) { + if (allowVarargs) + sym.flags_field |= VARARGS; + } + }, + + // The following attributes for a Code attribute are not currently handled + // StackMapTable + // SourceDebugExtension + // LineNumberTable + // LocalVariableTypeTable + }; + + for (AttributeReader r: readers) + attributeReaders.put(r.name, r); + } + + /** Report unrecognized attribute. + */ + void unrecognized(Name attrName) { + if (checkClassFile) + printCCF("ccf.unrecognized.attribute", attrName); + } + + + + void readEnclosingMethodAttr(Symbol sym) { + // sym is a nested class with an "Enclosing Method" attribute + // remove sym from it's current owners scope and place it in + // the scope specified by the attribute + sym.owner.members().remove(sym); + ClassSymbol self = (ClassSymbol)sym; + ClassSymbol c = readClassSymbol(nextChar()); + NameAndType nt = (NameAndType)readPool(nextChar()); + + if (c.members_field == null) + throw badClassFile("bad.enclosing.class", self, c); + + MethodSymbol m = findMethod(nt, c.members_field, self.flags()); + if (nt != null && m == null) + throw badClassFile("bad.enclosing.method", self); + + self.name = simpleBinaryName(self.flatname, c.flatname) ; + self.owner = m != null ? m : c; + if (self.name.isEmpty()) + self.fullname = names.empty; + else + self.fullname = ClassSymbol.formFullName(self.name, self.owner); + + if (m != null) { + ((ClassType)sym.type).setEnclosingType(m.type); + } else if ((self.flags_field & STATIC) == 0) { + ((ClassType)sym.type).setEnclosingType(c.type); + } else { + ((ClassType)sym.type).setEnclosingType(Type.noType); + } + enterTypevars(self); + if (!missingTypeVariables.isEmpty()) { + ListBuffer typeVars = new ListBuffer(); + for (Type typevar : missingTypeVariables) { + typeVars.append(findTypeVar(typevar.tsym.name)); + } + foundTypeVariables = typeVars.toList(); + } else { + foundTypeVariables = List.nil(); + } + } + + // See java.lang.Class + private Name simpleBinaryName(Name self, Name enclosing) { + String simpleBinaryName = self.toString().substring(enclosing.toString().length()); + if (simpleBinaryName.length() < 1 || simpleBinaryName.charAt(0) != '$') + throw badClassFile("bad.enclosing.method", self); + int index = 1; + while (index < simpleBinaryName.length() && + isAsciiDigit(simpleBinaryName.charAt(index))) + index++; + return names.fromString(simpleBinaryName.substring(index)); + } + + private MethodSymbol findMethod(NameAndType nt, Scope scope, long flags) { + if (nt == null) + return null; + + MethodType type = nt.type.asMethodType(); + + for (Scope.Entry e = scope.lookup(nt.name); e.scope != null; e = e.next()) + if (e.sym.kind == MTH && isSameBinaryType(e.sym.type.asMethodType(), type)) + return (MethodSymbol)e.sym; + + if (nt.name != names.init) + // not a constructor + return null; + if ((flags & INTERFACE) != 0) + // no enclosing instance + return null; + if (nt.type.getParameterTypes().isEmpty()) + // no parameters + return null; + + // A constructor of an inner class. + // Remove the first argument (the enclosing instance) + nt.type = new MethodType(nt.type.getParameterTypes().tail, + nt.type.getReturnType(), + nt.type.getThrownTypes(), + syms.methodClass); + // Try searching again + return findMethod(nt, scope, flags); + } + + /** Similar to Types.isSameType but avoids completion */ + private boolean isSameBinaryType(MethodType mt1, MethodType mt2) { + List types1 = types.erasure(mt1.getParameterTypes()) + .prepend(types.erasure(mt1.getReturnType())); + List types2 = mt2.getParameterTypes().prepend(mt2.getReturnType()); + while (!types1.isEmpty() && !types2.isEmpty()) { + if (types1.head.tsym != types2.head.tsym) + return false; + types1 = types1.tail; + types2 = types2.tail; + } + return types1.isEmpty() && types2.isEmpty(); + } + + /** + * Character.isDigit answers true to some non-ascii + * digits. This one does not. copied from java.lang.Class + */ + private static boolean isAsciiDigit(char c) { + return '0' <= c && c <= '9'; + } + + /** Read member attributes. + */ + void readMemberAttrs(Symbol sym) { + readAttrs(sym, AttributeKind.MEMBER); + } + + void readAttrs(Symbol sym, AttributeKind kind) { + char ac = nextChar(); + for (int i = 0; i < ac; i++) { + Name attrName = readName(nextChar()); + int attrLen = nextInt(); + AttributeReader r = attributeReaders.get(attrName); + if (r != null && r.accepts(kind)) + r.read(sym, attrLen); + else { + unrecognized(attrName); + bp = bp + attrLen; + } + } + } + + private boolean readingClassAttr = false; + private List missingTypeVariables = List.nil(); + private List foundTypeVariables = List.nil(); + + /** Read class attributes. + */ + void readClassAttrs(ClassSymbol c) { + readAttrs(c, AttributeKind.CLASS); + } + + /** Read code block. + */ + Code readCode(Symbol owner) { + nextChar(); // max_stack + nextChar(); // max_locals + final int code_length = nextInt(); + bp += code_length; + final char exception_table_length = nextChar(); + bp += exception_table_length * 8; + readMemberAttrs(owner); + return null; + } + +/************************************************************************ + * Reading Java-language annotations + ***********************************************************************/ + + /** Attach annotations. + */ + void attachAnnotations(final Symbol sym) { + int numAttributes = nextChar(); + if (numAttributes != 0) { + ListBuffer proxies = + new ListBuffer(); + for (int i = 0; i= V51.major && + proxy.type.tsym == syms.polymorphicSignatureType.tsym) { + sym.flags_field |= POLYMORPHIC_SIGNATURE; + } + } + annotate.later(new AnnotationCompleter(sym, proxies.toList())); + } + } + + /** Attach parameter annotations. + */ + void attachParameterAnnotations(final Symbol method) { + final MethodSymbol meth = (MethodSymbol)method; + int numParameters = buf[bp++] & 0xFF; + List parameters = meth.params(); + int pnum = 0; + while (parameters.tail != null) { + attachAnnotations(parameters.head); + parameters = parameters.tail; + pnum++; + } + if (pnum != numParameters) { + throw badClassFile("bad.runtime.invisible.param.annotations", meth); + } + } + + /** Attach the default value for an annotation element. + */ + void attachAnnotationDefault(final Symbol sym) { + final MethodSymbol meth = (MethodSymbol)sym; // only on methods + final Attribute value = readAttributeValue(); + annotate.later(new AnnotationDefaultCompleter(meth, value)); + } + + Type readTypeOrClassSymbol(int i) { + // support preliminary jsr175-format class files + if (buf[poolIdx[i]] == CONSTANT_Class) + return readClassSymbol(i).type; + return readType(i); + } + Type readEnumType(int i) { + // support preliminary jsr175-format class files + int index = poolIdx[i]; + int length = getChar(index + 1); + if (buf[index + length + 2] != ';') + return enterClass(readName(i)).type; + return readType(i); + } + + CompoundAnnotationProxy readCompoundAnnotation() { + Type t = readTypeOrClassSymbol(nextChar()); + int numFields = nextChar(); + ListBuffer> pairs = + new ListBuffer>(); + for (int i=0; i(name, value)); + } + return new CompoundAnnotationProxy(t, pairs.toList()); + } + + Attribute readAttributeValue() { + char c = (char) buf[bp++]; + switch (c) { + case 'B': + return new Attribute.Constant(syms.byteType, readPool(nextChar())); + case 'C': + return new Attribute.Constant(syms.charType, readPool(nextChar())); + case 'D': + return new Attribute.Constant(syms.doubleType, readPool(nextChar())); + case 'F': + return new Attribute.Constant(syms.floatType, readPool(nextChar())); + case 'I': + return new Attribute.Constant(syms.intType, readPool(nextChar())); + case 'J': + return new Attribute.Constant(syms.longType, readPool(nextChar())); + case 'S': + return new Attribute.Constant(syms.shortType, readPool(nextChar())); + case 'Z': + return new Attribute.Constant(syms.booleanType, readPool(nextChar())); + case 's': + return new Attribute.Constant(syms.stringType, readPool(nextChar()).toString()); + case 'e': + return new EnumAttributeProxy(readEnumType(nextChar()), readName(nextChar())); + case 'c': + return new Attribute.Class(types, readTypeOrClassSymbol(nextChar())); + case '[': { + int n = nextChar(); + ListBuffer l = new ListBuffer(); + for (int i=0; i values; + ArrayAttributeProxy(List values) { + super(null); + this.values = values; + } + public void accept(Visitor v) { ((ProxyVisitor)v).visitArrayAttributeProxy(this); } + @Override + public String toString() { + return "{" + values + "}"; + } + } + + /** A temporary proxy representing a compound attribute. + */ + static class CompoundAnnotationProxy extends Attribute { + final List> values; + public CompoundAnnotationProxy(Type type, + List> values) { + super(type); + this.values = values; + } + public void accept(Visitor v) { ((ProxyVisitor)v).visitCompoundAnnotationProxy(this); } + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("@"); + buf.append(type.tsym.getQualifiedName()); + buf.append("/*proxy*/{"); + boolean first = true; + for (List> v = values; + v.nonEmpty(); v = v.tail) { + Pair value = v.head; + if (!first) buf.append(","); + first = false; + buf.append(value.fst); + buf.append("="); + buf.append(value.snd); + } + buf.append("}"); + return buf.toString(); + } + } + + /** A temporary proxy representing a type annotation. + */ + static class TypeAnnotationProxy { + final CompoundAnnotationProxy compound; + final TypeAnnotationPosition position; + public TypeAnnotationProxy(CompoundAnnotationProxy compound, + TypeAnnotationPosition position) { + this.compound = compound; + this.position = position; + } + } + + class AnnotationDeproxy implements ProxyVisitor { + private ClassSymbol requestingOwner = currentOwner.kind == MTH + ? currentOwner.enclClass() : (ClassSymbol)currentOwner; + + List deproxyCompoundList(List pl) { + // also must fill in types!!!! + ListBuffer buf = + new ListBuffer(); + for (List l = pl; l.nonEmpty(); l=l.tail) { + buf.append(deproxyCompound(l.head)); + } + return buf.toList(); + } + + Attribute.Compound deproxyCompound(CompoundAnnotationProxy a) { + ListBuffer> buf = + new ListBuffer>(); + for (List> l = a.values; + l.nonEmpty(); + l = l.tail) { + MethodSymbol meth = findAccessMethod(a.type, l.head.fst); + buf.append(new Pair + (meth, deproxy(meth.type.getReturnType(), l.head.snd))); + } + return new Attribute.Compound(a.type, buf.toList()); + } + + MethodSymbol findAccessMethod(Type container, Name name) { + CompletionFailure failure = null; + try { + for (Scope.Entry e = container.tsym.members().lookup(name); + e.scope != null; + e = e.next()) { + Symbol sym = e.sym; + if (sym.kind == MTH && sym.type.getParameterTypes().length() == 0) + return (MethodSymbol) sym; + } + } catch (CompletionFailure ex) { + failure = ex; + } + // The method wasn't found: emit a warning and recover + JavaFileObject prevSource = log.useSource(requestingOwner.classfile); + try { + if (failure == null) { + log.warning("annotation.method.not.found", + container, + name); + } else { + log.warning("annotation.method.not.found.reason", + container, + name, + failure.getDetailValue());//diagnostic, if present + } + } finally { + log.useSource(prevSource); + } + // Construct a new method type and symbol. Use bottom + // type (typeof null) as return type because this type is + // a subtype of all reference types and can be converted + // to primitive types by unboxing. + MethodType mt = new MethodType(List.nil(), + syms.botType, + List.nil(), + syms.methodClass); + return new MethodSymbol(PUBLIC | ABSTRACT, name, mt, container.tsym); + } + + Attribute result; + Type type; + Attribute deproxy(Type t, Attribute a) { + Type oldType = type; + try { + type = t; + a.accept(this); + return result; + } finally { + type = oldType; + } + } + + // implement Attribute.Visitor below + + public void visitConstant(Attribute.Constant value) { + // assert value.type == type; + result = value; + } + + public void visitClass(Attribute.Class clazz) { + result = clazz; + } + + public void visitEnum(Attribute.Enum e) { + throw new AssertionError(); // shouldn't happen + } + + public void visitCompound(Attribute.Compound compound) { + throw new AssertionError(); // shouldn't happen + } + + public void visitArray(Attribute.Array array) { + throw new AssertionError(); // shouldn't happen + } + + public void visitError(Attribute.Error e) { + throw new AssertionError(); // shouldn't happen + } + + public void visitEnumAttributeProxy(EnumAttributeProxy proxy) { + // type.tsym.flatName() should == proxy.enumFlatName + TypeSymbol enumTypeSym = proxy.enumType.tsym; + VarSymbol enumerator = null; + CompletionFailure failure = null; + try { + for (Scope.Entry e = enumTypeSym.members().lookup(proxy.enumerator); + e.scope != null; + e = e.next()) { + if (e.sym.kind == VAR) { + enumerator = (VarSymbol)e.sym; + break; + } + } + } + catch (CompletionFailure ex) { + failure = ex; + } + if (enumerator == null) { + if (failure != null) { + log.warning("unknown.enum.constant.reason", + currentClassFile, enumTypeSym, proxy.enumerator, + failure.getDiagnostic()); + } else { + log.warning("unknown.enum.constant", + currentClassFile, enumTypeSym, proxy.enumerator); + } + result = new Attribute.Enum(enumTypeSym.type, + new VarSymbol(0, proxy.enumerator, syms.botType, enumTypeSym)); + } else { + result = new Attribute.Enum(enumTypeSym.type, enumerator); + } + } + + public void visitArrayAttributeProxy(ArrayAttributeProxy proxy) { + int length = proxy.values.length(); + Attribute[] ats = new Attribute[length]; + Type elemtype = types.elemtype(type); + int i = 0; + for (List p = proxy.values; p.nonEmpty(); p = p.tail) { + ats[i++] = deproxy(elemtype, p.head); + } + result = new Attribute.Array(type, ats); + } + + public void visitCompoundAnnotationProxy(CompoundAnnotationProxy proxy) { + result = deproxyCompound(proxy); + } + } + + class AnnotationDefaultCompleter extends AnnotationDeproxy implements Annotate.Annotator { + final MethodSymbol sym; + final Attribute value; + final JavaFileObject classFile = currentClassFile; + @Override + public String toString() { + return " ClassReader store default for " + sym.owner + "." + sym + " is " + value; + } + AnnotationDefaultCompleter(MethodSymbol sym, Attribute value) { + this.sym = sym; + this.value = value; + } + // implement Annotate.Annotator.enterAnnotation() + public void enterAnnotation() { + JavaFileObject previousClassFile = currentClassFile; + try { + currentClassFile = classFile; + sym.defaultValue = deproxy(sym.type.getReturnType(), value); + } finally { + currentClassFile = previousClassFile; + } + } + } + + class AnnotationCompleter extends AnnotationDeproxy implements Annotate.Annotator { + final Symbol sym; + final List l; + final JavaFileObject classFile; + @Override + public String toString() { + return " ClassReader annotate " + sym.owner + "." + sym + " with " + l; + } + AnnotationCompleter(Symbol sym, List l) { + this.sym = sym; + this.l = l; + this.classFile = currentClassFile; + } + // implement Annotate.Annotator.enterAnnotation() + public void enterAnnotation() { + JavaFileObject previousClassFile = currentClassFile; + try { + currentClassFile = classFile; + List newList = deproxyCompoundList(l); + sym.attributes_field = ((sym.attributes_field == null) + ? newList + : newList.prependList(sym.attributes_field)); + } finally { + currentClassFile = previousClassFile; + } + } + } + + +/************************************************************************ + * Reading Symbols + ***********************************************************************/ + + /** Read a field. + */ + VarSymbol readField() { + long flags = adjustFieldFlags(nextChar()); + Name name = readName(nextChar()); + Type type = readType(nextChar()); + VarSymbol v = new VarSymbol(flags, name, type, currentOwner); + readMemberAttrs(v); + return v; + } + + /** Read a method. + */ + MethodSymbol readMethod() { + long flags = adjustMethodFlags(nextChar()); + Name name = readName(nextChar()); + Type type = readType(nextChar()); + if (name == names.init && currentOwner.hasOuterInstance()) { + // Sometimes anonymous classes don't have an outer + // instance, however, there is no reliable way to tell so + // we never strip this$n + if (!currentOwner.name.isEmpty()) + type = new MethodType(adjustMethodParams(flags, type.getParameterTypes()), + type.getReturnType(), + type.getThrownTypes(), + syms.methodClass); + } + MethodSymbol m = new MethodSymbol(flags, name, type, currentOwner); + if (saveParameterNames) + initParameterNames(m); + Symbol prevOwner = currentOwner; + currentOwner = m; + try { + readMemberAttrs(m); + } finally { + currentOwner = prevOwner; + } + if (saveParameterNames) + setParameterNames(m, type); + return m; + } + + private List adjustMethodParams(long flags, List args) { + boolean isVarargs = (flags & VARARGS) != 0; + if (isVarargs) { + Type varargsElem = args.last(); + ListBuffer adjustedArgs = ListBuffer.lb(); + for (Type t : args) { + adjustedArgs.append(t != varargsElem ? + t : + ((ArrayType)t).makeVarargs()); + } + args = adjustedArgs.toList(); + } + return args.tail; + } + + /** + * Init the parameter names array. + * Parameter names are currently inferred from the names in the + * LocalVariableTable attributes of a Code attribute. + * (Note: this means parameter names are currently not available for + * methods without a Code attribute.) + * This method initializes an array in which to store the name indexes + * of parameter names found in LocalVariableTable attributes. It is + * slightly supersized to allow for additional slots with a start_pc of 0. + */ + void initParameterNames(MethodSymbol sym) { + // make allowance for synthetic parameters. + final int excessSlots = 4; + int expectedParameterSlots = + Code.width(sym.type.getParameterTypes()) + excessSlots; + if (parameterNameIndices == null + || parameterNameIndices.length < expectedParameterSlots) { + parameterNameIndices = new int[expectedParameterSlots]; + } else + Arrays.fill(parameterNameIndices, 0); + haveParameterNameIndices = false; + } + + /** + * Set the parameter names for a symbol from the name index in the + * parameterNameIndicies array. The type of the symbol may have changed + * while reading the method attributes (see the Signature attribute). + * This may be because of generic information or because anonymous + * synthetic parameters were added. The original type (as read from + * the method descriptor) is used to help guess the existence of + * anonymous synthetic parameters. + * On completion, sym.savedParameter names will either be null (if + * no parameter names were found in the class file) or will be set to a + * list of names, one per entry in sym.type.getParameterTypes, with + * any missing names represented by the empty name. + */ + void setParameterNames(MethodSymbol sym, Type jvmType) { + // if no names were found in the class file, there's nothing more to do + if (!haveParameterNameIndices) + return; + + int firstParam = ((sym.flags() & STATIC) == 0) ? 1 : 0; + // the code in readMethod may have skipped the first parameter when + // setting up the MethodType. If so, we make a corresponding allowance + // here for the position of the first parameter. Note that this + // assumes the skipped parameter has a width of 1 -- i.e. it is not + // a double width type (long or double.) + if (sym.name == names.init && currentOwner.hasOuterInstance()) { + // Sometimes anonymous classes don't have an outer + // instance, however, there is no reliable way to tell so + // we never strip this$n + if (!currentOwner.name.isEmpty()) + firstParam += 1; + } + + if (sym.type != jvmType) { + // reading the method attributes has caused the symbol's type to + // be changed. (i.e. the Signature attribute.) This may happen if + // there are hidden (synthetic) parameters in the descriptor, but + // not in the Signature. The position of these hidden parameters + // is unspecified; for now, assume they are at the beginning, and + // so skip over them. The primary case for this is two hidden + // parameters passed into Enum constructors. + int skip = Code.width(jvmType.getParameterTypes()) + - Code.width(sym.type.getParameterTypes()); + firstParam += skip; + } + List paramNames = List.nil(); + int index = firstParam; + for (Type t: sym.type.getParameterTypes()) { + int nameIdx = (index < parameterNameIndices.length + ? parameterNameIndices[index] : 0); + Name name = nameIdx == 0 ? names.empty : readName(nameIdx); + paramNames = paramNames.prepend(name); + index += Code.width(t); + } + sym.savedParameterNames = paramNames.reverse(); + } + + /** + * skip n bytes + */ + void skipBytes(int n) { + bp = bp + n; + } + + /** Skip a field or method + */ + void skipMember() { + bp = bp + 6; + char ac = nextChar(); + for (int i = 0; i < ac; i++) { + bp = bp + 2; + int attrLen = nextInt(); + bp = bp + attrLen; + } + } + + /** Enter type variables of this classtype and all enclosing ones in + * `typevars'. + */ + protected void enterTypevars(Type t) { + if (t.getEnclosingType() != null && t.getEnclosingType().tag == CLASS) + enterTypevars(t.getEnclosingType()); + for (List xs = t.getTypeArguments(); xs.nonEmpty(); xs = xs.tail) + typevars.enter(xs.head.tsym); + } + + protected void enterTypevars(Symbol sym) { + if (sym.owner.kind == MTH) { + enterTypevars(sym.owner); + enterTypevars(sym.owner.owner); + } + enterTypevars(sym.type); + } + + /** Read contents of a given class symbol `c'. Both external and internal + * versions of an inner class are read. + */ + void readClass(ClassSymbol c) { + ClassType ct = (ClassType)c.type; + + // allocate scope for members + c.members_field = new Scope(c); + + // prepare type variable table + typevars = typevars.dup(currentOwner); + if (ct.getEnclosingType().tag == CLASS) + enterTypevars(ct.getEnclosingType()); + + // read flags, or skip if this is an inner class + long flags = adjustClassFlags(nextChar()); + if (c.owner.kind == PCK) c.flags_field = flags; + + // read own class name and check that it matches + ClassSymbol self = readClassSymbol(nextChar()); + if (c != self) + throw badClassFile("class.file.wrong.class", + self.flatname); + + // class attributes must be read before class + // skip ahead to read class attributes + int startbp = bp; + nextChar(); + char interfaceCount = nextChar(); + bp += interfaceCount * 2; + char fieldCount = nextChar(); + for (int i = 0; i < fieldCount; i++) skipMember(); + char methodCount = nextChar(); + for (int i = 0; i < methodCount; i++) skipMember(); + readClassAttrs(c); + + if (readAllOfClassFile) { + for (int i = 1; i < poolObj.length; i++) readPool(i); + c.pool = new Pool(poolObj.length, poolObj); + } + + // reset and read rest of classinfo + bp = startbp; + int n = nextChar(); + if (ct.supertype_field == null) + ct.supertype_field = (n == 0) + ? Type.noType + : readClassSymbol(n).erasure(types); + n = nextChar(); + List is = List.nil(); + for (int i = 0; i < n; i++) { + Type _inter = readClassSymbol(nextChar()).erasure(types); + is = is.prepend(_inter); + } + if (ct.interfaces_field == null) + ct.interfaces_field = is.reverse(); + + Assert.check(fieldCount == nextChar()); + for (int i = 0; i < fieldCount; i++) enterMember(c, readField()); + Assert.check(methodCount == nextChar()); + for (int i = 0; i < methodCount; i++) enterMember(c, readMethod()); + + typevars = typevars.leave(); + } + + /** Read inner class info. For each inner/outer pair allocate a + * member class. + */ + void readInnerClasses(ClassSymbol c) { + int n = nextChar(); + for (int i = 0; i < n; i++) { + nextChar(); // skip inner class symbol + ClassSymbol outer = readClassSymbol(nextChar()); + Name name = readName(nextChar()); + if (name == null) name = names.empty; + long flags = adjustClassFlags(nextChar()); + if (outer != null) { // we have a member class + if (name == names.empty) + name = names.one; + ClassSymbol member = enterClass(name, outer); + if ((flags & STATIC) == 0) { + ((ClassType)member.type).setEnclosingType(outer.type); + if (member.erasure_field != null) + ((ClassType)member.erasure_field).setEnclosingType(types.erasure(outer.type)); + } + if (c == outer) { + member.flags_field = flags; + enterMember(c, member); + } + } + } + } + + /** Read a class file. + */ + private void readClassFile(ClassSymbol c) throws IOException { + int magic = nextInt(); + if (magic != JAVA_MAGIC) + throw badClassFile("illegal.start.of.class.file"); + + minorVersion = nextChar(); + majorVersion = nextChar(); + int maxMajor = Target.MAX().majorVersion; + int maxMinor = Target.MAX().minorVersion; + if (majorVersion > maxMajor || + majorVersion * 1000 + minorVersion < + Target.MIN().majorVersion * 1000 + Target.MIN().minorVersion) + { + if (majorVersion == (maxMajor + 1)) + log.warning("big.major.version", + currentClassFile, + majorVersion, + maxMajor); + else + throw badClassFile("wrong.version", + Integer.toString(majorVersion), + Integer.toString(minorVersion), + Integer.toString(maxMajor), + Integer.toString(maxMinor)); + } + else if (checkClassFile && + majorVersion == maxMajor && + minorVersion > maxMinor) + { + printCCF("found.later.version", + Integer.toString(minorVersion)); + } + indexPool(); + if (signatureBuffer.length < bp) { + int ns = Integer.highestOneBit(bp) << 1; + signatureBuffer = new byte[ns]; + } + readClass(c); + } + +/************************************************************************ + * Adjusting flags + ***********************************************************************/ + + long adjustFieldFlags(long flags) { + return flags; + } + long adjustMethodFlags(long flags) { + if ((flags & ACC_BRIDGE) != 0) { + flags &= ~ACC_BRIDGE; + flags |= BRIDGE; + if (!allowGenerics) + flags &= ~SYNTHETIC; + } + if ((flags & ACC_VARARGS) != 0) { + flags &= ~ACC_VARARGS; + flags |= VARARGS; + } + return flags; + } + long adjustClassFlags(long flags) { + return flags & ~ACC_SUPER; // SUPER and SYNCHRONIZED bits overloaded + } + +/************************************************************************ + * Loading Classes + ***********************************************************************/ + + /** Define a new class given its name and owner. + */ + public ClassSymbol defineClass(Name name, Symbol owner) { + ClassSymbol c = new ClassSymbol(0, name, owner); + if (owner.kind == PCK) + Assert.checkNull(classes.get(c.flatname), c); + c.completer = this; + return c; + } + + /** Create a new toplevel or member class symbol with given name + * and owner and enter in `classes' unless already there. + */ + public ClassSymbol enterClass(Name name, TypeSymbol owner) { + Name flatname = TypeSymbol.formFlatName(name, owner); + ClassSymbol c = classes.get(flatname); + if (c == null) { + c = defineClass(name, owner); + classes.put(flatname, c); + } else if ((c.name != name || c.owner != owner) && owner.kind == TYP && c.owner.kind == PCK) { + // reassign fields of classes that might have been loaded with + // their flat names. + c.owner.members().remove(c); + c.name = name; + c.owner = owner; + c.fullname = ClassSymbol.formFullName(name, owner); + } + return c; + } + + /** + * Creates a new toplevel class symbol with given flat name and + * given class (or source) file. + * + * @param flatName a fully qualified binary class name + * @param classFile the class file or compilation unit defining + * the class (may be {@code null}) + * @return a newly created class symbol + * @throws AssertionError if the class symbol already exists + */ + public ClassSymbol enterClass(Name flatName, JavaFileObject classFile) { + ClassSymbol cs = classes.get(flatName); + if (cs != null) { + String msg = Log.format("%s: completer = %s; class file = %s; source file = %s", + cs.fullname, + cs.completer, + cs.classfile, + cs.sourcefile); + throw new AssertionError(msg); + } + Name packageName = Convert.packagePart(flatName); + PackageSymbol owner = packageName.isEmpty() + ? syms.unnamedPackage + : enterPackage(packageName); + cs = defineClass(Convert.shortName(flatName), owner); + cs.classfile = classFile; + classes.put(flatName, cs); + return cs; + } + + /** Create a new member or toplevel class symbol with given flat name + * and enter in `classes' unless already there. + */ + public ClassSymbol enterClass(Name flatname) { + ClassSymbol c = classes.get(flatname); + if (c == null) + return enterClass(flatname, (JavaFileObject)null); + else + return c; + } + + private boolean suppressFlush = false; + + /** Completion for classes to be loaded. Before a class is loaded + * we make sure its enclosing class (if any) is loaded. + */ + public void complete(Symbol sym) throws CompletionFailure { + if (sym.kind == TYP) { + ClassSymbol c = (ClassSymbol)sym; + c.members_field = new Scope.ErrorScope(c); // make sure it's always defined + boolean saveSuppressFlush = suppressFlush; + suppressFlush = true; + try { + completeOwners(c.owner); + completeEnclosing(c); + } finally { + suppressFlush = saveSuppressFlush; + } + fillIn(c); + } else if (sym.kind == PCK) { + PackageSymbol p = (PackageSymbol)sym; + try { + fillIn(p); + } catch (IOException ex) { + throw new CompletionFailure(sym, ex.getLocalizedMessage()).initCause(ex); + } + } + if (!filling && !suppressFlush) + annotate.flush(); // finish attaching annotations + } + + /** complete up through the enclosing package. */ + private void completeOwners(Symbol o) { + if (o.kind != PCK) completeOwners(o.owner); + o.complete(); + } + + /** + * Tries to complete lexically enclosing classes if c looks like a + * nested class. This is similar to completeOwners but handles + * the situation when a nested class is accessed directly as it is + * possible with the Tree API or javax.lang.model.*. + */ + private void completeEnclosing(ClassSymbol c) { + if (c.owner.kind == PCK) { + Symbol owner = c.owner; + for (Name name : Convert.enclosingCandidates(Convert.shortName(c.name))) { + Symbol encl = owner.members().lookup(name).sym; + if (encl == null) + encl = classes.get(TypeSymbol.formFlatName(name, owner)); + if (encl != null) + encl.complete(); + } + } + } + + /** We can only read a single class file at a time; this + * flag keeps track of when we are currently reading a class + * file. + */ + private boolean filling = false; + + /** Fill in definition of class `c' from corresponding class or + * source file. + */ + private void fillIn(ClassSymbol c) { + if (completionFailureName == c.fullname) { + throw new CompletionFailure(c, "user-selected completion failure by class name"); + } + currentOwner = c; + warnedAttrs.clear(); + JavaFileObject classfile = c.classfile; + if (classfile != null) { + JavaFileObject previousClassFile = currentClassFile; + try { + if (filling) { + Assert.error("Filling " + classfile.toUri() + " during " + previousClassFile); + } + currentClassFile = classfile; + if (verbose) { + log.printVerbose("loading", currentClassFile.toString()); + } + if (classfile.getKind() == JavaFileObject.Kind.CLASS) { + filling = true; + try { + bp = 0; + buf = readInputStream(buf, classfile.openInputStream()); + readClassFile(c); + if (!missingTypeVariables.isEmpty() && !foundTypeVariables.isEmpty()) { + List missing = missingTypeVariables; + List found = foundTypeVariables; + missingTypeVariables = List.nil(); + foundTypeVariables = List.nil(); + filling = false; + ClassType ct = (ClassType)currentOwner.type; + ct.supertype_field = + types.subst(ct.supertype_field, missing, found); + ct.interfaces_field = + types.subst(ct.interfaces_field, missing, found); + } else if (missingTypeVariables.isEmpty() != + foundTypeVariables.isEmpty()) { + Name name = missingTypeVariables.head.tsym.name; + throw badClassFile("undecl.type.var", name); + } + } finally { + missingTypeVariables = List.nil(); + foundTypeVariables = List.nil(); + filling = false; + } + } else { + if (sourceCompleter != null) { + sourceCompleter.complete(c); + } else { + throw new IllegalStateException("Source completer required to read " + + classfile.toUri()); + } + } + return; + } catch (IOException ex) { + throw badClassFile("unable.to.access.file", ex.getMessage()); + } finally { + currentClassFile = previousClassFile; + } + } else { + JCDiagnostic diag = + diagFactory.fragment("class.file.not.found", c.flatname); + throw + newCompletionFailure(c, diag); + } + } + // where + private static byte[] readInputStream(byte[] buf, InputStream s) throws IOException { + try { + buf = ensureCapacity(buf, s.available()); + int r = s.read(buf); + int bp = 0; + while (r != -1) { + bp += r; + buf = ensureCapacity(buf, bp); + r = s.read(buf, bp, buf.length - bp); + } + return buf; + } finally { + try { + s.close(); + } catch (IOException e) { + /* Ignore any errors, as this stream may have already + * thrown a related exception which is the one that + * should be reported. + */ + } + } + } + /* + * ensureCapacity will increase the buffer as needed, taking note that + * the new buffer will always be greater than the needed and never + * exactly equal to the needed size or bp. If equal then the read (above) + * will infinitely loop as buf.length - bp == 0. + */ + private static byte[] ensureCapacity(byte[] buf, int needed) { + if (buf.length <= needed) { + byte[] old = buf; + buf = new byte[Integer.highestOneBit(needed) << 1]; + System.arraycopy(old, 0, buf, 0, old.length); + } + return buf; + } + /** Static factory for CompletionFailure objects. + * In practice, only one can be used at a time, so we share one + * to reduce the expense of allocating new exception objects. + */ + private CompletionFailure newCompletionFailure(TypeSymbol c, + JCDiagnostic diag) { + if (!cacheCompletionFailure) { + // log.warning("proc.messager", + // Log.getLocalizedString("class.file.not.found", c.flatname)); + // c.debug.printStackTrace(); + return new CompletionFailure(c, diag); + } else { + CompletionFailure result = cachedCompletionFailure; + result.sym = c; + result.diag = diag; + return result; + } + } + private CompletionFailure cachedCompletionFailure = + new CompletionFailure(null, (JCDiagnostic) null); + { + cachedCompletionFailure.setStackTrace(new StackTraceElement[0]); + } + + /** Load a toplevel class with given fully qualified name + * The class is entered into `classes' only if load was successful. + */ + public ClassSymbol loadClass(Name flatname) throws CompletionFailure { + boolean absent = classes.get(flatname) == null; + ClassSymbol c = enterClass(flatname); + if (c.members_field == null && c.completer != null) { + try { + c.complete(); + } catch (CompletionFailure ex) { + if (absent) classes.remove(flatname); + throw ex; + } + } + return c; + } + +/************************************************************************ + * Loading Packages + ***********************************************************************/ + + /** Check to see if a package exists, given its fully qualified name. + */ + public boolean packageExists(Name fullname) { + return enterPackage(fullname).exists(); + } + + /** Make a package, given its fully qualified name. + */ + public PackageSymbol enterPackage(Name fullname) { + PackageSymbol p = packages.get(fullname); + if (p == null) { + Assert.check(!fullname.isEmpty(), "rootPackage missing!"); + p = new PackageSymbol( + Convert.shortName(fullname), + enterPackage(Convert.packagePart(fullname))); + p.completer = this; + packages.put(fullname, p); + } + return p; + } + + /** Make a package, given its unqualified name and enclosing package. + */ + public PackageSymbol enterPackage(Name name, PackageSymbol owner) { + return enterPackage(TypeSymbol.formFullName(name, owner)); + } + + /** Include class corresponding to given class file in package, + * unless (1) we already have one the same kind (.class or .java), or + * (2) we have one of the other kind, and the given class file + * is older. + */ + protected void includeClassFile(PackageSymbol p, JavaFileObject file) { + if ((p.flags_field & EXISTS) == 0) + for (Symbol q = p; q != null && q.kind == PCK; q = q.owner) + q.flags_field |= EXISTS; + JavaFileObject.Kind kind = file.getKind(); + int seen; + if (kind == JavaFileObject.Kind.CLASS) + seen = CLASS_SEEN; + else + seen = SOURCE_SEEN; + String binaryName = fileManager.inferBinaryName(currentLoc, file); + int lastDot = binaryName.lastIndexOf("."); + Name classname = names.fromString(binaryName.substring(lastDot + 1)); + boolean isPkgInfo = classname == names.package_info; + ClassSymbol c = isPkgInfo + ? p.package_info + : (ClassSymbol) p.members_field.lookup(classname).sym; + if (c == null) { + c = enterClass(classname, p); + if (c.classfile == null) // only update the file if's it's newly created + c.classfile = file; + if (isPkgInfo) { + p.package_info = c; + } else { + if (c.owner == p) // it might be an inner class + p.members_field.enter(c); + } + } else if (c.classfile != null && (c.flags_field & seen) == 0) { + // if c.classfile == null, we are currently compiling this class + // and no further action is necessary. + // if (c.flags_field & seen) != 0, we have already encountered + // a file of the same kind; again no further action is necessary. + if ((c.flags_field & (CLASS_SEEN | SOURCE_SEEN)) != 0) + c.classfile = preferredFileObject(file, c.classfile); + } + c.flags_field |= seen; + } + + /** Implement policy to choose to derive information from a source + * file or a class file when both are present. May be overridden + * by subclasses. + */ + protected JavaFileObject preferredFileObject(JavaFileObject a, + JavaFileObject b) { + + if (preferSource) + return (a.getKind() == JavaFileObject.Kind.SOURCE) ? a : b; + else { + long adate = a.getLastModified(); + long bdate = b.getLastModified(); + // 6449326: policy for bad lastModifiedTime in ClassReader + //assert adate >= 0 && bdate >= 0; + return (adate > bdate) ? a : b; + } + } + + /** + * specifies types of files to be read when filling in a package symbol + */ + protected EnumSet getPackageFileKinds() { + return EnumSet.of(JavaFileObject.Kind.CLASS, JavaFileObject.Kind.SOURCE); + } + + /** + * this is used to support javadoc + */ + protected void extraFileActions(PackageSymbol pack, JavaFileObject fe) { + } + + protected Location currentLoc; // FIXME + + private boolean verbosePath = true; + + /** Load directory of package into members scope. + */ + private void fillIn(PackageSymbol p) throws IOException { + if (p.members_field == null) p.members_field = new Scope(p); + String packageName = p.fullname.toString(); + + Set kinds = getPackageFileKinds(); + + fillIn(p, PLATFORM_CLASS_PATH, + fileManager.list(PLATFORM_CLASS_PATH, + packageName, + EnumSet.of(JavaFileObject.Kind.CLASS), + false)); + + Set classKinds = EnumSet.copyOf(kinds); + classKinds.remove(JavaFileObject.Kind.SOURCE); + boolean wantClassFiles = !classKinds.isEmpty(); + + Set sourceKinds = EnumSet.copyOf(kinds); + sourceKinds.remove(JavaFileObject.Kind.CLASS); + boolean wantSourceFiles = !sourceKinds.isEmpty(); + + boolean haveSourcePath = fileManager.hasLocation(SOURCE_PATH); + + if (verbose && verbosePath) { + if (fileManager instanceof StandardJavaFileManager) { + StandardJavaFileManager fm = (StandardJavaFileManager)fileManager; + if (haveSourcePath && wantSourceFiles) { + List path = List.nil(); + for (File file : fm.getLocation(SOURCE_PATH)) { + path = path.prepend(file); + } + log.printVerbose("sourcepath", path.reverse().toString()); + } else if (wantSourceFiles) { + List path = List.nil(); + for (File file : fm.getLocation(CLASS_PATH)) { + path = path.prepend(file); + } + log.printVerbose("sourcepath", path.reverse().toString()); + } + if (wantClassFiles) { + List path = List.nil(); + for (File file : fm.getLocation(PLATFORM_CLASS_PATH)) { + path = path.prepend(file); + } + for (File file : fm.getLocation(CLASS_PATH)) { + path = path.prepend(file); + } + log.printVerbose("classpath", path.reverse().toString()); + } + } + } + + if (wantSourceFiles && !haveSourcePath) { + fillIn(p, CLASS_PATH, + fileManager.list(CLASS_PATH, + packageName, + kinds, + false)); + } else { + if (wantClassFiles) + fillIn(p, CLASS_PATH, + fileManager.list(CLASS_PATH, + packageName, + classKinds, + false)); + if (wantSourceFiles) + fillIn(p, SOURCE_PATH, + fileManager.list(SOURCE_PATH, + packageName, + sourceKinds, + false)); + } + verbosePath = false; + } + // where + private void fillIn(PackageSymbol p, + Location location, + Iterable files) + { + currentLoc = location; + for (JavaFileObject fo : files) { + switch (fo.getKind()) { + case CLASS: + case SOURCE: { + // TODO pass binaryName to includeClassFile + String binaryName = fileManager.inferBinaryName(currentLoc, fo); + String simpleName = binaryName.substring(binaryName.lastIndexOf(".") + 1); + if (SourceVersion.isIdentifier(simpleName) || + simpleName.equals("package-info")) + includeClassFile(p, fo); + break; + } + default: + extraFileActions(p, fo); + } + } + } + + /** Output for "-checkclassfile" option. + * @param key The key to look up the correct internationalized string. + * @param arg An argument for substitution into the output string. + */ + private void printCCF(String key, Object arg) { + log.printNoteLines(key, arg); + } + + + public interface SourceCompleter { + void complete(ClassSymbol sym) + throws CompletionFailure; + } + + /** + * A subclass of JavaFileObject for the sourcefile attribute found in a classfile. + * The attribute is only the last component of the original filename, so is unlikely + * to be valid as is, so operations other than those to access the name throw + * UnsupportedOperationException + */ + private static class SourceFileObject extends BaseFileObject { + + /** The file's name. + */ + private Name name; + private Name flatname; + + public SourceFileObject(Name name, Name flatname) { + super(null); // no file manager; never referenced for this file object + this.name = name; + this.flatname = flatname; + } + + @Override + public URI toUri() { + try { + return new URI(null, name.toString(), null); + } catch (URISyntaxException e) { + throw new CannotCreateUriError(name.toString(), e); + } + } + + @Override + public String getName() { + return name.toString(); + } + + @Override + public String getShortName() { + return getName(); + } + + @Override + public JavaFileObject.Kind getKind() { + return getKind(getName()); + } + + @Override + public InputStream openInputStream() { + throw new UnsupportedOperationException(); + } + + @Override + public OutputStream openOutputStream() { + throw new UnsupportedOperationException(); + } + + @Override + public CharBuffer getCharContent(boolean ignoreEncodingErrors) { + throw new UnsupportedOperationException(); + } + + @Override + public Reader openReader(boolean ignoreEncodingErrors) { + throw new UnsupportedOperationException(); + } + + @Override + public Writer openWriter() { + throw new UnsupportedOperationException(); + } + + @Override + public long getLastModified() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean delete() { + throw new UnsupportedOperationException(); + } + + @Override + protected String inferBinaryName(Iterable path) { + return flatname.toString(); + } + + @Override + public boolean isNameCompatible(String simpleName, JavaFileObject.Kind kind) { + return true; // fail-safe mode + } + + /** + * Check if two file objects are equal. + * SourceFileObjects are just placeholder objects for the value of a + * SourceFile attribute, and do not directly represent specific files. + * Two SourceFileObjects are equal if their names are equal. + */ + @Override + public boolean equals(Object other) { + if (this == other) + return true; + + if (!(other instanceof SourceFileObject)) + return false; + + SourceFileObject o = (SourceFileObject) other; + return name.equals(o.name); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/jvm/ClassWriter.java b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/ClassWriter.java new file mode 100644 index 0000000..909ea65 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/ClassWriter.java @@ -0,0 +1,1605 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.jvm; + +import java.io.*; +import java.util.Set; +import java.util.HashSet; + +import javax.tools.JavaFileManager; +import javax.tools.FileObject; +import javax.tools.JavaFileObject; + +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Attribute.RetentionPolicy; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.code.Type.*; +import com.sun.tools.javac.file.BaseFileObject; +import com.sun.tools.javac.util.*; + +import static com.sun.tools.javac.code.BoundKind.*; +import static com.sun.tools.javac.code.Flags.*; +import static com.sun.tools.javac.code.Kinds.*; +import static com.sun.tools.javac.code.TypeTags.*; +import static com.sun.tools.javac.jvm.UninitializedType.*; +import static com.sun.tools.javac.main.OptionName.*; +import static javax.tools.StandardLocation.CLASS_OUTPUT; + + +/** This class provides operations to map an internal symbol table graph + * rooted in a ClassSymbol into a classfile. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class ClassWriter extends ClassFile { + protected static final Context.Key classWriterKey = + new Context.Key(); + + private final Symtab syms; + + private final Options options; + + /** Switch: verbose output. + */ + private boolean verbose; + + /** Switch: scrable private names. + */ + private boolean scramble; + + /** Switch: scrable private names. + */ + private boolean scrambleAll; + + /** Switch: retrofit mode. + */ + private boolean retrofit; + + /** Switch: emit source file attribute. + */ + private boolean emitSourceFile; + + /** Switch: generate CharacterRangeTable attribute. + */ + private boolean genCrt; + + /** Switch: describe the generated stackmap + */ + boolean debugstackmap; + + /** + * Target class version. + */ + private Target target; + + /** + * Source language version. + */ + private Source source; + + /** Type utilities. */ + private Types types; + + /** The initial sizes of the data and constant pool buffers. + * sizes are increased when buffers get full. + */ + static final int DATA_BUF_SIZE = 0x0fff0; + static final int POOL_BUF_SIZE = 0x1fff0; + + /** An output buffer for member info. + */ + ByteBuffer databuf = new ByteBuffer(DATA_BUF_SIZE); + + /** An output buffer for the constant pool. + */ + ByteBuffer poolbuf = new ByteBuffer(POOL_BUF_SIZE); + + /** An output buffer for type signatures. + */ + ByteBuffer sigbuf = new ByteBuffer(); + + /** The constant pool. + */ + Pool pool; + + /** The inner classes to be written, as a set. + */ + Set innerClasses; + + /** The inner classes to be written, as a queue where + * enclosing classes come first. + */ + ListBuffer innerClassesQueue; + + /** The log to use for verbose output. + */ + private final Log log; + + /** The name table. */ + private final Names names; + + /** Access to files. */ + private final JavaFileManager fileManager; + + /** The tags and constants used in compressed stackmap. */ + static final int SAME_FRAME_SIZE = 64; + static final int SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247; + static final int SAME_FRAME_EXTENDED = 251; + static final int FULL_FRAME = 255; + static final int MAX_LOCAL_LENGTH_DIFF = 4; + + /** Get the ClassWriter instance for this context. */ + public static ClassWriter instance(Context context) { + ClassWriter instance = context.get(classWriterKey); + if (instance == null) + instance = new ClassWriter(context); + return instance; + } + + /** Construct a class writer, given an options table. + */ + private ClassWriter(Context context) { + context.put(classWriterKey, this); + + log = Log.instance(context); + names = Names.instance(context); + syms = Symtab.instance(context); + options = Options.instance(context); + target = Target.instance(context); + source = Source.instance(context); + types = Types.instance(context); + fileManager = context.get(JavaFileManager.class); + + verbose = options.isSet(VERBOSE); + scramble = options.isSet("-scramble"); + scrambleAll = options.isSet("-scrambleAll"); + retrofit = options.isSet("-retrofit"); + genCrt = options.isSet(XJCOV); + debugstackmap = options.isSet("debugstackmap"); + + emitSourceFile = options.isUnset(G_CUSTOM) || + options.isSet(G_CUSTOM, "source"); + + String dumpModFlags = options.get("dumpmodifiers"); + dumpClassModifiers = + (dumpModFlags != null && dumpModFlags.indexOf('c') != -1); + dumpFieldModifiers = + (dumpModFlags != null && dumpModFlags.indexOf('f') != -1); + dumpInnerClassModifiers = + (dumpModFlags != null && dumpModFlags.indexOf('i') != -1); + dumpMethodModifiers = + (dumpModFlags != null && dumpModFlags.indexOf('m') != -1); + } + +/****************************************************************** + * Diagnostics: dump generated class names and modifiers + ******************************************************************/ + + /** Value of option 'dumpmodifiers' is a string + * indicating which modifiers should be dumped for debugging: + * 'c' -- classes + * 'f' -- fields + * 'i' -- innerclass attributes + * 'm' -- methods + * For example, to dump everything: + * javac -XDdumpmodifiers=cifm MyProg.java + */ + private final boolean dumpClassModifiers; // -XDdumpmodifiers=c + private final boolean dumpFieldModifiers; // -XDdumpmodifiers=f + private final boolean dumpInnerClassModifiers; // -XDdumpmodifiers=i + private final boolean dumpMethodModifiers; // -XDdumpmodifiers=m + + + /** Return flags as a string, separated by " ". + */ + public static String flagNames(long flags) { + StringBuilder sbuf = new StringBuilder(); + int i = 0; + long f = flags & StandardFlags; + while (f != 0) { + if ((f & 1) != 0) { + sbuf.append(" "); + sbuf.append(flagName[i]); + } + f = f >> 1; + i++; + } + return sbuf.toString(); + } + //where + private final static String[] flagName = { + "PUBLIC", "PRIVATE", "PROTECTED", "STATIC", "FINAL", + "SUPER", "VOLATILE", "TRANSIENT", "NATIVE", "INTERFACE", + "ABSTRACT", "STRICTFP"}; + +/****************************************************************** + * Output routines + ******************************************************************/ + + /** Write a character into given byte buffer; + * byte buffer will not be grown. + */ + void putChar(ByteBuffer buf, int op, int x) { + buf.elems[op ] = (byte)((x >> 8) & 0xFF); + buf.elems[op+1] = (byte)((x ) & 0xFF); + } + + /** Write an integer into given byte buffer; + * byte buffer will not be grown. + */ + void putInt(ByteBuffer buf, int adr, int x) { + buf.elems[adr ] = (byte)((x >> 24) & 0xFF); + buf.elems[adr+1] = (byte)((x >> 16) & 0xFF); + buf.elems[adr+2] = (byte)((x >> 8) & 0xFF); + buf.elems[adr+3] = (byte)((x ) & 0xFF); + } + +/****************************************************************** + * Signature Generation + ******************************************************************/ + + /** Assemble signature of given type in string buffer. + */ + void assembleSig(Type type) { + switch (type.tag) { + case BYTE: + sigbuf.appendByte('B'); + break; + case SHORT: + sigbuf.appendByte('S'); + break; + case CHAR: + sigbuf.appendByte('C'); + break; + case INT: + sigbuf.appendByte('I'); + break; + case LONG: + sigbuf.appendByte('J'); + break; + case FLOAT: + sigbuf.appendByte('F'); + break; + case DOUBLE: + sigbuf.appendByte('D'); + break; + case BOOLEAN: + sigbuf.appendByte('Z'); + break; + case VOID: + sigbuf.appendByte('V'); + break; + case CLASS: + sigbuf.appendByte('L'); + assembleClassSig(type); + sigbuf.appendByte(';'); + break; + case ARRAY: + ArrayType at = (ArrayType)type; + sigbuf.appendByte('['); + assembleSig(at.elemtype); + break; + case METHOD: + MethodType mt = (MethodType)type; + sigbuf.appendByte('('); + assembleSig(mt.argtypes); + sigbuf.appendByte(')'); + assembleSig(mt.restype); + if (hasTypeVar(mt.thrown)) { + for (List l = mt.thrown; l.nonEmpty(); l = l.tail) { + sigbuf.appendByte('^'); + assembleSig(l.head); + } + } + break; + case WILDCARD: { + WildcardType ta = (WildcardType) type; + switch (ta.kind) { + case SUPER: + sigbuf.appendByte('-'); + assembleSig(ta.type); + break; + case EXTENDS: + sigbuf.appendByte('+'); + assembleSig(ta.type); + break; + case UNBOUND: + sigbuf.appendByte('*'); + break; + default: + throw new AssertionError(ta.kind); + } + break; + } + case TYPEVAR: + sigbuf.appendByte('T'); + sigbuf.appendName(type.tsym.name); + sigbuf.appendByte(';'); + break; + case FORALL: + ForAll ft = (ForAll)type; + assembleParamsSig(ft.tvars); + assembleSig(ft.qtype); + break; + case UNINITIALIZED_THIS: + case UNINITIALIZED_OBJECT: + // we don't yet have a spec for uninitialized types in the + // local variable table + assembleSig(types.erasure(((UninitializedType)type).qtype)); + break; + default: + throw new AssertionError("typeSig " + type.tag); + } + } + + boolean hasTypeVar(List l) { + while (l.nonEmpty()) { + if (l.head.tag == TypeTags.TYPEVAR) return true; + l = l.tail; + } + return false; + } + + void assembleClassSig(Type type) { + ClassType ct = (ClassType)type; + ClassSymbol c = (ClassSymbol)ct.tsym; + enterInner(c); + Type outer = ct.getEnclosingType(); + if (outer.allparams().nonEmpty()) { + boolean rawOuter = + c.owner.kind == MTH || // either a local class + c.name == names.empty; // or anonymous + assembleClassSig(rawOuter + ? types.erasure(outer) + : outer); + sigbuf.appendByte('.'); + Assert.check(c.flatname.startsWith(c.owner.enclClass().flatname)); + sigbuf.appendName(rawOuter + ? c.flatname.subName(c.owner.enclClass().flatname.getByteLength()+1,c.flatname.getByteLength()) + : c.name); + } else { + sigbuf.appendBytes(externalize(c.flatname)); + } + if (ct.getTypeArguments().nonEmpty()) { + sigbuf.appendByte('<'); + assembleSig(ct.getTypeArguments()); + sigbuf.appendByte('>'); + } + } + + + void assembleSig(List types) { + for (List ts = types; ts.nonEmpty(); ts = ts.tail) + assembleSig(ts.head); + } + + void assembleParamsSig(List typarams) { + sigbuf.appendByte('<'); + for (List ts = typarams; ts.nonEmpty(); ts = ts.tail) { + TypeVar tvar = (TypeVar)ts.head; + sigbuf.appendName(tvar.tsym.name); + List bounds = types.getBounds(tvar); + if ((bounds.head.tsym.flags() & INTERFACE) != 0) { + sigbuf.appendByte(':'); + } + for (List l = bounds; l.nonEmpty(); l = l.tail) { + sigbuf.appendByte(':'); + assembleSig(l.head); + } + } + sigbuf.appendByte('>'); + } + + /** Return signature of given type + */ + Name typeSig(Type type) { + Assert.check(sigbuf.length == 0); + //- System.out.println(" ? " + type); + assembleSig(type); + Name n = sigbuf.toName(names); + sigbuf.reset(); + //- System.out.println(" " + n); + return n; + } + + /** Given a type t, return the extended class name of its erasure in + * external representation. + */ + public Name xClassName(Type t) { + if (t.tag == CLASS) { + return names.fromUtf(externalize(t.tsym.flatName())); + } else if (t.tag == ARRAY) { + return typeSig(types.erasure(t)); + } else { + throw new AssertionError("xClassName"); + } + } + +/****************************************************************** + * Writing the Constant Pool + ******************************************************************/ + + /** Thrown when the constant pool is over full. + */ + public static class PoolOverflow extends Exception { + private static final long serialVersionUID = 0; + public PoolOverflow() {} + } + public static class StringOverflow extends Exception { + private static final long serialVersionUID = 0; + public final String value; + public StringOverflow(String s) { + value = s; + } + } + + /** Write constant pool to pool buffer. + * Note: during writing, constant pool + * might grow since some parts of constants still need to be entered. + */ + void writePool(Pool pool) throws PoolOverflow, StringOverflow { + int poolCountIdx = poolbuf.length; + poolbuf.appendChar(0); + int i = 1; + while (i < pool.pp) { + Object value = pool.pool[i]; + Assert.checkNonNull(value); + if (value instanceof Pool.Method) + value = ((Pool.Method)value).m; + else if (value instanceof Pool.Variable) + value = ((Pool.Variable)value).v; + + if (value instanceof MethodSymbol) { + MethodSymbol m = (MethodSymbol)value; + poolbuf.appendByte((m.owner.flags() & INTERFACE) != 0 + ? CONSTANT_InterfaceMethodref + : CONSTANT_Methodref); + poolbuf.appendChar(pool.put(m.owner)); + poolbuf.appendChar(pool.put(nameType(m))); + } else if (value instanceof VarSymbol) { + VarSymbol v = (VarSymbol)value; + poolbuf.appendByte(CONSTANT_Fieldref); + poolbuf.appendChar(pool.put(v.owner)); + poolbuf.appendChar(pool.put(nameType(v))); + } else if (value instanceof Name) { + poolbuf.appendByte(CONSTANT_Utf8); + byte[] bs = ((Name)value).toUtf(); + poolbuf.appendChar(bs.length); + poolbuf.appendBytes(bs, 0, bs.length); + if (bs.length > Pool.MAX_STRING_LENGTH) + throw new StringOverflow(value.toString()); + } else if (value instanceof ClassSymbol) { + ClassSymbol c = (ClassSymbol)value; + if (c.owner.kind == TYP) pool.put(c.owner); + poolbuf.appendByte(CONSTANT_Class); + if (c.type.tag == ARRAY) { + poolbuf.appendChar(pool.put(typeSig(c.type))); + } else { + poolbuf.appendChar(pool.put(names.fromUtf(externalize(c.flatname)))); + enterInner(c); + } + } else if (value instanceof NameAndType) { + NameAndType nt = (NameAndType)value; + poolbuf.appendByte(CONSTANT_NameandType); + poolbuf.appendChar(pool.put(nt.name)); + poolbuf.appendChar(pool.put(typeSig(nt.type))); + } else if (value instanceof Integer) { + poolbuf.appendByte(CONSTANT_Integer); + poolbuf.appendInt(((Integer)value).intValue()); + } else if (value instanceof Long) { + poolbuf.appendByte(CONSTANT_Long); + poolbuf.appendLong(((Long)value).longValue()); + i++; + } else if (value instanceof Float) { + poolbuf.appendByte(CONSTANT_Float); + poolbuf.appendFloat(((Float)value).floatValue()); + } else if (value instanceof Double) { + poolbuf.appendByte(CONSTANT_Double); + poolbuf.appendDouble(((Double)value).doubleValue()); + i++; + } else if (value instanceof String) { + poolbuf.appendByte(CONSTANT_String); + poolbuf.appendChar(pool.put(names.fromString((String)value))); + } else if (value instanceof Type) { + Type type = (Type)value; + if (type.tag == CLASS) enterInner((ClassSymbol)type.tsym); + poolbuf.appendByte(CONSTANT_Class); + poolbuf.appendChar(pool.put(xClassName(type))); + } else { + Assert.error("writePool " + value); + } + i++; + } + if (pool.pp > Pool.MAX_ENTRIES) + throw new PoolOverflow(); + putChar(poolbuf, poolCountIdx, pool.pp); + } + + /** Given a field, return its name. + */ + Name fieldName(Symbol sym) { + if (scramble && (sym.flags() & PRIVATE) != 0 || + scrambleAll && (sym.flags() & (PROTECTED | PUBLIC)) == 0) + return names.fromString("_$" + sym.name.getIndex()); + else + return sym.name; + } + + /** Given a symbol, return its name-and-type. + */ + NameAndType nameType(Symbol sym) { + return new NameAndType(fieldName(sym), + retrofit + ? sym.erasure(types) + : sym.externalType(types)); + // if we retrofit, then the NameAndType has been read in as is + // and no change is necessary. If we compile normally, the + // NameAndType is generated from a symbol reference, and the + // adjustment of adding an additional this$n parameter needs to be made. + } + +/****************************************************************** + * Writing Attributes + ******************************************************************/ + + /** Write header for an attribute to data buffer and return + * position past attribute length index. + */ + int writeAttr(Name attrName) { + databuf.appendChar(pool.put(attrName)); + databuf.appendInt(0); + return databuf.length; + } + + /** Fill in attribute length. + */ + void endAttr(int index) { + putInt(databuf, index - 4, databuf.length - index); + } + + /** Leave space for attribute count and return index for + * number of attributes field. + */ + int beginAttrs() { + databuf.appendChar(0); + return databuf.length; + } + + /** Fill in number of attributes. + */ + void endAttrs(int index, int count) { + putChar(databuf, index - 2, count); + } + + /** Write the EnclosingMethod attribute if needed. + * Returns the number of attributes written (0 or 1). + */ + int writeEnclosingMethodAttribute(ClassSymbol c) { + if (!target.hasEnclosingMethodAttribute() || + c.owner.kind != MTH && // neither a local class + c.name != names.empty) // nor anonymous + return 0; + + int alenIdx = writeAttr(names.EnclosingMethod); + ClassSymbol enclClass = c.owner.enclClass(); + MethodSymbol enclMethod = + (c.owner.type == null // local to init block + || c.owner.kind != MTH) // or member init + ? null + : (MethodSymbol)c.owner; + databuf.appendChar(pool.put(enclClass)); + databuf.appendChar(enclMethod == null ? 0 : pool.put(nameType(c.owner))); + endAttr(alenIdx); + return 1; + } + + /** Write flag attributes; return number of attributes written. + */ + int writeFlagAttrs(long flags) { + int acount = 0; + if ((flags & DEPRECATED) != 0) { + int alenIdx = writeAttr(names.Deprecated); + endAttr(alenIdx); + acount++; + } + if ((flags & ENUM) != 0 && !target.useEnumFlag()) { + int alenIdx = writeAttr(names.Enum); + endAttr(alenIdx); + acount++; + } + if ((flags & SYNTHETIC) != 0 && !target.useSyntheticFlag()) { + int alenIdx = writeAttr(names.Synthetic); + endAttr(alenIdx); + acount++; + } + if ((flags & BRIDGE) != 0 && !target.useBridgeFlag()) { + int alenIdx = writeAttr(names.Bridge); + endAttr(alenIdx); + acount++; + } + if ((flags & VARARGS) != 0 && !target.useVarargsFlag()) { + int alenIdx = writeAttr(names.Varargs); + endAttr(alenIdx); + acount++; + } + if ((flags & ANNOTATION) != 0 && !target.useAnnotationFlag()) { + int alenIdx = writeAttr(names.Annotation); + endAttr(alenIdx); + acount++; + } + return acount; + } + + /** Write member (field or method) attributes; + * return number of attributes written. + */ + int writeMemberAttrs(Symbol sym) { + int acount = writeFlagAttrs(sym.flags()); + long flags = sym.flags(); + if (source.allowGenerics() && + (flags & (SYNTHETIC|BRIDGE)) != SYNTHETIC && + (flags & ANONCONSTR) == 0 && + (!types.isSameType(sym.type, sym.erasure(types)) || + hasTypeVar(sym.type.getThrownTypes()))) { + // note that a local class with captured variables + // will get a signature attribute + int alenIdx = writeAttr(names.Signature); + databuf.appendChar(pool.put(typeSig(sym.type))); + endAttr(alenIdx); + acount++; + } + acount += writeJavaAnnotations(sym.getAnnotationMirrors()); + return acount; + } + + /** Write method parameter annotations; + * return number of attributes written. + */ + int writeParameterAttrs(MethodSymbol m) { + boolean hasVisible = false; + boolean hasInvisible = false; + if (m.params != null) for (VarSymbol s : m.params) { + for (Attribute.Compound a : s.getAnnotationMirrors()) { + switch (types.getRetention(a)) { + case SOURCE: break; + case CLASS: hasInvisible = true; break; + case RUNTIME: hasVisible = true; break; + default: ;// /* fail soft */ throw new AssertionError(vis); + } + } + } + + int attrCount = 0; + if (hasVisible) { + int attrIndex = writeAttr(names.RuntimeVisibleParameterAnnotations); + databuf.appendByte(m.params.length()); + for (VarSymbol s : m.params) { + ListBuffer buf = new ListBuffer(); + for (Attribute.Compound a : s.getAnnotationMirrors()) + if (types.getRetention(a) == RetentionPolicy.RUNTIME) + buf.append(a); + databuf.appendChar(buf.length()); + for (Attribute.Compound a : buf) + writeCompoundAttribute(a); + } + endAttr(attrIndex); + attrCount++; + } + if (hasInvisible) { + int attrIndex = writeAttr(names.RuntimeInvisibleParameterAnnotations); + databuf.appendByte(m.params.length()); + for (VarSymbol s : m.params) { + ListBuffer buf = new ListBuffer(); + for (Attribute.Compound a : s.getAnnotationMirrors()) + if (types.getRetention(a) == RetentionPolicy.CLASS) + buf.append(a); + databuf.appendChar(buf.length()); + for (Attribute.Compound a : buf) + writeCompoundAttribute(a); + } + endAttr(attrIndex); + attrCount++; + } + return attrCount; + } + +/********************************************************************** + * Writing Java-language annotations (aka metadata, attributes) + **********************************************************************/ + + /** Write Java-language annotations; return number of JVM + * attributes written (zero or one). + */ + int writeJavaAnnotations(List attrs) { + if (attrs.isEmpty()) return 0; + ListBuffer visibles = new ListBuffer(); + ListBuffer invisibles = new ListBuffer(); + for (Attribute.Compound a : attrs) { + switch (types.getRetention(a)) { + case SOURCE: break; + case CLASS: invisibles.append(a); break; + case RUNTIME: visibles.append(a); break; + default: ;// /* fail soft */ throw new AssertionError(vis); + } + } + + int attrCount = 0; + if (visibles.length() != 0) { + int attrIndex = writeAttr(names.RuntimeVisibleAnnotations); + databuf.appendChar(visibles.length()); + for (Attribute.Compound a : visibles) + writeCompoundAttribute(a); + endAttr(attrIndex); + attrCount++; + } + if (invisibles.length() != 0) { + int attrIndex = writeAttr(names.RuntimeInvisibleAnnotations); + databuf.appendChar(invisibles.length()); + for (Attribute.Compound a : invisibles) + writeCompoundAttribute(a); + endAttr(attrIndex); + attrCount++; + } + return attrCount; + } + + /** A visitor to write an attribute including its leading + * single-character marker. + */ + class AttributeWriter implements Attribute.Visitor { + public void visitConstant(Attribute.Constant _value) { + Object value = _value.value; + switch (_value.type.tag) { + case BYTE: + databuf.appendByte('B'); + break; + case CHAR: + databuf.appendByte('C'); + break; + case SHORT: + databuf.appendByte('S'); + break; + case INT: + databuf.appendByte('I'); + break; + case LONG: + databuf.appendByte('J'); + break; + case FLOAT: + databuf.appendByte('F'); + break; + case DOUBLE: + databuf.appendByte('D'); + break; + case BOOLEAN: + databuf.appendByte('Z'); + break; + case CLASS: + Assert.check(value instanceof String); + databuf.appendByte('s'); + value = names.fromString(value.toString()); // CONSTANT_Utf8 + break; + default: + throw new AssertionError(_value.type); + } + databuf.appendChar(pool.put(value)); + } + public void visitEnum(Attribute.Enum e) { + databuf.appendByte('e'); + databuf.appendChar(pool.put(typeSig(e.value.type))); + databuf.appendChar(pool.put(e.value.name)); + } + public void visitClass(Attribute.Class clazz) { + databuf.appendByte('c'); + databuf.appendChar(pool.put(typeSig(clazz.type))); + } + public void visitCompound(Attribute.Compound compound) { + databuf.appendByte('@'); + writeCompoundAttribute(compound); + } + public void visitError(Attribute.Error x) { + throw new AssertionError(x); + } + public void visitArray(Attribute.Array array) { + databuf.appendByte('['); + databuf.appendChar(array.values.length); + for (Attribute a : array.values) { + a.accept(this); + } + } + } + AttributeWriter awriter = new AttributeWriter(); + + /** Write a compound attribute excluding the '@' marker. */ + void writeCompoundAttribute(Attribute.Compound c) { + databuf.appendChar(pool.put(typeSig(c.type))); + databuf.appendChar(c.values.length()); + for (Pair p : c.values) { + databuf.appendChar(pool.put(p.fst.name)); + p.snd.accept(awriter); + } + } +/********************************************************************** + * Writing Objects + **********************************************************************/ + + /** Enter an inner class into the `innerClasses' set/queue. + */ + void enterInner(ClassSymbol c) { + if (c.type.isCompound()) { + throw new AssertionError("Unexpected intersection type: " + c.type); + } + try { + c.complete(); + } catch (CompletionFailure ex) { + System.err.println("error: " + c + ": " + ex.getMessage()); + throw ex; + } + if (c.type.tag != CLASS) return; // arrays + if (pool != null && // pool might be null if called from xClassName + c.owner.kind != PCK && + (innerClasses == null || !innerClasses.contains(c))) { +// log.errWriter.println("enter inner " + c);//DEBUG + if (c.owner.kind == TYP) enterInner((ClassSymbol)c.owner); + pool.put(c); + pool.put(c.name); + if (innerClasses == null) { + innerClasses = new HashSet(); + innerClassesQueue = new ListBuffer(); + pool.put(names.InnerClasses); + } + innerClasses.add(c); + innerClassesQueue.append(c); + } + } + + /** Write "inner classes" attribute. + */ + void writeInnerClasses() { + int alenIdx = writeAttr(names.InnerClasses); + databuf.appendChar(innerClassesQueue.length()); + for (List l = innerClassesQueue.toList(); + l.nonEmpty(); + l = l.tail) { + ClassSymbol inner = l.head; + char flags = (char) adjustFlags(inner.flags_field); + if ((flags & INTERFACE) != 0) flags |= ABSTRACT; // Interfaces are always ABSTRACT + if (inner.name.isEmpty()) flags &= ~FINAL; // Anonymous class: unset FINAL flag + if (dumpInnerClassModifiers) { + log.errWriter.println("INNERCLASS " + inner.name); + log.errWriter.println("---" + flagNames(flags)); + } + databuf.appendChar(pool.get(inner)); + databuf.appendChar( + inner.owner.kind == TYP ? pool.get(inner.owner) : 0); + databuf.appendChar( + !inner.name.isEmpty() ? pool.get(inner.name) : 0); + databuf.appendChar(flags); + } + endAttr(alenIdx); + } + + /** Write field symbol, entering all references into constant pool. + */ + void writeField(VarSymbol v) { + int flags = adjustFlags(v.flags()); + databuf.appendChar(flags); + if (dumpFieldModifiers) { + log.errWriter.println("FIELD " + fieldName(v)); + log.errWriter.println("---" + flagNames(v.flags())); + } + databuf.appendChar(pool.put(fieldName(v))); + databuf.appendChar(pool.put(typeSig(v.erasure(types)))); + int acountIdx = beginAttrs(); + int acount = 0; + if (v.getConstValue() != null) { + int alenIdx = writeAttr(names.ConstantValue); + databuf.appendChar(pool.put(v.getConstValue())); + endAttr(alenIdx); + acount++; + } + acount += writeMemberAttrs(v); + endAttrs(acountIdx, acount); + } + + /** Write method symbol, entering all references into constant pool. + */ + void writeMethod(MethodSymbol m) { + int flags = adjustFlags(m.flags()); + databuf.appendChar(flags); + if (dumpMethodModifiers) { + log.errWriter.println("METHOD " + fieldName(m)); + log.errWriter.println("---" + flagNames(m.flags())); + } + databuf.appendChar(pool.put(fieldName(m))); + databuf.appendChar(pool.put(typeSig(m.externalType(types)))); + int acountIdx = beginAttrs(); + int acount = 0; + if (m.code != null) { + int alenIdx = writeAttr(names.Code); + writeCode(m.code); + m.code = null; // to conserve space + endAttr(alenIdx); + acount++; + } + List thrown = m.erasure(types).getThrownTypes(); + if (thrown.nonEmpty()) { + int alenIdx = writeAttr(names.Exceptions); + databuf.appendChar(thrown.length()); + for (List l = thrown; l.nonEmpty(); l = l.tail) + databuf.appendChar(pool.put(l.head.tsym)); + endAttr(alenIdx); + acount++; + } + if (m.defaultValue != null) { + int alenIdx = writeAttr(names.AnnotationDefault); + m.defaultValue.accept(awriter); + endAttr(alenIdx); + acount++; + } + acount += writeMemberAttrs(m); + acount += writeParameterAttrs(m); + endAttrs(acountIdx, acount); + } + + /** Write code attribute of method. + */ + void writeCode(Code code) { + databuf.appendChar(code.max_stack); + databuf.appendChar(code.max_locals); + databuf.appendInt(code.cp); + databuf.appendBytes(code.code, 0, code.cp); + databuf.appendChar(code.catchInfo.length()); + for (List l = code.catchInfo.toList(); + l.nonEmpty(); + l = l.tail) { + for (int i = 0; i < l.head.length; i++) + databuf.appendChar(l.head[i]); + } + int acountIdx = beginAttrs(); + int acount = 0; + + if (code.lineInfo.nonEmpty()) { + int alenIdx = writeAttr(names.LineNumberTable); + databuf.appendChar(code.lineInfo.length()); + for (List l = code.lineInfo.reverse(); + l.nonEmpty(); + l = l.tail) + for (int i = 0; i < l.head.length; i++) + databuf.appendChar(l.head[i]); + endAttr(alenIdx); + acount++; + } + + if (genCrt && (code.crt != null)) { + CRTable crt = code.crt; + int alenIdx = writeAttr(names.CharacterRangeTable); + int crtIdx = beginAttrs(); + int crtEntries = crt.writeCRT(databuf, code.lineMap, log); + endAttrs(crtIdx, crtEntries); + endAttr(alenIdx); + acount++; + } + + // counter for number of generic local variables + int nGenericVars = 0; + + if (code.varBufferSize > 0) { + int alenIdx = writeAttr(names.LocalVariableTable); + databuf.appendChar(code.varBufferSize); + + for (int i=0; i= 0 + && var.start_pc <= code.cp); + databuf.appendChar(var.start_pc); + Assert.check(var.length >= 0 + && (var.start_pc + var.length) <= code.cp); + databuf.appendChar(var.length); + VarSymbol sym = var.sym; + databuf.appendChar(pool.put(sym.name)); + Type vartype = sym.erasure(types); + if (needsLocalVariableTypeEntry(sym.type)) + nGenericVars++; + databuf.appendChar(pool.put(typeSig(vartype))); + databuf.appendChar(var.reg); + } + endAttr(alenIdx); + acount++; + } + + if (nGenericVars > 0) { + int alenIdx = writeAttr(names.LocalVariableTypeTable); + databuf.appendChar(nGenericVars); + int count = 0; + + for (int i=0; i 0) { + if (debugstackmap) System.out.println("Stack map for " + code.meth); + int alenIdx = writeAttr(code.stackMap.getAttributeName(names)); + writeStackMap(code); + endAttr(alenIdx); + acount++; + } + endAttrs(acountIdx, acount); + } + //where + private boolean needsLocalVariableTypeEntry(Type t) { + //a local variable needs a type-entry if its type T is generic + //(i.e. |T| != T) and if it's not an intersection type (not supported + //in signature attribute grammar) + return (!types.isSameType(t, types.erasure(t)) && + !t.isCompound()); + } + + void writeStackMap(Code code) { + int nframes = code.stackMapBufferSize; + if (debugstackmap) System.out.println(" nframes = " + nframes); + databuf.appendChar(nframes); + + switch (code.stackMap) { + case CLDC: + for (int i=0; i interfaces = types.interfaces(c.type); + List typarams = c.type.getTypeArguments(); + + int flags = adjustFlags(c.flags()); + if ((flags & PROTECTED) != 0) flags |= PUBLIC; + flags = flags & ClassFlags & ~STRICTFP; + if ((flags & INTERFACE) == 0) flags |= ACC_SUPER; + if (c.isInner() && c.name.isEmpty()) flags &= ~FINAL; + if (dumpClassModifiers) { + log.errWriter.println(); + log.errWriter.println("CLASSFILE " + c.getQualifiedName()); + log.errWriter.println("---" + flagNames(flags)); + } + databuf.appendChar(flags); + + databuf.appendChar(pool.put(c)); + databuf.appendChar(supertype.tag == CLASS ? pool.put(supertype.tsym) : 0); + databuf.appendChar(interfaces.length()); + for (List l = interfaces; l.nonEmpty(); l = l.tail) + databuf.appendChar(pool.put(l.head.tsym)); + int fieldsCount = 0; + int methodsCount = 0; + for (Scope.Entry e = c.members().elems; e != null; e = e.sibling) { + switch (e.sym.kind) { + case VAR: fieldsCount++; break; + case MTH: if ((e.sym.flags() & HYPOTHETICAL) == 0) methodsCount++; + break; + case TYP: enterInner((ClassSymbol)e.sym); break; + default : Assert.error(); + } + } + databuf.appendChar(fieldsCount); + writeFields(c.members().elems); + databuf.appendChar(methodsCount); + writeMethods(c.members().elems); + + int acountIdx = beginAttrs(); + int acount = 0; + + boolean sigReq = + typarams.length() != 0 || supertype.allparams().length() != 0; + for (List l = interfaces; !sigReq && l.nonEmpty(); l = l.tail) + sigReq = l.head.allparams().length() != 0; + if (sigReq) { + Assert.check(source.allowGenerics()); + int alenIdx = writeAttr(names.Signature); + if (typarams.length() != 0) assembleParamsSig(typarams); + assembleSig(supertype); + for (List l = interfaces; l.nonEmpty(); l = l.tail) + assembleSig(l.head); + databuf.appendChar(pool.put(sigbuf.toName(names))); + sigbuf.reset(); + endAttr(alenIdx); + acount++; + } + + if (c.sourcefile != null && emitSourceFile) { + int alenIdx = writeAttr(names.SourceFile); + // WHM 6/29/1999: Strip file path prefix. We do it here at + // the last possible moment because the sourcefile may be used + // elsewhere in error diagnostics. Fixes 4241573. + //databuf.appendChar(c.pool.put(c.sourcefile)); + String simpleName = BaseFileObject.getSimpleName(c.sourcefile); + databuf.appendChar(c.pool.put(names.fromString(simpleName))); + endAttr(alenIdx); + acount++; + } + + if (genCrt) { + // Append SourceID attribute + int alenIdx = writeAttr(names.SourceID); + databuf.appendChar(c.pool.put(names.fromString(Long.toString(getLastModified(c.sourcefile))))); + endAttr(alenIdx); + acount++; + // Append CompilationID attribute + alenIdx = writeAttr(names.CompilationID); + databuf.appendChar(c.pool.put(names.fromString(Long.toString(System.currentTimeMillis())))); + endAttr(alenIdx); + acount++; + } + + acount += writeFlagAttrs(c.flags()); + acount += writeJavaAnnotations(c.getAnnotationMirrors()); + acount += writeEnclosingMethodAttribute(c); + + poolbuf.appendInt(JAVA_MAGIC); + poolbuf.appendChar(target.minorVersion); + poolbuf.appendChar(target.majorVersion); + + writePool(c.pool); + + if (innerClasses != null) { + writeInnerClasses(); + acount++; + } + endAttrs(acountIdx, acount); + + poolbuf.appendBytes(databuf.elems, 0, databuf.length); + out.write(poolbuf.elems, 0, poolbuf.length); + + pool = c.pool = null; // to conserve space + } + + int adjustFlags(final long flags) { + int result = (int)flags; + if ((flags & SYNTHETIC) != 0 && !target.useSyntheticFlag()) + result &= ~SYNTHETIC; + if ((flags & ENUM) != 0 && !target.useEnumFlag()) + result &= ~ENUM; + if ((flags & ANNOTATION) != 0 && !target.useAnnotationFlag()) + result &= ~ANNOTATION; + + if ((flags & BRIDGE) != 0 && target.useBridgeFlag()) + result |= ACC_BRIDGE; + if ((flags & VARARGS) != 0 && target.useVarargsFlag()) + result |= ACC_VARARGS; + return result; + } + + long getLastModified(FileObject filename) { + long mod = 0; + try { + mod = filename.getLastModified(); + } catch (SecurityException e) { + throw new AssertionError("CRT: couldn't get source file modification date: " + e.getMessage()); + } + return mod; + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/jvm/Code.java b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/Code.java new file mode 100644 index 0000000..ec080df --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/Code.java @@ -0,0 +1,2196 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.jvm; + +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; + +import static com.sun.tools.javac.code.TypeTags.*; +import static com.sun.tools.javac.jvm.ByteCodes.*; +import static com.sun.tools.javac.jvm.UninitializedType.*; +import static com.sun.tools.javac.jvm.ClassWriter.StackMapTableFrame; + +/** An internal structure that corresponds to the code attribute of + * methods in a classfile. The class also provides some utility operations to + * generate bytecode instructions. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Code { + + public final boolean debugCode; + public final boolean needStackMap; + + public enum StackMapFormat { + NONE, + CLDC { + Name getAttributeName(Names names) { + return names.StackMap; + } + }, + JSR202 { + Name getAttributeName(Names names) { + return names.StackMapTable; + } + }; + Name getAttributeName(Names names) { + return names.empty; + } + } + + final Types types; + final Symtab syms; + +/*---------- classfile fields: --------------- */ + + /** The maximum stack size. + */ + public int max_stack = 0; + + /** The maximum number of local variable slots. + */ + public int max_locals = 0; + + /** The code buffer. + */ + public byte[] code = new byte[64]; + + /** the current code pointer. + */ + public int cp = 0; + + /** Check the code against VM spec limits; if + * problems report them and return true. + */ + public boolean checkLimits(DiagnosticPosition pos, Log log) { + if (cp > ClassFile.MAX_CODE) { + log.error(pos, "limit.code"); + return true; + } + if (max_locals > ClassFile.MAX_LOCALS) { + log.error(pos, "limit.locals"); + return true; + } + if (max_stack > ClassFile.MAX_STACK) { + log.error(pos, "limit.stack"); + return true; + } + return false; + } + + /** A buffer for expression catch data. Each enter is a vector + * of four unsigned shorts. + */ + ListBuffer catchInfo = new ListBuffer(); + + /** A buffer for line number information. Each entry is a vector + * of two unsigned shorts. + */ + List lineInfo = List.nil(); // handled in stack fashion + + /** The CharacterRangeTable + */ + public CRTable crt; + +/*---------- internal fields: --------------- */ + + /** Are we generating code with jumps >= 32K? + */ + public boolean fatcode; + + /** Code generation enabled? + */ + private boolean alive = true; + + /** The current machine state (registers and stack). + */ + State state; + + /** Is it forbidden to compactify code, because something is + * pointing to current location? + */ + private boolean fixedPc = false; + + /** The next available register. + */ + public int nextreg = 0; + + /** A chain for jumps to be resolved before the next opcode is emitted. + * We do this lazily to avoid jumps to jumps. + */ + Chain pendingJumps = null; + + /** The position of the currently statement, if we are at the + * start of this statement, NOPOS otherwise. + * We need this to emit line numbers lazily, which we need to do + * because of jump-to-jump optimization. + */ + int pendingStatPos = Position.NOPOS; + + /** Set true when a stackMap is needed at the current PC. */ + boolean pendingStackMap = false; + + /** The stack map format to be generated. */ + StackMapFormat stackMap; + + /** Switch: emit variable debug info. + */ + boolean varDebugInfo; + + /** Switch: emit line number info. + */ + boolean lineDebugInfo; + + /** Emit line number info if map supplied + */ + Position.LineMap lineMap; + + /** The constant pool of the current class. + */ + final Pool pool; + + final MethodSymbol meth; + + /** Construct a code object, given the settings of the fatcode, + * debugging info switches and the CharacterRangeTable. + */ + public Code(MethodSymbol meth, + boolean fatcode, + Position.LineMap lineMap, + boolean varDebugInfo, + StackMapFormat stackMap, + boolean debugCode, + CRTable crt, + Symtab syms, + Types types, + Pool pool) { + this.meth = meth; + this.fatcode = fatcode; + this.lineMap = lineMap; + this.lineDebugInfo = lineMap != null; + this.varDebugInfo = varDebugInfo; + this.crt = crt; + this.syms = syms; + this.types = types; + this.debugCode = debugCode; + this.stackMap = stackMap; + switch (stackMap) { + case CLDC: + case JSR202: + this.needStackMap = true; + break; + default: + this.needStackMap = false; + } + state = new State(); + lvar = new LocalVar[20]; + this.pool = pool; + } + + +/* ************************************************************************** + * Typecodes & related stuff + ****************************************************************************/ + + /** Given a type, return its type code (used implicitly in the + * JVM architecture). + */ + public static int typecode(Type type) { + switch (type.tag) { + case BYTE: return BYTEcode; + case SHORT: return SHORTcode; + case CHAR: return CHARcode; + case INT: return INTcode; + case LONG: return LONGcode; + case FLOAT: return FLOATcode; + case DOUBLE: return DOUBLEcode; + case BOOLEAN: return BYTEcode; + case VOID: return VOIDcode; + case CLASS: + case ARRAY: + case METHOD: + case BOT: + case TYPEVAR: + case UNINITIALIZED_THIS: + case UNINITIALIZED_OBJECT: + return OBJECTcode; + default: throw new AssertionError("typecode " + type.tag); + } + } + + /** Collapse type code for subtypes of int to INTcode. + */ + public static int truncate(int tc) { + switch (tc) { + case BYTEcode: case SHORTcode: case CHARcode: return INTcode; + default: return tc; + } + } + + /** The width in bytes of objects of the type. + */ + public static int width(int typecode) { + switch (typecode) { + case LONGcode: case DOUBLEcode: return 2; + case VOIDcode: return 0; + default: return 1; + } + } + + public static int width(Type type) { + return type == null ? 1 : width(typecode(type)); + } + + /** The total width taken up by a vector of objects. + */ + public static int width(List types) { + int w = 0; + for (List l = types; l.nonEmpty(); l = l.tail) + w = w + width(l.head); + return w; + } + + /** Given a type, return its code for allocating arrays of that type. + */ + public static int arraycode(Type type) { + switch (type.tag) { + case BYTE: return 8; + case BOOLEAN: return 4; + case SHORT: return 9; + case CHAR: return 5; + case INT: return 10; + case LONG: return 11; + case FLOAT: return 6; + case DOUBLE: return 7; + case CLASS: return 0; + case ARRAY: return 1; + default: throw new AssertionError("arraycode " + type); + } + } + + +/* ************************************************************************** + * Emit code + ****************************************************************************/ + + /** The current output code pointer. + */ + public int curPc() { + if (pendingJumps != null) resolvePending(); + if (pendingStatPos != Position.NOPOS) markStatBegin(); + fixedPc = true; + return cp; + } + + /** Emit a byte of code. + */ + private void emit1(int od) { + if (!alive) return; + if (cp == code.length) { + byte[] newcode = new byte[cp * 2]; + System.arraycopy(code, 0, newcode, 0, cp); + code = newcode; + } + code[cp++] = (byte)od; + } + + /** Emit two bytes of code. + */ + private void emit2(int od) { + if (!alive) return; + if (cp + 2 > code.length) { + emit1(od >> 8); + emit1(od); + } else { + code[cp++] = (byte)(od >> 8); + code[cp++] = (byte)od; + } + } + + /** Emit four bytes of code. + */ + public void emit4(int od) { + if (!alive) return; + if (cp + 4 > code.length) { + emit1(od >> 24); + emit1(od >> 16); + emit1(od >> 8); + emit1(od); + } else { + code[cp++] = (byte)(od >> 24); + code[cp++] = (byte)(od >> 16); + code[cp++] = (byte)(od >> 8); + code[cp++] = (byte)od; + } + } + + /** Emit an opcode. + */ + private void emitop(int op) { + if (pendingJumps != null) resolvePending(); + if (alive) { + if (pendingStatPos != Position.NOPOS) + markStatBegin(); + if (pendingStackMap) { + pendingStackMap = false; + emitStackMap(); + } + if (debugCode) + System.err.println("emit@" + cp + " stack=" + + state.stacksize + ": " + + mnem(op)); + emit1(op); + } + } + + void postop() { + Assert.check(alive || state.stacksize == 0); + } + + /** Emit a multinewarray instruction. + */ + public void emitMultianewarray(int ndims, int type, Type arrayType) { + emitop(multianewarray); + if (!alive) return; + emit2(type); + emit1(ndims); + state.pop(ndims); + state.push(arrayType); + } + + /** Emit newarray. + */ + public void emitNewarray(int elemcode, Type arrayType) { + emitop(newarray); + if (!alive) return; + emit1(elemcode); + state.pop(1); // count + state.push(arrayType); + } + + /** Emit anewarray. + */ + public void emitAnewarray(int od, Type arrayType) { + emitop(anewarray); + if (!alive) return; + emit2(od); + state.pop(1); + state.push(arrayType); + } + + /** Emit an invokeinterface instruction. + */ + public void emitInvokeinterface(int meth, Type mtype) { + int argsize = width(mtype.getParameterTypes()); + emitop(invokeinterface); + if (!alive) return; + emit2(meth); + emit1(argsize + 1); + emit1(0); + state.pop(argsize + 1); + state.push(mtype.getReturnType()); + } + + /** Emit an invokespecial instruction. + */ + public void emitInvokespecial(int meth, Type mtype) { + int argsize = width(mtype.getParameterTypes()); + emitop(invokespecial); + if (!alive) return; + emit2(meth); + Symbol sym = (Symbol)pool.pool[meth]; + state.pop(argsize); + if (sym.isConstructor()) + state.markInitialized((UninitializedType)state.peek()); + state.pop(1); + state.push(mtype.getReturnType()); + } + + /** Emit an invokestatic instruction. + */ + public void emitInvokestatic(int meth, Type mtype) { + int argsize = width(mtype.getParameterTypes()); + emitop(invokestatic); + if (!alive) return; + emit2(meth); + state.pop(argsize); + state.push(mtype.getReturnType()); + } + + /** Emit an invokevirtual instruction. + */ + public void emitInvokevirtual(int meth, Type mtype) { + int argsize = width(mtype.getParameterTypes()); + emitop(invokevirtual); + if (!alive) return; + emit2(meth); + state.pop(argsize + 1); + state.push(mtype.getReturnType()); + } + + /** Emit an invokedynamic instruction. + */ + public void emitInvokedynamic(int desc, Type mtype) { + // N.B. this format is under consideration by the JSR 292 EG + int argsize = width(mtype.getParameterTypes()); + emitop(invokedynamic); + if (!alive) return; + emit2(desc); + emit2(0); + state.pop(argsize); + state.push(mtype.getReturnType()); + } + + /** Emit an opcode with no operand field. + */ + public void emitop0(int op) { + emitop(op); + if (!alive) return; + switch (op) { + case aaload: { + state.pop(1);// index + Type a = state.stack[state.stacksize-1]; + state.pop(1); + //sometimes 'null type' is treated as a one-dimensional array type + //see Gen.visitLiteral - we should handle this case accordingly + Type stackType = a.tag == BOT ? + syms.objectType : + types.erasure(types.elemtype(a)); + state.push(stackType); } + break; + case goto_: + markDead(); + break; + case nop: + case ineg: + case lneg: + case fneg: + case dneg: + break; + case aconst_null: + state.push(syms.botType); + break; + case iconst_m1: + case iconst_0: + case iconst_1: + case iconst_2: + case iconst_3: + case iconst_4: + case iconst_5: + case iload_0: + case iload_1: + case iload_2: + case iload_3: + state.push(syms.intType); + break; + case lconst_0: + case lconst_1: + case lload_0: + case lload_1: + case lload_2: + case lload_3: + state.push(syms.longType); + break; + case fconst_0: + case fconst_1: + case fconst_2: + case fload_0: + case fload_1: + case fload_2: + case fload_3: + state.push(syms.floatType); + break; + case dconst_0: + case dconst_1: + case dload_0: + case dload_1: + case dload_2: + case dload_3: + state.push(syms.doubleType); + break; + case aload_0: + state.push(lvar[0].sym.type); + break; + case aload_1: + state.push(lvar[1].sym.type); + break; + case aload_2: + state.push(lvar[2].sym.type); + break; + case aload_3: + state.push(lvar[3].sym.type); + break; + case iaload: + case baload: + case caload: + case saload: + state.pop(2); + state.push(syms.intType); + break; + case laload: + state.pop(2); + state.push(syms.longType); + break; + case faload: + state.pop(2); + state.push(syms.floatType); + break; + case daload: + state.pop(2); + state.push(syms.doubleType); + break; + case istore_0: + case istore_1: + case istore_2: + case istore_3: + case fstore_0: + case fstore_1: + case fstore_2: + case fstore_3: + case astore_0: + case astore_1: + case astore_2: + case astore_3: + case pop: + case lshr: + case lshl: + case lushr: + state.pop(1); + break; + case areturn: + case ireturn: + case freturn: + Assert.check(state.nlocks == 0); + state.pop(1); + markDead(); + break; + case athrow: + state.pop(1); + markDead(); + break; + case lstore_0: + case lstore_1: + case lstore_2: + case lstore_3: + case dstore_0: + case dstore_1: + case dstore_2: + case dstore_3: + case pop2: + state.pop(2); + break; + case lreturn: + case dreturn: + Assert.check(state.nlocks == 0); + state.pop(2); + markDead(); + break; + case dup: + state.push(state.stack[state.stacksize-1]); + break; + case return_: + Assert.check(state.nlocks == 0); + markDead(); + break; + case arraylength: + state.pop(1); + state.push(syms.intType); + break; + case isub: + case iadd: + case imul: + case idiv: + case imod: + case ishl: + case ishr: + case iushr: + case iand: + case ior: + case ixor: + state.pop(1); + // state.pop(1); + // state.push(syms.intType); + break; + case aastore: + state.pop(3); + break; + case land: + case lor: + case lxor: + case lmod: + case ldiv: + case lmul: + case lsub: + case ladd: + state.pop(2); + break; + case lcmp: + state.pop(4); + state.push(syms.intType); + break; + case l2i: + state.pop(2); + state.push(syms.intType); + break; + case i2l: + state.pop(1); + state.push(syms.longType); + break; + case i2f: + state.pop(1); + state.push(syms.floatType); + break; + case i2d: + state.pop(1); + state.push(syms.doubleType); + break; + case l2f: + state.pop(2); + state.push(syms.floatType); + break; + case l2d: + state.pop(2); + state.push(syms.doubleType); + break; + case f2i: + state.pop(1); + state.push(syms.intType); + break; + case f2l: + state.pop(1); + state.push(syms.longType); + break; + case f2d: + state.pop(1); + state.push(syms.doubleType); + break; + case d2i: + state.pop(2); + state.push(syms.intType); + break; + case d2l: + state.pop(2); + state.push(syms.longType); + break; + case d2f: + state.pop(2); + state.push(syms.floatType); + break; + case tableswitch: + case lookupswitch: + state.pop(1); + // the caller is responsible for patching up the state + break; + case dup_x1: { + Type val1 = state.pop1(); + Type val2 = state.pop1(); + state.push(val1); + state.push(val2); + state.push(val1); + break; + } + case bastore: + state.pop(3); + break; + case int2byte: + case int2char: + case int2short: + break; + case fmul: + case fadd: + case fsub: + case fdiv: + case fmod: + state.pop(1); + break; + case castore: + case iastore: + case fastore: + case sastore: + state.pop(3); + break; + case lastore: + case dastore: + state.pop(4); + break; + case dup2: + if (state.stack[state.stacksize-1] != null) { + Type value1 = state.pop1(); + Type value2 = state.pop1(); + state.push(value2); + state.push(value1); + state.push(value2); + state.push(value1); + } else { + Type value = state.pop2(); + state.push(value); + state.push(value); + } + break; + case dup2_x1: + if (state.stack[state.stacksize-1] != null) { + Type value1 = state.pop1(); + Type value2 = state.pop1(); + Type value3 = state.pop1(); + state.push(value2); + state.push(value1); + state.push(value3); + state.push(value2); + state.push(value1); + } else { + Type value1 = state.pop2(); + Type value2 = state.pop1(); + state.push(value1); + state.push(value2); + state.push(value1); + } + break; + case dup2_x2: + if (state.stack[state.stacksize-1] != null) { + Type value1 = state.pop1(); + Type value2 = state.pop1(); + if (state.stack[state.stacksize-1] != null) { + // form 1 + Type value3 = state.pop1(); + Type value4 = state.pop1(); + state.push(value2); + state.push(value1); + state.push(value4); + state.push(value3); + state.push(value2); + state.push(value1); + } else { + // form 3 + Type value3 = state.pop2(); + state.push(value2); + state.push(value1); + state.push(value3); + state.push(value2); + state.push(value1); + } + } else { + Type value1 = state.pop2(); + if (state.stack[state.stacksize-1] != null) { + // form 2 + Type value2 = state.pop1(); + Type value3 = state.pop1(); + state.push(value1); + state.push(value3); + state.push(value2); + state.push(value1); + } else { + // form 4 + Type value2 = state.pop2(); + state.push(value1); + state.push(value2); + state.push(value1); + } + } + break; + case dup_x2: { + Type value1 = state.pop1(); + if (state.stack[state.stacksize-1] != null) { + // form 1 + Type value2 = state.pop1(); + Type value3 = state.pop1(); + state.push(value1); + state.push(value3); + state.push(value2); + state.push(value1); + } else { + // form 2 + Type value2 = state.pop2(); + state.push(value1); + state.push(value2); + state.push(value1); + } + } + break; + case fcmpl: + case fcmpg: + state.pop(2); + state.push(syms.intType); + break; + case dcmpl: + case dcmpg: + state.pop(4); + state.push(syms.intType); + break; + case swap: { + Type value1 = state.pop1(); + Type value2 = state.pop1(); + state.push(value1); + state.push(value2); + break; + } + case dadd: + case dsub: + case dmul: + case ddiv: + case dmod: + state.pop(2); + break; + case ret: + markDead(); + break; + case wide: + // must be handled by the caller. + return; + case monitorenter: + case monitorexit: + state.pop(1); + break; + + default: + throw new AssertionError(mnem(op)); + } + postop(); + } + + /** Emit an opcode with a one-byte operand field. + */ + public void emitop1(int op, int od) { + emitop(op); + if (!alive) return; + emit1(od); + switch (op) { + case bipush: + state.push(syms.intType); + break; + case ldc1: + state.push(typeForPool(pool.pool[od])); + break; + default: + throw new AssertionError(mnem(op)); + } + postop(); + } + + /** The type of a constant pool entry. */ + private Type typeForPool(Object o) { + if (o instanceof Integer) return syms.intType; + if (o instanceof Float) return syms.floatType; + if (o instanceof String) return syms.stringType; + if (o instanceof Long) return syms.longType; + if (o instanceof Double) return syms.doubleType; + if (o instanceof ClassSymbol) return syms.classType; + if (o instanceof Type.ArrayType) return syms.classType; + throw new AssertionError(o); + } + + /** Emit an opcode with a one-byte operand field; + * widen if field does not fit in a byte. + */ + public void emitop1w(int op, int od) { + if (od > 0xFF) { + emitop(wide); + emitop(op); + emit2(od); + } else { + emitop(op); + emit1(od); + } + if (!alive) return; + switch (op) { + case iload: + state.push(syms.intType); + break; + case lload: + state.push(syms.longType); + break; + case fload: + state.push(syms.floatType); + break; + case dload: + state.push(syms.doubleType); + break; + case aload: + state.push(lvar[od].sym.type); + break; + case lstore: + case dstore: + state.pop(2); + break; + case istore: + case fstore: + case astore: + state.pop(1); + break; + case ret: + markDead(); + break; + default: + throw new AssertionError(mnem(op)); + } + postop(); + } + + /** Emit an opcode with two one-byte operand fields; + * widen if either field does not fit in a byte. + */ + public void emitop1w(int op, int od1, int od2) { + if (od1 > 0xFF || od2 < -128 || od2 > 127) { + emitop(wide); + emitop(op); + emit2(od1); + emit2(od2); + } else { + emitop(op); + emit1(od1); + emit1(od2); + } + if (!alive) return; + switch (op) { + case iinc: + break; + default: + throw new AssertionError(mnem(op)); + } + } + + /** Emit an opcode with a two-byte operand field. + */ + public void emitop2(int op, int od) { + emitop(op); + if (!alive) return; + emit2(od); + switch (op) { + case getstatic: + state.push(((Symbol)(pool.pool[od])).erasure(types)); + break; + case putstatic: + state.pop(((Symbol)(pool.pool[od])).erasure(types)); + break; + case new_: + state.push(uninitializedObject(((Symbol)(pool.pool[od])).erasure(types), cp-3)); + break; + case sipush: + state.push(syms.intType); + break; + case if_acmp_null: + case if_acmp_nonnull: + case ifeq: + case ifne: + case iflt: + case ifge: + case ifgt: + case ifle: + state.pop(1); + break; + case if_icmpeq: + case if_icmpne: + case if_icmplt: + case if_icmpge: + case if_icmpgt: + case if_icmple: + case if_acmpeq: + case if_acmpne: + state.pop(2); + break; + case goto_: + markDead(); + break; + case putfield: + state.pop(((Symbol)(pool.pool[od])).erasure(types)); + state.pop(1); // object ref + break; + case getfield: + state.pop(1); // object ref + state.push(((Symbol)(pool.pool[od])).erasure(types)); + break; + case checkcast: { + state.pop(1); // object ref + Object o = pool.pool[od]; + Type t = (o instanceof Symbol) + ? ((Symbol)o).erasure(types) + : types.erasure(((Type)o)); + state.push(t); + break; } + case ldc2w: + state.push(typeForPool(pool.pool[od])); + break; + case instanceof_: + state.pop(1); + state.push(syms.intType); + break; + case ldc2: + state.push(typeForPool(pool.pool[od])); + break; + case jsr: + break; + default: + throw new AssertionError(mnem(op)); + } + // postop(); + } + + /** Emit an opcode with a four-byte operand field. + */ + public void emitop4(int op, int od) { + emitop(op); + if (!alive) return; + emit4(od); + switch (op) { + case goto_w: + markDead(); + break; + case jsr_w: + break; + default: + throw new AssertionError(mnem(op)); + } + // postop(); + } + + /** Align code pointer to next `incr' boundary. + */ + public void align(int incr) { + if (alive) + while (cp % incr != 0) emitop0(nop); + } + + /** Place a byte into code at address pc. Pre: pc + 1 <= cp. + */ + private void put1(int pc, int op) { + code[pc] = (byte)op; + } + + /** Place two bytes into code at address pc. Pre: pc + 2 <= cp. + */ + private void put2(int pc, int od) { + // pre: pc + 2 <= cp + put1(pc, od >> 8); + put1(pc+1, od); + } + + /** Place four bytes into code at address pc. Pre: pc + 4 <= cp. + */ + public void put4(int pc, int od) { + // pre: pc + 4 <= cp + put1(pc , od >> 24); + put1(pc+1, od >> 16); + put1(pc+2, od >> 8); + put1(pc+3, od); + } + + /** Return code byte at position pc as an unsigned int. + */ + private int get1(int pc) { + return code[pc] & 0xFF; + } + + /** Return two code bytes at position pc as an unsigned int. + */ + private int get2(int pc) { + return (get1(pc) << 8) | get1(pc+1); + } + + /** Return four code bytes at position pc as an int. + */ + public int get4(int pc) { + // pre: pc + 4 <= cp + return + (get1(pc) << 24) | + (get1(pc+1) << 16) | + (get1(pc+2) << 8) | + (get1(pc+3)); + } + + /** Is code generation currently enabled? + */ + public boolean isAlive() { + return alive || pendingJumps != null; + } + + /** Switch code generation on/off. + */ + public void markDead() { + alive = false; + } + + /** Declare an entry point; return current code pointer + */ + public int entryPoint() { + int pc = curPc(); + alive = true; + pendingStackMap = needStackMap; + return pc; + } + + /** Declare an entry point with initial state; + * return current code pointer + */ + public int entryPoint(State state) { + int pc = curPc(); + alive = true; + this.state = state.dup(); + Assert.check(state.stacksize <= max_stack); + if (debugCode) System.err.println("entry point " + state); + pendingStackMap = needStackMap; + return pc; + } + + /** Declare an entry point with initial state plus a pushed value; + * return current code pointer + */ + public int entryPoint(State state, Type pushed) { + int pc = curPc(); + alive = true; + this.state = state.dup(); + Assert.check(state.stacksize <= max_stack); + this.state.push(pushed); + if (debugCode) System.err.println("entry point " + state); + pendingStackMap = needStackMap; + return pc; + } + + +/************************************************************************** + * Stack map generation + *************************************************************************/ + + /** An entry in the stack map. */ + static class StackMapFrame { + int pc; + Type[] locals; + Type[] stack; + } + + /** A buffer of cldc stack map entries. */ + StackMapFrame[] stackMapBuffer = null; + + /** A buffer of compressed StackMapTable entries. */ + StackMapTableFrame[] stackMapTableBuffer = null; + int stackMapBufferSize = 0; + + /** The last PC at which we generated a stack map. */ + int lastStackMapPC = -1; + + /** The last stack map frame in StackMapTable. */ + StackMapFrame lastFrame = null; + + /** The stack map frame before the last one. */ + StackMapFrame frameBeforeLast = null; + + /** Emit a stack map entry. */ + public void emitStackMap() { + int pc = curPc(); + if (!needStackMap) return; + + + + switch (stackMap) { + case CLDC: + emitCLDCStackMap(pc, getLocalsSize()); + break; + case JSR202: + emitStackMapFrame(pc, getLocalsSize()); + break; + default: + throw new AssertionError("Should have chosen a stackmap format"); + } + // DEBUG code follows + if (debugCode) state.dump(pc); + } + + private int getLocalsSize() { + int nextLocal = 0; + for (int i=max_locals-1; i>=0; i--) { + if (state.defined.isMember(i) && lvar[i] != null) { + nextLocal = i + width(lvar[i].sym.erasure(types)); + break; + } + } + return nextLocal; + } + + /** Emit a CLDC stack map frame. */ + void emitCLDCStackMap(int pc, int localsSize) { + if (lastStackMapPC == pc) { + // drop existing stackmap at this offset + stackMapBuffer[--stackMapBufferSize] = null; + } + lastStackMapPC = pc; + + if (stackMapBuffer == null) { + stackMapBuffer = new StackMapFrame[20]; + } else if (stackMapBuffer.length == stackMapBufferSize) { + StackMapFrame[] newStackMapBuffer = + new StackMapFrame[stackMapBufferSize << 1]; + System.arraycopy(stackMapBuffer, 0, newStackMapBuffer, + 0, stackMapBufferSize); + stackMapBuffer = newStackMapBuffer; + } + StackMapFrame frame = + stackMapBuffer[stackMapBufferSize++] = new StackMapFrame(); + frame.pc = pc; + + frame.locals = new Type[localsSize]; + for (int i=0; i 1) i++; + } + } + frame.locals = new Type[localCount]; + for (int i=0, j=0; i 1) i++; + } + + int stackCount = 0; + for (int i=0; i arg_types = ((MethodType)meth.externalType(types)).argtypes; + int len = arg_types.length(); + int count = 0; + if (!meth.isStatic()) { + Type thisType = meth.owner.type; + frame.locals = new Type[len+1]; + if (meth.isConstructor() && thisType != syms.objectType) { + frame.locals[count++] = UninitializedType.uninitializedThis(thisType); + } else { + frame.locals[count++] = types.erasure(thisType); + } + } else { + frame.locals = new Type[len]; + } + for (Type arg_type : arg_types) { + frame.locals[count++] = types.erasure(arg_type); + } + frame.pc = -1; + frame.stack = null; + return frame; + } + + +/************************************************************************** + * Operations having to do with jumps + *************************************************************************/ + + /** A chain represents a list of unresolved jumps. Jump locations + * are sorted in decreasing order. + */ + public static class Chain { + + /** The position of the jump instruction. + */ + public final int pc; + + /** The machine state after the jump instruction. + * Invariant: all elements of a chain list have the same stacksize + * and compatible stack and register contents. + */ + Code.State state; + + /** The next jump in the list. + */ + public final Chain next; + + /** Construct a chain from its jump position, stacksize, previous + * chain, and machine state. + */ + public Chain(int pc, Chain next, Code.State state) { + this.pc = pc; + this.next = next; + this.state = state; + } + } + + /** Negate a branch opcode. + */ + public static int negate(int opcode) { + if (opcode == if_acmp_null) return if_acmp_nonnull; + else if (opcode == if_acmp_nonnull) return if_acmp_null; + else return ((opcode + 1) ^ 1) - 1; + } + + /** Emit a jump instruction. + * Return code pointer of instruction to be patched. + */ + public int emitJump(int opcode) { + if (fatcode) { + if (opcode == goto_ || opcode == jsr) { + emitop4(opcode + goto_w - goto_, 0); + } else { + emitop2(negate(opcode), 8); + emitop4(goto_w, 0); + alive = true; + pendingStackMap = needStackMap; + } + return cp - 5; + } else { + emitop2(opcode, 0); + return cp - 3; + } + } + + /** Emit a branch with given opcode; return its chain. + * branch differs from jump in that jsr is treated as no-op. + */ + public Chain branch(int opcode) { + Chain result = null; + if (opcode == goto_) { + result = pendingJumps; + pendingJumps = null; + } + if (opcode != dontgoto && isAlive()) { + result = new Chain(emitJump(opcode), + result, + state.dup()); + fixedPc = fatcode; + if (opcode == goto_) alive = false; + } + return result; + } + + /** Resolve chain to point to given target. + */ + public void resolve(Chain chain, int target) { + boolean changed = false; + State newState = state; + for (; chain != null; chain = chain.next) { + Assert.check(state != chain.state + && (target > chain.pc || state.stacksize == 0)); + if (target >= cp) { + target = cp; + } else if (get1(target) == goto_) { + if (fatcode) target = target + get4(target + 1); + else target = target + get2(target + 1); + } + if (get1(chain.pc) == goto_ && + chain.pc + 3 == target && target == cp && !fixedPc) { + // If goto the next instruction, the jump is not needed: + // compact the code. + cp = cp - 3; + target = target - 3; + if (chain.next == null) { + // This is the only jump to the target. Exit the loop + // without setting new state. The code is reachable + // from the instruction before goto_. + alive = true; + break; + } + } else { + if (fatcode) + put4(chain.pc + 1, target - chain.pc); + else if (target - chain.pc < Short.MIN_VALUE || + target - chain.pc > Short.MAX_VALUE) + fatcode = true; + else + put2(chain.pc + 1, target - chain.pc); + Assert.check(!alive || + chain.state.stacksize == newState.stacksize && + chain.state.nlocks == newState.nlocks); + } + fixedPc = true; + if (cp == target) { + changed = true; + if (debugCode) + System.err.println("resolving chain state=" + chain.state); + if (alive) { + newState = chain.state.join(newState); + } else { + newState = chain.state; + alive = true; + } + } + } + Assert.check(!changed || state != newState); + if (state != newState) { + setDefined(newState.defined); + state = newState; + pendingStackMap = needStackMap; + } + } + + /** Resolve chain to point to current code pointer. + */ + public void resolve(Chain chain) { + Assert.check( + !alive || + chain==null || + state.stacksize == chain.state.stacksize && + state.nlocks == chain.state.nlocks); + pendingJumps = mergeChains(chain, pendingJumps); + } + + /** Resolve any pending jumps. + */ + public void resolvePending() { + Chain x = pendingJumps; + pendingJumps = null; + resolve(x, cp); + } + + /** Merge the jumps in of two chains into one. + */ + public static Chain mergeChains(Chain chain1, Chain chain2) { + // recursive merge sort + if (chain2 == null) return chain1; + if (chain1 == null) return chain2; + Assert.check( + chain1.state.stacksize == chain2.state.stacksize && + chain1.state.nlocks == chain2.state.nlocks); + if (chain1.pc < chain2.pc) + return new Chain( + chain2.pc, + mergeChains(chain1, chain2.next), + chain2.state); + return new Chain( + chain1.pc, + mergeChains(chain1.next, chain2), + chain1.state); + } + + +/* ************************************************************************** + * Catch clauses + ****************************************************************************/ + + /** Add a catch clause to code. + */ + public void addCatch( + char startPc, char endPc, char handlerPc, char catchType) { + catchInfo.append(new char[]{startPc, endPc, handlerPc, catchType}); + } + + +/* ************************************************************************** + * Line numbers + ****************************************************************************/ + + /** Add a line number entry. + */ + public void addLineNumber(char startPc, char lineNumber) { + if (lineDebugInfo) { + if (lineInfo.nonEmpty() && lineInfo.head[0] == startPc) + lineInfo = lineInfo.tail; + if (lineInfo.isEmpty() || lineInfo.head[1] != lineNumber) + lineInfo = lineInfo.prepend(new char[]{startPc, lineNumber}); + } + } + + /** Mark beginning of statement. + */ + public void statBegin(int pos) { + if (pos != Position.NOPOS) { + pendingStatPos = pos; + } + } + + /** Force stat begin eagerly + */ + public void markStatBegin() { + if (alive && lineDebugInfo) { + int line = lineMap.getLineNumber(pendingStatPos); + char cp1 = (char)cp; + char line1 = (char)line; + if (cp1 == cp && line1 == line) + addLineNumber(cp1, line1); + } + pendingStatPos = Position.NOPOS; + } + + +/* ************************************************************************** + * Simulated VM machine state + ****************************************************************************/ + + class State implements Cloneable { + /** The set of registers containing values. */ + Bits defined; + + /** The (types of the) contents of the machine stack. */ + Type[] stack; + + /** The first stack position currently unused. */ + int stacksize; + + /** The numbers of registers containing locked monitors. */ + int[] locks; + int nlocks; + + State() { + defined = new Bits(); + stack = new Type[16]; + } + + State dup() { + try { + State state = (State)super.clone(); + state.defined = defined.dup(); + state.stack = stack.clone(); + if (locks != null) state.locks = locks.clone(); + if (debugCode) { + System.err.println("duping state " + this); + dump(); + } + return state; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(ex); + } + } + + void lock(int register) { + if (locks == null) { + locks = new int[20]; + } else if (locks.length == nlocks) { + int[] newLocks = new int[locks.length << 1]; + System.arraycopy(locks, 0, newLocks, 0, locks.length); + locks = newLocks; + } + locks[nlocks] = register; + nlocks++; + } + + void unlock(int register) { + nlocks--; + Assert.check(locks[nlocks] == register); + locks[nlocks] = -1; + } + + void push(Type t) { + if (debugCode) System.err.println(" pushing " + t); + switch (t.tag) { + case TypeTags.VOID: + return; + case TypeTags.BYTE: + case TypeTags.CHAR: + case TypeTags.SHORT: + case TypeTags.BOOLEAN: + t = syms.intType; + break; + default: + break; + } + if (stacksize+2 >= stack.length) { + Type[] newstack = new Type[2*stack.length]; + System.arraycopy(stack, 0, newstack, 0, stack.length); + stack = newstack; + } + stack[stacksize++] = t; + switch (width(t)) { + case 1: + break; + case 2: + stack[stacksize++] = null; + break; + default: + throw new AssertionError(t); + } + if (stacksize > max_stack) + max_stack = stacksize; + } + + Type pop1() { + if (debugCode) System.err.println(" popping " + 1); + stacksize--; + Type result = stack[stacksize]; + stack[stacksize] = null; + Assert.check(result != null && width(result) == 1); + return result; + } + + Type peek() { + return stack[stacksize-1]; + } + + Type pop2() { + if (debugCode) System.err.println(" popping " + 2); + stacksize -= 2; + Type result = stack[stacksize]; + stack[stacksize] = null; + Assert.check(stack[stacksize+1] == null + && result != null && width(result) == 2); + return result; + } + + void pop(int n) { + if (debugCode) System.err.println(" popping " + n); + while (n > 0) { + stack[--stacksize] = null; + n--; + } + } + + void pop(Type t) { + pop(width(t)); + } + + /** Force the top of the stack to be treated as this supertype + * of its current type. */ + void forceStackTop(Type t) { + if (!alive) return; + switch (t.tag) { + case CLASS: + case ARRAY: + int width = width(t); + Type old = stack[stacksize-width]; + Assert.check(types.isSubtype(types.erasure(old), + types.erasure(t))); + stack[stacksize-width] = t; + break; + default: + } + } + + void markInitialized(UninitializedType old) { + Type newtype = old.initializedType(); + for (int i=0; i=0; i--) { + if (defined.isMember(i)) { + lastLocal = i; + break; + } + } + if (lastLocal >= 0) + System.err.println(" locals:"); + for (int i=0; i<=lastLocal; i++) { + System.err.print(" " + i + ": "); + if (defined.isMember(i)) { + LocalVar var = lvar[i]; + if (var == null) { + System.err.println("(none)"); + } else if (var.sym == null) + System.err.println("UNKNOWN!"); + else + System.err.println("" + var.sym + " of type " + + var.sym.erasure(types)); + } else { + System.err.println("undefined"); + } + } + if (nlocks != 0) { + System.err.print(" locks:"); + for (int i=0; iThis is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Gen extends JCTree.Visitor { + protected static final Context.Key genKey = + new Context.Key(); + + private final Log log; + private final Symtab syms; + private final Check chk; + private final Resolve rs; + private final TreeMaker make; + private final Names names; + private final Target target; + private final Type stringBufferType; + private final Map stringBufferAppend; + private Name accessDollar; + private final Types types; + + /** Switch: GJ mode? + */ + private final boolean allowGenerics; + + /** Set when Miranda method stubs are to be generated. */ + private final boolean generateIproxies; + + /** Format of stackmap tables to be generated. */ + private final Code.StackMapFormat stackMap; + + /** A type that serves as the expected type for all method expressions. + */ + private final Type methodType; + + public static Gen instance(Context context) { + Gen instance = context.get(genKey); + if (instance == null) + instance = new Gen(context); + return instance; + } + + protected Gen(Context context) { + context.put(genKey, this); + + names = Names.instance(context); + log = Log.instance(context); + syms = Symtab.instance(context); + chk = Check.instance(context); + rs = Resolve.instance(context); + make = TreeMaker.instance(context); + target = Target.instance(context); + types = Types.instance(context); + methodType = new MethodType(null, null, null, syms.methodClass); + allowGenerics = Source.instance(context).allowGenerics(); + stringBufferType = target.useStringBuilder() + ? syms.stringBuilderType + : syms.stringBufferType; + stringBufferAppend = new HashMap(); + accessDollar = names. + fromString("access" + target.syntheticNameChar()); + + Options options = Options.instance(context); + lineDebugInfo = + options.isUnset(G_CUSTOM) || + options.isSet(G_CUSTOM, "lines"); + varDebugInfo = + options.isUnset(G_CUSTOM) + ? options.isSet(G) + : options.isSet(G_CUSTOM, "vars"); + genCrt = options.isSet(XJCOV); + debugCode = options.isSet("debugcode"); + allowInvokedynamic = target.hasInvokedynamic() || options.isSet("invokedynamic"); + + generateIproxies = + target.requiresIproxy() || + options.isSet("miranda"); + + if (target.generateStackMapTable()) { + // ignore cldc because we cannot have both stackmap formats + this.stackMap = StackMapFormat.JSR202; + } else { + if (target.generateCLDCStackmap()) { + this.stackMap = StackMapFormat.CLDC; + } else { + this.stackMap = StackMapFormat.NONE; + } + } + + // by default, avoid jsr's for simple finalizers + int setjsrlimit = 50; + String jsrlimitString = options.get("jsrlimit"); + if (jsrlimitString != null) { + try { + setjsrlimit = Integer.parseInt(jsrlimitString); + } catch (NumberFormatException ex) { + // ignore ill-formed numbers for jsrlimit + } + } + this.jsrlimit = setjsrlimit; + this.useJsrLocally = false; // reset in visitTry + } + + /** Switches + */ + private final boolean lineDebugInfo; + private final boolean varDebugInfo; + private final boolean genCrt; + private final boolean debugCode; + private final boolean allowInvokedynamic; + + /** Default limit of (approximate) size of finalizer to inline. + * Zero means always use jsr. 100 or greater means never use + * jsr. + */ + private final int jsrlimit; + + /** True if jsr is used. + */ + private boolean useJsrLocally; + + /* Constant pool, reset by genClass. + */ + private Pool pool = new Pool(); + + /** Code buffer, set by genMethod. + */ + private Code code; + + /** Items structure, set by genMethod. + */ + private Items items; + + /** Environment for symbol lookup, set by genClass + */ + private Env attrEnv; + + /** The top level tree. + */ + private JCCompilationUnit toplevel; + + /** The number of code-gen errors in this class. + */ + private int nerrs = 0; + + /** A hash table mapping syntax trees to their ending source positions. + */ + private Map endPositions; + + /** Generate code to load an integer constant. + * @param n The integer to be loaded. + */ + void loadIntConst(int n) { + items.makeImmediateItem(syms.intType, n).load(); + } + + /** The opcode that loads a zero constant of a given type code. + * @param tc The given type code (@see ByteCode). + */ + public static int zero(int tc) { + switch(tc) { + case INTcode: case BYTEcode: case SHORTcode: case CHARcode: + return iconst_0; + case LONGcode: + return lconst_0; + case FLOATcode: + return fconst_0; + case DOUBLEcode: + return dconst_0; + default: + throw new AssertionError("zero"); + } + } + + /** The opcode that loads a one constant of a given type code. + * @param tc The given type code (@see ByteCode). + */ + public static int one(int tc) { + return zero(tc) + 1; + } + + /** Generate code to load -1 of the given type code (either int or long). + * @param tc The given type code (@see ByteCode). + */ + void emitMinusOne(int tc) { + if (tc == LONGcode) { + items.makeImmediateItem(syms.longType, new Long(-1)).load(); + } else { + code.emitop0(iconst_m1); + } + } + + /** Construct a symbol to reflect the qualifying type that should + * appear in the byte code as per JLS 13.1. + * + * For target >= 1.2: Clone a method with the qualifier as owner (except + * for those cases where we need to work around VM bugs). + * + * For target <= 1.1: If qualified variable or method is defined in a + * non-accessible class, clone it with the qualifier class as owner. + * + * @param sym The accessed symbol + * @param site The qualifier's type. + */ + Symbol binaryQualifier(Symbol sym, Type site) { + + if (site.tag == ARRAY) { + if (sym == syms.lengthVar || + sym.owner != syms.arrayClass) + return sym; + // array clone can be qualified by the array type in later targets + Symbol qualifier = target.arrayBinaryCompatibility() + ? new ClassSymbol(Flags.PUBLIC, site.tsym.name, + site, syms.noSymbol) + : syms.objectType.tsym; + return sym.clone(qualifier); + } + + if (sym.owner == site.tsym || + (sym.flags() & (STATIC | SYNTHETIC)) == (STATIC | SYNTHETIC)) { + return sym; + } + if (!target.obeyBinaryCompatibility()) + return rs.isAccessible(attrEnv, (TypeSymbol)sym.owner) + ? sym + : sym.clone(site.tsym); + + if (!target.interfaceFieldsBinaryCompatibility()) { + if ((sym.owner.flags() & INTERFACE) != 0 && sym.kind == VAR) + return sym; + } + + // leave alone methods inherited from Object + // JLS 13.1. + if (sym.owner == syms.objectType.tsym) + return sym; + + if (!target.interfaceObjectOverridesBinaryCompatibility()) { + if ((sym.owner.flags() & INTERFACE) != 0 && + syms.objectType.tsym.members().lookup(sym.name).scope != null) + return sym; + } + + return sym.clone(site.tsym); + } + + /** Insert a reference to given type in the constant pool, + * checking for an array with too many dimensions; + * return the reference's index. + * @param type The type for which a reference is inserted. + */ + int makeRef(DiagnosticPosition pos, Type type) { + checkDimension(pos, type); + return pool.put(type.tag == CLASS ? (Object)type.tsym : (Object)type); + } + + /** Check if the given type is an array with too many dimensions. + */ + private void checkDimension(DiagnosticPosition pos, Type t) { + switch (t.tag) { + case METHOD: + checkDimension(pos, t.getReturnType()); + for (List args = t.getParameterTypes(); args.nonEmpty(); args = args.tail) + checkDimension(pos, args.head); + break; + case ARRAY: + if (types.dimensions(t) > ClassFile.MAX_DIMENSIONS) { + log.error(pos, "limit.dimensions"); + nerrs++; + } + break; + default: + break; + } + } + + /** Create a tempory variable. + * @param type The variable's type. + */ + LocalItem makeTemp(Type type) { + VarSymbol v = new VarSymbol(Flags.SYNTHETIC, + names.empty, + type, + env.enclMethod.sym); + code.newLocal(v); + return items.makeLocalItem(v); + } + + /** Generate code to call a non-private method or constructor. + * @param pos Position to be used for error reporting. + * @param site The type of which the method is a member. + * @param name The method's name. + * @param argtypes The method's argument types. + * @param isStatic A flag that indicates whether we call a + * static or instance method. + */ + void callMethod(DiagnosticPosition pos, + Type site, Name name, List argtypes, + boolean isStatic) { + Symbol msym = rs. + resolveInternalMethod(pos, attrEnv, site, name, argtypes, null); + if (isStatic) items.makeStaticItem(msym).invoke(); + else items.makeMemberItem(msym, name == names.init).invoke(); + } + + /** Is the given method definition an access method + * resulting from a qualified super? This is signified by an odd + * access code. + */ + private boolean isAccessSuper(JCMethodDecl enclMethod) { + return + (enclMethod.mods.flags & SYNTHETIC) != 0 && + isOddAccessName(enclMethod.name); + } + + /** Does given name start with "access$" and end in an odd digit? + */ + private boolean isOddAccessName(Name name) { + return + name.startsWith(accessDollar) && + (name.getByteAt(name.getByteLength() - 1) & 1) == 1; + } + +/* ************************************************************************ + * Non-local exits + *************************************************************************/ + + /** Generate code to invoke the finalizer associated with given + * environment. + * Any calls to finalizers are appended to the environments `cont' chain. + * Mark beginning of gap in catch all range for finalizer. + */ + void genFinalizer(Env env) { + if (code.isAlive() && env.info.finalize != null) + env.info.finalize.gen(); + } + + /** Generate code to call all finalizers of structures aborted by + * a non-local + * exit. Return target environment of the non-local exit. + * @param target The tree representing the structure that's aborted + * @param env The environment current at the non-local exit. + */ + Env unwind(JCTree target, Env env) { + Env env1 = env; + while (true) { + genFinalizer(env1); + if (env1.tree == target) break; + env1 = env1.next; + } + return env1; + } + + /** Mark end of gap in catch-all range for finalizer. + * @param env the environment which might contain the finalizer + * (if it does, env.info.gaps != null). + */ + void endFinalizerGap(Env env) { + if (env.info.gaps != null && env.info.gaps.length() % 2 == 1) + env.info.gaps.append(code.curPc()); + } + + /** Mark end of all gaps in catch-all ranges for finalizers of environments + * lying between, and including to two environments. + * @param from the most deeply nested environment to mark + * @param to the least deeply nested environment to mark + */ + void endFinalizerGaps(Env from, Env to) { + Env last = null; + while (last != to) { + endFinalizerGap(from); + last = from; + from = from.next; + } + } + + /** Do any of the structures aborted by a non-local exit have + * finalizers that require an empty stack? + * @param target The tree representing the structure that's aborted + * @param env The environment current at the non-local exit. + */ + boolean hasFinally(JCTree target, Env env) { + while (env.tree != target) { + if (env.tree.getTag() == JCTree.TRY && env.info.finalize.hasFinalizer()) + return true; + env = env.next; + } + return false; + } + +/* ************************************************************************ + * Normalizing class-members. + *************************************************************************/ + + /** Distribute member initializer code into constructors and + * method. + * @param defs The list of class member declarations. + * @param c The enclosing class. + */ + List normalizeDefs(List defs, ClassSymbol c) { + ListBuffer initCode = new ListBuffer(); + ListBuffer clinitCode = new ListBuffer(); + ListBuffer methodDefs = new ListBuffer(); + // Sort definitions into three listbuffers: + // - initCode for instance initializers + // - clinitCode for class initializers + // - methodDefs for method definitions + for (List l = defs; l.nonEmpty(); l = l.tail) { + JCTree def = l.head; + switch (def.getTag()) { + case JCTree.BLOCK: + JCBlock block = (JCBlock)def; + if ((block.flags & STATIC) != 0) + clinitCode.append(block); + else + initCode.append(block); + break; + case JCTree.METHODDEF: + methodDefs.append(def); + break; + case JCTree.VARDEF: + JCVariableDecl vdef = (JCVariableDecl) def; + VarSymbol sym = vdef.sym; + checkDimension(vdef.pos(), sym.type); + if (vdef.init != null) { + if ((sym.flags() & STATIC) == 0) { + // Always initialize instance variables. + JCStatement init = make.at(vdef.pos()). + Assignment(sym, vdef.init); + initCode.append(init); + if (endPositions != null) { + Integer endPos = endPositions.remove(vdef); + if (endPos != null) endPositions.put(init, endPos); + } + } else if (sym.getConstValue() == null) { + // Initialize class (static) variables only if + // they are not compile-time constants. + JCStatement init = make.at(vdef.pos). + Assignment(sym, vdef.init); + clinitCode.append(init); + if (endPositions != null) { + Integer endPos = endPositions.remove(vdef); + if (endPos != null) endPositions.put(init, endPos); + } + } else { + checkStringConstant(vdef.init.pos(), sym.getConstValue()); + } + } + break; + default: + Assert.error(); + } + } + // Insert any instance initializers into all constructors. + if (initCode.length() != 0) { + List inits = initCode.toList(); + for (JCTree t : methodDefs) { + normalizeMethod((JCMethodDecl)t, inits); + } + } + // If there are class initializers, create a method + // that contains them as its body. + if (clinitCode.length() != 0) { + MethodSymbol clinit = new MethodSymbol( + STATIC, names.clinit, + new MethodType( + List.nil(), syms.voidType, + List.nil(), syms.methodClass), + c); + c.members().enter(clinit); + List clinitStats = clinitCode.toList(); + JCBlock block = make.at(clinitStats.head.pos()).Block(0, clinitStats); + block.endpos = TreeInfo.endPos(clinitStats.last()); + methodDefs.append(make.MethodDef(clinit, block)); + } + // Return all method definitions. + return methodDefs.toList(); + } + + /** Check a constant value and report if it is a string that is + * too large. + */ + private void checkStringConstant(DiagnosticPosition pos, Object constValue) { + if (nerrs != 0 || // only complain about a long string once + constValue == null || + !(constValue instanceof String) || + ((String)constValue).length() < Pool.MAX_STRING_LENGTH) + return; + log.error(pos, "limit.string"); + nerrs++; + } + + /** Insert instance initializer code into initial constructor. + * @param md The tree potentially representing a + * constructor's definition. + * @param initCode The list of instance initializer statements. + */ + void normalizeMethod(JCMethodDecl md, List initCode) { + if (md.name == names.init && TreeInfo.isInitialConstructor(md)) { + // We are seeing a constructor that does not call another + // constructor of the same class. + List stats = md.body.stats; + ListBuffer newstats = new ListBuffer(); + + if (stats.nonEmpty()) { + // Copy initializers of synthetic variables generated in + // the translation of inner classes. + while (TreeInfo.isSyntheticInit(stats.head)) { + newstats.append(stats.head); + stats = stats.tail; + } + // Copy superclass constructor call + newstats.append(stats.head); + stats = stats.tail; + // Copy remaining synthetic initializers. + while (stats.nonEmpty() && + TreeInfo.isSyntheticInit(stats.head)) { + newstats.append(stats.head); + stats = stats.tail; + } + // Now insert the initializer code. + newstats.appendList(initCode); + // And copy all remaining statements. + while (stats.nonEmpty()) { + newstats.append(stats.head); + stats = stats.tail; + } + } + md.body.stats = newstats.toList(); + if (md.body.endpos == Position.NOPOS) + md.body.endpos = TreeInfo.endPos(md.body.stats.last()); + } + } + +/* ******************************************************************** + * Adding miranda methods + *********************************************************************/ + + /** Add abstract methods for all methods defined in one of + * the interfaces of a given class, + * provided they are not already implemented in the class. + * + * @param c The class whose interfaces are searched for methods + * for which Miranda methods should be added. + */ + void implementInterfaceMethods(ClassSymbol c) { + implementInterfaceMethods(c, c); + } + + /** Add abstract methods for all methods defined in one of + * the interfaces of a given class, + * provided they are not already implemented in the class. + * + * @param c The class whose interfaces are searched for methods + * for which Miranda methods should be added. + * @param site The class in which a definition may be needed. + */ + void implementInterfaceMethods(ClassSymbol c, ClassSymbol site) { + for (List l = types.interfaces(c.type); l.nonEmpty(); l = l.tail) { + ClassSymbol i = (ClassSymbol)l.head.tsym; + for (Scope.Entry e = i.members().elems; + e != null; + e = e.sibling) + { + if (e.sym.kind == MTH && (e.sym.flags() & STATIC) == 0) + { + MethodSymbol absMeth = (MethodSymbol)e.sym; + MethodSymbol implMeth = absMeth.binaryImplementation(site, types); + if (implMeth == null) + addAbstractMethod(site, absMeth); + else if ((implMeth.flags() & IPROXY) != 0) + adjustAbstractMethod(site, implMeth, absMeth); + } + } + implementInterfaceMethods(i, site); + } + } + + /** Add an abstract methods to a class + * which implicitly implements a method defined in some interface + * implemented by the class. These methods are called "Miranda methods". + * Enter the newly created method into its enclosing class scope. + * Note that it is not entered into the class tree, as the emitter + * doesn't need to see it there to emit an abstract method. + * + * @param c The class to which the Miranda method is added. + * @param m The interface method symbol for which a Miranda method + * is added. + */ + private void addAbstractMethod(ClassSymbol c, + MethodSymbol m) { + MethodSymbol absMeth = new MethodSymbol( + m.flags() | IPROXY | SYNTHETIC, m.name, + m.type, // was c.type.memberType(m), but now only !generics supported + c); + c.members().enter(absMeth); // add to symbol table + } + + private void adjustAbstractMethod(ClassSymbol c, + MethodSymbol pm, + MethodSymbol im) { + MethodType pmt = (MethodType)pm.type; + Type imt = types.memberType(c.type, im); + pmt.thrown = chk.intersect(pmt.getThrownTypes(), imt.getThrownTypes()); + } + +/* ************************************************************************ + * Traversal methods + *************************************************************************/ + + /** Visitor argument: The current environment. + */ + Env env; + + /** Visitor argument: The expected type (prototype). + */ + Type pt; + + /** Visitor result: The item representing the computed value. + */ + Item result; + + /** Visitor method: generate code for a definition, catching and reporting + * any completion failures. + * @param tree The definition to be visited. + * @param env The environment current at the definition. + */ + public void genDef(JCTree tree, Env env) { + Env prevEnv = this.env; + try { + this.env = env; + tree.accept(this); + } catch (CompletionFailure ex) { + chk.completionError(tree.pos(), ex); + } finally { + this.env = prevEnv; + } + } + + /** Derived visitor method: check whether CharacterRangeTable + * should be emitted, if so, put a new entry into CRTable + * and call method to generate bytecode. + * If not, just call method to generate bytecode. + * @see #genStat(Tree, Env) + * + * @param tree The tree to be visited. + * @param env The environment to use. + * @param crtFlags The CharacterRangeTable flags + * indicating type of the entry. + */ + public void genStat(JCTree tree, Env env, int crtFlags) { + if (!genCrt) { + genStat(tree, env); + return; + } + int startpc = code.curPc(); + genStat(tree, env); + if (tree.getTag() == JCTree.BLOCK) crtFlags |= CRT_BLOCK; + code.crt.put(tree, crtFlags, startpc, code.curPc()); + } + + /** Derived visitor method: generate code for a statement. + */ + public void genStat(JCTree tree, Env env) { + if (code.isAlive()) { + code.statBegin(tree.pos); + genDef(tree, env); + } else if (env.info.isSwitch && tree.getTag() == JCTree.VARDEF) { + // variables whose declarations are in a switch + // can be used even if the decl is unreachable. + code.newLocal(((JCVariableDecl) tree).sym); + } + } + + /** Derived visitor method: check whether CharacterRangeTable + * should be emitted, if so, put a new entry into CRTable + * and call method to generate bytecode. + * If not, just call method to generate bytecode. + * @see #genStats(List, Env) + * + * @param trees The list of trees to be visited. + * @param env The environment to use. + * @param crtFlags The CharacterRangeTable flags + * indicating type of the entry. + */ + public void genStats(List trees, Env env, int crtFlags) { + if (!genCrt) { + genStats(trees, env); + return; + } + if (trees.length() == 1) { // mark one statement with the flags + genStat(trees.head, env, crtFlags | CRT_STATEMENT); + } else { + int startpc = code.curPc(); + genStats(trees, env); + code.crt.put(trees, crtFlags, startpc, code.curPc()); + } + } + + /** Derived visitor method: generate code for a list of statements. + */ + public void genStats(List trees, Env env) { + for (List l = trees; l.nonEmpty(); l = l.tail) + genStat(l.head, env, CRT_STATEMENT); + } + + /** Derived visitor method: check whether CharacterRangeTable + * should be emitted, if so, put a new entry into CRTable + * and call method to generate bytecode. + * If not, just call method to generate bytecode. + * @see #genCond(Tree,boolean) + * + * @param tree The tree to be visited. + * @param crtFlags The CharacterRangeTable flags + * indicating type of the entry. + */ + public CondItem genCond(JCTree tree, int crtFlags) { + if (!genCrt) return genCond(tree, false); + int startpc = code.curPc(); + CondItem item = genCond(tree, (crtFlags & CRT_FLOW_CONTROLLER) != 0); + code.crt.put(tree, crtFlags, startpc, code.curPc()); + return item; + } + + /** Derived visitor method: generate code for a boolean + * expression in a control-flow context. + * @param _tree The expression to be visited. + * @param markBranches The flag to indicate that the condition is + * a flow controller so produced conditions + * should contain a proper tree to generate + * CharacterRangeTable branches for them. + */ + public CondItem genCond(JCTree _tree, boolean markBranches) { + JCTree inner_tree = TreeInfo.skipParens(_tree); + if (inner_tree.getTag() == JCTree.CONDEXPR) { + JCConditional tree = (JCConditional)inner_tree; + CondItem cond = genCond(tree.cond, CRT_FLOW_CONTROLLER); + if (cond.isTrue()) { + code.resolve(cond.trueJumps); + CondItem result = genCond(tree.truepart, CRT_FLOW_TARGET); + if (markBranches) result.tree = tree.truepart; + return result; + } + if (cond.isFalse()) { + code.resolve(cond.falseJumps); + CondItem result = genCond(tree.falsepart, CRT_FLOW_TARGET); + if (markBranches) result.tree = tree.falsepart; + return result; + } + Chain secondJumps = cond.jumpFalse(); + code.resolve(cond.trueJumps); + CondItem first = genCond(tree.truepart, CRT_FLOW_TARGET); + if (markBranches) first.tree = tree.truepart; + Chain falseJumps = first.jumpFalse(); + code.resolve(first.trueJumps); + Chain trueJumps = code.branch(goto_); + code.resolve(secondJumps); + CondItem second = genCond(tree.falsepart, CRT_FLOW_TARGET); + CondItem result = items.makeCondItem(second.opcode, + Code.mergeChains(trueJumps, second.trueJumps), + Code.mergeChains(falseJumps, second.falseJumps)); + if (markBranches) result.tree = tree.falsepart; + return result; + } else { + CondItem result = genExpr(_tree, syms.booleanType).mkCond(); + if (markBranches) result.tree = _tree; + return result; + } + } + + /** Visitor method: generate code for an expression, catching and reporting + * any completion failures. + * @param tree The expression to be visited. + * @param pt The expression's expected type (proto-type). + */ + public Item genExpr(JCTree tree, Type pt) { + Type prevPt = this.pt; + try { + if (tree.type.constValue() != null) { + // Short circuit any expressions which are constants + checkStringConstant(tree.pos(), tree.type.constValue()); + result = items.makeImmediateItem(tree.type, tree.type.constValue()); + } else { + this.pt = pt; + tree.accept(this); + } + return result.coerce(pt); + } catch (CompletionFailure ex) { + chk.completionError(tree.pos(), ex); + code.state.stacksize = 1; + return items.makeStackItem(pt); + } finally { + this.pt = prevPt; + } + } + + /** Derived visitor method: generate code for a list of method arguments. + * @param trees The argument expressions to be visited. + * @param pts The expression's expected types (i.e. the formal parameter + * types of the invoked method). + */ + public void genArgs(List trees, List pts) { + for (List l = trees; l.nonEmpty(); l = l.tail) { + genExpr(l.head, pts.head).load(); + pts = pts.tail; + } + // require lists be of same length + Assert.check(pts.isEmpty()); + } + +/* ************************************************************************ + * Visitor methods for statements and definitions + *************************************************************************/ + + /** Thrown when the byte code size exceeds limit. + */ + public static class CodeSizeOverflow extends RuntimeException { + private static final long serialVersionUID = 0; + public CodeSizeOverflow() {} + } + + public void visitMethodDef(JCMethodDecl tree) { + // Create a new local environment that points pack at method + // definition. + Env localEnv = env.dup(tree); + localEnv.enclMethod = tree; + + // The expected type of every return statement in this method + // is the method's return type. + this.pt = tree.sym.erasure(types).getReturnType(); + + checkDimension(tree.pos(), tree.sym.erasure(types)); + genMethod(tree, localEnv, false); + } +//where + /** Generate code for a method. + * @param tree The tree representing the method definition. + * @param env The environment current for the method body. + * @param fatcode A flag that indicates whether all jumps are + * within 32K. We first invoke this method under + * the assumption that fatcode == false, i.e. all + * jumps are within 32K. If this fails, fatcode + * is set to true and we try again. + */ + void genMethod(JCMethodDecl tree, Env env, boolean fatcode) { + MethodSymbol meth = tree.sym; +// System.err.println("Generating " + meth + " in " + meth.owner); //DEBUG + if (Code.width(types.erasure(env.enclMethod.sym.type).getParameterTypes()) + + (((tree.mods.flags & STATIC) == 0 || meth.isConstructor()) ? 1 : 0) > + ClassFile.MAX_PARAMETERS) { + log.error(tree.pos(), "limit.parameters"); + nerrs++; + } + + else if (tree.body != null) { + // Create a new code structure and initialize it. + int startpcCrt = initCode(tree, env, fatcode); + + try { + genStat(tree.body, env); + } catch (CodeSizeOverflow e) { + // Failed due to code limit, try again with jsr/ret + startpcCrt = initCode(tree, env, fatcode); + genStat(tree.body, env); + } + + if (code.state.stacksize != 0) { + log.error(tree.body.pos(), "stack.sim.error", tree); + throw new AssertionError(); + } + + // If last statement could complete normally, insert a + // return at the end. + if (code.isAlive()) { + code.statBegin(TreeInfo.endPos(tree.body)); + if (env.enclMethod == null || + env.enclMethod.sym.type.getReturnType().tag == VOID) { + code.emitop0(return_); + } else { + // sometime dead code seems alive (4415991); + // generate a small loop instead + int startpc = code.entryPoint(); + CondItem c = items.makeCondItem(goto_); + code.resolve(c.jumpTrue(), startpc); + } + } + if (genCrt) + code.crt.put(tree.body, + CRT_BLOCK, + startpcCrt, + code.curPc()); + + code.endScopes(0); + + // If we exceeded limits, panic + if (code.checkLimits(tree.pos(), log)) { + nerrs++; + return; + } + + // If we generated short code but got a long jump, do it again + // with fatCode = true. + if (!fatcode && code.fatcode) genMethod(tree, env, true); + + // Clean up + if(stackMap == StackMapFormat.JSR202) { + code.lastFrame = null; + code.frameBeforeLast = null; + } + } + } + + private int initCode(JCMethodDecl tree, Env env, boolean fatcode) { + MethodSymbol meth = tree.sym; + + // Create a new code structure. + meth.code = code = new Code(meth, + fatcode, + lineDebugInfo ? toplevel.lineMap : null, + varDebugInfo, + stackMap, + debugCode, + genCrt ? new CRTable(tree, env.toplevel.endPositions) + : null, + syms, + types, + pool); + items = new Items(pool, code, syms, types); + if (code.debugCode) + System.err.println(meth + " for body " + tree); + + // If method is not static, create a new local variable address + // for `this'. + if ((tree.mods.flags & STATIC) == 0) { + Type selfType = meth.owner.type; + if (meth.isConstructor() && selfType != syms.objectType) + selfType = UninitializedType.uninitializedThis(selfType); + code.setDefined( + code.newLocal( + new VarSymbol(FINAL, names._this, selfType, meth.owner))); + } + + // Mark all parameters as defined from the beginning of + // the method. + for (List l = tree.params; l.nonEmpty(); l = l.tail) { + checkDimension(l.head.pos(), l.head.sym.type); + code.setDefined(code.newLocal(l.head.sym)); + } + + // Get ready to generate code for method body. + int startpcCrt = genCrt ? code.curPc() : 0; + code.entryPoint(); + + // Suppress initial stackmap + code.pendingStackMap = false; + + return startpcCrt; + } + + public void visitVarDef(JCVariableDecl tree) { + VarSymbol v = tree.sym; + code.newLocal(v); + if (tree.init != null) { + checkStringConstant(tree.init.pos(), v.getConstValue()); + if (v.getConstValue() == null || varDebugInfo) { + genExpr(tree.init, v.erasure(types)).load(); + items.makeLocalItem(v).store(); + } + } + checkDimension(tree.pos(), v.type); + } + + public void visitSkip(JCSkip tree) { + } + + public void visitBlock(JCBlock tree) { + int limit = code.nextreg; + Env localEnv = env.dup(tree, new GenContext()); + genStats(tree.stats, localEnv); + // End the scope of all block-local variables in variable info. + if (env.tree.getTag() != JCTree.METHODDEF) { + code.statBegin(tree.endpos); + code.endScopes(limit); + code.pendingStatPos = Position.NOPOS; + } + } + + public void visitDoLoop(JCDoWhileLoop tree) { + genLoop(tree, tree.body, tree.cond, List.nil(), false); + } + + public void visitWhileLoop(JCWhileLoop tree) { + genLoop(tree, tree.body, tree.cond, List.nil(), true); + } + + public void visitForLoop(JCForLoop tree) { + int limit = code.nextreg; + genStats(tree.init, env); + genLoop(tree, tree.body, tree.cond, tree.step, true); + code.endScopes(limit); + } + //where + /** Generate code for a loop. + * @param loop The tree representing the loop. + * @param body The loop's body. + * @param cond The loop's controling condition. + * @param step "Step" statements to be inserted at end of + * each iteration. + * @param testFirst True if the loop test belongs before the body. + */ + private void genLoop(JCStatement loop, + JCStatement body, + JCExpression cond, + List step, + boolean testFirst) { + Env loopEnv = env.dup(loop, new GenContext()); + int startpc = code.entryPoint(); + if (testFirst) { + CondItem c; + if (cond != null) { + code.statBegin(cond.pos); + c = genCond(TreeInfo.skipParens(cond), CRT_FLOW_CONTROLLER); + } else { + c = items.makeCondItem(goto_); + } + Chain loopDone = c.jumpFalse(); + code.resolve(c.trueJumps); + genStat(body, loopEnv, CRT_STATEMENT | CRT_FLOW_TARGET); + code.resolve(loopEnv.info.cont); + genStats(step, loopEnv); + code.resolve(code.branch(goto_), startpc); + code.resolve(loopDone); + } else { + genStat(body, loopEnv, CRT_STATEMENT | CRT_FLOW_TARGET); + code.resolve(loopEnv.info.cont); + genStats(step, loopEnv); + CondItem c; + if (cond != null) { + code.statBegin(cond.pos); + c = genCond(TreeInfo.skipParens(cond), CRT_FLOW_CONTROLLER); + } else { + c = items.makeCondItem(goto_); + } + code.resolve(c.jumpTrue(), startpc); + code.resolve(c.falseJumps); + } + code.resolve(loopEnv.info.exit); + } + + public void visitForeachLoop(JCEnhancedForLoop tree) { + throw new AssertionError(); // should have been removed by Lower. + } + + public void visitLabelled(JCLabeledStatement tree) { + Env localEnv = env.dup(tree, new GenContext()); + genStat(tree.body, localEnv, CRT_STATEMENT); + code.resolve(localEnv.info.exit); + } + + public void visitSwitch(JCSwitch tree) { + int limit = code.nextreg; + Assert.check(tree.selector.type.tag != CLASS); + int startpcCrt = genCrt ? code.curPc() : 0; + Item sel = genExpr(tree.selector, syms.intType); + List cases = tree.cases; + if (cases.isEmpty()) { + // We are seeing: switch {} + sel.load().drop(); + if (genCrt) + code.crt.put(TreeInfo.skipParens(tree.selector), + CRT_FLOW_CONTROLLER, startpcCrt, code.curPc()); + } else { + // We are seeing a nonempty switch. + sel.load(); + if (genCrt) + code.crt.put(TreeInfo.skipParens(tree.selector), + CRT_FLOW_CONTROLLER, startpcCrt, code.curPc()); + Env switchEnv = env.dup(tree, new GenContext()); + switchEnv.info.isSwitch = true; + + // Compute number of labels and minimum and maximum label values. + // For each case, store its label in an array. + int lo = Integer.MAX_VALUE; // minimum label. + int hi = Integer.MIN_VALUE; // maximum label. + int nlabels = 0; // number of labels. + + int[] labels = new int[cases.length()]; // the label array. + int defaultIndex = -1; // the index of the default clause. + + List l = cases; + for (int i = 0; i < labels.length; i++) { + if (l.head.pat != null) { + int val = ((Number)l.head.pat.type.constValue()).intValue(); + labels[i] = val; + if (val < lo) lo = val; + if (hi < val) hi = val; + nlabels++; + } else { + Assert.check(defaultIndex == -1); + defaultIndex = i; + } + l = l.tail; + } + + // Determine whether to issue a tableswitch or a lookupswitch + // instruction. + long table_space_cost = 4 + ((long) hi - lo + 1); // words + long table_time_cost = 3; // comparisons + long lookup_space_cost = 3 + 2 * (long) nlabels; + long lookup_time_cost = nlabels; + int opcode = + nlabels > 0 && + table_space_cost + 3 * table_time_cost <= + lookup_space_cost + 3 * lookup_time_cost + ? + tableswitch : lookupswitch; + + int startpc = code.curPc(); // the position of the selector operation + code.emitop0(opcode); + code.align(4); + int tableBase = code.curPc(); // the start of the jump table + int[] offsets = null; // a table of offsets for a lookupswitch + code.emit4(-1); // leave space for default offset + if (opcode == tableswitch) { + code.emit4(lo); // minimum label + code.emit4(hi); // maximum label + for (long i = lo; i <= hi; i++) { // leave space for jump table + code.emit4(-1); + } + } else { + code.emit4(nlabels); // number of labels + for (int i = 0; i < nlabels; i++) { + code.emit4(-1); code.emit4(-1); // leave space for lookup table + } + offsets = new int[labels.length]; + } + Code.State stateSwitch = code.state.dup(); + code.markDead(); + + // For each case do: + l = cases; + for (int i = 0; i < labels.length; i++) { + JCCase c = l.head; + l = l.tail; + + int pc = code.entryPoint(stateSwitch); + // Insert offset directly into code or else into the + // offsets table. + if (i != defaultIndex) { + if (opcode == tableswitch) { + code.put4( + tableBase + 4 * (labels[i] - lo + 3), + pc - startpc); + } else { + offsets[i] = pc - startpc; + } + } else { + code.put4(tableBase, pc - startpc); + } + + // Generate code for the statements in this case. + genStats(c.stats, switchEnv, CRT_FLOW_TARGET); + } + + // Resolve all breaks. + code.resolve(switchEnv.info.exit); + + // If we have not set the default offset, we do so now. + if (code.get4(tableBase) == -1) { + code.put4(tableBase, code.entryPoint(stateSwitch) - startpc); + } + + if (opcode == tableswitch) { + // Let any unfilled slots point to the default case. + int defaultOffset = code.get4(tableBase); + for (long i = lo; i <= hi; i++) { + int t = (int)(tableBase + 4 * (i - lo + 3)); + if (code.get4(t) == -1) + code.put4(t, defaultOffset); + } + } else { + // Sort non-default offsets and copy into lookup table. + if (defaultIndex >= 0) + for (int i = defaultIndex; i < labels.length - 1; i++) { + labels[i] = labels[i+1]; + offsets[i] = offsets[i+1]; + } + if (nlabels > 0) + qsort2(labels, offsets, 0, nlabels - 1); + for (int i = 0; i < nlabels; i++) { + int caseidx = tableBase + 8 * (i + 1); + code.put4(caseidx, labels[i]); + code.put4(caseidx + 4, offsets[i]); + } + } + } + code.endScopes(limit); + } +//where + /** Sort (int) arrays of keys and values + */ + static void qsort2(int[] keys, int[] values, int lo, int hi) { + int i = lo; + int j = hi; + int pivot = keys[(i+j)/2]; + do { + while (keys[i] < pivot) i++; + while (pivot < keys[j]) j--; + if (i <= j) { + int temp1 = keys[i]; + keys[i] = keys[j]; + keys[j] = temp1; + int temp2 = values[i]; + values[i] = values[j]; + values[j] = temp2; + i++; + j--; + } + } while (i <= j); + if (lo < j) qsort2(keys, values, lo, j); + if (i < hi) qsort2(keys, values, i, hi); + } + + public void visitSynchronized(JCSynchronized tree) { + int limit = code.nextreg; + // Generate code to evaluate lock and save in temporary variable. + final LocalItem lockVar = makeTemp(syms.objectType); + genExpr(tree.lock, tree.lock.type).load().duplicate(); + lockVar.store(); + + // Generate code to enter monitor. + code.emitop0(monitorenter); + code.state.lock(lockVar.reg); + + // Generate code for a try statement with given body, no catch clauses + // in a new environment with the "exit-monitor" operation as finalizer. + final Env syncEnv = env.dup(tree, new GenContext()); + syncEnv.info.finalize = new GenFinalizer() { + void gen() { + genLast(); + Assert.check(syncEnv.info.gaps.length() % 2 == 0); + syncEnv.info.gaps.append(code.curPc()); + } + void genLast() { + if (code.isAlive()) { + lockVar.load(); + code.emitop0(monitorexit); + code.state.unlock(lockVar.reg); + } + } + }; + syncEnv.info.gaps = new ListBuffer(); + genTry(tree.body, List.nil(), syncEnv); + code.endScopes(limit); + } + + public void visitTry(final JCTry tree) { + // Generate code for a try statement with given body and catch clauses, + // in a new environment which calls the finally block if there is one. + final Env tryEnv = env.dup(tree, new GenContext()); + final Env oldEnv = env; + if (!useJsrLocally) { + useJsrLocally = + (stackMap == StackMapFormat.NONE) && + (jsrlimit <= 0 || + jsrlimit < 100 && + estimateCodeComplexity(tree.finalizer)>jsrlimit); + } + tryEnv.info.finalize = new GenFinalizer() { + void gen() { + if (useJsrLocally) { + if (tree.finalizer != null) { + Code.State jsrState = code.state.dup(); + jsrState.push(Code.jsrReturnValue); + tryEnv.info.cont = + new Chain(code.emitJump(jsr), + tryEnv.info.cont, + jsrState); + } + Assert.check(tryEnv.info.gaps.length() % 2 == 0); + tryEnv.info.gaps.append(code.curPc()); + } else { + Assert.check(tryEnv.info.gaps.length() % 2 == 0); + tryEnv.info.gaps.append(code.curPc()); + genLast(); + } + } + void genLast() { + if (tree.finalizer != null) + genStat(tree.finalizer, oldEnv, CRT_BLOCK); + } + boolean hasFinalizer() { + return tree.finalizer != null; + } + }; + tryEnv.info.gaps = new ListBuffer(); + genTry(tree.body, tree.catchers, tryEnv); + } + //where + /** Generate code for a try or synchronized statement + * @param body The body of the try or synchronized statement. + * @param catchers The lis of catch clauses. + * @param env the environment current for the body. + */ + void genTry(JCTree body, List catchers, Env env) { + int limit = code.nextreg; + int startpc = code.curPc(); + Code.State stateTry = code.state.dup(); + genStat(body, env, CRT_BLOCK); + int endpc = code.curPc(); + boolean hasFinalizer = + env.info.finalize != null && + env.info.finalize.hasFinalizer(); + List gaps = env.info.gaps.toList(); + code.statBegin(TreeInfo.endPos(body)); + genFinalizer(env); + code.statBegin(TreeInfo.endPos(env.tree)); + Chain exitChain = code.branch(goto_); + endFinalizerGap(env); + if (startpc != endpc) for (List l = catchers; l.nonEmpty(); l = l.tail) { + // start off with exception on stack + code.entryPoint(stateTry, l.head.param.sym.type); + genCatch(l.head, env, startpc, endpc, gaps); + genFinalizer(env); + if (hasFinalizer || l.tail.nonEmpty()) { + code.statBegin(TreeInfo.endPos(env.tree)); + exitChain = Code.mergeChains(exitChain, + code.branch(goto_)); + } + endFinalizerGap(env); + } + if (hasFinalizer) { + // Create a new register segement to avoid allocating + // the same variables in finalizers and other statements. + code.newRegSegment(); + + // Add a catch-all clause. + + // start off with exception on stack + int catchallpc = code.entryPoint(stateTry, syms.throwableType); + + // Register all exception ranges for catch all clause. + // The range of the catch all clause is from the beginning + // of the try or synchronized block until the present + // code pointer excluding all gaps in the current + // environment's GenContext. + int startseg = startpc; + while (env.info.gaps.nonEmpty()) { + int endseg = env.info.gaps.next().intValue(); + registerCatch(body.pos(), startseg, endseg, + catchallpc, 0); + startseg = env.info.gaps.next().intValue(); + } + code.statBegin(TreeInfo.finalizerPos(env.tree)); + code.markStatBegin(); + + Item excVar = makeTemp(syms.throwableType); + excVar.store(); + genFinalizer(env); + excVar.load(); + registerCatch(body.pos(), startseg, + env.info.gaps.next().intValue(), + catchallpc, 0); + code.emitop0(athrow); + code.markDead(); + + // If there are jsr's to this finalizer, ... + if (env.info.cont != null) { + // Resolve all jsr's. + code.resolve(env.info.cont); + + // Mark statement line number + code.statBegin(TreeInfo.finalizerPos(env.tree)); + code.markStatBegin(); + + // Save return address. + LocalItem retVar = makeTemp(syms.throwableType); + retVar.store(); + + // Generate finalizer code. + env.info.finalize.genLast(); + + // Return. + code.emitop1w(ret, retVar.reg); + code.markDead(); + } + } + + // Resolve all breaks. + code.resolve(exitChain); + + code.endScopes(limit); + } + + /** Generate code for a catch clause. + * @param tree The catch clause. + * @param env The environment current in the enclosing try. + * @param startpc Start pc of try-block. + * @param endpc End pc of try-block. + */ + void genCatch(JCCatch tree, + Env env, + int startpc, int endpc, + List gaps) { + if (startpc != endpc) { + List subClauses = TreeInfo.isMultiCatch(tree) ? + ((JCTypeUnion)tree.param.vartype).alternatives : + List.of(tree.param.vartype); + while (gaps.nonEmpty()) { + for (JCExpression subCatch : subClauses) { + int catchType = makeRef(tree.pos(), subCatch.type); + int end = gaps.head.intValue(); + registerCatch(tree.pos(), + startpc, end, code.curPc(), + catchType); + } + gaps = gaps.tail; + startpc = gaps.head.intValue(); + gaps = gaps.tail; + } + if (startpc < endpc) { + for (JCExpression subCatch : subClauses) { + int catchType = makeRef(tree.pos(), subCatch.type); + registerCatch(tree.pos(), + startpc, endpc, code.curPc(), + catchType); + } + } + VarSymbol exparam = tree.param.sym; + code.statBegin(tree.pos); + code.markStatBegin(); + int limit = code.nextreg; + int exlocal = code.newLocal(exparam); + items.makeLocalItem(exparam).store(); + code.statBegin(TreeInfo.firstStatPos(tree.body)); + genStat(tree.body, env, CRT_BLOCK); + code.endScopes(limit); + code.statBegin(TreeInfo.endPos(tree.body)); + } + } + + /** Register a catch clause in the "Exceptions" code-attribute. + */ + void registerCatch(DiagnosticPosition pos, + int startpc, int endpc, + int handler_pc, int catch_type) { + if (startpc != endpc) { + char startpc1 = (char)startpc; + char endpc1 = (char)endpc; + char handler_pc1 = (char)handler_pc; + if (startpc1 == startpc && + endpc1 == endpc && + handler_pc1 == handler_pc) { + code.addCatch(startpc1, endpc1, handler_pc1, + (char)catch_type); + } else { + if (!useJsrLocally && !target.generateStackMapTable()) { + useJsrLocally = true; + throw new CodeSizeOverflow(); + } else { + log.error(pos, "limit.code.too.large.for.try.stmt"); + nerrs++; + } + } + } + } + + /** Very roughly estimate the number of instructions needed for + * the given tree. + */ + int estimateCodeComplexity(JCTree tree) { + if (tree == null) return 0; + class ComplexityScanner extends TreeScanner { + int complexity = 0; + public void scan(JCTree tree) { + if (complexity > jsrlimit) return; + super.scan(tree); + } + public void visitClassDef(JCClassDecl tree) {} + public void visitDoLoop(JCDoWhileLoop tree) + { super.visitDoLoop(tree); complexity++; } + public void visitWhileLoop(JCWhileLoop tree) + { super.visitWhileLoop(tree); complexity++; } + public void visitForLoop(JCForLoop tree) + { super.visitForLoop(tree); complexity++; } + public void visitSwitch(JCSwitch tree) + { super.visitSwitch(tree); complexity+=5; } + public void visitCase(JCCase tree) + { super.visitCase(tree); complexity++; } + public void visitSynchronized(JCSynchronized tree) + { super.visitSynchronized(tree); complexity+=6; } + public void visitTry(JCTry tree) + { super.visitTry(tree); + if (tree.finalizer != null) complexity+=6; } + public void visitCatch(JCCatch tree) + { super.visitCatch(tree); complexity+=2; } + public void visitConditional(JCConditional tree) + { super.visitConditional(tree); complexity+=2; } + public void visitIf(JCIf tree) + { super.visitIf(tree); complexity+=2; } + // note: for break, continue, and return we don't take unwind() into account. + public void visitBreak(JCBreak tree) + { super.visitBreak(tree); complexity+=1; } + public void visitContinue(JCContinue tree) + { super.visitContinue(tree); complexity+=1; } + public void visitReturn(JCReturn tree) + { super.visitReturn(tree); complexity+=1; } + public void visitThrow(JCThrow tree) + { super.visitThrow(tree); complexity+=1; } + public void visitAssert(JCAssert tree) + { super.visitAssert(tree); complexity+=5; } + public void visitApply(JCMethodInvocation tree) + { super.visitApply(tree); complexity+=2; } + public void visitNewClass(JCNewClass tree) + { scan(tree.encl); scan(tree.args); complexity+=2; } + public void visitNewArray(JCNewArray tree) + { super.visitNewArray(tree); complexity+=5; } + public void visitAssign(JCAssign tree) + { super.visitAssign(tree); complexity+=1; } + public void visitAssignop(JCAssignOp tree) + { super.visitAssignop(tree); complexity+=2; } + public void visitUnary(JCUnary tree) + { complexity+=1; + if (tree.type.constValue() == null) super.visitUnary(tree); } + public void visitBinary(JCBinary tree) + { complexity+=1; + if (tree.type.constValue() == null) super.visitBinary(tree); } + public void visitTypeTest(JCInstanceOf tree) + { super.visitTypeTest(tree); complexity+=1; } + public void visitIndexed(JCArrayAccess tree) + { super.visitIndexed(tree); complexity+=1; } + public void visitSelect(JCFieldAccess tree) + { super.visitSelect(tree); + if (tree.sym.kind == VAR) complexity+=1; } + public void visitIdent(JCIdent tree) { + if (tree.sym.kind == VAR) { + complexity+=1; + if (tree.type.constValue() == null && + tree.sym.owner.kind == TYP) + complexity+=1; + } + } + public void visitLiteral(JCLiteral tree) + { complexity+=1; } + public void visitTree(JCTree tree) {} + public void visitWildcard(JCWildcard tree) { + throw new AssertionError(this.getClass().getName()); + } + } + ComplexityScanner scanner = new ComplexityScanner(); + tree.accept(scanner); + return scanner.complexity; + } + + public void visitIf(JCIf tree) { + int limit = code.nextreg; + Chain thenExit = null; + CondItem c = genCond(TreeInfo.skipParens(tree.cond), + CRT_FLOW_CONTROLLER); + Chain elseChain = c.jumpFalse(); + if (!c.isFalse()) { + code.resolve(c.trueJumps); + genStat(tree.thenpart, env, CRT_STATEMENT | CRT_FLOW_TARGET); + thenExit = code.branch(goto_); + } + if (elseChain != null) { + code.resolve(elseChain); + if (tree.elsepart != null) + genStat(tree.elsepart, env,CRT_STATEMENT | CRT_FLOW_TARGET); + } + code.resolve(thenExit); + code.endScopes(limit); + } + + public void visitExec(JCExpressionStatement tree) { + // Optimize x++ to ++x and x-- to --x. + JCExpression e = tree.expr; + switch (e.getTag()) { + case JCTree.POSTINC: + ((JCUnary) e).setTag(JCTree.PREINC); + break; + case JCTree.POSTDEC: + ((JCUnary) e).setTag(JCTree.PREDEC); + break; + } + genExpr(tree.expr, tree.expr.type).drop(); + } + + public void visitBreak(JCBreak tree) { + Env targetEnv = unwind(tree.target, env); + Assert.check(code.state.stacksize == 0); + targetEnv.info.addExit(code.branch(goto_)); + endFinalizerGaps(env, targetEnv); + } + + public void visitContinue(JCContinue tree) { + Env targetEnv = unwind(tree.target, env); + Assert.check(code.state.stacksize == 0); + targetEnv.info.addCont(code.branch(goto_)); + endFinalizerGaps(env, targetEnv); + } + + public void visitReturn(JCReturn tree) { + int limit = code.nextreg; + final Env targetEnv; + if (tree.expr != null) { + Item r = genExpr(tree.expr, pt).load(); + if (hasFinally(env.enclMethod, env)) { + r = makeTemp(pt); + r.store(); + } + targetEnv = unwind(env.enclMethod, env); + r.load(); + code.emitop0(ireturn + Code.truncate(Code.typecode(pt))); + } else { + targetEnv = unwind(env.enclMethod, env); + code.emitop0(return_); + } + endFinalizerGaps(env, targetEnv); + code.endScopes(limit); + } + + public void visitThrow(JCThrow tree) { + genExpr(tree.expr, tree.expr.type).load(); + code.emitop0(athrow); + } + +/* ************************************************************************ + * Visitor methods for expressions + *************************************************************************/ + + public void visitApply(JCMethodInvocation tree) { + // Generate code for method. + Item m = genExpr(tree.meth, methodType); + // Generate code for all arguments, where the expected types are + // the parameters of the method's external type (that is, any implicit + // outer instance of a super(...) call appears as first parameter). + genArgs(tree.args, + TreeInfo.symbol(tree.meth).externalType(types).getParameterTypes()); + result = m.invoke(); + } + + public void visitConditional(JCConditional tree) { + Chain thenExit = null; + CondItem c = genCond(tree.cond, CRT_FLOW_CONTROLLER); + Chain elseChain = c.jumpFalse(); + if (!c.isFalse()) { + code.resolve(c.trueJumps); + int startpc = genCrt ? code.curPc() : 0; + genExpr(tree.truepart, pt).load(); + code.state.forceStackTop(tree.type); + if (genCrt) code.crt.put(tree.truepart, CRT_FLOW_TARGET, + startpc, code.curPc()); + thenExit = code.branch(goto_); + } + if (elseChain != null) { + code.resolve(elseChain); + int startpc = genCrt ? code.curPc() : 0; + genExpr(tree.falsepart, pt).load(); + code.state.forceStackTop(tree.type); + if (genCrt) code.crt.put(tree.falsepart, CRT_FLOW_TARGET, + startpc, code.curPc()); + } + code.resolve(thenExit); + result = items.makeStackItem(pt); + } + + public void visitNewClass(JCNewClass tree) { + // Enclosing instances or anonymous classes should have been eliminated + // by now. + Assert.check(tree.encl == null && tree.def == null); + + code.emitop2(new_, makeRef(tree.pos(), tree.type)); + code.emitop0(dup); + + // Generate code for all arguments, where the expected types are + // the parameters of the constructor's external type (that is, + // any implicit outer instance appears as first parameter). + genArgs(tree.args, tree.constructor.externalType(types).getParameterTypes()); + + items.makeMemberItem(tree.constructor, true).invoke(); + result = items.makeStackItem(tree.type); + } + + public void visitNewArray(JCNewArray tree) { + + if (tree.elems != null) { + Type elemtype = types.elemtype(tree.type); + loadIntConst(tree.elems.length()); + Item arr = makeNewArray(tree.pos(), tree.type, 1); + int i = 0; + for (List l = tree.elems; l.nonEmpty(); l = l.tail) { + arr.duplicate(); + loadIntConst(i); + i++; + genExpr(l.head, elemtype).load(); + items.makeIndexedItem(elemtype).store(); + } + result = arr; + } else { + for (List l = tree.dims; l.nonEmpty(); l = l.tail) { + genExpr(l.head, syms.intType).load(); + } + result = makeNewArray(tree.pos(), tree.type, tree.dims.length()); + } + } +//where + /** Generate code to create an array with given element type and number + * of dimensions. + */ + Item makeNewArray(DiagnosticPosition pos, Type type, int ndims) { + Type elemtype = types.elemtype(type); + if (types.dimensions(type) > ClassFile.MAX_DIMENSIONS) { + log.error(pos, "limit.dimensions"); + nerrs++; + } + int elemcode = Code.arraycode(elemtype); + if (elemcode == 0 || (elemcode == 1 && ndims == 1)) { + code.emitAnewarray(makeRef(pos, elemtype), type); + } else if (elemcode == 1) { + code.emitMultianewarray(ndims, makeRef(pos, type), type); + } else { + code.emitNewarray(elemcode, type); + } + return items.makeStackItem(type); + } + + public void visitParens(JCParens tree) { + result = genExpr(tree.expr, tree.expr.type); + } + + public void visitAssign(JCAssign tree) { + Item l = genExpr(tree.lhs, tree.lhs.type); + genExpr(tree.rhs, tree.lhs.type).load(); + result = items.makeAssignItem(l); + } + + public void visitAssignop(JCAssignOp tree) { + OperatorSymbol operator = (OperatorSymbol) tree.operator; + Item l; + if (operator.opcode == string_add) { + // Generate code to make a string buffer + makeStringBuffer(tree.pos()); + + // Generate code for first string, possibly save one + // copy under buffer + l = genExpr(tree.lhs, tree.lhs.type); + if (l.width() > 0) { + code.emitop0(dup_x1 + 3 * (l.width() - 1)); + } + + // Load first string and append to buffer. + l.load(); + appendString(tree.lhs); + + // Append all other strings to buffer. + appendStrings(tree.rhs); + + // Convert buffer to string. + bufferToString(tree.pos()); + } else { + // Generate code for first expression + l = genExpr(tree.lhs, tree.lhs.type); + + // If we have an increment of -32768 to +32767 of a local + // int variable we can use an incr instruction instead of + // proceeding further. + if ((tree.getTag() == JCTree.PLUS_ASG || tree.getTag() == JCTree.MINUS_ASG) && + l instanceof LocalItem && + tree.lhs.type.tag <= INT && + tree.rhs.type.tag <= INT && + tree.rhs.type.constValue() != null) { + int ival = ((Number) tree.rhs.type.constValue()).intValue(); + if (tree.getTag() == JCTree.MINUS_ASG) ival = -ival; + ((LocalItem)l).incr(ival); + result = l; + return; + } + // Otherwise, duplicate expression, load one copy + // and complete binary operation. + l.duplicate(); + l.coerce(operator.type.getParameterTypes().head).load(); + completeBinop(tree.lhs, tree.rhs, operator).coerce(tree.lhs.type); + } + result = items.makeAssignItem(l); + } + + public void visitUnary(JCUnary tree) { + OperatorSymbol operator = (OperatorSymbol)tree.operator; + if (tree.getTag() == JCTree.NOT) { + CondItem od = genCond(tree.arg, false); + result = od.negate(); + } else { + Item od = genExpr(tree.arg, operator.type.getParameterTypes().head); + switch (tree.getTag()) { + case JCTree.POS: + result = od.load(); + break; + case JCTree.NEG: + result = od.load(); + code.emitop0(operator.opcode); + break; + case JCTree.COMPL: + result = od.load(); + emitMinusOne(od.typecode); + code.emitop0(operator.opcode); + break; + case JCTree.PREINC: case JCTree.PREDEC: + od.duplicate(); + if (od instanceof LocalItem && + (operator.opcode == iadd || operator.opcode == isub)) { + ((LocalItem)od).incr(tree.getTag() == JCTree.PREINC ? 1 : -1); + result = od; + } else { + od.load(); + code.emitop0(one(od.typecode)); + code.emitop0(operator.opcode); + // Perform narrowing primitive conversion if byte, + // char, or short. Fix for 4304655. + if (od.typecode != INTcode && + Code.truncate(od.typecode) == INTcode) + code.emitop0(int2byte + od.typecode - BYTEcode); + result = items.makeAssignItem(od); + } + break; + case JCTree.POSTINC: case JCTree.POSTDEC: + od.duplicate(); + if (od instanceof LocalItem && + (operator.opcode == iadd || operator.opcode == isub)) { + Item res = od.load(); + ((LocalItem)od).incr(tree.getTag() == JCTree.POSTINC ? 1 : -1); + result = res; + } else { + Item res = od.load(); + od.stash(od.typecode); + code.emitop0(one(od.typecode)); + code.emitop0(operator.opcode); + // Perform narrowing primitive conversion if byte, + // char, or short. Fix for 4304655. + if (od.typecode != INTcode && + Code.truncate(od.typecode) == INTcode) + code.emitop0(int2byte + od.typecode - BYTEcode); + od.store(); + result = res; + } + break; + case JCTree.NULLCHK: + result = od.load(); + code.emitop0(dup); + genNullCheck(tree.pos()); + break; + default: + Assert.error(); + } + } + } + + /** Generate a null check from the object value at stack top. */ + private void genNullCheck(DiagnosticPosition pos) { + callMethod(pos, syms.objectType, names.getClass, + List.nil(), false); + code.emitop0(pop); + } + + public void visitBinary(JCBinary tree) { + OperatorSymbol operator = (OperatorSymbol)tree.operator; + if (operator.opcode == string_add) { + // Create a string buffer. + makeStringBuffer(tree.pos()); + // Append all strings to buffer. + appendStrings(tree); + // Convert buffer to string. + bufferToString(tree.pos()); + result = items.makeStackItem(syms.stringType); + } else if (tree.getTag() == JCTree.AND) { + CondItem lcond = genCond(tree.lhs, CRT_FLOW_CONTROLLER); + if (!lcond.isFalse()) { + Chain falseJumps = lcond.jumpFalse(); + code.resolve(lcond.trueJumps); + CondItem rcond = genCond(tree.rhs, CRT_FLOW_TARGET); + result = items. + makeCondItem(rcond.opcode, + rcond.trueJumps, + Code.mergeChains(falseJumps, + rcond.falseJumps)); + } else { + result = lcond; + } + } else if (tree.getTag() == JCTree.OR) { + CondItem lcond = genCond(tree.lhs, CRT_FLOW_CONTROLLER); + if (!lcond.isTrue()) { + Chain trueJumps = lcond.jumpTrue(); + code.resolve(lcond.falseJumps); + CondItem rcond = genCond(tree.rhs, CRT_FLOW_TARGET); + result = items. + makeCondItem(rcond.opcode, + Code.mergeChains(trueJumps, rcond.trueJumps), + rcond.falseJumps); + } else { + result = lcond; + } + } else { + Item od = genExpr(tree.lhs, operator.type.getParameterTypes().head); + od.load(); + result = completeBinop(tree.lhs, tree.rhs, operator); + } + } +//where + /** Make a new string buffer. + */ + void makeStringBuffer(DiagnosticPosition pos) { + code.emitop2(new_, makeRef(pos, stringBufferType)); + code.emitop0(dup); + callMethod( + pos, stringBufferType, names.init, List.nil(), false); + } + + /** Append value (on tos) to string buffer (on tos - 1). + */ + void appendString(JCTree tree) { + Type t = tree.type.baseType(); + if (t.tag > lastBaseTag && t.tsym != syms.stringType.tsym) { + t = syms.objectType; + } + items.makeMemberItem(getStringBufferAppend(tree, t), false).invoke(); + } + Symbol getStringBufferAppend(JCTree tree, Type t) { + Assert.checkNull(t.constValue()); + Symbol method = stringBufferAppend.get(t); + if (method == null) { + method = rs.resolveInternalMethod(tree.pos(), + attrEnv, + stringBufferType, + names.append, + List.of(t), + null); + stringBufferAppend.put(t, method); + } + return method; + } + + /** Add all strings in tree to string buffer. + */ + void appendStrings(JCTree tree) { + tree = TreeInfo.skipParens(tree); + if (tree.getTag() == JCTree.PLUS && tree.type.constValue() == null) { + JCBinary op = (JCBinary) tree; + if (op.operator.kind == MTH && + ((OperatorSymbol) op.operator).opcode == string_add) { + appendStrings(op.lhs); + appendStrings(op.rhs); + return; + } + } + genExpr(tree, tree.type).load(); + appendString(tree); + } + + /** Convert string buffer on tos to string. + */ + void bufferToString(DiagnosticPosition pos) { + callMethod( + pos, + stringBufferType, + names.toString, + List.nil(), + false); + } + + /** Complete generating code for operation, with left operand + * already on stack. + * @param lhs The tree representing the left operand. + * @param rhs The tree representing the right operand. + * @param operator The operator symbol. + */ + Item completeBinop(JCTree lhs, JCTree rhs, OperatorSymbol operator) { + MethodType optype = (MethodType)operator.type; + int opcode = operator.opcode; + if (opcode >= if_icmpeq && opcode <= if_icmple && + rhs.type.constValue() instanceof Number && + ((Number) rhs.type.constValue()).intValue() == 0) { + opcode = opcode + (ifeq - if_icmpeq); + } else if (opcode >= if_acmpeq && opcode <= if_acmpne && + TreeInfo.isNull(rhs)) { + opcode = opcode + (if_acmp_null - if_acmpeq); + } else { + // The expected type of the right operand is + // the second parameter type of the operator, except for + // shifts with long shiftcount, where we convert the opcode + // to a short shift and the expected type to int. + Type rtype = operator.erasure(types).getParameterTypes().tail.head; + if (opcode >= ishll && opcode <= lushrl) { + opcode = opcode + (ishl - ishll); + rtype = syms.intType; + } + // Generate code for right operand and load. + genExpr(rhs, rtype).load(); + // If there are two consecutive opcode instructions, + // emit the first now. + if (opcode >= (1 << preShift)) { + code.emitop0(opcode >> preShift); + opcode = opcode & 0xFF; + } + } + if (opcode >= ifeq && opcode <= if_acmpne || + opcode == if_acmp_null || opcode == if_acmp_nonnull) { + return items.makeCondItem(opcode); + } else { + code.emitop0(opcode); + return items.makeStackItem(optype.restype); + } + } + + public void visitTypeCast(JCTypeCast tree) { + result = genExpr(tree.expr, tree.clazz.type).load(); + // Additional code is only needed if we cast to a reference type + // which is not statically a supertype of the expression's type. + // For basic types, the coerce(...) in genExpr(...) will do + // the conversion. + if (tree.clazz.type.tag > lastBaseTag && + types.asSuper(tree.expr.type, tree.clazz.type.tsym) == null) { + code.emitop2(checkcast, makeRef(tree.pos(), tree.clazz.type)); + } + } + + public void visitWildcard(JCWildcard tree) { + throw new AssertionError(this.getClass().getName()); + } + + public void visitTypeTest(JCInstanceOf tree) { + genExpr(tree.expr, tree.expr.type).load(); + code.emitop2(instanceof_, makeRef(tree.pos(), tree.clazz.type)); + result = items.makeStackItem(syms.booleanType); + } + + public void visitIndexed(JCArrayAccess tree) { + genExpr(tree.indexed, tree.indexed.type).load(); + genExpr(tree.index, syms.intType).load(); + result = items.makeIndexedItem(tree.type); + } + + public void visitIdent(JCIdent tree) { + Symbol sym = tree.sym; + if (tree.name == names._this || tree.name == names._super) { + Item res = tree.name == names._this + ? items.makeThisItem() + : items.makeSuperItem(); + if (sym.kind == MTH) { + // Generate code to address the constructor. + res.load(); + res = items.makeMemberItem(sym, true); + } + result = res; + } else if (sym.kind == VAR && sym.owner.kind == MTH) { + result = items.makeLocalItem((VarSymbol)sym); + } else if ((sym.flags() & STATIC) != 0) { + if (!isAccessSuper(env.enclMethod)) + sym = binaryQualifier(sym, env.enclClass.type); + result = items.makeStaticItem(sym); + } else { + items.makeThisItem().load(); + sym = binaryQualifier(sym, env.enclClass.type); + result = items.makeMemberItem(sym, (sym.flags() & PRIVATE) != 0); + } + } + + public void visitSelect(JCFieldAccess tree) { + Symbol sym = tree.sym; + + if (tree.name == names._class) { + Assert.check(target.hasClassLiterals()); + code.emitop2(ldc2, makeRef(tree.pos(), tree.selected.type)); + result = items.makeStackItem(pt); + return; + } + + Symbol ssym = TreeInfo.symbol(tree.selected); + + // Are we selecting via super? + boolean selectSuper = + ssym != null && (ssym.kind == TYP || ssym.name == names._super); + + // Are we accessing a member of the superclass in an access method + // resulting from a qualified super? + boolean accessSuper = isAccessSuper(env.enclMethod); + + Item base = (selectSuper) + ? items.makeSuperItem() + : genExpr(tree.selected, tree.selected.type); + + if (sym.kind == VAR && ((VarSymbol) sym).getConstValue() != null) { + // We are seeing a variable that is constant but its selecting + // expression is not. + if ((sym.flags() & STATIC) != 0) { + if (!selectSuper && (ssym == null || ssym.kind != TYP)) + base = base.load(); + base.drop(); + } else { + base.load(); + genNullCheck(tree.selected.pos()); + } + result = items. + makeImmediateItem(sym.type, ((VarSymbol) sym).getConstValue()); + } else { + if (!accessSuper) + sym = binaryQualifier(sym, tree.selected.type); + if ((sym.flags() & STATIC) != 0) { + if (!selectSuper && (ssym == null || ssym.kind != TYP)) + base = base.load(); + base.drop(); + result = items.makeStaticItem(sym); + } else { + base.load(); + if (sym == syms.lengthVar) { + code.emitop0(arraylength); + result = items.makeStackItem(syms.intType); + } else { + result = items. + makeMemberItem(sym, + (sym.flags() & PRIVATE) != 0 || + selectSuper || accessSuper); + } + } + } + } + + public void visitLiteral(JCLiteral tree) { + if (tree.type.tag == TypeTags.BOT) { + code.emitop0(aconst_null); + if (types.dimensions(pt) > 1) { + code.emitop2(checkcast, makeRef(tree.pos(), pt)); + result = items.makeStackItem(pt); + } else { + result = items.makeStackItem(tree.type); + } + } + else + result = items.makeImmediateItem(tree.type, tree.value); + } + + public void visitLetExpr(LetExpr tree) { + int limit = code.nextreg; + genStats(tree.defs, env); + result = genExpr(tree.expr, tree.expr.type).load(); + code.endScopes(limit); + } + +/* ************************************************************************ + * main method + *************************************************************************/ + + /** Generate code for a class definition. + * @param env The attribution environment that belongs to the + * outermost class containing this class definition. + * We need this for resolving some additional symbols. + * @param cdef The tree representing the class definition. + * @return True if code is generated with no errors. + */ + public boolean genClass(Env env, JCClassDecl cdef) { + try { + attrEnv = env; + ClassSymbol c = cdef.sym; + this.toplevel = env.toplevel; + this.endPositions = toplevel.endPositions; + // If this is a class definition requiring Miranda methods, + // add them. + if (generateIproxies && + (c.flags() & (INTERFACE|ABSTRACT)) == ABSTRACT + && !allowGenerics // no Miranda methods available with generics + ) + implementInterfaceMethods(c); + cdef.defs = normalizeDefs(cdef.defs, c); + c.pool = pool; + pool.reset(); + Env localEnv = + new Env(cdef, new GenContext()); + localEnv.toplevel = env.toplevel; + localEnv.enclClass = cdef; + for (List l = cdef.defs; l.nonEmpty(); l = l.tail) { + genDef(l.head, localEnv); + } + if (pool.numEntries() > Pool.MAX_ENTRIES) { + log.error(cdef.pos(), "limit.pool"); + nerrs++; + } + if (nerrs != 0) { + // if errors, discard code + for (List l = cdef.defs; l.nonEmpty(); l = l.tail) { + if (l.head.getTag() == JCTree.METHODDEF) + ((JCMethodDecl) l.head).sym.code = null; + } + } + cdef.defs = List.nil(); // discard trees + return nerrs == 0; + } finally { + // note: this method does NOT support recursion. + attrEnv = null; + this.env = null; + toplevel = null; + endPositions = null; + nerrs = 0; + } + } + +/* ************************************************************************ + * Auxiliary classes + *************************************************************************/ + + /** An abstract class for finalizer generation. + */ + abstract class GenFinalizer { + /** Generate code to clean up when unwinding. */ + abstract void gen(); + + /** Generate code to clean up at last. */ + abstract void genLast(); + + /** Does this finalizer have some nontrivial cleanup to perform? */ + boolean hasFinalizer() { return true; } + } + + /** code generation contexts, + * to be used as type parameter for environments. + */ + static class GenContext { + + /** A chain for all unresolved jumps that exit the current environment. + */ + Chain exit = null; + + /** A chain for all unresolved jumps that continue in the + * current environment. + */ + Chain cont = null; + + /** A closure that generates the finalizer of the current environment. + * Only set for Synchronized and Try contexts. + */ + GenFinalizer finalize = null; + + /** Is this a switch statement? If so, allocate registers + * even when the variable declaration is unreachable. + */ + boolean isSwitch = false; + + /** A list buffer containing all gaps in the finalizer range, + * where a catch all exception should not apply. + */ + ListBuffer gaps = null; + + /** Add given chain to exit chain. + */ + void addExit(Chain c) { + exit = Code.mergeChains(c, exit); + } + + /** Add given chain to cont chain. + */ + void addCont(Chain c) { + cont = Code.mergeChains(c, cont); + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/jvm/Items.java b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/Items.java new file mode 100644 index 0000000..5550239 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/Items.java @@ -0,0 +1,796 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.jvm; + +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.code.Type.*; +import com.sun.tools.javac.jvm.Code.*; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.Assert; + +import static com.sun.tools.javac.jvm.ByteCodes.*; + +/** A helper class for code generation. Items are objects + * that stand for addressable entities in the bytecode. Each item + * supports a fixed protocol for loading the item on the stack, storing + * into it, converting it into a jump condition, and several others. + * There are many individual forms of items, such as local, static, + * indexed, or instance variables, values on the top of stack, the + * special values this or super, etc. Individual items are represented as + * inner classes in class Items. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Items { + + /** The current constant pool. + */ + Pool pool; + + /** The current code buffer. + */ + Code code; + + /** The current symbol table. + */ + Symtab syms; + + /** Type utilities. */ + Types types; + + /** Items that exist only once (flyweight pattern). + */ + private final Item voidItem; + private final Item thisItem; + private final Item superItem; + private final Item[] stackItem = new Item[TypeCodeCount]; + + public Items(Pool pool, Code code, Symtab syms, Types types) { + this.code = code; + this.pool = pool; + this.types = types; + voidItem = new Item(VOIDcode) { + public String toString() { return "void"; } + }; + thisItem = new SelfItem(false); + superItem = new SelfItem(true); + for (int i = 0; i < VOIDcode; i++) stackItem[i] = new StackItem(i); + stackItem[VOIDcode] = voidItem; + this.syms = syms; + } + + /** Make a void item + */ + Item makeVoidItem() { + return voidItem; + } + /** Make an item representing `this'. + */ + Item makeThisItem() { + return thisItem; + } + + /** Make an item representing `super'. + */ + Item makeSuperItem() { + return superItem; + } + + /** Make an item representing a value on stack. + * @param type The value's type. + */ + Item makeStackItem(Type type) { + return stackItem[Code.typecode(type)]; + } + + /** Make an item representing an indexed expression. + * @param type The expression's type. + */ + Item makeIndexedItem(Type type) { + return new IndexedItem(type); + } + + /** Make an item representing a local variable. + * @param v The represented variable. + */ + LocalItem makeLocalItem(VarSymbol v) { + return new LocalItem(v.erasure(types), v.adr); + } + + /** Make an item representing a local anonymous variable. + * @param type The represented variable's type. + * @param reg The represented variable's register. + */ + private LocalItem makeLocalItem(Type type, int reg) { + return new LocalItem(type, reg); + } + + /** Make an item representing a static variable or method. + * @param member The represented symbol. + */ + Item makeStaticItem(Symbol member) { + return new StaticItem(member); + } + + /** Make an item representing an instance variable or method. + * @param member The represented symbol. + * @param nonvirtual Is the reference not virtual? (true for constructors + * and private members). + */ + Item makeMemberItem(Symbol member, boolean nonvirtual) { + return new MemberItem(member, nonvirtual); + } + + /** Make an item representing a literal. + * @param type The literal's type. + * @param value The literal's value. + */ + Item makeImmediateItem(Type type, Object value) { + return new ImmediateItem(type, value); + } + + /** Make an item representing an assignment expression. + * @param lhs The item representing the assignment's left hand side. + */ + Item makeAssignItem(Item lhs) { + return new AssignItem(lhs); + } + + /** Make an item representing a conditional or unconditional jump. + * @param opcode The jump's opcode. + * @param trueJumps A chain encomassing all jumps that can be taken + * if the condition evaluates to true. + * @param falseJumps A chain encomassing all jumps that can be taken + * if the condition evaluates to false. + */ + CondItem makeCondItem(int opcode, Chain trueJumps, Chain falseJumps) { + return new CondItem(opcode, trueJumps, falseJumps); + } + + /** Make an item representing a conditional or unconditional jump. + * @param opcode The jump's opcode. + */ + CondItem makeCondItem(int opcode) { + return makeCondItem(opcode, null, null); + } + + /** The base class of all items, which implements default behavior. + */ + abstract class Item { + + /** The type code of values represented by this item. + */ + int typecode; + + Item(int typecode) { + this.typecode = typecode; + } + + /** Generate code to load this item onto stack. + */ + Item load() { + throw new AssertionError(); + } + + /** Generate code to store top of stack into this item. + */ + void store() { + throw new AssertionError("store unsupported: " + this); + } + + /** Generate code to invoke method represented by this item. + */ + Item invoke() { + throw new AssertionError(this); + } + + /** Generate code to use this item twice. + */ + void duplicate() {} + + /** Generate code to avoid having to use this item. + */ + void drop() {} + + /** Generate code to stash a copy of top of stack - of typecode toscode - + * under this item. + */ + void stash(int toscode) { + stackItem[toscode].duplicate(); + } + + /** Generate code to turn item into a testable condition. + */ + CondItem mkCond() { + load(); + return makeCondItem(ifne); + } + + /** Generate code to coerce item to given type code. + * @param targetcode The type code to coerce to. + */ + Item coerce(int targetcode) { + if (typecode == targetcode) + return this; + else { + load(); + int typecode1 = Code.truncate(typecode); + int targetcode1 = Code.truncate(targetcode); + if (typecode1 != targetcode1) { + int offset = targetcode1 > typecode1 ? targetcode1 - 1 + : targetcode1; + code.emitop0(i2l + typecode1 * 3 + offset); + } + if (targetcode != targetcode1) { + code.emitop0(int2byte + targetcode - BYTEcode); + } + return stackItem[targetcode]; + } + } + + /** Generate code to coerce item to given type. + * @param targettype The type to coerce to. + */ + Item coerce(Type targettype) { + return coerce(Code.typecode(targettype)); + } + + /** Return the width of this item on stack as a number of words. + */ + int width() { + return 0; + } + + public abstract String toString(); + } + + /** An item representing a value on stack. + */ + class StackItem extends Item { + + StackItem(int typecode) { + super(typecode); + } + + Item load() { + return this; + } + + void duplicate() { + code.emitop0(width() == 2 ? dup2 : dup); + } + + void drop() { + code.emitop0(width() == 2 ? pop2 : pop); + } + + void stash(int toscode) { + code.emitop0( + (width() == 2 ? dup_x2 : dup_x1) + 3 * (Code.width(toscode) - 1)); + } + + int width() { + return Code.width(typecode); + } + + public String toString() { + return "stack(" + typecodeNames[typecode] + ")"; + } + } + + /** An item representing an indexed expression. + */ + class IndexedItem extends Item { + + IndexedItem(Type type) { + super(Code.typecode(type)); + } + + Item load() { + code.emitop0(iaload + typecode); + return stackItem[typecode]; + } + + void store() { + code.emitop0(iastore + typecode); + } + + void duplicate() { + code.emitop0(dup2); + } + + void drop() { + code.emitop0(pop2); + } + + void stash(int toscode) { + code.emitop0(dup_x2 + 3 * (Code.width(toscode) - 1)); + } + + int width() { + return 2; + } + + public String toString() { + return "indexed(" + ByteCodes.typecodeNames[typecode] + ")"; + } + } + + /** An item representing `this' or `super'. + */ + class SelfItem extends Item { + + /** Flag which determines whether this item represents `this' or `super'. + */ + boolean isSuper; + + SelfItem(boolean isSuper) { + super(OBJECTcode); + this.isSuper = isSuper; + } + + Item load() { + code.emitop0(aload_0); + return stackItem[typecode]; + } + + public String toString() { + return isSuper ? "super" : "this"; + } + } + + /** An item representing a local variable. + */ + class LocalItem extends Item { + + /** The variable's register. + */ + int reg; + + /** The variable's type. + */ + Type type; + + LocalItem(Type type, int reg) { + super(Code.typecode(type)); + Assert.check(reg >= 0); + this.type = type; + this.reg = reg; + } + + Item load() { + if (reg <= 3) + code.emitop0(iload_0 + Code.truncate(typecode) * 4 + reg); + else + code.emitop1w(iload + Code.truncate(typecode), reg); + return stackItem[typecode]; + } + + void store() { + if (reg <= 3) + code.emitop0(istore_0 + Code.truncate(typecode) * 4 + reg); + else + code.emitop1w(istore + Code.truncate(typecode), reg); + code.setDefined(reg); + } + + void incr(int x) { + if (typecode == INTcode && x >= -32768 && x <= 32767) { + code.emitop1w(iinc, reg, x); + } else { + load(); + if (x >= 0) { + makeImmediateItem(syms.intType, x).load(); + code.emitop0(iadd); + } else { + makeImmediateItem(syms.intType, -x).load(); + code.emitop0(isub); + } + makeStackItem(syms.intType).coerce(typecode); + store(); + } + } + + public String toString() { + return "localItem(type=" + type + "; reg=" + reg + ")"; + } + } + + /** An item representing a static variable or method. + */ + class StaticItem extends Item { + + /** The represented symbol. + */ + Symbol member; + + StaticItem(Symbol member) { + super(Code.typecode(member.erasure(types))); + this.member = member; + } + + Item load() { + code.emitop2(getstatic, pool.put(member)); + return stackItem[typecode]; + } + + void store() { + code.emitop2(putstatic, pool.put(member)); + } + + Item invoke() { + MethodType mtype = (MethodType)member.erasure(types); + int rescode = Code.typecode(mtype.restype); + code.emitInvokestatic(pool.put(member), mtype); + return stackItem[rescode]; + } + + public String toString() { + return "static(" + member + ")"; + } + } + + /** An item representing an instance variable or method. + */ + class MemberItem extends Item { + + /** The represented symbol. + */ + Symbol member; + + /** Flag that determines whether or not access is virtual. + */ + boolean nonvirtual; + + MemberItem(Symbol member, boolean nonvirtual) { + super(Code.typecode(member.erasure(types))); + this.member = member; + this.nonvirtual = nonvirtual; + } + + Item load() { + code.emitop2(getfield, pool.put(member)); + return stackItem[typecode]; + } + + void store() { + code.emitop2(putfield, pool.put(member)); + } + + Item invoke() { + MethodType mtype = (MethodType)member.externalType(types); + int rescode = Code.typecode(mtype.restype); + if ((member.owner.flags() & Flags.INTERFACE) != 0) { + code.emitInvokeinterface(pool.put(member), mtype); + } else if (nonvirtual) { + code.emitInvokespecial(pool.put(member), mtype); + } else { + code.emitInvokevirtual(pool.put(member), mtype); + } + return stackItem[rescode]; + } + + void duplicate() { + stackItem[OBJECTcode].duplicate(); + } + + void drop() { + stackItem[OBJECTcode].drop(); + } + + void stash(int toscode) { + stackItem[OBJECTcode].stash(toscode); + } + + int width() { + return 1; + } + + public String toString() { + return "member(" + member + (nonvirtual ? " nonvirtual)" : ")"); + } + } + + /** An item representing a literal. + */ + class ImmediateItem extends Item { + + /** The literal's value. + */ + Object value; + + ImmediateItem(Type type, Object value) { + super(Code.typecode(type)); + this.value = value; + } + + private void ldc() { + int idx = pool.put(value); + if (typecode == LONGcode || typecode == DOUBLEcode) { + code.emitop2(ldc2w, idx); + } else if (idx <= 255) { + code.emitop1(ldc1, idx); + } else { + code.emitop2(ldc2, idx); + } + } + + Item load() { + switch (typecode) { + case INTcode: case BYTEcode: case SHORTcode: case CHARcode: + int ival = ((Number)value).intValue(); + if (-1 <= ival && ival <= 5) + code.emitop0(iconst_0 + ival); + else if (Byte.MIN_VALUE <= ival && ival <= Byte.MAX_VALUE) + code.emitop1(bipush, ival); + else if (Short.MIN_VALUE <= ival && ival <= Short.MAX_VALUE) + code.emitop2(sipush, ival); + else + ldc(); + break; + case LONGcode: + long lval = ((Number)value).longValue(); + if (lval == 0 || lval == 1) + code.emitop0(lconst_0 + (int)lval); + else + ldc(); + break; + case FLOATcode: + float fval = ((Number)value).floatValue(); + if (isPosZero(fval) || fval == 1.0 || fval == 2.0) + code.emitop0(fconst_0 + (int)fval); + else { + ldc(); + } + break; + case DOUBLEcode: + double dval = ((Number)value).doubleValue(); + if (isPosZero(dval) || dval == 1.0) + code.emitop0(dconst_0 + (int)dval); + else + ldc(); + break; + case OBJECTcode: + ldc(); + break; + default: + Assert.error(); + } + return stackItem[typecode]; + } + //where + /** Return true iff float number is positive 0. + */ + private boolean isPosZero(float x) { + return x == 0.0f && 1.0f / x > 0.0f; + } + /** Return true iff double number is positive 0. + */ + private boolean isPosZero(double x) { + return x == 0.0d && 1.0d / x > 0.0d; + } + + CondItem mkCond() { + int ival = ((Number)value).intValue(); + return makeCondItem(ival != 0 ? goto_ : dontgoto); + } + + Item coerce(int targetcode) { + if (typecode == targetcode) { + return this; + } else { + switch (targetcode) { + case INTcode: + if (Code.truncate(typecode) == INTcode) + return this; + else + return new ImmediateItem( + syms.intType, + ((Number)value).intValue()); + case LONGcode: + return new ImmediateItem( + syms.longType, + ((Number)value).longValue()); + case FLOATcode: + return new ImmediateItem( + syms.floatType, + ((Number)value).floatValue()); + case DOUBLEcode: + return new ImmediateItem( + syms.doubleType, + ((Number)value).doubleValue()); + case BYTEcode: + return new ImmediateItem( + syms.byteType, + (int)(byte)((Number)value).intValue()); + case CHARcode: + return new ImmediateItem( + syms.charType, + (int)(char)((Number)value).intValue()); + case SHORTcode: + return new ImmediateItem( + syms.shortType, + (int)(short)((Number)value).intValue()); + default: + return super.coerce(targetcode); + } + } + } + + public String toString() { + return "immediate(" + value + ")"; + } + } + + /** An item representing an assignment expressions. + */ + class AssignItem extends Item { + + /** The item representing the assignment's left hand side. + */ + Item lhs; + + AssignItem(Item lhs) { + super(lhs.typecode); + this.lhs = lhs; + } + + Item load() { + lhs.stash(typecode); + lhs.store(); + return stackItem[typecode]; + } + + void duplicate() { + load().duplicate(); + } + + void drop() { + lhs.store(); + } + + void stash(int toscode) { + Assert.error(); + } + + int width() { + return lhs.width() + Code.width(typecode); + } + + public String toString() { + return "assign(lhs = " + lhs + ")"; + } + } + + /** An item representing a conditional or unconditional jump. + */ + class CondItem extends Item { + + /** A chain encomassing all jumps that can be taken + * if the condition evaluates to true. + */ + Chain trueJumps; + + /** A chain encomassing all jumps that can be taken + * if the condition evaluates to false. + */ + Chain falseJumps; + + /** The jump's opcode. + */ + int opcode; + + /* + * An abstract syntax tree of this item. It is needed + * for branch entries in 'CharacterRangeTable' attribute. + */ + JCTree tree; + + CondItem(int opcode, Chain truejumps, Chain falsejumps) { + super(BYTEcode); + this.opcode = opcode; + this.trueJumps = truejumps; + this.falseJumps = falsejumps; + } + + Item load() { + Chain trueChain = null; + Chain falseChain = jumpFalse(); + if (!isFalse()) { + code.resolve(trueJumps); + code.emitop0(iconst_1); + trueChain = code.branch(goto_); + } + if (falseChain != null) { + code.resolve(falseChain); + code.emitop0(iconst_0); + } + code.resolve(trueChain); + return stackItem[typecode]; + } + + void duplicate() { + load().duplicate(); + } + + void drop() { + load().drop(); + } + + void stash(int toscode) { + Assert.error(); + } + + CondItem mkCond() { + return this; + } + + Chain jumpTrue() { + if (tree == null) return Code.mergeChains(trueJumps, code.branch(opcode)); + // we should proceed further in -Xjcov mode only + int startpc = code.curPc(); + Chain c = Code.mergeChains(trueJumps, code.branch(opcode)); + code.crt.put(tree, CRTable.CRT_BRANCH_TRUE, startpc, code.curPc()); + return c; + } + + Chain jumpFalse() { + if (tree == null) return Code.mergeChains(falseJumps, code.branch(Code.negate(opcode))); + // we should proceed further in -Xjcov mode only + int startpc = code.curPc(); + Chain c = Code.mergeChains(falseJumps, code.branch(Code.negate(opcode))); + code.crt.put(tree, CRTable.CRT_BRANCH_FALSE, startpc, code.curPc()); + return c; + } + + CondItem negate() { + CondItem c = new CondItem(Code.negate(opcode), falseJumps, trueJumps); + c.tree = tree; + return c; + } + + int width() { + // a CondItem doesn't have a size on the stack per se. + throw new AssertionError(); + } + + boolean isTrue() { + return falseJumps == null && opcode == goto_; + } + + boolean isFalse() { + return trueJumps == null && opcode == dontgoto; + } + + public String toString() { + return "cond(" + Code.mnem(opcode) + ")"; + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/jvm/Pool.java b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/Pool.java new file mode 100644 index 0000000..a22c3a7 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/Pool.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 1999, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.jvm; + +import java.util.*; + +import com.sun.tools.javac.code.Symbol.*; + +/** An internal structure that corresponds to the constant pool of a classfile. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Pool { + + public static final int MAX_ENTRIES = 0xFFFF; + public static final int MAX_STRING_LENGTH = 0xFFFF; + + /** Index of next constant to be entered. + */ + int pp; + + /** The initial pool buffer. + */ + Object[] pool; + + /** A hashtable containing all constants in the pool. + */ + Map indices; + + /** Construct a pool with given number of elements and element array. + */ + public Pool(int pp, Object[] pool) { + this.pp = pp; + this.pool = pool; + this.indices = new HashMap(pool.length); + for (int i = 1; i < pp; i++) { + if (pool[i] != null) indices.put(pool[i], i); + } + } + + /** Construct an empty pool. + */ + public Pool() { + this(1, new Object[64]); + } + + /** Return the number of entries in the constant pool. + */ + public int numEntries() { + return pp; + } + + /** Remove everything from this pool. + */ + public void reset() { + pp = 1; + indices.clear(); + } + + /** Double pool buffer in size. + */ + private void doublePool() { + Object[] newpool = new Object[pool.length * 2]; + System.arraycopy(pool, 0, newpool, 0, pool.length); + pool = newpool; + } + + /** Place an object in the pool, unless it is already there. + * If object is a symbol also enter its owner unless the owner is a + * package. Return the object's index in the pool. + */ + public int put(Object value) { + if (value instanceof MethodSymbol) + value = new Method((MethodSymbol)value); + else if (value instanceof VarSymbol) + value = new Variable((VarSymbol)value); +// assert !(value instanceof Type.TypeVar); + Integer index = indices.get(value); + if (index == null) { +// System.err.println("put " + value + " " + value.getClass());//DEBUG + index = pp; + indices.put(value, index); + if (pp == pool.length) doublePool(); + pool[pp++] = value; + if (value instanceof Long || value instanceof Double) { + if (pp == pool.length) doublePool(); + pool[pp++] = null; + } + } + return index.intValue(); + } + + /** Return the given object's index in the pool, + * or -1 if object is not in there. + */ + public int get(Object o) { + Integer n = indices.get(o); + return n == null ? -1 : n.intValue(); + } + + static class Method extends DelegatedSymbol { + MethodSymbol m; + Method(MethodSymbol m) { + super(m); + this.m = m; + } + public boolean equals(Object other) { + if (!(other instanceof Method)) return false; + MethodSymbol o = ((Method)other).m; + return + o.name == m.name && + o.owner == m.owner && + o.type.equals(m.type); + } + public int hashCode() { + return + m.name.hashCode() * 33 + + m.owner.hashCode() * 9 + + m.type.hashCode(); + } + } + + static class Variable extends DelegatedSymbol { + VarSymbol v; + Variable(VarSymbol v) { + super(v); + this.v = v; + } + public boolean equals(Object other) { + if (!(other instanceof Variable)) return false; + VarSymbol o = ((Variable)other).v; + return + o.name == v.name && + o.owner == v.owner && + o.type.equals(v.type); + } + public int hashCode() { + return + v.name.hashCode() * 33 + + v.owner.hashCode() * 9 + + v.type.hashCode(); + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/jvm/Target.java b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/Target.java new file mode 100644 index 0000000..3c81daa --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/Target.java @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.jvm; + +import java.util.*; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.util.*; + +import static com.sun.tools.javac.main.OptionName.*; + +/** The classfile version target. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public enum Target { + JDK1_1("1.1", 45, 3), + JDK1_2("1.2", 46, 0), + JDK1_3("1.3", 47, 0), + + /** J2SE1.4 = Merlin. */ + JDK1_4("1.4", 48, 0), + + /** Support for the JSR14 prototype compiler (targeting 1.4 VMs + * augmented with a few support classes). This is a transitional + * option that will not be supported in the product. */ + JSR14("jsr14", 48, 0), + + /** The following are undocumented transitional targets that we + * had used to test VM fixes in update releases. We do not + * promise to retain support for them. */ + JDK1_4_1("1.4.1", 48, 0), + JDK1_4_2("1.4.2", 48, 0), + + /** Tiger. */ + JDK1_5("1.5", 49, 0), + + /** JDK 6. */ + JDK1_6("1.6", 50, 0), + + /** JDK 7. */ + JDK1_7("1.7", 51, 0); + + private static final Context.Key targetKey = + new Context.Key(); + + public static Target instance(Context context) { + Target instance = context.get(targetKey); + if (instance == null) { + Options options = Options.instance(context); + String targetString = options.get(TARGET); + if (targetString != null) instance = lookup(targetString); + if (instance == null) instance = DEFAULT; + context.put(targetKey, instance); + } + return instance; + } + + private static Target MIN; + public static Target MIN() { return MIN; } + + private static Target MAX; + public static Target MAX() { return MAX; } + + private static Map tab = new HashMap(); + static { + for (Target t : values()) { + if (MIN == null) MIN = t; + MAX = t; + tab.put(t.name, t); + } + tab.put("5", JDK1_5); + tab.put("6", JDK1_6); + tab.put("7", JDK1_7); + } + + public final String name; + public final int majorVersion; + public final int minorVersion; + private Target(String name, int majorVersion, int minorVersion) { + this.name = name; + this.majorVersion = majorVersion; + this.minorVersion = minorVersion; + } + + public static final Target DEFAULT = JDK1_7; + + public static Target lookup(String name) { + return tab.get(name); + } + + /** In -target 1.1 and earlier, the compiler is required to emit + * synthetic method definitions in abstract classes for interface + * methods that are not overridden. We call them "Miranda" methods. + */ + public boolean requiresIproxy() { + return compareTo(JDK1_1) <= 0; + } + + /** Beginning in 1.4, we take advantage of the possibility of emitting + * code to initialize fields before calling the superclass constructor. + * This is allowed by the VM spec, but the verifier refused to allow + * it until 1.4. This is necesary to translate some code involving + * inner classes. See, for example, 4030374. + */ + public boolean initializeFieldsBeforeSuper() { + return compareTo(JDK1_4) >= 0; + } + + /** Beginning with -target 1.2 we obey the JLS rules for binary + * compatibility, emitting as the qualifying type of a reference + * to a method or field the type of the qualifier. In earlier + * targets we use as the qualifying type the class in which the + * member was found. The following methods named + * *binaryCompatibility() indicate places where we vary from this + * general rule. */ + public boolean obeyBinaryCompatibility() { + return compareTo(JDK1_2) >= 0; + } + + /** Starting in 1.5, the compiler uses an array type as + * the qualifier for method calls (such as clone) where required by + * the language and VM spec. Earlier versions of the compiler + * qualified them by Object. + */ + public boolean arrayBinaryCompatibility() { + return compareTo(JDK1_5) >= 0; + } + + /** Beginning after 1.2, we follow the binary compatibility rules for + * interface fields. The 1.2 VMs had bugs handling interface fields + * when compiled using binary compatibility (see 4400598), so this is + * an accommodation to them. + */ + public boolean interfaceFieldsBinaryCompatibility() { + return compareTo(JDK1_2) > 0; + } + + /** Beginning in -target 1.5, we follow the binary compatibility + * rules for interface methods that redefine Object methods. + * Earlier VMs had bugs handling such methods compiled using binary + * compatibility (see 4392595, 4398791, 4392595, 4400415). + * The VMs were fixed during or soon after 1.4. See 4392595. + */ + public boolean interfaceObjectOverridesBinaryCompatibility() { + return compareTo(JDK1_5) >= 0; + } + + /** Beginning in -target 1.4.2, we make synthetic variables + * package-private instead of private. This is to prevent the + * necessity of access methods, which effectively relax the + * protection of the field but bloat the class files and affect + * execution. + */ + public boolean usePrivateSyntheticFields() { + return compareTo(JDK1_4_2) < 0; + } + + /** Sometimes we need to create a field to cache a value like a + * class literal of the assertions flag. In -target 1.4.2 and + * later we create a new synthetic class for this instead of + * using the outermost class. See 4401576. + */ + public boolean useInnerCacheClass() { + return compareTo(JDK1_4_2) >= 0; + } + + /** Return true if cldc-style stack maps need to be generated. */ + public boolean generateCLDCStackmap() { + return false; + } + + /** Beginning in -target 6, we generate stackmap attribute in + * compact format. */ + public boolean generateStackMapTable() { + return compareTo(JDK1_6) >= 0; + } + + /** Beginning in -target 6, package-info classes are marked synthetic. + */ + public boolean isPackageInfoSynthetic() { + return compareTo(JDK1_6) >= 0; + } + + /** Do we generate "empty" stackmap slots after double and long? + */ + public boolean generateEmptyAfterBig() { + return false; + } + + /** Beginning in 1.5, we have an unsynchronized version of + * StringBuffer called StringBuilder that can be used by the + * compiler for string concatenation. + */ + public boolean useStringBuilder() { + return compareTo(JDK1_5) >= 0; + } + + /** Beginning in 1.5, we have flag bits we can use instead of + * marker attributes. + */ + public boolean useSyntheticFlag() { + return compareTo(JDK1_5) >= 0; + } + public boolean useEnumFlag() { + return compareTo(JDK1_5) >= 0; + } + public boolean useAnnotationFlag() { + return compareTo(JDK1_5) >= 0; + } + public boolean useVarargsFlag() { + return compareTo(JDK1_5) >= 0; + } + public boolean useBridgeFlag() { + return compareTo(JDK1_5) >= 0; + } + + /** Return the character to be used in constructing synthetic + * identifiers, where not specified by the JLS. + */ + public char syntheticNameChar() { + return '$'; + } + + /** Does the VM have direct support for class literals? + */ + public boolean hasClassLiterals() { + return compareTo(JDK1_5) >= 0; + } + + /** Does the VM support an invokedynamic instruction? + */ + public boolean hasInvokedynamic() { + return compareTo(JDK1_7) >= 0; + } + + /** Does the VM support polymorphic method handle invocation? + * Affects the linkage information output to the classfile. + * An alias for {@code hasInvokedynamic}, since all the JSR 292 features appear together. + */ + public boolean hasMethodHandles() { + return hasInvokedynamic(); + } + + /** Although we may not have support for class literals, should we + * avoid initializing the class that the literal refers to? + * See 4468823 + */ + public boolean classLiteralsNoInit() { + return compareTo(JDK1_4_2) >= 0; + } + + /** Although we may not have support for class literals, when we + * throw a NoClassDefFoundError, should we initialize its cause? + */ + public boolean hasInitCause() { + return compareTo(JDK1_4) >= 0; + } + + /** For bootstrapping, we use J2SE1.4's wrapper class constructors + * to implement boxing. + */ + public boolean boxWithConstructors() { + return compareTo(JDK1_5) < 0; + } + + /** For bootstrapping, we use J2SE1.4's java.util.Collection + * instead of java.lang.Iterable. + */ + public boolean hasIterable() { + return compareTo(JDK1_5) >= 0; + } + + /** For bootstrapping javac only, we do without java.lang.Enum if + * necessary. + */ + public boolean compilerBootstrap(Symbol c) { + return + this == JSR14 && + (c.flags() & Flags.ENUM) != 0 && + c.flatName().toString().startsWith("com.sun.tools.") + // && !Target.class.getSuperclass().getName().equals("java.lang.Enum") + ; + } + + /** In J2SE1.5.0, we introduced the "EnclosingMethod" attribute + * for improved reflection support. + */ + public boolean hasEnclosingMethodAttribute() { + return compareTo(JDK1_5) >= 0 || this == JSR14; + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/jvm/UninitializedType.java b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/UninitializedType.java new file mode 100644 index 0000000..82ba522 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/jvm/UninitializedType.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2003, 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.jvm; + +import com.sun.tools.javac.code.*; + + +/** These pseudo-types appear in the generated verifier tables to + * indicate objects that have been allocated but not yet constructed. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +class UninitializedType extends Type.DelegatedType { + public static final int UNINITIALIZED_THIS = TypeTags.TypeTagCount; + public static final int UNINITIALIZED_OBJECT = UNINITIALIZED_THIS + 1; + + public static UninitializedType uninitializedThis(Type qtype) { + return new UninitializedType(UNINITIALIZED_THIS, qtype, -1); + } + + public static UninitializedType uninitializedObject(Type qtype, int offset) { + return new UninitializedType(UNINITIALIZED_OBJECT, qtype, offset); + } + + public final int offset; // PC where allocation took place + private UninitializedType(int tag, Type qtype, int offset) { + super(tag, qtype); + this.offset = offset; + } + + Type initializedType() { + return qtype; + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/main/CommandLine.java b/douyu-javac/src/main/java/com/sun/tools/javac/main/CommandLine.java new file mode 100644 index 0000000..262dc2d --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/main/CommandLine.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.main; + +import java.io.IOException; +import java.io.Reader; +import java.io.FileReader; +import java.io.BufferedReader; +import java.io.StreamTokenizer; +import com.sun.tools.javac.util.ListBuffer; + +/** + * Various utility methods for processing Java tool command line arguments. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class CommandLine { + /** + * Process Win32-style command files for the specified command line + * arguments and return the resulting arguments. A command file argument + * is of the form '@file' where 'file' is the name of the file whose + * contents are to be parsed for additional arguments. The contents of + * the command file are parsed using StreamTokenizer and the original + * '@file' argument replaced with the resulting tokens. Recursive command + * files are not supported. The '@' character itself can be quoted with + * the sequence '@@'. + */ + public static String[] parse(String[] args) + throws IOException + { + ListBuffer newArgs = new ListBuffer(); + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + if (arg.length() > 1 && arg.charAt(0) == '@') { + arg = arg.substring(1); + if (arg.charAt(0) == '@') { + newArgs.append(arg); + } else { + loadCmdFile(arg, newArgs); + } + } else { + newArgs.append(arg); + } + } + return newArgs.toList().toArray(new String[newArgs.length()]); + } + + private static void loadCmdFile(String name, ListBuffer args) + throws IOException + { + Reader r = new BufferedReader(new FileReader(name)); + StreamTokenizer st = new StreamTokenizer(r); + st.resetSyntax(); + st.wordChars(' ', 255); + st.whitespaceChars(0, ' '); + st.commentChar('#'); + st.quoteChar('"'); + st.quoteChar('\''); + while (st.nextToken() != StreamTokenizer.TT_EOF) { + args.append(st.sval); + } + r.close(); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/main/JavaCompiler.java b/douyu-javac/src/main/java/com/sun/tools/javac/main/JavaCompiler.java new file mode 100644 index 0000000..fe1cb42 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/main/JavaCompiler.java @@ -0,0 +1,1628 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.main; + +import java.io.*; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.Queue; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.annotation.processing.Processor; +import javax.lang.model.SourceVersion; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.DiagnosticListener; + +import com.sun.source.util.TaskEvent; +import com.sun.source.util.TaskListener; + +import com.sun.tools.javac.file.JavacFileManager; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Lint.LintCategory; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.tree.*; +import com.sun.tools.javac.tree.JCTree.*; +import com.sun.tools.javac.parser.*; +import com.sun.tools.javac.comp.*; +import com.sun.tools.javac.jvm.*; +import com.sun.tools.javac.processing.*; + +import static javax.tools.StandardLocation.CLASS_OUTPUT; +import static com.sun.tools.javac.main.OptionName.*; +import static com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag.*; +import static com.sun.tools.javac.util.ListBuffer.lb; + + +/** This class could be the main entry point for GJC when GJC is used as a + * component in a larger software system. It provides operations to + * construct a new compiler, and to run a new compiler on a set of source + * files. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class JavaCompiler implements ClassReader.SourceCompleter { + /** The context key for the compiler. */ + protected static final Context.Key compilerKey = + new Context.Key(); + + /** Get the JavaCompiler instance for this context. */ + public static JavaCompiler instance(Context context) { + JavaCompiler instance = context.get(compilerKey); + if (instance == null) + instance = new JavaCompiler(context); + return instance; + } + + /** The current version number as a string. + */ + public static String version() { + return version("release"); // mm.nn.oo[-milestone] + } + + /** The current full version number as a string. + */ + public static String fullVersion() { + return version("full"); // mm.mm.oo[-milestone]-build + } + + private static final String versionRBName = "com.sun.tools.javac.resources.version"; + private static ResourceBundle versionRB; + + private static String version(String key) { + if (versionRB == null) { + try { + versionRB = ResourceBundle.getBundle(versionRBName); + } catch (MissingResourceException e) { + return Log.getLocalizedString("version.not.available"); + } + } + try { + return versionRB.getString(key); + } + catch (MissingResourceException e) { + return Log.getLocalizedString("version.not.available"); + } + } + + /** + * Control how the compiler's latter phases (attr, flow, desugar, generate) + * are connected. Each individual file is processed by each phase in turn, + * but with different compile policies, you can control the order in which + * each class is processed through its next phase. + * + *

Generally speaking, the compiler will "fail fast" in the face of + * errors, although not aggressively so. flow, desugar, etc become no-ops + * once any errors have occurred. No attempt is currently made to determine + * if it might be safe to process a class through its next phase because + * it does not depend on any unrelated errors that might have occurred. + */ + protected static enum CompilePolicy { + /** + * Just attribute the parse trees. + */ + ATTR_ONLY, + + /** + * Just attribute and do flow analysis on the parse trees. + * This should catch most user errors. + */ + CHECK_ONLY, + + /** + * Attribute everything, then do flow analysis for everything, + * then desugar everything, and only then generate output. + * This means no output will be generated if there are any + * errors in any classes. + */ + SIMPLE, + + /** + * Groups the classes for each source file together, then process + * each group in a manner equivalent to the {@code SIMPLE} policy. + * This means no output will be generated if there are any + * errors in any of the classes in a source file. + */ + BY_FILE, + + /** + * Completely process each entry on the todo list in turn. + * -- this is the same for 1.5. + * Means output might be generated for some classes in a compilation unit + * and not others. + */ + BY_TODO; + + static CompilePolicy decode(String option) { + if (option == null) + return DEFAULT_COMPILE_POLICY; + else if (option.equals("attr")) + return ATTR_ONLY; + else if (option.equals("check")) + return CHECK_ONLY; + else if (option.equals("simple")) + return SIMPLE; + else if (option.equals("byfile")) + return BY_FILE; + else if (option.equals("bytodo")) + return BY_TODO; + else + return DEFAULT_COMPILE_POLICY; + } + } + + private static CompilePolicy DEFAULT_COMPILE_POLICY = CompilePolicy.BY_TODO; + + protected static enum ImplicitSourcePolicy { + /** Don't generate or process implicitly read source files. */ + NONE, + /** Generate classes for implicitly read source files. */ + CLASS, + /** Like CLASS, but generate warnings if annotation processing occurs */ + UNSET; + + static ImplicitSourcePolicy decode(String option) { + if (option == null) + return UNSET; + else if (option.equals("none")) + return NONE; + else if (option.equals("class")) + return CLASS; + else + return UNSET; + } + } + + /** The log to be used for error reporting. + */ + public Log log; + + /** Factory for creating diagnostic objects + */ + JCDiagnostic.Factory diagFactory; + + /** The tree factory module. + */ + protected TreeMaker make; + + /** The class reader. + */ + protected ClassReader reader; + + /** The class writer. + */ + protected ClassWriter writer; + + /** The module for the symbol table entry phases. + */ + protected Enter enter; + + /** The symbol table. + */ + protected Symtab syms; + + /** The language version. + */ + protected Source source; + + /** The module for code generation. + */ + protected Gen gen; + + /** The name table. + */ + protected Names names; + + /** The attributor. + */ + protected Attr attr; + + /** The attributor. + */ + protected Check chk; + + /** The flow analyzer. + */ + protected Flow flow; + + /** The type eraser. + */ + protected TransTypes transTypes; + + /** The syntactic sugar desweetener. + */ + protected Lower lower; + + /** The annotation annotator. + */ + protected Annotate annotate; + + /** Force a completion failure on this name + */ + protected final Name completionFailureName; + + /** Type utilities. + */ + protected Types types; + + /** Access to file objects. + */ + protected JavaFileManager fileManager; + + /** Factory for parsers. + */ + protected ParserFactory parserFactory; + + /** Optional listener for progress events + */ + protected TaskListener taskListener; + + /** + * Annotation processing may require and provide a new instance + * of the compiler to be used for the analyze and generate phases. + */ + protected JavaCompiler delegateCompiler; + + /** + * Command line options. + */ + protected Options options; + + protected Context context; + + /** + * Flag set if any annotation processing occurred. + **/ + protected boolean annotationProcessingOccurred; + + /** + * Flag set if any implicit source files read. + **/ + protected boolean implicitSourceFilesRead; + + /** Construct a new compiler using a shared context. + */ + public JavaCompiler(Context context) { + this.context = context; + context.put(compilerKey, this); + + // if fileManager not already set, register the JavacFileManager to be used + if (context.get(JavaFileManager.class) == null) + JavacFileManager.preRegister(context); + + names = Names.instance(context); + log = Log.instance(context); + diagFactory = JCDiagnostic.Factory.instance(context); + reader = ClassReader.instance(context); + make = TreeMaker.instance(context); + writer = ClassWriter.instance(context); + enter = Enter.instance(context); + todo = Todo.instance(context); + + fileManager = context.get(JavaFileManager.class); + parserFactory = ParserFactory.instance(context); + + try { + // catch completion problems with predefineds + syms = Symtab.instance(context); + } catch (CompletionFailure ex) { + // inlined Check.completionError as it is not initialized yet + log.error("cant.access", ex.sym, ex.getDetailValue()); + if (ex instanceof ClassReader.BadClassFile) + throw new Abort(); + } + source = Source.instance(context); + attr = Attr.instance(context); + chk = Check.instance(context); + gen = Gen.instance(context); + flow = Flow.instance(context); + transTypes = TransTypes.instance(context); + lower = Lower.instance(context); + annotate = Annotate.instance(context); + types = Types.instance(context); + taskListener = context.get(TaskListener.class); + + reader.sourceCompleter = this; + + options = Options.instance(context); + + verbose = options.isSet(VERBOSE); + sourceOutput = options.isSet(PRINTSOURCE); // used to be -s + stubOutput = options.isSet("-stubs"); + relax = options.isSet("-relax"); + printFlat = options.isSet("-printflat"); + attrParseOnly = options.isSet("-attrparseonly"); + encoding = options.get(ENCODING); + lineDebugInfo = options.isUnset(G_CUSTOM) || + options.isSet(G_CUSTOM, "lines"); + genEndPos = options.isSet(XJCOV) || + context.get(DiagnosticListener.class) != null; + devVerbose = options.isSet("dev"); + processPcks = options.isSet("process.packages"); + werror = options.isSet(WERROR); + + if (source.compareTo(Source.DEFAULT) < 0) { + if (options.isUnset(XLINT_CUSTOM, "-" + LintCategory.OPTIONS.option)) { + if (fileManager instanceof BaseFileManager) { + if (((BaseFileManager) fileManager).isDefaultBootClassPath()) + log.warning(LintCategory.OPTIONS, "source.no.bootclasspath", source.name); + } + } + } + + verboseCompilePolicy = options.isSet("verboseCompilePolicy"); + + if (attrParseOnly) + compilePolicy = CompilePolicy.ATTR_ONLY; + else + compilePolicy = CompilePolicy.decode(options.get("compilePolicy")); + + implicitSourcePolicy = ImplicitSourcePolicy.decode(options.get("-implicit")); + + completionFailureName = + options.isSet("failcomplete") + ? names.fromString(options.get("failcomplete")) + : null; + + shouldStopPolicy = + options.isSet("shouldStopPolicy") + ? CompileState.valueOf(options.get("shouldStopPolicy")) + : null; + if (options.isUnset("oldDiags")) + log.setDiagnosticFormatter(RichDiagnosticFormatter.instance(context)); + } + + /* Switches: + */ + + /** Verbose output. + */ + public boolean verbose; + + /** Emit plain Java source files rather than class files. + */ + public boolean sourceOutput; + + /** Emit stub source files rather than class files. + */ + public boolean stubOutput; + + /** Generate attributed parse tree only. + */ + public boolean attrParseOnly; + + /** Switch: relax some constraints for producing the jsr14 prototype. + */ + boolean relax; + + /** Debug switch: Emit Java sources after inner class flattening. + */ + public boolean printFlat; + + /** The encoding to be used for source input. + */ + public String encoding; + + /** Generate code with the LineNumberTable attribute for debugging + */ + public boolean lineDebugInfo; + + /** Switch: should we store the ending positions? + */ + public boolean genEndPos; + + /** Switch: should we debug ignored exceptions + */ + protected boolean devVerbose; + + /** Switch: should we (annotation) process packages as well + */ + protected boolean processPcks; + + /** Switch: treat warnings as errors + */ + protected boolean werror; + + /** Switch: is annotation processing requested explitly via + * CompilationTask.setProcessors? + */ + protected boolean explicitAnnotationProcessingRequested = false; + + /** + * The policy for the order in which to perform the compilation + */ + protected CompilePolicy compilePolicy; + + /** + * The policy for what to do with implicitly read source files + */ + protected ImplicitSourcePolicy implicitSourcePolicy; + + /** + * Report activity related to compilePolicy + */ + public boolean verboseCompilePolicy; + + /** + * Policy of how far to continue processing. null means until first + * error. + */ + public CompileState shouldStopPolicy; + + /** A queue of all as yet unattributed classes. + */ + public Todo todo; + + /** Ordered list of compiler phases for each compilation unit. */ + public enum CompileState { + PARSE(1), + ENTER(2), + PROCESS(3), + ATTR(4), + FLOW(5), + TRANSTYPES(6), + LOWER(7), + GENERATE(8); + CompileState(int value) { + this.value = value; + } + boolean isDone(CompileState other) { + return value >= other.value; + } + private int value; + }; + /** Partial map to record which compiler phases have been executed + * for each compilation unit. Used for ATTR and FLOW phases. + */ + protected class CompileStates extends HashMap,CompileState> { + private static final long serialVersionUID = 1812267524140424433L; + boolean isDone(Env env, CompileState cs) { + CompileState ecs = get(env); + return ecs != null && ecs.isDone(cs); + } + } + private CompileStates compileStates = new CompileStates(); + + /** The set of currently compiled inputfiles, needed to ensure + * we don't accidentally overwrite an input file when -s is set. + * initialized by `compile'. + */ + protected Set inputFiles = new HashSet(); + + protected boolean shouldStop(CompileState cs) { + if (shouldStopPolicy == null) + return (errorCount() > 0 || unrecoverableError()); + else + return cs.ordinal() > shouldStopPolicy.ordinal(); + } + + /** The number of errors reported so far. + */ + public int errorCount() { + if (delegateCompiler != null && delegateCompiler != this) + return delegateCompiler.errorCount(); + else { + if (werror && log.nerrors == 0 && log.nwarnings > 0) { + log.error("warnings.and.werror"); + } + } + return log.nerrors; + } + + protected final Queue stopIfError(CompileState cs, Queue queue) { + return shouldStop(cs) ? ListBuffer.lb() : queue; + } + + protected final List stopIfError(CompileState cs, List list) { + return shouldStop(cs) ? List.nil() : list; + } + + /** The number of warnings reported so far. + */ + public int warningCount() { + if (delegateCompiler != null && delegateCompiler != this) + return delegateCompiler.warningCount(); + else + return log.nwarnings; + } + + /** Try to open input stream with given name. + * Report an error if this fails. + * @param filename The file name of the input stream to be opened. + */ + public CharSequence readSource(JavaFileObject filename) { + try { + inputFiles.add(filename); + return filename.getCharContent(false); + } catch (IOException e) { + log.error("error.reading.file", filename, JavacFileManager.getMessage(e)); + return null; + } + } + + /** Parse contents of input stream. + * @param filename The name of the file from which input stream comes. + * @param input The input stream to be parsed. + */ + protected JCCompilationUnit parse(JavaFileObject filename, CharSequence content) { + long msec = now(); + JCCompilationUnit tree = make.TopLevel(List.nil(), + null, List.nil()); + if (content != null) { + if (verbose) { + log.printVerbose("parsing.started", filename); + } + if (taskListener != null) { + TaskEvent e = new TaskEvent(TaskEvent.Kind.PARSE, filename); + taskListener.started(e); + } + Parser parser = parserFactory.newParser(content, keepComments(), genEndPos, lineDebugInfo); + tree = parser.parseCompilationUnit(); + if (verbose) { + log.printVerbose("parsing.done", Long.toString(elapsed(msec))); + } + } + + tree.sourcefile = filename; + + if (content != null && taskListener != null) { + TaskEvent e = new TaskEvent(TaskEvent.Kind.PARSE, tree); + taskListener.finished(e); + } + + return tree; + } + // where + public boolean keepComments = false; + protected boolean keepComments() { + return keepComments || sourceOutput || stubOutput; + } + + + /** Parse contents of file. + * @param filename The name of the file to be parsed. + */ + @Deprecated + public JCTree.JCCompilationUnit parse(String filename) { + JavacFileManager fm = (JavacFileManager)fileManager; + return parse(fm.getJavaFileObjectsFromStrings(List.of(filename)).iterator().next()); + } + + /** Parse contents of file. + * @param filename The name of the file to be parsed. + */ + public JCTree.JCCompilationUnit parse(JavaFileObject filename) { + JavaFileObject prev = log.useSource(filename); + try { + JCTree.JCCompilationUnit t = parse(filename, readSource(filename)); + if (t.endPositions != null) + log.setEndPosTable(filename, t.endPositions); + return t; + } finally { + log.useSource(prev); + } + } + + /** Resolve an identifier which may be the binary name of a class or + * the Java name of a class or package. + * @param name The name to resolve + */ + public Symbol resolveBinaryNameOrIdent(String name) { + try { + Name flatname = names.fromString(name.replace("/", ".")); + return reader.loadClass(flatname); + } catch (CompletionFailure ignore) { + return resolveIdent(name); + } + } + + /** Resolve an identifier. + * @param name The identifier to resolve + */ + public Symbol resolveIdent(String name) { + if (name.equals("")) + return syms.errSymbol; + JavaFileObject prev = log.useSource(null); + try { + JCExpression tree = null; + for (String s : name.split("\\.", -1)) { + if (!SourceVersion.isIdentifier(s)) // TODO: check for keywords + return syms.errSymbol; + tree = (tree == null) ? make.Ident(names.fromString(s)) + : make.Select(tree, names.fromString(s)); + } + JCCompilationUnit toplevel = + make.TopLevel(List.nil(), null, List.nil()); + toplevel.packge = syms.unnamedPackage; + return attr.attribIdent(tree, toplevel); + } finally { + log.useSource(prev); + } + } + + /** Emit plain Java source for a class. + * @param env The attribution environment of the outermost class + * containing this class. + * @param cdef The class definition to be printed. + */ + JavaFileObject printSource(Env env, JCClassDecl cdef) throws IOException { + JavaFileObject outFile + = fileManager.getJavaFileForOutput(CLASS_OUTPUT, + cdef.sym.flatname.toString(), + JavaFileObject.Kind.SOURCE, + null); + if (inputFiles.contains(outFile)) { + log.error(cdef.pos(), "source.cant.overwrite.input.file", outFile); + return null; + } else { + BufferedWriter out = new BufferedWriter(outFile.openWriter()); + try { + new Pretty(out, true).printUnit(env.toplevel, cdef); + if (verbose) + log.printVerbose("wrote.file", outFile); + } finally { + out.close(); + } + return outFile; + } + } + + /** Generate code and emit a class file for a given class + * @param env The attribution environment of the outermost class + * containing this class. + * @param cdef The class definition from which code is generated. + */ + JavaFileObject genCode(Env env, JCClassDecl cdef) throws IOException { + try { + if (gen.genClass(env, cdef) && (errorCount() == 0)) + return writer.writeClass(cdef.sym); + } catch (ClassWriter.PoolOverflow ex) { + log.error(cdef.pos(), "limit.pool"); + } catch (ClassWriter.StringOverflow ex) { + log.error(cdef.pos(), "limit.string.overflow", + ex.value.substring(0, 20)); + } catch (CompletionFailure ex) { + chk.completionError(cdef.pos(), ex); + } + return null; + } + + /** Complete compiling a source file that has been accessed + * by the class file reader. + * @param c The class the source file of which needs to be compiled. + * @param filename The name of the source file. + * @param f An input stream that reads the source file. + */ + public void complete(ClassSymbol c) throws CompletionFailure { +// System.err.println("completing " + c);//DEBUG + if (completionFailureName == c.fullname) { + throw new CompletionFailure(c, "user-selected completion failure by class name"); + } + JCCompilationUnit tree; + JavaFileObject filename = c.classfile; + JavaFileObject prev = log.useSource(filename); + + try { + tree = parse(filename, filename.getCharContent(false)); + } catch (IOException e) { + log.error("error.reading.file", filename, JavacFileManager.getMessage(e)); + tree = make.TopLevel(List.nil(), null, List.nil()); + } finally { + log.useSource(prev); + } + + if (taskListener != null) { + TaskEvent e = new TaskEvent(TaskEvent.Kind.ENTER, tree); + taskListener.started(e); + } + + enter.complete(List.of(tree), c); + + if (taskListener != null) { + TaskEvent e = new TaskEvent(TaskEvent.Kind.ENTER, tree); + taskListener.finished(e); + } + + if (enter.getEnv(c) == null) { + boolean isPkgInfo = + tree.sourcefile.isNameCompatible("package-info", + JavaFileObject.Kind.SOURCE); + if (isPkgInfo) { + if (enter.getEnv(tree.packge) == null) { + JCDiagnostic diag = + diagFactory.fragment("file.does.not.contain.package", + c.location()); + throw reader.new BadClassFile(c, filename, diag); + } + } else { + JCDiagnostic diag = + diagFactory.fragment("file.doesnt.contain.class", + c.getQualifiedName()); + throw reader.new BadClassFile(c, filename, diag); + } + } + + implicitSourceFilesRead = true; + } + + /** Track when the JavaCompiler has been used to compile something. */ + private boolean hasBeenUsed = false; + private long start_msec = 0; + public long elapsed_msec = 0; + + public void compile(List sourceFileObject) + throws Throwable { + compile(sourceFileObject, List.nil(), null); + } + + /** + * Main method: compile a list of files, return all compiled classes + * + * @param sourceFileObjects file objects to be compiled + * @param classnames class names to process for annotations + * @param processors user provided annotation processors to bypass + * discovery, {@code null} means that no processors were provided + */ + public void compile(List sourceFileObjects, + List classnames, + Iterable processors) + { + if (processors != null && processors.iterator().hasNext()) + explicitAnnotationProcessingRequested = true; + // as a JavaCompiler can only be used once, throw an exception if + // it has been used before. + if (hasBeenUsed) + throw new AssertionError("attempt to reuse JavaCompiler"); + hasBeenUsed = true; + + // forcibly set the equivalent of -Xlint:-options, so that no further + // warnings about command line options are generated from this point on + options.put(XLINT_CUSTOM + "-" + LintCategory.OPTIONS.option, "true"); + options.remove(XLINT_CUSTOM + LintCategory.OPTIONS.option); + + start_msec = now(); + + try { + initProcessAnnotations(processors); + + // These method calls must be chained to avoid memory leaks + delegateCompiler = + processAnnotations( + enterTrees(stopIfError(CompileState.PARSE, parseFiles(sourceFileObjects))), + classnames); + + delegateCompiler.compile2(); + delegateCompiler.close(); + elapsed_msec = delegateCompiler.elapsed_msec; + } catch (Abort ex) { + if (devVerbose) + ex.printStackTrace(System.err); + } finally { + if (procEnvImpl != null) + procEnvImpl.close(); + } + } + + /** + * The phases following annotation processing: attribution, + * desugar, and finally code generation. + */ + private void compile2() { + try { + switch (compilePolicy) { + case ATTR_ONLY: + attribute(todo); + break; + + case CHECK_ONLY: + flow(attribute(todo)); + break; + + case SIMPLE: + generate(desugar(flow(attribute(todo)))); + break; + + case BY_FILE: { + Queue>> q = todo.groupByFile(); + while (!q.isEmpty() && !shouldStop(CompileState.ATTR)) { + generate(desugar(flow(attribute(q.remove())))); + } + } + break; + + case BY_TODO: + while (!todo.isEmpty()) + generate(desugar(flow(attribute(todo.remove())))); + break; + + default: + Assert.error("unknown compile policy"); + } + } catch (Abort ex) { + if (devVerbose) + ex.printStackTrace(System.err); + } + + if (verbose) { + elapsed_msec = elapsed(start_msec); + log.printVerbose("total", Long.toString(elapsed_msec)); + } + + reportDeferredDiagnostics(); + + if (!log.hasDiagnosticListener()) { + printCount("error", errorCount()); + printCount("warn", warningCount()); + } + } + + private List rootClasses; + + /** + * Parses a list of files. + */ + public List parseFiles(Iterable fileObjects) { + if (shouldStop(CompileState.PARSE)) + return List.nil(); + + //parse all files + ListBuffer trees = lb(); + Set filesSoFar = new HashSet(); + for (JavaFileObject fileObject : fileObjects) { + if (!filesSoFar.contains(fileObject)) { + filesSoFar.add(fileObject); + trees.append(parse(fileObject)); + } + } + return trees.toList(); + } + + /** + * Enter the symbols found in a list of parse trees. + * As a side-effect, this puts elements on the "todo" list. + * Also stores a list of all top level classes in rootClasses. + */ + public List enterTrees(List roots) { + //enter symbols for all files + if (taskListener != null) { + for (JCCompilationUnit unit: roots) { + TaskEvent e = new TaskEvent(TaskEvent.Kind.ENTER, unit); + taskListener.started(e); + } + } + + enter.main(roots); + + if (taskListener != null) { + for (JCCompilationUnit unit: roots) { + TaskEvent e = new TaskEvent(TaskEvent.Kind.ENTER, unit); + taskListener.finished(e); + } + } + + //If generating source, remember the classes declared in + //the original compilation units listed on the command line. + if (sourceOutput || stubOutput) { + ListBuffer cdefs = lb(); + for (JCCompilationUnit unit : roots) { + for (List defs = unit.defs; + defs.nonEmpty(); + defs = defs.tail) { + if (defs.head instanceof JCClassDecl) + cdefs.append((JCClassDecl)defs.head); + } + } + rootClasses = cdefs.toList(); + } + + // Ensure the input files have been recorded. Although this is normally + // done by readSource, it may not have been done if the trees were read + // in a prior round of annotation processing, and the trees have been + // cleaned and are being reused. + for (JCCompilationUnit unit : roots) { + inputFiles.add(unit.sourcefile); + } + + return roots; + } + + /** + * Set to true to enable skeleton annotation processing code. + * Currently, we assume this variable will be replaced more + * advanced logic to figure out if annotation processing is + * needed. + */ + boolean processAnnotations = false; + + /** + * Object to handle annotation processing. + */ + private JavacProcessingEnvironment procEnvImpl = null; + + /** + * Check if we should process annotations. + * If so, and if no scanner is yet registered, then set up the DocCommentScanner + * to catch doc comments, and set keepComments so the parser records them in + * the compilation unit. + * + * @param processors user provided annotation processors to bypass + * discovery, {@code null} means that no processors were provided + */ + public void initProcessAnnotations(Iterable processors) { + // Process annotations if processing is not disabled and there + // is at least one Processor available. + if (options.isSet(PROC, "none")) { + processAnnotations = false; + } else if (procEnvImpl == null) { + procEnvImpl = new JavacProcessingEnvironment(context, processors); + processAnnotations = procEnvImpl.atLeastOneProcessor(); + + if (processAnnotations) { + options.put("save-parameter-names", "save-parameter-names"); + reader.saveParameterNames = true; + keepComments = true; + genEndPos = true; + if (taskListener != null) + taskListener.started(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING)); + log.deferDiagnostics = true; + } else { // free resources + procEnvImpl.close(); + } + } + } + + // TODO: called by JavacTaskImpl + public JavaCompiler processAnnotations(List roots) { + return processAnnotations(roots, List.nil()); + } + + /** + * Process any anotations found in the specifed compilation units. + * @param roots a list of compilation units + * @return an instance of the compiler in which to complete the compilation + */ + // Implementation note: when this method is called, log.deferredDiagnostics + // will have been set true by initProcessAnnotations, meaning that any diagnostics + // that are reported will go into the log.deferredDiagnostics queue. + // By the time this method exits, log.deferDiagnostics must be set back to false, + // and all deferredDiagnostics must have been handled: i.e. either reported + // or determined to be transient, and therefore suppressed. + public JavaCompiler processAnnotations(List roots, + List classnames) { + if (shouldStop(CompileState.PROCESS)) { + // Errors were encountered. + // Unless all the errors are resolve errors, the errors were parse errors + // or other errors during enter which cannot be fixed by running + // any annotation processors. + if (unrecoverableError()) { + log.reportDeferredDiagnostics(); + return this; + } + } + + // ASSERT: processAnnotations and procEnvImpl should have been set up by + // by initProcessAnnotations + + // NOTE: The !classnames.isEmpty() checks should be refactored to Main. + + if (!processAnnotations) { + // If there are no annotation processors present, and + // annotation processing is to occur with compilation, + // emit a warning. + if (options.isSet(PROC, "only")) { + log.warning("proc.proc-only.requested.no.procs"); + todo.clear(); + } + // If not processing annotations, classnames must be empty + if (!classnames.isEmpty()) { + log.error("proc.no.explicit.annotation.processing.requested", + classnames); + } + log.reportDeferredDiagnostics(); + return this; // continue regular compilation + } + + try { + List classSymbols = List.nil(); + List pckSymbols = List.nil(); + if (!classnames.isEmpty()) { + // Check for explicit request for annotation + // processing + if (!explicitAnnotationProcessingRequested()) { + log.error("proc.no.explicit.annotation.processing.requested", + classnames); + log.reportDeferredDiagnostics(); + return this; // TODO: Will this halt compilation? + } else { + boolean errors = false; + for (String nameStr : classnames) { + Symbol sym = resolveBinaryNameOrIdent(nameStr); + if (sym == null || (sym.kind == Kinds.PCK && !processPcks)) { + log.error("proc.cant.find.class", nameStr); + errors = true; + continue; + } + try { + if (sym.kind == Kinds.PCK) + sym.complete(); + if (sym.exists()) { + if (sym.kind == Kinds.PCK) + pckSymbols = pckSymbols.prepend((PackageSymbol)sym); + else + classSymbols = classSymbols.prepend((ClassSymbol)sym); + continue; + } + Assert.check(sym.kind == Kinds.PCK); + log.warning("proc.package.does.not.exist", nameStr); + pckSymbols = pckSymbols.prepend((PackageSymbol)sym); + } catch (CompletionFailure e) { + log.error("proc.cant.find.class", nameStr); + errors = true; + continue; + } + } + if (errors) { + log.reportDeferredDiagnostics(); + return this; + } + } + } + try { + JavaCompiler c = procEnvImpl.doProcessing(context, roots, classSymbols, pckSymbols); + if (c != this) + annotationProcessingOccurred = c.annotationProcessingOccurred = true; + // doProcessing will have handled deferred diagnostics + Assert.check(c.log.deferDiagnostics == false + && c.log.deferredDiagnostics.size() == 0); + return c; + } finally { + procEnvImpl.close(); + } + } catch (CompletionFailure ex) { + log.error("cant.access", ex.sym, ex.getDetailValue()); + log.reportDeferredDiagnostics(); + return this; + } + } + + private boolean unrecoverableError() { + for (JCDiagnostic d: log.deferredDiagnostics) { + if (d.getKind() == JCDiagnostic.Kind.ERROR && !d.isFlagSet(RECOVERABLE)) + return true; + } + return false; + } + + boolean explicitAnnotationProcessingRequested() { + return + explicitAnnotationProcessingRequested || + explicitAnnotationProcessingRequested(options); + } + + static boolean explicitAnnotationProcessingRequested(Options options) { + return + options.isSet(PROCESSOR) || + options.isSet(PROCESSORPATH) || + options.isSet(PROC, "only") || + options.isSet(XPRINT); + } + + /** + * Attribute a list of parse trees, such as found on the "todo" list. + * Note that attributing classes may cause additional files to be + * parsed and entered via the SourceCompleter. + * Attribution of the entries in the list does not stop if any errors occur. + * @returns a list of environments for attributd classes. + */ + public Queue> attribute(Queue> envs) { + ListBuffer> results = lb(); + while (!envs.isEmpty()) + results.append(attribute(envs.remove())); + return stopIfError(CompileState.ATTR, results); + } + + /** + * Attribute a parse tree. + * @returns the attributed parse tree + */ + public Env attribute(Env env) { + if (compileStates.isDone(env, CompileState.ATTR)) + return env; + + if (verboseCompilePolicy) + printNote("[attribute " + env.enclClass.sym + "]"); + if (verbose) + log.printVerbose("checking.attribution", env.enclClass.sym); + + if (taskListener != null) { + TaskEvent e = new TaskEvent(TaskEvent.Kind.ANALYZE, env.toplevel, env.enclClass.sym); + taskListener.started(e); + } + + JavaFileObject prev = log.useSource( + env.enclClass.sym.sourcefile != null ? + env.enclClass.sym.sourcefile : + env.toplevel.sourcefile); + try { + attr.attrib(env); + if (errorCount() > 0 && !shouldStop(CompileState.ATTR)) { + //if in fail-over mode, ensure that AST expression nodes + //are correctly initialized (e.g. they have a type/symbol) + attr.postAttr(env); + } + compileStates.put(env, CompileState.ATTR); + } + finally { + log.useSource(prev); + } + + return env; + } + + /** + * Perform dataflow checks on attributed parse trees. + * These include checks for definite assignment and unreachable statements. + * If any errors occur, an empty list will be returned. + * @returns the list of attributed parse trees + */ + public Queue> flow(Queue> envs) { + ListBuffer> results = lb(); + for (Env env: envs) { + flow(env, results); + } + return stopIfError(CompileState.FLOW, results); + } + + /** + * Perform dataflow checks on an attributed parse tree. + */ + public Queue> flow(Env env) { + ListBuffer> results = lb(); + flow(env, results); + return stopIfError(CompileState.FLOW, results); + } + + /** + * Perform dataflow checks on an attributed parse tree. + */ + protected void flow(Env env, Queue> results) { + try { + if (shouldStop(CompileState.FLOW)) + return; + + if (relax || compileStates.isDone(env, CompileState.FLOW)) { + results.add(env); + return; + } + + if (verboseCompilePolicy) + printNote("[flow " + env.enclClass.sym + "]"); + JavaFileObject prev = log.useSource( + env.enclClass.sym.sourcefile != null ? + env.enclClass.sym.sourcefile : + env.toplevel.sourcefile); + try { + make.at(Position.FIRSTPOS); + TreeMaker localMake = make.forToplevel(env.toplevel); + flow.analyzeTree(env, localMake); + compileStates.put(env, CompileState.FLOW); + + if (shouldStop(CompileState.FLOW)) + return; + + results.add(env); + } + finally { + log.useSource(prev); + } + } + finally { + if (taskListener != null) { + TaskEvent e = new TaskEvent(TaskEvent.Kind.ANALYZE, env.toplevel, env.enclClass.sym); + taskListener.finished(e); + } + } + } + + /** + * Prepare attributed parse trees, in conjunction with their attribution contexts, + * for source or code generation. + * If any errors occur, an empty list will be returned. + * @returns a list containing the classes to be generated + */ + public Queue, JCClassDecl>> desugar(Queue> envs) { + ListBuffer, JCClassDecl>> results = lb(); + for (Env env: envs) + desugar(env, results); + return stopIfError(CompileState.FLOW, results); + } + + HashMap, Queue, JCClassDecl>>> desugaredEnvs = + new HashMap, Queue, JCClassDecl>>>(); + + /** + * Prepare attributed parse trees, in conjunction with their attribution contexts, + * for source or code generation. If the file was not listed on the command line, + * the current implicitSourcePolicy is taken into account. + * The preparation stops as soon as an error is found. + */ + protected void desugar(final Env env, Queue, JCClassDecl>> results) { + if (shouldStop(CompileState.TRANSTYPES)) + return; + + if (implicitSourcePolicy == ImplicitSourcePolicy.NONE + && !inputFiles.contains(env.toplevel.sourcefile)) { + return; + } + + if (compileStates.isDone(env, CompileState.LOWER)) { + results.addAll(desugaredEnvs.get(env)); + return; + } + + /** + * Ensure that superclasses of C are desugared before C itself. This is + * required for two reasons: (i) as erasure (TransTypes) destroys + * information needed in flow analysis and (ii) as some checks carried + * out during lowering require that all synthetic fields/methods have + * already been added to C and its superclasses. + */ + class ScanNested extends TreeScanner { + Set> dependencies = new LinkedHashSet>(); + @Override + public void visitClassDef(JCClassDecl node) { + Type st = types.supertype(node.sym.type); + if (st.tag == TypeTags.CLASS) { + ClassSymbol c = st.tsym.outermostClass(); + Env stEnv = enter.getEnv(c); + if (stEnv != null && env != stEnv) { + if (dependencies.add(stEnv)) + scan(stEnv.tree); + } + } + super.visitClassDef(node); + } + } + ScanNested scanner = new ScanNested(); + scanner.scan(env.tree); + for (Env dep: scanner.dependencies) { + if (!compileStates.isDone(dep, CompileState.FLOW)) + desugaredEnvs.put(dep, desugar(flow(attribute(dep)))); + } + + //We need to check for error another time as more classes might + //have been attributed and analyzed at this stage + if (shouldStop(CompileState.TRANSTYPES)) + return; + + if (verboseCompilePolicy) + printNote("[desugar " + env.enclClass.sym + "]"); + + JavaFileObject prev = log.useSource(env.enclClass.sym.sourcefile != null ? + env.enclClass.sym.sourcefile : + env.toplevel.sourcefile); + try { + //save tree prior to rewriting + JCTree untranslated = env.tree; + + make.at(Position.FIRSTPOS); + TreeMaker localMake = make.forToplevel(env.toplevel); + + if (env.tree instanceof JCCompilationUnit) { + if (!(stubOutput || sourceOutput || printFlat)) { + if (shouldStop(CompileState.LOWER)) + return; + List pdef = lower.translateTopLevelClass(env, env.tree, localMake); + if (pdef.head != null) { + Assert.check(pdef.tail.isEmpty()); + results.add(new Pair, JCClassDecl>(env, (JCClassDecl)pdef.head)); + } + } + return; + } + + if (stubOutput) { + //emit stub Java source file, only for compilation + //units enumerated explicitly on the command line + JCClassDecl cdef = (JCClassDecl)env.tree; + if (untranslated instanceof JCClassDecl && + rootClasses.contains((JCClassDecl)untranslated) && + ((cdef.mods.flags & (Flags.PROTECTED|Flags.PUBLIC)) != 0 || + cdef.sym.packge().getQualifiedName() == names.java_lang)) { + results.add(new Pair, JCClassDecl>(env, removeMethodBodies(cdef))); + } + return; + } + + if (shouldStop(CompileState.TRANSTYPES)) + return; + + env.tree = transTypes.translateTopLevelClass(env.tree, localMake); + compileStates.put(env, CompileState.TRANSTYPES); + + if (shouldStop(CompileState.LOWER)) + return; + + if (sourceOutput) { + //emit standard Java source file, only for compilation + //units enumerated explicitly on the command line + JCClassDecl cdef = (JCClassDecl)env.tree; + if (untranslated instanceof JCClassDecl && + rootClasses.contains((JCClassDecl)untranslated)) { + results.add(new Pair, JCClassDecl>(env, cdef)); + } + return; + } + + //translate out inner classes + List cdefs = lower.translateTopLevelClass(env, env.tree, localMake); + compileStates.put(env, CompileState.LOWER); + + if (shouldStop(CompileState.LOWER)) + return; + + //generate code for each class + for (List l = cdefs; l.nonEmpty(); l = l.tail) { + JCClassDecl cdef = (JCClassDecl)l.head; + results.add(new Pair, JCClassDecl>(env, cdef)); + } + } + finally { + log.useSource(prev); + } + + } + + /** Generates the source or class file for a list of classes. + * The decision to generate a source file or a class file is + * based upon the compiler's options. + * Generation stops if an error occurs while writing files. + */ + public void generate(Queue, JCClassDecl>> queue) { + generate(queue, null); + } + + public void generate(Queue, JCClassDecl>> queue, Queue results) { + if (shouldStop(CompileState.GENERATE)) + return; + + boolean usePrintSource = (stubOutput || sourceOutput || printFlat); + + for (Pair, JCClassDecl> x: queue) { + Env env = x.fst; + JCClassDecl cdef = x.snd; + + if (verboseCompilePolicy) { + printNote("[generate " + + (usePrintSource ? " source" : "code") + + " " + cdef.sym + "]"); + } + + if (taskListener != null) { + TaskEvent e = new TaskEvent(TaskEvent.Kind.GENERATE, env.toplevel, cdef.sym); + taskListener.started(e); + } + + JavaFileObject prev = log.useSource(env.enclClass.sym.sourcefile != null ? + env.enclClass.sym.sourcefile : + env.toplevel.sourcefile); + try { + JavaFileObject file; + if (usePrintSource) + file = printSource(env, cdef); + else + file = genCode(env, cdef); + if (results != null && file != null) + results.add(file); + } catch (IOException ex) { + log.error(cdef.pos(), "class.cant.write", + cdef.sym, ex.getMessage()); + return; + } finally { + log.useSource(prev); + } + + if (taskListener != null) { + TaskEvent e = new TaskEvent(TaskEvent.Kind.GENERATE, env.toplevel, cdef.sym); + taskListener.finished(e); + } + } + } + + // where + Map>> groupByFile(Queue> envs) { + // use a LinkedHashMap to preserve the order of the original list as much as possible + Map>> map = new LinkedHashMap>>(); + for (Env env: envs) { + Queue> sublist = map.get(env.toplevel); + if (sublist == null) { + sublist = new ListBuffer>(); + map.put(env.toplevel, sublist); + } + sublist.add(env); + } + return map; + } + + JCClassDecl removeMethodBodies(JCClassDecl cdef) { + final boolean isInterface = (cdef.mods.flags & Flags.INTERFACE) != 0; + class MethodBodyRemover extends TreeTranslator { + @Override + public void visitMethodDef(JCMethodDecl tree) { + tree.mods.flags &= ~Flags.SYNCHRONIZED; + for (JCVariableDecl vd : tree.params) + vd.mods.flags &= ~Flags.FINAL; + tree.body = null; + super.visitMethodDef(tree); + } + @Override + public void visitVarDef(JCVariableDecl tree) { + if (tree.init != null && tree.init.type.constValue() == null) + tree.init = null; + super.visitVarDef(tree); + } + @Override + public void visitClassDef(JCClassDecl tree) { + ListBuffer newdefs = lb(); + for (List it = tree.defs; it.tail != null; it = it.tail) { + JCTree t = it.head; + switch (t.getTag()) { + case JCTree.CLASSDEF: + if (isInterface || + (((JCClassDecl) t).mods.flags & (Flags.PROTECTED|Flags.PUBLIC)) != 0 || + (((JCClassDecl) t).mods.flags & (Flags.PRIVATE)) == 0 && ((JCClassDecl) t).sym.packge().getQualifiedName() == names.java_lang) + newdefs.append(t); + break; + case JCTree.METHODDEF: + if (isInterface || + (((JCMethodDecl) t).mods.flags & (Flags.PROTECTED|Flags.PUBLIC)) != 0 || + ((JCMethodDecl) t).sym.name == names.init || + (((JCMethodDecl) t).mods.flags & (Flags.PRIVATE)) == 0 && ((JCMethodDecl) t).sym.packge().getQualifiedName() == names.java_lang) + newdefs.append(t); + break; + case JCTree.VARDEF: + if (isInterface || (((JCVariableDecl) t).mods.flags & (Flags.PROTECTED|Flags.PUBLIC)) != 0 || + (((JCVariableDecl) t).mods.flags & (Flags.PRIVATE)) == 0 && ((JCVariableDecl) t).sym.packge().getQualifiedName() == names.java_lang) + newdefs.append(t); + break; + default: + break; + } + } + tree.defs = newdefs.toList(); + super.visitClassDef(tree); + } + } + MethodBodyRemover r = new MethodBodyRemover(); + return r.translate(cdef); + } + + public void reportDeferredDiagnostics() { + if (errorCount() == 0 + && annotationProcessingOccurred + && implicitSourceFilesRead + && implicitSourcePolicy == ImplicitSourcePolicy.UNSET) { + if (explicitAnnotationProcessingRequested()) + log.warning("proc.use.implicit"); + else + log.warning("proc.use.proc.or.implicit"); + } + chk.reportDeferredDiagnostics(); + } + + /** Close the compiler, flushing the logs + */ + public void close() { + close(true); + } + + public void close(boolean disposeNames) { + rootClasses = null; + reader = null; + make = null; + writer = null; + enter = null; + if (todo != null) + todo.clear(); + todo = null; + parserFactory = null; + syms = null; + source = null; + attr = null; + chk = null; + gen = null; + flow = null; + transTypes = null; + lower = null; + annotate = null; + types = null; + + log.flush(); + try { + fileManager.flush(); + } catch (IOException e) { + throw new Abort(e); + } finally { + if (names != null && disposeNames) + names.dispose(); + names = null; + } + } + + protected void printNote(String lines) { + Log.printLines(log.noticeWriter, lines); + } + + /** Print numbers of errors and warnings. + */ + protected void printCount(String kind, int count) { + if (count != 0) { + String key; + if (count == 1) + key = "count." + kind; + else + key = "count." + kind + ".plural"; + log.printErrLines(key, String.valueOf(count)); + log.errWriter.flush(); + } + } + + private static long now() { + return System.currentTimeMillis(); + } + + private static long elapsed(long then) { + return now() - then; + } + + public void initRound(JavaCompiler prev) { + genEndPos = prev.genEndPos; + keepComments = prev.keepComments; + start_msec = prev.start_msec; + hasBeenUsed = true; + } + + public static void enableLogging() { + Logger logger = Logger.getLogger(com.sun.tools.javac.Main.class.getPackage().getName()); + logger.setLevel(Level.ALL); + for (Handler h : logger.getParent().getHandlers()) { + h.setLevel(Level.ALL); + } + + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/main/JavacOption.java b/douyu-javac/src/main/java/com/sun/tools/javac/main/JavacOption.java new file mode 100644 index 0000000..e39793d --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/main/JavacOption.java @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2006, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.main; + +import java.io.PrintWriter; +import java.util.LinkedHashMap; +import java.util.Map; +import com.sun.tools.javac.util.Log; +import com.sun.tools.javac.util.Options; + +/** + * TODO: describe com.sun.tools.javac.main.JavacOption + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own + * risk. This code and its internal interfaces are subject to change + * or deletion without notice.

+ */ +public interface JavacOption { + + OptionKind getKind(); + + /** Does this option take a (separate) operand? + * @return true if this option takes a separate operand + */ + boolean hasArg(); + + /** Does argument string match option pattern? + * @param arg the command line argument string + * @return true if {@code arg} matches this option + */ + boolean matches(String arg); + + /** Process an option with an argument. + * @param options the accumulated set of analyzed options + * @param option the option to be processed + * @param arg the arg for the option to be processed + * @return true if an error was detected + */ + boolean process(Options options, String option, String arg); + + /** Process the option with no argument. + * @param options the accumulated set of analyzed options + * @param option the option to be processed + * @return true if an error was detected + */ + boolean process(Options options, String option); + + OptionName getName(); + + enum OptionKind { + NORMAL, + EXTENDED, + HIDDEN, + } + + enum ChoiceKind { + ONEOF, + ANYOF + } + + /** This class represents an option recognized by the main program + */ + static class Option implements JavacOption { + + /** Option string. + */ + OptionName name; + + /** Documentation key for arguments. + */ + String argsNameKey; + + /** Documentation key for description. + */ + String descrKey; + + /** Suffix option (-foo=bar or -foo:bar) + */ + boolean hasSuffix; + + /** The kind of choices for this option, if any. + */ + ChoiceKind choiceKind; + + /** The choices for this option, if any, and whether or not the choices + * are hidden + */ + Map choices; + + Option(OptionName name, String argsNameKey, String descrKey) { + this.name = name; + this.argsNameKey = argsNameKey; + this.descrKey = descrKey; + char lastChar = name.optionName.charAt(name.optionName.length()-1); + hasSuffix = lastChar == ':' || lastChar == '='; + } + + Option(OptionName name, String descrKey) { + this(name, null, descrKey); + } + + Option(OptionName name, String descrKey, ChoiceKind choiceKind, String... choices) { + this(name, descrKey, choiceKind, createChoices(choices)); + } + + private static Map createChoices(String... choices) { + Map map = new LinkedHashMap(); + for (String c: choices) + map.put(c, false); + return map; + } + + Option(OptionName name, String descrKey, ChoiceKind choiceKind, + Map choices) { + this(name, null, descrKey); + if (choiceKind == null || choices == null) + throw new NullPointerException(); + this.choiceKind = choiceKind; + this.choices = choices; + } + + @Override + public String toString() { + return name.optionName; + } + + public boolean hasArg() { + return argsNameKey != null && !hasSuffix; + } + + public boolean matches(String option) { + if (!hasSuffix) + return option.equals(name.optionName); + + if (!option.startsWith(name.optionName)) + return false; + + if (choices != null) { + String arg = option.substring(name.optionName.length()); + if (choiceKind == ChoiceKind.ONEOF) + return choices.keySet().contains(arg); + else { + for (String a: arg.split(",+")) { + if (!choices.keySet().contains(a)) + return false; + } + } + } + + return true; + } + + /** Print a line of documentation describing this option, if standard. + * @param out the stream to which to write the documentation + */ + void help(PrintWriter out) { + String s = " " + helpSynopsis(); + out.print(s); + for (int j = Math.min(s.length(), 28); j < 29; j++) out.print(" "); + Log.printLines(out, Main.getLocalizedString(descrKey)); + } + + String helpSynopsis() { + StringBuilder sb = new StringBuilder(); + sb.append(name); + if (argsNameKey == null) { + if (choices != null) { + String sep = "{"; + for (Map.Entry e: choices.entrySet()) { + if (!e.getValue()) { + sb.append(sep); + sb.append(e.getKey()); + sep = ","; + } + } + sb.append("}"); + } + } else { + if (!hasSuffix) + sb.append(" "); + sb.append(Main.getLocalizedString(argsNameKey)); + } + + return sb.toString(); + } + + /** Print a line of documentation describing this option, if non-standard. + * @param out the stream to which to write the documentation + */ + void xhelp(PrintWriter out) {} + + /** Process the option (with arg). Return true if error detected. + */ + public boolean process(Options options, String option, String arg) { + if (options != null) { + if (choices != null) { + if (choiceKind == ChoiceKind.ONEOF) { + // some clients like to see just one of option+choice set + for (String s: choices.keySet()) + options.remove(option + s); + String opt = option + arg; + options.put(opt, opt); + // some clients like to see option (without trailing ":") + // set to arg + String nm = option.substring(0, option.length() - 1); + options.put(nm, arg); + } else { + // set option+word for each word in arg + for (String a: arg.split(",+")) { + String opt = option + a; + options.put(opt, opt); + } + } + } + options.put(option, arg); + } + return false; + } + + /** Process the option (without arg). Return true if error detected. + */ + public boolean process(Options options, String option) { + if (hasSuffix) + return process(options, name.optionName, option.substring(name.optionName.length())); + else + return process(options, option, option); + } + + public OptionKind getKind() { return OptionKind.NORMAL; } + + public OptionName getName() { return name; } + }; + + /** A nonstandard or extended (-X) option + */ + static class XOption extends Option { + XOption(OptionName name, String argsNameKey, String descrKey) { + super(name, argsNameKey, descrKey); + } + XOption(OptionName name, String descrKey) { + this(name, null, descrKey); + } + XOption(OptionName name, String descrKey, ChoiceKind kind, String... choices) { + super(name, descrKey, kind, choices); + } + XOption(OptionName name, String descrKey, ChoiceKind kind, Map choices) { + super(name, descrKey, kind, choices); + } + @Override + void help(PrintWriter out) {} + @Override + void xhelp(PrintWriter out) { super.help(out); } + @Override + public OptionKind getKind() { return OptionKind.EXTENDED; } + }; + + /** A hidden (implementor) option + */ + static class HiddenOption extends Option { + HiddenOption(OptionName name) { + super(name, null, null); + } + HiddenOption(OptionName name, String argsNameKey) { + super(name, argsNameKey, null); + } + @Override + void help(PrintWriter out) {} + @Override + void xhelp(PrintWriter out) {} + @Override + public OptionKind getKind() { return OptionKind.HIDDEN; } + }; + +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/main/Main.java b/douyu-javac/src/main/java/com/sun/tools/javac/main/Main.java new file mode 100644 index 0000000..012c2ce --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/main/Main.java @@ -0,0 +1,587 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.main; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URL; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.util.MissingResourceException; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.annotation.processing.Processor; + +import com.sun.tools.javac.code.Source; +import com.sun.tools.javac.file.CacheFSInfo; +import com.sun.tools.javac.file.JavacFileManager; +import com.sun.tools.javac.jvm.Target; +import com.sun.tools.javac.main.JavacOption.Option; +import com.sun.tools.javac.main.RecognizedOptions.OptionHelper; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.processing.AnnotationProcessingError; + +import static com.sun.tools.javac.main.OptionName.*; + +/** This class provides a commandline interface to the GJC compiler. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Main { + + /** The name of the compiler, for use in diagnostics. + */ + String ownName; + + /** The writer to use for diagnostic output. + */ + PrintWriter out; + + /** + * If true, certain errors will cause an exception, such as command line + * arg errors, or exceptions in user provided code. + */ + boolean apiMode; + + + /** Result codes. + */ + static final int + EXIT_OK = 0, // Compilation completed with no errors. + EXIT_ERROR = 1, // Completed but reported errors. + EXIT_CMDERR = 2, // Bad command-line arguments + EXIT_SYSERR = 3, // System error or resource exhaustion. + EXIT_ABNORMAL = 4; // Compiler terminated abnormally + + private Option[] recognizedOptions = RecognizedOptions.getJavaCompilerOptions(new OptionHelper() { + + public void setOut(PrintWriter out) { + Main.this.out = out; + } + + public void error(String key, Object... args) { + Main.this.error(key, args); + } + + public void printVersion() { + Log.printLines(out, getLocalizedString("version", ownName, JavaCompiler.version())); + } + + public void printFullVersion() { + Log.printLines(out, getLocalizedString("fullVersion", ownName, JavaCompiler.fullVersion())); + } + + public void printHelp() { + help(); + } + + public void printXhelp() { + xhelp(); + } + + public void addFile(File f) { + if (!filenames.contains(f)) + filenames.append(f); + } + + public void addClassName(String s) { + classnames.append(s); + } + + }); + + /** + * Construct a compiler instance. + */ + public Main(String name) { + this(name, new PrintWriter(System.err, true)); + } + + /** + * Construct a compiler instance. + */ + public Main(String name, PrintWriter out) { + this.ownName = name; + this.out = out; + } + /** A table of all options that's passed to the JavaCompiler constructor. */ + private Options options = null; + + /** The list of source files to process + */ + public ListBuffer filenames = null; // XXX sb protected + + /** List of class files names passed on the command line + */ + public ListBuffer classnames = null; // XXX sb protected + + /** Print a string that explains usage. + */ + void help() { + Log.printLines(out, getLocalizedString("msg.usage.header", ownName)); + for (int i=0; i processArgs(String[] flags) { // XXX sb protected + int ac = 0; + while (ac < flags.length) { + String flag = flags[ac]; + ac++; + + Option option = null; + + if (flag.length() > 0) { + // quick hack to speed up file processing: + // if the option does not begin with '-', there is no need to check + // most of the compiler options. + int firstOptionToCheck = flag.charAt(0) == '-' ? 0 : recognizedOptions.length-1; + for (int j=firstOptionToCheck; jnil(), null); + } + + /** Programmatic interface for main function. + * @param args The command line parameters. + */ + public int compile(String[] args, + Context context, + List fileObjects, + Iterable processors) + { + if (options == null) + options = Options.instance(context); // creates a new one + + filenames = new ListBuffer(); + classnames = new ListBuffer(); + JavaCompiler comp = null; + /* + * TODO: Logic below about what is an acceptable command line + * should be updated to take annotation processing semantics + * into account. + */ + try { + if (args.length == 0 && fileObjects.isEmpty()) { + help(); + return EXIT_CMDERR; + } + + List files; + try { + files = processArgs(CommandLine.parse(args)); + if (files == null) { + // null signals an error in options, abort + return EXIT_CMDERR; + } else if (files.isEmpty() && fileObjects.isEmpty() && classnames.isEmpty()) { + // it is allowed to compile nothing if just asking for help or version info + if (options.isSet(HELP) + || options.isSet(X) + || options.isSet(VERSION) + || options.isSet(FULLVERSION)) + return EXIT_OK; + if (JavaCompiler.explicitAnnotationProcessingRequested(options)) { + error("err.no.source.files.classes"); + } else { + error("err.no.source.files"); + } + return EXIT_CMDERR; + } + } catch (java.io.FileNotFoundException e) { + Log.printLines(out, ownName + ": " + + getLocalizedString("err.file.not.found", + e.getMessage())); + return EXIT_SYSERR; + } + + boolean forceStdOut = options.isSet("stdout"); + if (forceStdOut) { + out.flush(); + out = new PrintWriter(System.out, true); + } + + context.put(Log.outKey, out); + + // allow System property in following line as a Mustang legacy + boolean batchMode = (options.isUnset("nonBatchMode") + && System.getProperty("nonBatchMode") == null); + if (batchMode) + CacheFSInfo.preRegister(context); + + fileManager = context.get(JavaFileManager.class); + + comp = JavaCompiler.instance(context); + if (comp == null) return EXIT_SYSERR; + + Log log = Log.instance(context); + + if (!files.isEmpty()) { + // add filenames to fileObjects + comp = JavaCompiler.instance(context); + List otherFiles = List.nil(); + JavacFileManager dfm = (JavacFileManager)fileManager; + for (JavaFileObject fo : dfm.getJavaFileObjectsFromFiles(files)) + otherFiles = otherFiles.prepend(fo); + for (JavaFileObject fo : otherFiles) + fileObjects = fileObjects.prepend(fo); + } + comp.compile(fileObjects, + classnames.toList(), + processors); + + if (log.expectDiagKeys != null) { + if (log.expectDiagKeys.isEmpty()) { + Log.printLines(log.noticeWriter, "all expected diagnostics found"); + return EXIT_OK; + } else { + Log.printLines(log.noticeWriter, "expected diagnostic keys not found: " + log.expectDiagKeys); + return EXIT_ERROR; + } + } + + if (comp.errorCount() != 0) + return EXIT_ERROR; + } catch (IOException ex) { + ioMessage(ex); + return EXIT_SYSERR; + } catch (OutOfMemoryError ex) { + resourceMessage(ex); + return EXIT_SYSERR; + } catch (StackOverflowError ex) { + resourceMessage(ex); + return EXIT_SYSERR; + } catch (FatalError ex) { + feMessage(ex); + return EXIT_SYSERR; + } catch (AnnotationProcessingError ex) { + if (apiMode) + throw new RuntimeException(ex.getCause()); + apMessage(ex); + return EXIT_SYSERR; + } catch (ClientCodeException ex) { + // as specified by javax.tools.JavaCompiler#getTask + // and javax.tools.JavaCompiler.CompilationTask#call + throw new RuntimeException(ex.getCause()); + } catch (PropagatedException ex) { + throw ex.getCause(); + } catch (Throwable ex) { + // Nasty. If we've already reported an error, compensate + // for buggy compiler error recovery by swallowing thrown + // exceptions. + if (comp == null || comp.errorCount() == 0 || + options == null || options.isSet("dev")) + bugMessage(ex); + return EXIT_ABNORMAL; + } finally { + if (comp != null) { + try { + comp.close(); + } catch (ClientCodeException ex) { + throw new RuntimeException(ex.getCause()); + } + } + filenames = null; + options = null; + } + return EXIT_OK; + } + + /** Print a message reporting an internal error. + */ + void bugMessage(Throwable ex) { + Log.printLines(out, getLocalizedString("msg.bug", + JavaCompiler.version())); + ex.printStackTrace(out); + } + + /** Print a message reporting a fatal error. + */ + void feMessage(Throwable ex) { + Log.printLines(out, ex.getMessage()); + if (ex.getCause() != null && options.isSet("dev")) { + ex.getCause().printStackTrace(out); + } + } + + /** Print a message reporting an input/output error. + */ + void ioMessage(Throwable ex) { + Log.printLines(out, getLocalizedString("msg.io")); + ex.printStackTrace(out); + } + + /** Print a message reporting an out-of-resources error. + */ + void resourceMessage(Throwable ex) { + Log.printLines(out, getLocalizedString("msg.resource")); +// System.out.println("(name buffer len = " + Name.names.length + " " + Name.nc);//DEBUG + ex.printStackTrace(out); + } + + /** Print a message reporting an uncaught exception from an + * annotation processor. + */ + void apMessage(AnnotationProcessingError ex) { + Log.printLines(out, + getLocalizedString("msg.proc.annotation.uncaught.exception")); + ex.getCause().printStackTrace(out); + } + + /** Display the location and checksum of a class. */ + void showClass(String className) { + out.println("javac: show class: " + className); + URL url = getClass().getResource('/' + className.replace('.', '/') + ".class"); + if (url == null) + out.println(" class not found"); + else { + out.println(" " + url); + try { + final String algorithm = "MD5"; + byte[] digest; + MessageDigest md = MessageDigest.getInstance(algorithm); + DigestInputStream in = new DigestInputStream(url.openStream(), md); + try { + byte[] buf = new byte[8192]; + int n; + do { n = in.read(buf); } while (n > 0); + digest = md.digest(); + } finally { + in.close(); + } + StringBuilder sb = new StringBuilder(); + for (byte b: digest) + sb.append(String.format("%02x", b)); + out.println(" " + algorithm + " checksum: " + sb); + } catch (Exception e) { + out.println(" cannot compute digest: " + e); + } + } + } + + private JavaFileManager fileManager; + + /* ************************************************************************ + * Internationalization + *************************************************************************/ + + /** Find a localized string in the resource bundle. + * @param key The key for the localized string. + */ + public static String getLocalizedString(String key, Object... args) { // FIXME sb private + try { + if (messages == null) + messages = new JavacMessages(javacBundleName); + return messages.getLocalizedString("javac." + key, args); + } + catch (MissingResourceException e) { + throw new Error("Fatal Error: Resource for javac is missing", e); + } + } + + public static void useRawMessages(boolean enable) { + if (enable) { + messages = new JavacMessages(javacBundleName) { + @Override + public String getLocalizedString(String key, Object... args) { + return key; + } + }; + } else { + messages = new JavacMessages(javacBundleName); + } + } + + private static final String javacBundleName = + "com.sun.tools.javac.resources.javac"; + + private static JavacMessages messages; +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/main/OptionName.java b/douyu-javac/src/main/java/com/sun/tools/javac/main/OptionName.java new file mode 100644 index 0000000..cfaff65 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/main/OptionName.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.main; + + +/** + * TODO: describe com.sun.tools.javac.main.OptionName + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own + * risk. This code and its internal interfaces are subject to change + * or deletion without notice.

+ */ +public enum OptionName { + G("-g"), + G_NONE("-g:none"), + G_CUSTOM("-g:"), + XLINT("-Xlint"), + XLINT_CUSTOM("-Xlint:"), + DIAGS("-XDdiags="), + NOWARN("-nowarn"), + VERBOSE("-verbose"), + DEPRECATION("-deprecation"), + CLASSPATH("-classpath"), + CP("-cp"), + SOURCEPATH("-sourcepath"), + BOOTCLASSPATH("-bootclasspath"), + XBOOTCLASSPATH_PREPEND("-Xbootclasspath/p:"), + XBOOTCLASSPATH_APPEND("-Xbootclasspath/a:"), + XBOOTCLASSPATH("-Xbootclasspath:"), + EXTDIRS("-extdirs"), + DJAVA_EXT_DIRS("-Djava.ext.dirs="), + ENDORSEDDIRS("-endorseddirs"), + DJAVA_ENDORSED_DIRS("-Djava.endorsed.dirs="), + PROC("-proc:"), + PROCESSOR("-processor"), + PROCESSORPATH("-processorpath"), + D("-d"), + S("-s"), + IMPLICIT("-implicit:"), + ENCODING("-encoding"), + SOURCE("-source"), + TARGET("-target"), + VERSION("-version"), + FULLVERSION("-fullversion"), + HELP("-help"), + A("-A"), + X("-X"), + J("-J"), + MOREINFO("-moreinfo"), + WERROR("-Werror"), + COMPLEXINFERENCE("-complexinference"), + PROMPT("-prompt"), + DOE("-doe"), + PRINTSOURCE("-printsource"), + WARNUNCHECKED("-warnunchecked"), + XMAXERRS("-Xmaxerrs"), + XMAXWARNS("-Xmaxwarns"), + XSTDOUT("-Xstdout"), + XPKGINFO("-Xpkginfo:"), + XPRINT("-Xprint"), + XPRINTROUNDS("-XprintRounds"), + XPRINTPROCESSORINFO("-XprintProcessorInfo"), + XPREFER("-Xprefer:"), + O("-O"), + XJCOV("-Xjcov"), + XD("-XD"), + AT("@"), + SOURCEFILE("sourcefile"); + + public final String optionName; + + OptionName(String optionName) { + this.optionName = optionName; + } + + @Override + public String toString() { + return optionName; + } + +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/main/RecognizedOptions.java b/douyu-javac/src/main/java/com/sun/tools/javac/main/RecognizedOptions.java new file mode 100644 index 0000000..6b4eb57 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/main/RecognizedOptions.java @@ -0,0 +1,643 @@ +/* + * Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.main; + +import com.sun.tools.javac.code.Lint; +import com.sun.tools.javac.code.Source; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.jvm.Target; +import com.sun.tools.javac.main.JavacOption.HiddenOption; +import com.sun.tools.javac.main.JavacOption.Option; +import com.sun.tools.javac.main.JavacOption.XOption; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Options; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import javax.lang.model.SourceVersion; + +import static com.sun.tools.javac.main.OptionName.*; + +/** + * TODO: describe com.sun.tools.javac.main.RecognizedOptions + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own + * risk. This code and its internal interfaces are subject to change + * or deletion without notice.

+ */ +public class RecognizedOptions { + + private RecognizedOptions() {} + + public interface OptionHelper { + + void setOut(PrintWriter out); + + void error(String key, Object... args); + + void printVersion(); + + void printFullVersion(); + + void printHelp(); + + void printXhelp(); + + void addFile(File f); + + void addClassName(String s); + + } + + public static class GrumpyHelper implements OptionHelper { + + public void setOut(PrintWriter out) { + throw new IllegalArgumentException(); + } + + public void error(String key, Object... args) { + throw new IllegalArgumentException(Main.getLocalizedString(key, args)); + } + + public void printVersion() { + throw new IllegalArgumentException(); + } + + public void printFullVersion() { + throw new IllegalArgumentException(); + } + + public void printHelp() { + throw new IllegalArgumentException(); + } + + public void printXhelp() { + throw new IllegalArgumentException(); + } + + public void addFile(File f) { + throw new IllegalArgumentException(f.getPath()); + } + + public void addClassName(String s) { + throw new IllegalArgumentException(s); + } + + } + + static Set javacOptions = EnumSet.of( + G, + G_NONE, + G_CUSTOM, + XLINT, + XLINT_CUSTOM, + NOWARN, + VERBOSE, + DEPRECATION, + CLASSPATH, + CP, + SOURCEPATH, + BOOTCLASSPATH, + XBOOTCLASSPATH_PREPEND, + XBOOTCLASSPATH_APPEND, + XBOOTCLASSPATH, + EXTDIRS, + DJAVA_EXT_DIRS, + ENDORSEDDIRS, + DJAVA_ENDORSED_DIRS, + PROC, + PROCESSOR, + PROCESSORPATH, + D, + S, + IMPLICIT, + ENCODING, + SOURCE, + TARGET, + VERSION, + FULLVERSION, + DIAGS, + HELP, + A, + X, + J, + MOREINFO, + WERROR, + // COMPLEXINFERENCE, + PROMPT, + DOE, + PRINTSOURCE, + WARNUNCHECKED, + XMAXERRS, + XMAXWARNS, + XSTDOUT, + XPKGINFO, + XPRINT, + XPRINTROUNDS, + XPRINTPROCESSORINFO, + XPREFER, + O, + XJCOV, + XD, + AT, + SOURCEFILE); + + static Set javacFileManagerOptions = EnumSet.of( + CLASSPATH, + CP, + SOURCEPATH, + BOOTCLASSPATH, + XBOOTCLASSPATH_PREPEND, + XBOOTCLASSPATH_APPEND, + XBOOTCLASSPATH, + EXTDIRS, + DJAVA_EXT_DIRS, + ENDORSEDDIRS, + DJAVA_ENDORSED_DIRS, + PROCESSORPATH, + D, + S, + ENCODING, + SOURCE); + + static Set javacToolOptions = EnumSet.of( + G, + G_NONE, + G_CUSTOM, + XLINT, + XLINT_CUSTOM, + NOWARN, + VERBOSE, + DEPRECATION, + PROC, + PROCESSOR, + IMPLICIT, + SOURCE, + TARGET, + // VERSION, + // FULLVERSION, + // HELP, + A, + // X, + // J, + MOREINFO, + WERROR, + // COMPLEXINFERENCE, + PROMPT, + DOE, + PRINTSOURCE, + WARNUNCHECKED, + XMAXERRS, + XMAXWARNS, + // XSTDOUT, + XPKGINFO, + XPRINT, + XPRINTROUNDS, + XPRINTPROCESSORINFO, + XPREFER, + O, + XJCOV, + XD); + + static Option[] getJavaCompilerOptions(OptionHelper helper) { + return getOptions(helper, javacOptions); + } + + public static Option[] getJavacFileManagerOptions(OptionHelper helper) { + return getOptions(helper, javacFileManagerOptions); + } + + public static Option[] getJavacToolOptions(OptionHelper helper) { + return getOptions(helper, javacToolOptions); + } + + static Option[] getOptions(OptionHelper helper, Set desired) { + ListBuffer

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ + +public class AnnotationProxyMaker { + + private final Attribute.Compound anno; + private final Class annoType; + + + private AnnotationProxyMaker(Attribute.Compound anno, + Class annoType) { + this.anno = anno; + this.annoType = annoType; + } + + + /** + * Returns a dynamic proxy for an annotation mirror. + */ + public static A generateAnnotation( + Attribute.Compound anno, Class annoType) { + AnnotationProxyMaker apm = new AnnotationProxyMaker(anno, annoType); + return annoType.cast(apm.generateAnnotation()); + } + + + /** + * Returns a dynamic proxy for an annotation mirror. + */ + private Annotation generateAnnotation() { + return AnnotationParser.annotationForMap(annoType, + getAllReflectedValues()); + } + + /** + * Returns a map from element names to their values in "dynamic + * proxy return form". Includes all elements, whether explicit or + * defaulted. + */ + private Map getAllReflectedValues() { + Map res = new LinkedHashMap(); + + for (Map.Entry entry : + getAllValues().entrySet()) { + MethodSymbol meth = entry.getKey(); + Object value = generateValue(meth, entry.getValue()); + if (value != null) { + res.put(meth.name.toString(), value); + } else { + // Ignore this element. May (properly) lead to + // IncompleteAnnotationException somewhere down the line. + } + } + return res; + } + + /** + * Returns a map from element symbols to their values. + * Includes all elements, whether explicit or defaulted. + */ + private Map getAllValues() { + Map res = + new LinkedHashMap(); + + // First find the default values. + ClassSymbol sym = (ClassSymbol) anno.type.tsym; + for (Scope.Entry e = sym.members().elems; e != null; e = e.sibling) { + if (e.sym.kind == Kinds.MTH) { + MethodSymbol m = (MethodSymbol) e.sym; + Attribute def = m.getDefaultValue(); + if (def != null) + res.put(m, def); + } + } + // Next find the explicit values, possibly overriding defaults. + for (Pair p : anno.values) + res.put(p.fst, p.snd); + return res; + } + + /** + * Converts an element value to its "dynamic proxy return form". + * Returns an exception proxy on some errors, but may return null if + * a useful exception cannot or should not be generated at this point. + */ + private Object generateValue(MethodSymbol meth, Attribute attr) { + ValueVisitor vv = new ValueVisitor(meth); + return vv.getValue(attr); + } + + + private class ValueVisitor implements Attribute.Visitor { + + private MethodSymbol meth; // annotation element being visited + private Class returnClass; // return type of annotation element + private Object value; // value in "dynamic proxy return form" + + ValueVisitor(MethodSymbol meth) { + this.meth = meth; + } + + Object getValue(Attribute attr) { + Method method; // runtime method of annotation element + try { + method = annoType.getMethod(meth.name.toString()); + } catch (NoSuchMethodException e) { + return null; + } + returnClass = method.getReturnType(); + attr.accept(this); + if (!(value instanceof ExceptionProxy) && + !AnnotationType.invocationHandlerReturnType(returnClass) + .isInstance(value)) { + typeMismatch(method, attr); + } + return value; + } + + + public void visitConstant(Attribute.Constant c) { + value = c.getValue(); + } + + public void visitClass(Attribute.Class c) { + value = new MirroredTypeExceptionProxy(c.type); + } + + public void visitArray(Attribute.Array a) { + Name elemName = ((ArrayType) a.type).elemtype.tsym.getQualifiedName(); + + if (elemName.equals(elemName.table.names.java_lang_Class)) { // Class[] + // Construct a proxy for a MirroredTypesException + ListBuffer elems = new ListBuffer(); + for (Attribute value : a.values) { + Type elem = ((Attribute.Class) value).type; + elems.append(elem); + } + value = new MirroredTypesExceptionProxy(elems.toList()); + + } else { + int len = a.values.length; + Class returnClassSaved = returnClass; + returnClass = returnClass.getComponentType(); + try { + Object res = Array.newInstance(returnClass, len); + for (int i = 0; i < len; i++) { + a.values[i].accept(this); + if (value == null || value instanceof ExceptionProxy) { + return; + } + try { + Array.set(res, i, value); + } catch (IllegalArgumentException e) { + value = null; // indicates a type mismatch + return; + } + } + value = res; + } finally { + returnClass = returnClassSaved; + } + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public void visitEnum(Attribute.Enum e) { + if (returnClass.isEnum()) { + String constName = e.value.toString(); + try { + value = Enum.valueOf((Class)returnClass, constName); + } catch (IllegalArgumentException ex) { + value = new EnumConstantNotPresentExceptionProxy( + (Class>) returnClass, constName); + } + } else { + value = null; // indicates a type mismatch + } + } + + public void visitCompound(Attribute.Compound c) { + try { + Class nested = + returnClass.asSubclass(Annotation.class); + value = generateAnnotation(c, nested); + } catch (ClassCastException ex) { + value = null; // indicates a type mismatch + } + } + + public void visitError(Attribute.Error e) { + value = null; // indicates a type mismatch + } + + + /** + * Sets "value" to an ExceptionProxy indicating a type mismatch. + */ + private void typeMismatch(Method method, final Attribute attr) { + class AnnotationTypeMismatchExceptionProxy extends ExceptionProxy { + static final long serialVersionUID = 269; + transient final Method method; + AnnotationTypeMismatchExceptionProxy(Method method) { + this.method = method; + } + public String toString() { + return ""; // eg: @Anno(value=) + } + protected RuntimeException generateException() { + return new AnnotationTypeMismatchException(method, + attr.type.toString()); + } + } + value = new AnnotationTypeMismatchExceptionProxy(method); + } + } + + + /** + * ExceptionProxy for MirroredTypeException. + * The toString, hashCode, and equals methods foward to the underlying + * type. + */ + private static final class MirroredTypeExceptionProxy extends ExceptionProxy { + static final long serialVersionUID = 269; + + private transient TypeMirror type; + private final String typeString; + + MirroredTypeExceptionProxy(TypeMirror t) { + type = t; + typeString = t.toString(); + } + + public String toString() { + return typeString; + } + + public int hashCode() { + return (type != null ? type : typeString).hashCode(); + } + + public boolean equals(Object obj) { + return type != null && + obj instanceof MirroredTypeExceptionProxy && + type.equals(((MirroredTypeExceptionProxy) obj).type); + } + + protected RuntimeException generateException() { + return new MirroredTypeException(type); + } + + // Explicitly set all transient fields. + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { + s.defaultReadObject(); + type = null; + } + } + + + /** + * ExceptionProxy for MirroredTypesException. + * The toString, hashCode, and equals methods foward to the underlying + * types. + */ + private static final class MirroredTypesExceptionProxy extends ExceptionProxy { + static final long serialVersionUID = 269; + + private transient List types; + private final String typeStrings; + + MirroredTypesExceptionProxy(List ts) { + types = ts; + typeStrings = ts.toString(); + } + + public String toString() { + return typeStrings; + } + + public int hashCode() { + return (types != null ? types : typeStrings).hashCode(); + } + + public boolean equals(Object obj) { + return types != null && + obj instanceof MirroredTypesExceptionProxy && + types.equals( + ((MirroredTypesExceptionProxy) obj).types); + } + + protected RuntimeException generateException() { + return new MirroredTypesException(types); + } + + // Explicitly set all transient fields. + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { + s.defaultReadObject(); + types = null; + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/model/FilteredMemberList.java b/douyu-javac/src/main/java/com/sun/tools/javac/model/FilteredMemberList.java new file mode 100644 index 0000000..59b3e60 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/model/FilteredMemberList.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.model; + +import java.util.AbstractList; +import java.util.Iterator; +import java.util.NoSuchElementException; +import com.sun.tools.javac.code.Scope; +import com.sun.tools.javac.code.Symbol; + +import static com.sun.tools.javac.code.Flags.*; + +/** + * Utility to construct a view of a symbol's members, + * filtering out unwanted elements such as synthetic ones. + * This view is most efficiently accessed through its iterator() method. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class FilteredMemberList extends AbstractList { + + private final Scope scope; + + public FilteredMemberList(Scope scope) { + this.scope = scope; + } + + public int size() { + int cnt = 0; + for (Scope.Entry e = scope.elems; e != null; e = e.sibling) { + if (!unwanted(e.sym)) + cnt++; + } + return cnt; + } + + public Symbol get(int index) { + for (Scope.Entry e = scope.elems; e != null; e = e.sibling) { + if (!unwanted(e.sym) && (index-- == 0)) + return e.sym; + } + throw new IndexOutOfBoundsException(); + } + + // A more efficient implementation than AbstractList's. + public Iterator iterator() { + return new Iterator() { + + /** The next entry to examine, or null if none. */ + private Scope.Entry nextEntry = scope.elems; + + private boolean hasNextForSure = false; + + public boolean hasNext() { + if (hasNextForSure) { + return true; + } + while (nextEntry != null && unwanted(nextEntry.sym)) { + nextEntry = nextEntry.sibling; + } + hasNextForSure = (nextEntry != null); + return hasNextForSure; + } + + public Symbol next() { + if (hasNext()) { + Symbol result = nextEntry.sym; + nextEntry = nextEntry.sibling; + hasNextForSure = false; + return result; + } else { + throw new NoSuchElementException(); + } + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + /** + * Tests whether this is a symbol that should never be seen by + * clients, such as a synthetic class. Returns true for null. + */ + private static boolean unwanted(Symbol s) { + return s == null || (s.flags() & SYNTHETIC) != 0; + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/model/JavacElements.java b/douyu-javac/src/main/java/com/sun/tools/javac/model/JavacElements.java new file mode 100644 index 0000000..c00a372 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/model/JavacElements.java @@ -0,0 +1,635 @@ +/* + * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.model; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Inherited; +import java.util.Map; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.*; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.util.Elements; +import javax.tools.JavaFileObject; +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.code.TypeTags; +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.main.JavaCompiler; +import com.sun.tools.javac.processing.PrintingProcessor; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.*; +import com.sun.tools.javac.tree.TreeInfo; +import com.sun.tools.javac.tree.TreeScanner; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.Name; + +import static javax.lang.model.util.ElementFilter.methodsIn; + +/** + * Utility methods for operating on program elements. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own + * risk. This code and its internal interfaces are subject to change + * or deletion without notice.

+ */ +public class JavacElements implements Elements { + + private JavaCompiler javaCompiler; + private Symtab syms; + private Names names; + private Types types; + private Enter enter; + + public static JavacElements instance(Context context) { + JavacElements instance = context.get(JavacElements.class); + if (instance == null) + instance = new JavacElements(context); + return instance; + } + + /** + * Public for use only by JavacProcessingEnvironment + */ + protected JavacElements(Context context) { + setContext(context); + } + + /** + * Use a new context. May be called from outside to update + * internal state for a new annotation-processing round. + */ + public void setContext(Context context) { + context.put(JavacElements.class, this); + javaCompiler = JavaCompiler.instance(context); + syms = Symtab.instance(context); + names = Names.instance(context); + types = Types.instance(context); + enter = Enter.instance(context); + } + + + /** + * An internal-use utility that creates a reified annotation. + */ + public static A getAnnotation(Symbol annotated, + Class annoType) { + if (!annoType.isAnnotation()) + throw new IllegalArgumentException("Not an annotation type: " + + annoType); + String name = annoType.getName(); + for (Attribute.Compound anno : annotated.getAnnotationMirrors()) + if (name.equals(anno.type.tsym.flatName().toString())) + return AnnotationProxyMaker.generateAnnotation(anno, annoType); + return null; + } + + /** + * An internal-use utility that creates a reified annotation. + * This overloaded version take annotation inheritance into account. + */ + public static A getAnnotation(ClassSymbol annotated, + Class annoType) { + boolean inherited = annoType.isAnnotationPresent(Inherited.class); + A result = null; + while (annotated.name != annotated.name.table.names.java_lang_Object) { + result = getAnnotation((Symbol)annotated, annoType); + if (result != null || !inherited) + break; + Type sup = annotated.getSuperclass(); + if (sup.tag != TypeTags.CLASS || sup.isErroneous()) + break; + annotated = (ClassSymbol) sup.tsym; + } + return result; + } + + + public PackageSymbol getPackageElement(CharSequence name) { + String strName = name.toString(); + if (strName.equals("")) + return syms.unnamedPackage; + return SourceVersion.isName(strName) + ? nameToSymbol(strName, PackageSymbol.class) + : null; + } + + public ClassSymbol getTypeElement(CharSequence name) { + String strName = name.toString(); + return SourceVersion.isName(strName) + ? nameToSymbol(strName, ClassSymbol.class) + : null; + } + + /** + * Returns a symbol given the type's or packages's canonical name, + * or null if the name isn't found. + */ + private S nameToSymbol(String nameStr, Class clazz) { + Name name = names.fromString(nameStr); + // First check cache. + Symbol sym = (clazz == ClassSymbol.class) + ? syms.classes.get(name) + : syms.packages.get(name); + + try { + if (sym == null) + sym = javaCompiler.resolveIdent(nameStr); + + sym.complete(); + + return (sym.kind != Kinds.ERR && + sym.exists() && + clazz.isInstance(sym) && + name.equals(sym.getQualifiedName())) + ? clazz.cast(sym) + : null; + } catch (CompletionFailure e) { + return null; + } + } + + public JavacSourcePosition getSourcePosition(Element e) { + Pair treeTop = getTreeAndTopLevel(e); + if (treeTop == null) + return null; + JCTree tree = treeTop.fst; + JCCompilationUnit toplevel = treeTop.snd; + JavaFileObject sourcefile = toplevel.sourcefile; + if (sourcefile == null) + return null; + return new JavacSourcePosition(sourcefile, tree.pos, toplevel.lineMap); + } + + public JavacSourcePosition getSourcePosition(Element e, AnnotationMirror a) { + Pair treeTop = getTreeAndTopLevel(e); + if (treeTop == null) + return null; + JCTree tree = treeTop.fst; + JCCompilationUnit toplevel = treeTop.snd; + JavaFileObject sourcefile = toplevel.sourcefile; + if (sourcefile == null) + return null; + + JCTree annoTree = matchAnnoToTree(a, e, tree); + if (annoTree == null) + return null; + return new JavacSourcePosition(sourcefile, annoTree.pos, + toplevel.lineMap); + } + + public JavacSourcePosition getSourcePosition(Element e, AnnotationMirror a, + AnnotationValue v) { + // TODO: better accuracy in getSourcePosition(... AnnotationValue) + return getSourcePosition(e, a); + } + + /** + * Returns the tree for an annotation given the annotated element + * and the element's own tree. Returns null if the tree cannot be found. + */ + private JCTree matchAnnoToTree(AnnotationMirror findme, + Element e, JCTree tree) { + Symbol sym = cast(Symbol.class, e); + class Vis extends JCTree.Visitor { + List result = null; + public void visitTopLevel(JCCompilationUnit tree) { + result = tree.packageAnnotations; + } + public void visitClassDef(JCClassDecl tree) { + result = tree.mods.annotations; + } + public void visitMethodDef(JCMethodDecl tree) { + result = tree.mods.annotations; + } + public void visitVarDef(JCVariableDecl tree) { + result = tree.mods.annotations; + } + } + Vis vis = new Vis(); + tree.accept(vis); + if (vis.result == null) + return null; + return matchAnnoToTree(cast(Attribute.Compound.class, findme), + sym.getAnnotationMirrors(), + vis.result); + } + + /** + * Returns the tree for an annotation given a list of annotations + * in which to search (recursively) and their corresponding trees. + * Returns null if the tree cannot be found. + */ + private JCTree matchAnnoToTree(Attribute.Compound findme, + List annos, + List trees) { + for (Attribute.Compound anno : annos) { + for (JCAnnotation tree : trees) { + JCTree match = matchAnnoToTree(findme, anno, tree); + if (match != null) + return match; + } + } + return null; + } + + /** + * Returns the tree for an annotation given an Attribute to + * search (recursively) and its corresponding tree. + * Returns null if the tree cannot be found. + */ + private JCTree matchAnnoToTree(final Attribute.Compound findme, + final Attribute attr, + final JCTree tree) { + if (attr == findme) + return (tree.type.tsym == findme.type.tsym) ? tree : null; + + class Vis implements Attribute.Visitor { + JCTree result = null; + public void visitConstant(Attribute.Constant value) { + } + public void visitClass(Attribute.Class clazz) { + } + public void visitCompound(Attribute.Compound anno) { + for (Pair pair : anno.values) { + JCExpression expr = scanForAssign(pair.fst, tree); + if (expr != null) { + JCTree match = matchAnnoToTree(findme, pair.snd, expr); + if (match != null) { + result = match; + return; + } + } + } + } + public void visitArray(Attribute.Array array) { + if (tree.getTag() == JCTree.NEWARRAY && + types.elemtype(array.type).tsym == findme.type.tsym) { + List elems = ((JCNewArray) tree).elems; + for (Attribute value : array.values) { + if (value == findme) { + result = elems.head; + return; + } + elems = elems.tail; + } + } + } + public void visitEnum(Attribute.Enum e) { + } + public void visitError(Attribute.Error e) { + } + } + Vis vis = new Vis(); + attr.accept(vis); + return vis.result; + } + + /** + * Scans for a JCAssign node with a LHS matching a given + * symbol, and returns its RHS. Does not scan nested JCAnnotations. + */ + private JCExpression scanForAssign(final MethodSymbol sym, + final JCTree tree) { + class TS extends TreeScanner { + JCExpression result = null; + public void scan(JCTree t) { + if (t != null && result == null) + t.accept(this); + } + public void visitAnnotation(JCAnnotation t) { + if (t == tree) + scan(t.args); + } + public void visitAssign(JCAssign t) { + if (t.lhs.getTag() == JCTree.IDENT) { + JCIdent ident = (JCIdent) t.lhs; + if (ident.sym == sym) + result = t.rhs; + } + } + } + TS scanner = new TS(); + tree.accept(scanner); + return scanner.result; + } + + /** + * Returns the tree node corresponding to this element, or null + * if none can be found. + */ + public JCTree getTree(Element e) { + Pair treeTop = getTreeAndTopLevel(e); + return (treeTop != null) ? treeTop.fst : null; + } + + public String getDocComment(Element e) { + // Our doc comment is contained in a map in our toplevel, + // indexed by our tree. Find our enter environment, which gives + // us our toplevel. It also gives us a tree that contains our + // tree: walk it to find our tree. This is painful. + Pair treeTop = getTreeAndTopLevel(e); + if (treeTop == null) + return null; + JCTree tree = treeTop.fst; + JCCompilationUnit toplevel = treeTop.snd; + if (toplevel.docComments == null) + return null; + return toplevel.docComments.get(tree); + } + + public PackageElement getPackageOf(Element e) { + return cast(Symbol.class, e).packge(); + } + + public boolean isDeprecated(Element e) { + Symbol sym = cast(Symbol.class, e); + return (sym.flags() & Flags.DEPRECATED) != 0; + } + + public Name getBinaryName(TypeElement type) { + return cast(TypeSymbol.class, type).flatName(); + } + + public Map getElementValuesWithDefaults( + AnnotationMirror a) { + Attribute.Compound anno = cast(Attribute.Compound.class, a); + DeclaredType annotype = a.getAnnotationType(); + Map valmap = anno.getElementValues(); + + for (ExecutableElement ex : + methodsIn(annotype.asElement().getEnclosedElements())) { + MethodSymbol meth = (MethodSymbol) ex; + Attribute defaultValue = meth.getDefaultValue(); + if (defaultValue != null && !valmap.containsKey(meth)) { + valmap.put(meth, defaultValue); + } + } + return valmap; + } + + /** + * {@inheritDoc} + */ + public FilteredMemberList getAllMembers(TypeElement element) { + Symbol sym = cast(Symbol.class, element); + Scope scope = sym.members().dupUnshared(); + List closure = types.closure(sym.asType()); + for (Type t : closure) + addMembers(scope, t); + return new FilteredMemberList(scope); + } + // where + private void addMembers(Scope scope, Type type) { + members: + for (Scope.Entry e = type.asElement().members().elems; e != null; e = e.sibling) { + Scope.Entry overrider = scope.lookup(e.sym.getSimpleName()); + while (overrider.scope != null) { + if (overrider.sym.kind == e.sym.kind + && (overrider.sym.flags() & Flags.SYNTHETIC) == 0) + { + if (overrider.sym.getKind() == ElementKind.METHOD + && overrides((ExecutableElement)overrider.sym, (ExecutableElement)e.sym, (TypeElement)type.asElement())) { + continue members; + } + } + overrider = overrider.next(); + } + boolean derived = e.sym.getEnclosingElement() != scope.owner; + ElementKind kind = e.sym.getKind(); + boolean initializer = kind == ElementKind.CONSTRUCTOR + || kind == ElementKind.INSTANCE_INIT + || kind == ElementKind.STATIC_INIT; + if (!derived || (!initializer && e.sym.isInheritedIn(scope.owner, types))) + scope.enter(e.sym); + } + } + + /** + * Returns all annotations of an element, whether + * inherited or directly present. + * + * @param e the element being examined + * @return all annotations of the element + */ + public List getAllAnnotationMirrors(Element e) { + Symbol sym = cast(Symbol.class, e); + List annos = sym.getAnnotationMirrors(); + while (sym.getKind() == ElementKind.CLASS) { + Type sup = ((ClassSymbol) sym).getSuperclass(); + if (sup.tag != TypeTags.CLASS || sup.isErroneous() || + sup.tsym == syms.objectType.tsym) { + break; + } + sym = sup.tsym; + List oldAnnos = annos; + for (Attribute.Compound anno : sym.getAnnotationMirrors()) { + if (isInherited(anno.type) && + !containsAnnoOfType(oldAnnos, anno.type)) { + annos = annos.prepend(anno); + } + } + } + return annos; + } + + /** + * Tests whether an annotation type is @Inherited. + */ + private boolean isInherited(Type annotype) { + for (Attribute.Compound anno : annotype.tsym.getAnnotationMirrors()) { + if (anno.type.tsym == syms.inheritedType.tsym) + return true; + } + return false; + } + + /** + * Tests whether a list of annotations contains an annotation + * of a given type. + */ + private static boolean containsAnnoOfType(List annos, + Type type) { + for (Attribute.Compound anno : annos) { + if (anno.type.tsym == type.tsym) + return true; + } + return false; + } + + public boolean hides(Element hiderEl, Element hideeEl) { + Symbol hider = cast(Symbol.class, hiderEl); + Symbol hidee = cast(Symbol.class, hideeEl); + + // Fields only hide fields; methods only methods; types only types. + // Names must match. Nothing hides itself (just try it). + if (hider == hidee || + hider.kind != hidee.kind || + hider.name != hidee.name) { + return false; + } + + // Only static methods can hide other methods. + // Methods only hide methods with matching signatures. + if (hider.kind == Kinds.MTH) { + if (!hider.isStatic() || + !types.isSubSignature(hider.type, hidee.type)) { + return false; + } + } + + // Hider must be in a subclass of hidee's class. + // Note that if M1 hides M2, and M2 hides M3, and M3 is accessible + // in M1's class, then M1 and M2 both hide M3. + ClassSymbol hiderClass = hider.owner.enclClass(); + ClassSymbol hideeClass = hidee.owner.enclClass(); + if (hiderClass == null || hideeClass == null || + !hiderClass.isSubClass(hideeClass, types)) { + return false; + } + + // Hidee must be accessible in hider's class. + // The method isInheritedIn is poorly named: it checks only access. + return hidee.isInheritedIn(hiderClass, types); + } + + public boolean overrides(ExecutableElement riderEl, + ExecutableElement rideeEl, TypeElement typeEl) { + MethodSymbol rider = cast(MethodSymbol.class, riderEl); + MethodSymbol ridee = cast(MethodSymbol.class, rideeEl); + ClassSymbol origin = cast(ClassSymbol.class, typeEl); + + return rider.name == ridee.name && + + // not reflexive as per JLS + rider != ridee && + + // we don't care if ridee is static, though that wouldn't + // compile + !rider.isStatic() && + + // Symbol.overrides assumes the following + ridee.isMemberOf(origin, types) && + + // check access and signatures; don't check return types + rider.overrides(ridee, origin, types, false); + } + + public String getConstantExpression(Object value) { + return Constants.format(value); + } + + /** + * Print a representation of the elements to the given writer in + * the specified order. The main purpose of this method is for + * diagnostics. The exact format of the output is not + * specified and is subject to change. + * + * @param w the writer to print the output to + * @param elements the elements to print + */ + public void printElements(java.io.Writer w, Element... elements) { + for (Element element : elements) + (new PrintingProcessor.PrintingElementVisitor(w, this)).visit(element).flush(); + } + + public Name getName(CharSequence cs) { + return names.fromString(cs.toString()); + } + + /** + * Returns the tree node and compilation unit corresponding to this + * element, or null if they can't be found. + */ + private Pair getTreeAndTopLevel(Element e) { + Symbol sym = cast(Symbol.class, e); + Env enterEnv = getEnterEnv(sym); + if (enterEnv == null) + return null; + JCTree tree = TreeInfo.declarationFor(sym, enterEnv.tree); + if (tree == null || enterEnv.toplevel == null) + return null; + return new Pair(tree, enterEnv.toplevel); + } + + /** + * Returns the best approximation for the tree node and compilation unit + * corresponding to the given element, annotation and value. + * If the element is null, null is returned. + * If the annotation is null or cannot be found, the tree node and + * compilation unit for the element is returned. + * If the annotation value is null or cannot be found, the tree node and + * compilation unit for the annotation is returned. + */ + public Pair getTreeAndTopLevel( + Element e, AnnotationMirror a, AnnotationValue v) { + if (e == null) + return null; + + Pair elemTreeTop = getTreeAndTopLevel(e); + if (elemTreeTop == null) + return null; + + if (a == null) + return elemTreeTop; + + JCTree annoTree = matchAnnoToTree(a, e, elemTreeTop.fst); + if (annoTree == null) + return elemTreeTop; + + // 6388543: if v != null, we should search within annoTree to find + // the tree matching v. For now, we ignore v and return the tree of + // the annotation. + return new Pair(annoTree, elemTreeTop.snd); + } + + /** + * Returns a symbol's enter environment, or null if it has none. + */ + private Env getEnterEnv(Symbol sym) { + // Get enclosing class of sym, or sym itself if it is a class + // or package. + TypeSymbol ts = (sym.kind != Kinds.PCK) + ? sym.enclClass() + : (PackageSymbol) sym; + return (ts != null) + ? enter.getEnv(ts) + : null; + } + + /** + * Returns an object cast to the specified type. + * @throws NullPointerException if the object is {@code null} + * @throws IllegalArgumentException if the object is of the wrong type + */ + private static T cast(Class clazz, Object o) { + if (! clazz.isInstance(o)) + throw new IllegalArgumentException(o.toString()); + return clazz.cast(o); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/model/JavacSourcePosition.java b/douyu-javac/src/main/java/com/sun/tools/javac/model/JavacSourcePosition.java new file mode 100644 index 0000000..3cc1475 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/model/JavacSourcePosition.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.model; + +import javax.tools.JavaFileObject; +import com.sun.tools.javac.util.Position; + +/** + * Implementation of model API SourcePosition based on javac internal state. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own + * risk. This code and its internal interfaces are subject to change + * or deletion without notice.

+ */ +class JavacSourcePosition { + + final JavaFileObject sourcefile; + final int pos; + final Position.LineMap lineMap; + + JavacSourcePosition(JavaFileObject sourcefile, + int pos, + Position.LineMap lineMap) { + this.sourcefile = sourcefile; + this.pos = pos; + this.lineMap = (pos != Position.NOPOS) ? lineMap : null; + } + + public JavaFileObject getFile() { + return sourcefile; + } + + public int getOffset() { + return pos; // makes use of fact that Position.NOPOS == -1 + } + + public int getLine() { + return (lineMap != null) ? lineMap.getLineNumber(pos) : -1; + } + + public int getColumn() { + return (lineMap != null) ? lineMap.getColumnNumber(pos) : -1; + } + + public String toString() { + int line = getLine(); + return (line > 0) + ? sourcefile + ":" + line + : sourcefile.toString(); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/model/JavacTypes.java b/douyu-javac/src/main/java/com/sun/tools/javac/model/JavacTypes.java new file mode 100644 index 0000000..88406c3 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/model/JavacTypes.java @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.model; + +import java.util.List; +import java.util.Set; +import java.util.EnumSet; +import javax.lang.model.element.*; +import javax.lang.model.type.*; +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.util.*; + +/** + * Utility methods for operating on types. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own + * risk. This code and its internal interfaces are subject to change + * or deletion without notice.

+ */ +public class JavacTypes implements javax.lang.model.util.Types { + + private Symtab syms; + private Types types; + + public static JavacTypes instance(Context context) { + JavacTypes instance = context.get(JavacTypes.class); + if (instance == null) + instance = new JavacTypes(context); + return instance; + } + + /** + * Public for use only by JavacProcessingEnvironment + */ + protected JavacTypes(Context context) { + setContext(context); + } + + /** + * Use a new context. May be called from outside to update + * internal state for a new annotation-processing round. + */ + public void setContext(Context context) { + context.put(JavacTypes.class, this); + syms = Symtab.instance(context); + types = Types.instance(context); + } + + public Element asElement(TypeMirror t) { + switch (t.getKind()) { + case DECLARED: + case ERROR: + case TYPEVAR: + Type type = cast(Type.class, t); + return type.asElement(); + default: + return null; + } + } + + public boolean isSameType(TypeMirror t1, TypeMirror t2) { + return types.isSameType((Type) t1, (Type) t2); + } + + public boolean isSubtype(TypeMirror t1, TypeMirror t2) { + validateTypeNotIn(t1, EXEC_OR_PKG); + validateTypeNotIn(t2, EXEC_OR_PKG); + return types.isSubtype((Type) t1, (Type) t2); + } + + public boolean isAssignable(TypeMirror t1, TypeMirror t2) { + validateTypeNotIn(t1, EXEC_OR_PKG); + validateTypeNotIn(t2, EXEC_OR_PKG); + return types.isAssignable((Type) t1, (Type) t2); + } + + public boolean contains(TypeMirror t1, TypeMirror t2) { + validateTypeNotIn(t1, EXEC_OR_PKG); + validateTypeNotIn(t2, EXEC_OR_PKG); + return types.containsType((Type) t1, (Type) t2); + } + + public boolean isSubsignature(ExecutableType m1, ExecutableType m2) { + return types.isSubSignature((Type) m1, (Type) m2); + } + + public List directSupertypes(TypeMirror t) { + validateTypeNotIn(t, EXEC_OR_PKG); + Type type = (Type) t; + Type sup = types.supertype(type); + return (sup == Type.noType || sup == type || sup == null) + ? types.interfaces(type) + : types.interfaces(type).prepend(sup); + } + + public TypeMirror erasure(TypeMirror t) { + if (t.getKind() == TypeKind.PACKAGE) + throw new IllegalArgumentException(t.toString()); + return types.erasure((Type) t); + } + + public TypeElement boxedClass(PrimitiveType p) { + return types.boxedClass((Type) p); + } + + public PrimitiveType unboxedType(TypeMirror t) { + if (t.getKind() != TypeKind.DECLARED) + throw new IllegalArgumentException(t.toString()); + Type unboxed = types.unboxedType((Type) t); + if (! unboxed.isPrimitive()) // only true primitives, not void + throw new IllegalArgumentException(t.toString()); + return unboxed; + } + + public TypeMirror capture(TypeMirror t) { + validateTypeNotIn(t, EXEC_OR_PKG); + return types.capture((Type) t); + } + + public PrimitiveType getPrimitiveType(TypeKind kind) { + switch (kind) { + case BOOLEAN: return syms.booleanType; + case BYTE: return syms.byteType; + case SHORT: return syms.shortType; + case INT: return syms.intType; + case LONG: return syms.longType; + case CHAR: return syms.charType; + case FLOAT: return syms.floatType; + case DOUBLE: return syms.doubleType; + default: + throw new IllegalArgumentException("Not a primitive type: " + kind); + } + } + + public NullType getNullType() { + return (NullType) syms.botType; + } + + public NoType getNoType(TypeKind kind) { + switch (kind) { + case VOID: return syms.voidType; + case NONE: return Type.noType; + default: + throw new IllegalArgumentException(kind.toString()); + } + } + + public ArrayType getArrayType(TypeMirror componentType) { + switch (componentType.getKind()) { + case VOID: + case EXECUTABLE: + case WILDCARD: // heh! + case PACKAGE: + throw new IllegalArgumentException(componentType.toString()); + } + return new Type.ArrayType((Type) componentType, syms.arrayClass); + } + + public WildcardType getWildcardType(TypeMirror extendsBound, + TypeMirror superBound) { + BoundKind bkind; + Type bound; + if (extendsBound == null && superBound == null) { + bkind = BoundKind.UNBOUND; + bound = syms.objectType; + } else if (superBound == null) { + bkind = BoundKind.EXTENDS; + bound = (Type) extendsBound; + } else if (extendsBound == null) { + bkind = BoundKind.SUPER; + bound = (Type) superBound; + } else { + throw new IllegalArgumentException( + "Extends and super bounds cannot both be provided"); + } + switch (bound.getKind()) { + case ARRAY: + case DECLARED: + case ERROR: + case TYPEVAR: + return new Type.WildcardType(bound, bkind, syms.boundClass); + default: + throw new IllegalArgumentException(bound.toString()); + } + } + + public DeclaredType getDeclaredType(TypeElement typeElem, + TypeMirror... typeArgs) { + ClassSymbol sym = (ClassSymbol) typeElem; + + if (typeArgs.length == 0) + return (DeclaredType) sym.erasure(types); + if (sym.type.getEnclosingType().isParameterized()) + throw new IllegalArgumentException(sym.toString()); + + return getDeclaredType0(sym.type.getEnclosingType(), sym, typeArgs); + } + + public DeclaredType getDeclaredType(DeclaredType enclosing, + TypeElement typeElem, + TypeMirror... typeArgs) { + if (enclosing == null) + return getDeclaredType(typeElem, typeArgs); + + ClassSymbol sym = (ClassSymbol) typeElem; + Type outer = (Type) enclosing; + + if (outer.tsym != sym.owner.enclClass()) + throw new IllegalArgumentException(enclosing.toString()); + if (!outer.isParameterized()) + return getDeclaredType(typeElem, typeArgs); + + return getDeclaredType0(outer, sym, typeArgs); + } + // where + private DeclaredType getDeclaredType0(Type outer, + ClassSymbol sym, + TypeMirror... typeArgs) { + if (typeArgs.length != sym.type.getTypeArguments().length()) + throw new IllegalArgumentException( + "Incorrect number of type arguments"); + + ListBuffer targs = new ListBuffer(); + for (TypeMirror t : typeArgs) { + if (!(t instanceof ReferenceType || t instanceof WildcardType)) + throw new IllegalArgumentException(t.toString()); + targs.append((Type) t); + } + // TODO: Would like a way to check that type args match formals. + + return (DeclaredType) new Type.ClassType(outer, targs.toList(), sym); + } + + /** + * Returns the type of an element when that element is viewed as + * a member of, or otherwise directly contained by, a given type. + * For example, + * when viewed as a member of the parameterized type {@code Set}, + * the {@code Set.add} method is an {@code ExecutableType} + * whose parameter is of type {@code String}. + * + * @param containing the containing type + * @param element the element + * @return the type of the element as viewed from the containing type + * @throws IllegalArgumentException if the element is not a valid one + * for the given type + */ + public TypeMirror asMemberOf(DeclaredType containing, Element element) { + Type site = (Type)containing; + Symbol sym = (Symbol)element; + if (types.asSuper(site, sym.getEnclosingElement()) == null) + throw new IllegalArgumentException(sym + "@" + site); + return types.memberType(site, sym); + } + + + private static final Set EXEC_OR_PKG = + EnumSet.of(TypeKind.EXECUTABLE, TypeKind.PACKAGE); + + /** + * Throws an IllegalArgumentException if a type's kind is one of a set. + */ + private void validateTypeNotIn(TypeMirror t, Set invalidKinds) { + if (invalidKinds.contains(t.getKind())) + throw new IllegalArgumentException(t.toString()); + } + + /** + * Returns an object cast to the specified type. + * @throws NullPointerException if the object is {@code null} + * @throws IllegalArgumentException if the object is of the wrong type + */ + private static T cast(Class clazz, Object o) { + if (! clazz.isInstance(o)) + throw new IllegalArgumentException(o.toString()); + return clazz.cast(o); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/nio/JavacPathFileManager.java b/douyu-javac/src/main/java/com/sun/tools/javac/nio/JavacPathFileManager.java new file mode 100644 index 0000000..c2cd335 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/nio/JavacPathFileManager.java @@ -0,0 +1,546 @@ +///* +// * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved. +// * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +// * +// * This code is free software; you can redistribute it and/or modify it +// * under the terms of the GNU General Public License version 2 only, as +// * published by the Free Software Foundation. Oracle designates this +// * particular file as subject to the "Classpath" exception as provided +// * by Oracle in the LICENSE file that accompanied this code. +// * +// * This code is distributed in the hope that it will be useful, but WITHOUT +// * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// * version 2 for more details (a copy is included in the LICENSE file that +// * accompanied this code). +// * +// * You should have received a copy of the GNU General Public License version +// * 2 along with this work; if not, write to the Free Software Foundation, +// * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +// * +// * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +// * or visit www.oracle.com if you need additional information or have any +// * questions. +// */ +// +//package com.sun.tools.javac.nio; +// +// +//import java.io.File; +//import java.io.FileNotFoundException; +//import java.io.IOException; +//import java.net.MalformedURLException; +//import java.net.URL; +//import java.nio.charset.Charset; +//import java.nio.file.Files; +//import java.nio.file.FileSystem; +//import java.nio.file.FileSystems; +//import java.nio.file.FileVisitOption; +//import java.nio.file.FileVisitResult; +//import java.nio.file.Path; +//import java.nio.file.SimpleFileVisitor; +//import java.nio.file.attribute.BasicFileAttributes; +//import java.util.ArrayList; +//import java.util.Arrays; +//import java.util.Collection; +//import java.util.Collections; +//import java.util.EnumSet; +//import java.util.HashMap; +//import java.util.Iterator; +//import java.util.LinkedHashSet; +//import java.util.Map; +//import java.util.Set; +//import javax.lang.model.SourceVersion; +//import javax.tools.FileObject; +//import javax.tools.JavaFileManager; +//import javax.tools.JavaFileObject; +//import javax.tools.JavaFileObject.Kind; +//import javax.tools.StandardLocation; +// +//import static java.nio.file.FileVisitOption.*; +//import static javax.tools.StandardLocation.*; +// +//import com.sun.tools.javac.file.Paths; +//import com.sun.tools.javac.util.BaseFileManager; +//import com.sun.tools.javac.util.Context; +//import com.sun.tools.javac.util.List; +//import com.sun.tools.javac.util.ListBuffer; +// +//import static com.sun.tools.javac.main.OptionName.*; +// +// +//// NOTE the imports carefully for this compilation unit. +//// +//// Path: java.nio.file.Path -- the new NIO type for which this file manager exists +//// +//// Paths: com.sun.tools.javac.file.Paths -- legacy javac type for handling path options +//// The other Paths (java.nio.file.Paths) is not used +// +//// NOTE this and related classes depend on new API in JDK 7. +//// This requires special handling while bootstrapping the JDK build, +//// when these classes might not yet have been compiled. To workaround +//// this, the build arranges to make stubs of these classes available +//// when compiling this and related classes. The set of stub files +//// is specified in make/build.properties. +// +///** +// * Implementation of PathFileManager: a JavaFileManager based on the use +// * of java.nio.file.Path. +// * +// *

Just as a Path is somewhat analagous to a File, so too is this +// * JavacPathFileManager analogous to JavacFileManager, as it relates to the +// * support of FileObjects based on File objects (i.e. just RegularFileObject, +// * not ZipFileObject and its variants.) +// * +// *

The default values for the standard locations supported by this file +// * manager are the same as the default values provided by JavacFileManager -- +// * i.e. as determined by the javac.file.Paths class. To override these values, +// * call {@link #setLocation}. +// * +// *

To reduce confusion with Path objects, the locations such as "class path", +// * "source path", etc, are generically referred to here as "search paths". +// * +// *

This is NOT part of any supported API. +// * If you write code that depends on this, you do so at your own risk. +// * This code and its internal interfaces are subject to change or +// * deletion without notice. +// */ +//public class JavacPathFileManager extends BaseFileManager implements PathFileManager { +// protected FileSystem defaultFileSystem; +// +// /** +// * Create a JavacPathFileManager using a given context, optionally registering +// * it as the JavaFileManager for that context. +// */ +// public JavacPathFileManager(Context context, boolean register, Charset charset) { +// super(charset); +// if (register) +// context.put(JavaFileManager.class, this); +// pathsForLocation = new HashMap(); +// fileSystems = new HashMap(); +// setContext(context); +// } +// +// /** +// * Set the context for JavacPathFileManager. +// */ +// @Override +// protected void setContext(Context context) { +// super.setContext(context); +// searchPaths = Paths.instance(context); +// } +// +// @Override +// public FileSystem getDefaultFileSystem() { +// if (defaultFileSystem == null) +// defaultFileSystem = FileSystems.getDefault(); +// return defaultFileSystem; +// } +// +// @Override +// public void setDefaultFileSystem(FileSystem fs) { +// defaultFileSystem = fs; +// } +// +// @Override +// public void flush() throws IOException { +// contentCache.clear(); +// } +// +// @Override +// public void close() throws IOException { +// for (FileSystem fs: fileSystems.values()) +// fs.close(); +// } +// +// @Override +// public ClassLoader getClassLoader(Location location) { +// nullCheck(location); +// Iterable path = getLocation(location); +// if (path == null) +// return null; +// ListBuffer lb = new ListBuffer(); +// for (Path p: path) { +// try { +// lb.append(p.toUri().toURL()); +// } catch (MalformedURLException e) { +// throw new AssertionError(e); +// } +// } +// +// return getClassLoader(lb.toArray(new URL[lb.size()])); +// } +// +// @Override +// public boolean isDefaultBootClassPath() { +// return searchPaths.isDefaultBootClassPath(); +// } +// +// // +// +// public boolean hasLocation(Location location) { +// return (getLocation(location) != null); +// } +// +// public Iterable getLocation(Location location) { +// nullCheck(location); +// lazyInitSearchPaths(); +// PathsForLocation path = pathsForLocation.get(location); +// if (path == null && !pathsForLocation.containsKey(location)) { +// setDefaultForLocation(location); +// path = pathsForLocation.get(location); +// } +// return path; +// } +// +// private Path getOutputLocation(Location location) { +// Iterable paths = getLocation(location); +// return (paths == null ? null : paths.iterator().next()); +// } +// +// public void setLocation(Location location, Iterable searchPath) +// throws IOException +// { +// nullCheck(location); +// lazyInitSearchPaths(); +// if (searchPath == null) { +// setDefaultForLocation(location); +// } else { +// if (location.isOutputLocation()) +// checkOutputPath(searchPath); +// PathsForLocation pl = new PathsForLocation(); +// for (Path p: searchPath) +// pl.add(p); // TODO -Xlint:path warn if path not found +// pathsForLocation.put(location, pl); +// } +// } +// +// private void checkOutputPath(Iterable searchPath) throws IOException { +// Iterator pathIter = searchPath.iterator(); +// if (!pathIter.hasNext()) +// throw new IllegalArgumentException("empty path for directory"); +// Path path = pathIter.next(); +// if (pathIter.hasNext()) +// throw new IllegalArgumentException("path too long for directory"); +// if (!isDirectory(path)) +// throw new IOException(path + ": not a directory"); +// } +// +// private void setDefaultForLocation(Location locn) { +// Collection files = null; +// if (locn instanceof StandardLocation) { +// switch ((StandardLocation) locn) { +// case CLASS_PATH: +// files = searchPaths.userClassPath(); +// break; +// case PLATFORM_CLASS_PATH: +// files = searchPaths.bootClassPath(); +// break; +// case SOURCE_PATH: +// files = searchPaths.sourcePath(); +// break; +// case CLASS_OUTPUT: { +// String arg = options.get(D); +// files = (arg == null ? null : Collections.singleton(new File(arg))); +// break; +// } +// case SOURCE_OUTPUT: { +// String arg = options.get(S); +// files = (arg == null ? null : Collections.singleton(new File(arg))); +// break; +// } +// } +// } +// +// PathsForLocation pl = new PathsForLocation(); +// if (files != null) { +// for (File f: files) +// pl.add(f.toPath()); +// } +// pathsForLocation.put(locn, pl); +// } +// +// private void lazyInitSearchPaths() { +// if (!inited) { +// setDefaultForLocation(PLATFORM_CLASS_PATH); +// setDefaultForLocation(CLASS_PATH); +// setDefaultForLocation(SOURCE_PATH); +// inited = true; +// } +// } +// // where +// private boolean inited = false; +// +// private Map pathsForLocation; +// private Paths searchPaths; +// +// private static class PathsForLocation extends LinkedHashSet { +// private static final long serialVersionUID = 6788510222394486733L; +// } +// +// // +// +// // +// +// @Override +// public Path getPath(FileObject fo) { +// nullCheck(fo); +// if (!(fo instanceof PathFileObject)) +// throw new IllegalArgumentException(); +// return ((PathFileObject) fo).getPath(); +// } +// +// @Override +// public boolean isSameFile(FileObject a, FileObject b) { +// nullCheck(a); +// nullCheck(b); +// if (!(a instanceof PathFileObject)) +// throw new IllegalArgumentException("Not supported: " + a); +// if (!(b instanceof PathFileObject)) +// throw new IllegalArgumentException("Not supported: " + b); +// return ((PathFileObject) a).isSameFile((PathFileObject) b); +// } +// +// @Override +// public Iterable list(Location location, +// String packageName, Set kinds, boolean recurse) +// throws IOException { +// // validatePackageName(packageName); +// nullCheck(packageName); +// nullCheck(kinds); +// +// Iterable paths = getLocation(location); +// if (paths == null) +// return List.nil(); +// ListBuffer results = new ListBuffer(); +// +// for (Path path : paths) +// list(path, packageName, kinds, recurse, results); +// +// return results.toList(); +// } +// +// private void list(Path path, String packageName, final Set kinds, +// boolean recurse, final ListBuffer results) +// throws IOException { +// if (!Files.exists(path)) +// return; +// +// final Path pathDir; +// if (isDirectory(path)) +// pathDir = path; +// else { +// FileSystem fs = getFileSystem(path); +// if (fs == null) +// return; +// pathDir = fs.getRootDirectories().iterator().next(); +// } +// String sep = path.getFileSystem().getSeparator(); +// Path packageDir = packageName.isEmpty() ? pathDir +// : pathDir.resolve(packageName.replace(".", sep)); +// if (!Files.exists(packageDir)) +// return; +// +///* Alternate impl of list, superceded by use of Files.walkFileTree */ +//// Deque queue = new LinkedList(); +//// queue.add(packageDir); +//// +//// Path dir; +//// while ((dir = queue.poll()) != null) { +//// DirectoryStream ds = dir.newDirectoryStream(); +//// try { +//// for (Path p: ds) { +//// String name = p.getFileName().toString(); +//// if (isDirectory(p)) { +//// if (recurse && SourceVersion.isIdentifier(name)) { +//// queue.add(p); +//// } +//// } else { +//// if (kinds.contains(getKind(name))) { +//// JavaFileObject fe = +//// PathFileObject.createDirectoryPathFileObject(this, p, pathDir); +//// results.append(fe); +//// } +//// } +//// } +//// } finally { +//// ds.close(); +//// } +//// } +// int maxDepth = (recurse ? Integer.MAX_VALUE : 1); +// Set opts = EnumSet.of(FOLLOW_LINKS); +// Files.walkFileTree(packageDir, opts, maxDepth, +// new SimpleFileVisitor() { +// @Override +// public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { +// Path name = dir.getFileName(); +// if (name == null || SourceVersion.isIdentifier(name.toString())) // JSR 292? +// return FileVisitResult.CONTINUE; +// else +// return FileVisitResult.SKIP_SUBTREE; +// } +// +// @Override +// public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { +// if (attrs.isRegularFile() && kinds.contains(getKind(file.getFileName().toString()))) { +// JavaFileObject fe = +// PathFileObject.createDirectoryPathFileObject( +// JavacPathFileManager.this, file, pathDir); +// results.append(fe); +// } +// return FileVisitResult.CONTINUE; +// } +// }); +// } +// +// @Override +// public Iterable getJavaFileObjectsFromPaths( +// Iterable paths) { +// ArrayList result; +// if (paths instanceof Collection) +// result = new ArrayList(((Collection)paths).size()); +// else +// result = new ArrayList(); +// for (Path p: paths) +// result.add(PathFileObject.createSimplePathFileObject(this, nullCheck(p))); +// return result; +// } +// +// @Override +// public Iterable getJavaFileObjects(Path... paths) { +// return getJavaFileObjectsFromPaths(Arrays.asList(nullCheck(paths))); +// } +// +// @Override +// public JavaFileObject getJavaFileForInput(Location location, +// String className, Kind kind) throws IOException { +// return getFileForInput(location, getRelativePath(className, kind)); +// } +// +// @Override +// public FileObject getFileForInput(Location location, +// String packageName, String relativeName) throws IOException { +// return getFileForInput(location, getRelativePath(packageName, relativeName)); +// } +// +// private JavaFileObject getFileForInput(Location location, String relativePath) +// throws IOException { +// for (Path p: getLocation(location)) { +// if (isDirectory(p)) { +// Path f = resolve(p, relativePath); +// if (Files.exists(f)) +// return PathFileObject.createDirectoryPathFileObject(this, f, p); +// } else { +// FileSystem fs = getFileSystem(p); +// if (fs != null) { +// Path file = getPath(fs, relativePath); +// if (Files.exists(file)) +// return PathFileObject.createJarPathFileObject(this, file); +// } +// } +// } +// return null; +// } +// +// @Override +// public JavaFileObject getJavaFileForOutput(Location location, +// String className, Kind kind, FileObject sibling) throws IOException { +// return getFileForOutput(location, getRelativePath(className, kind), sibling); +// } +// +// @Override +// public FileObject getFileForOutput(Location location, String packageName, +// String relativeName, FileObject sibling) +// throws IOException { +// return getFileForOutput(location, getRelativePath(packageName, relativeName), sibling); +// } +// +// private JavaFileObject getFileForOutput(Location location, +// String relativePath, FileObject sibling) { +// Path dir = getOutputLocation(location); +// if (dir == null) { +// if (location == CLASS_OUTPUT) { +// Path siblingDir = null; +// if (sibling != null && sibling instanceof PathFileObject) { +// siblingDir = ((PathFileObject) sibling).getPath().getParent(); +// } +// return PathFileObject.createSiblingPathFileObject(this, +// siblingDir.resolve(getBaseName(relativePath)), +// relativePath); +// } else if (location == SOURCE_OUTPUT) { +// dir = getOutputLocation(CLASS_OUTPUT); +// } +// } +// +// Path file; +// if (dir != null) { +// file = resolve(dir, relativePath); +// return PathFileObject.createDirectoryPathFileObject(this, file, dir); +// } else { +// file = getPath(getDefaultFileSystem(), relativePath); +// return PathFileObject.createSimplePathFileObject(this, file); +// } +// +// } +// +// @Override +// public String inferBinaryName(Location location, JavaFileObject fo) { +// nullCheck(fo); +// // Need to match the path semantics of list(location, ...) +// Iterable paths = getLocation(location); +// if (paths == null) { +// return null; +// } +// +// if (!(fo instanceof PathFileObject)) +// throw new IllegalArgumentException(fo.getClass().getName()); +// +// return ((PathFileObject) fo).inferBinaryName(paths); +// } +// +// private FileSystem getFileSystem(Path p) throws IOException { +// FileSystem fs = fileSystems.get(p); +// if (fs == null) { +// fs = FileSystems.newFileSystem(p, null); +// fileSystems.put(p, fs); +// } +// return fs; +// } +// +// private Map fileSystems; +// +// // +// +// // +// +// private static String getRelativePath(String className, Kind kind) { +// return className.replace(".", "/") + kind.extension; +// } +// +// private static String getRelativePath(String packageName, String relativeName) { +// return packageName.replace(".", "/") + relativeName; +// } +// +// private static String getBaseName(String relativePath) { +// int lastSep = relativePath.lastIndexOf("/"); +// return relativePath.substring(lastSep + 1); // safe if "/" not found +// } +// +// private static boolean isDirectory(Path path) throws IOException { +// BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class); +// return attrs.isDirectory(); +// } +// +// private static Path getPath(FileSystem fs, String relativePath) { +// return fs.getPath(relativePath.replace("/", fs.getSeparator())); +// } +// +// private static Path resolve(Path base, String relativePath) { +// FileSystem fs = base.getFileSystem(); +// Path rp = fs.getPath(relativePath.replace("/", fs.getSeparator())); +// return base.resolve(rp); +// } +// +// // +// +//} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/nio/PathFileManager.java b/douyu-javac/src/main/java/com/sun/tools/javac/nio/PathFileManager.java new file mode 100644 index 0000000..482d86b --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/nio/PathFileManager.java @@ -0,0 +1,125 @@ +///* +// * Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved. +// * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +// * +// * This code is free software; you can redistribute it and/or modify it +// * under the terms of the GNU General Public License version 2 only, as +// * published by the Free Software Foundation. Oracle designates this +// * particular file as subject to the "Classpath" exception as provided +// * by Oracle in the LICENSE file that accompanied this code. +// * +// * This code is distributed in the hope that it will be useful, but WITHOUT +// * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// * version 2 for more details (a copy is included in the LICENSE file that +// * accompanied this code). +// * +// * You should have received a copy of the GNU General Public License version +// * 2 along with this work; if not, write to the Free Software Foundation, +// * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +// * +// * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +// * or visit www.oracle.com if you need additional information or have any +// * questions. +// */ +// +//package com.sun.tools.javac.nio; +// +//import java.io.IOException; +//import java.nio.file.FileSystem; +//import java.nio.file.Path; +//import javax.tools.FileObject; +//import javax.tools.JavaFileManager; +//import javax.tools.JavaFileObject; +// +///** +// * File manager based on {@linkplain File java.nio.file.Path}. +// * +// * Eventually, this should be moved to javax.tools. +// * Also, JavaCompiler might reasonably provide a method getPathFileManager, +// * similar to {@link javax.tools.JavaCompiler#getStandardFileManager +// * getStandardFileManager}. However, would need to be handled carefully +// * as another forward reference from langtools to jdk. +// * +// *

This is NOT part of any supported API. +// * If you write code that depends on this, you do so at your own risk. +// * This code and its internal interfaces are subject to change or +// * deletion without notice. +// */ +//public interface PathFileManager extends JavaFileManager { +// /** +// * Get the default file system used to create paths. If no value has been +// * set, the default file system is {@link FileSystems#getDefault}. +// */ +// FileSystem getDefaultFileSystem(); +// +// /** +// * Set the default file system used to create paths. +// * @param fs the default file system used to create any new paths. +// */ +// void setDefaultFileSystem(FileSystem fs); +// +// /** +// * Get file objects representing the given files. +// * +// * @param paths a list of paths +// * @return a list of file objects +// * @throws IllegalArgumentException if the list of paths includes +// * a directory +// */ +// Iterable getJavaFileObjectsFromPaths( +// Iterable paths); +// +// /** +// * Get file objects representing the given paths. +// * Convenience method equivalent to: +// * +// *

+//     *     getJavaFileObjectsFromPaths({@linkplain java.util.Arrays#asList Arrays.asList}(paths))
+//     * 
+// * +// * @param paths an array of paths +// * @return a list of file objects +// * @throws IllegalArgumentException if the array of files includes +// * a directory +// * @throws NullPointerException if the given array contains null +// * elements +// */ +// Iterable getJavaFileObjects(Path... paths); +// +// /** +// * Return the Path for a file object that has been obtained from this +// * file manager. +// * +// * @param fo A file object that has been obtained from this file manager. +// * @return The underlying Path object. +// * @throws IllegalArgumentException is the file object was not obtained from +// * from this file manager. +// */ +// Path getPath(FileObject fo); +// +// /** +// * Get the search path associated with the given location. +// * +// * @param location a location +// * @return a list of paths or {@code null} if this location has no +// * associated search path +// * @see #setLocation +// */ +// Iterable getLocation(Location location); +// +// /** +// * Associate the given search path with the given location. Any +// * previous value will be discarded. +// * +// * @param location a location +// * @param searchPath a list of files, if {@code null} use the default +// * search path for this location +// * @see #getLocation +// * @throws IllegalArgumentException if location is an output +// * location and searchpath does not contain exactly one element +// * @throws IOException if location is an output location and searchpath +// * does not represent an existing directory +// */ +// void setLocation(Location location, Iterable searchPath) throws IOException; +//} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/nio/PathFileObject.java b/douyu-javac/src/main/java/com/sun/tools/javac/nio/PathFileObject.java new file mode 100644 index 0000000..8dce09a --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/nio/PathFileObject.java @@ -0,0 +1,317 @@ +///* +// * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved. +// * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +// * +// * This code is free software; you can redistribute it and/or modify it +// * under the terms of the GNU General Public License version 2 only, as +// * published by the Free Software Foundation. Oracle designates this +// * particular file as subject to the "Classpath" exception as provided +// * by Oracle in the LICENSE file that accompanied this code. +// * +// * This code is distributed in the hope that it will be useful, but WITHOUT +// * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// * version 2 for more details (a copy is included in the LICENSE file that +// * accompanied this code). +// * +// * You should have received a copy of the GNU General Public License version +// * 2 along with this work; if not, write to the Free Software Foundation, +// * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +// * +// * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +// * or visit www.oracle.com if you need additional information or have any +// * questions. +// */ +// +//package com.sun.tools.javac.nio; +// +//import java.io.IOException; +//import java.io.InputStream; +//import java.io.InputStreamReader; +//import java.io.OutputStream; +//import java.io.OutputStreamWriter; +//import java.io.Reader; +//import java.io.Writer; +//import java.net.URI; +//import java.nio.ByteBuffer; +//import java.nio.CharBuffer; +//import java.nio.charset.CharsetDecoder; +//import java.nio.file.Files; +//import java.nio.file.LinkOption; +//import java.nio.file.Path; +//import java.nio.file.attribute.BasicFileAttributes; +//import javax.lang.model.element.Modifier; +//import javax.lang.model.element.NestingKind; +//import javax.tools.JavaFileObject; +// +//import com.sun.tools.javac.util.BaseFileManager; +// +// +///** +// * Implementation of JavaFileObject using java.nio.file API. +// * +// *

PathFileObjects are, for the most part, straightforward wrappers around +// * Path objects. The primary complexity is the support for "inferBinaryName". +// * This is left as an abstract method, implemented by each of a number of +// * different factory methods, which compute the binary name based on +// * information available at the time the file object is created. +// * +// *

This is NOT part of any supported API. +// * If you write code that depends on this, you do so at your own risk. +// * This code and its internal interfaces are subject to change or +// * deletion without notice. +// */ +//abstract class PathFileObject implements JavaFileObject { +// private JavacPathFileManager fileManager; +// private Path path; +// +// /** +// * Create a PathFileObject within a directory, such that the binary name +// * can be inferred from the relationship to the parent directory. +// */ +// static PathFileObject createDirectoryPathFileObject(JavacPathFileManager fileManager, +// final Path path, final Path dir) { +// return new PathFileObject(fileManager, path) { +// @Override +// String inferBinaryName(Iterable paths) { +// return toBinaryName(dir.relativize(path)); +// } +// }; +// } +// +// /** +// * Create a PathFileObject in a file system such as a jar file, such that +// * the binary name can be inferred from its position within the filesystem. +// */ +// static PathFileObject createJarPathFileObject(JavacPathFileManager fileManager, +// final Path path) { +// return new PathFileObject(fileManager, path) { +// @Override +// String inferBinaryName(Iterable paths) { +// return toBinaryName(path); +// } +// }; +// } +// +// /** +// * Create a PathFileObject whose binary name can be inferred from the +// * relative path to a sibling. +// */ +// static PathFileObject createSiblingPathFileObject(JavacPathFileManager fileManager, +// final Path path, final String relativePath) { +// return new PathFileObject(fileManager, path) { +// @Override +// String inferBinaryName(Iterable paths) { +// return toBinaryName(relativePath, "/"); +// } +// }; +// } +// +// /** +// * Create a PathFileObject whose binary name might be inferred from its +// * position on a search path. +// */ +// static PathFileObject createSimplePathFileObject(JavacPathFileManager fileManager, +// final Path path) { +// return new PathFileObject(fileManager, path) { +// @Override +// String inferBinaryName(Iterable paths) { +// Path absPath = path.toAbsolutePath(); +// for (Path p: paths) { +// Path ap = p.toAbsolutePath(); +// if (absPath.startsWith(ap)) { +// try { +// Path rp = ap.relativize(absPath); +// if (rp != null) // maybe null if absPath same as ap +// return toBinaryName(rp); +// } catch (IllegalArgumentException e) { +// // ignore this p if cannot relativize path to p +// } +// } +// } +// return null; +// } +// }; +// } +// +// protected PathFileObject(JavacPathFileManager fileManager, Path path) { +// fileManager.getClass(); // null check +// path.getClass(); // null check +// this.fileManager = fileManager; +// this.path = path; +// } +// +// abstract String inferBinaryName(Iterable paths); +// +// /** +// * Return the Path for this object. +// * @return the Path for this object. +// */ +// Path getPath() { +// return path; +// } +// +// @Override +// public Kind getKind() { +// return BaseFileManager.getKind(path.getFileName().toString()); +// } +// +// @Override +// public boolean isNameCompatible(String simpleName, Kind kind) { +// simpleName.getClass(); +// // null check +// if (kind == Kind.OTHER && getKind() != kind) { +// return false; +// } +// String sn = simpleName + kind.extension; +// String pn = path.getFileName().toString(); +// if (pn.equals(sn)) { +// return true; +// } +// if (pn.equalsIgnoreCase(sn)) { +// try { +// // allow for Windows +// return path.toRealPath(LinkOption.NOFOLLOW_LINKS).getFileName().toString().equals(sn); +// } catch (IOException e) { +// } +// } +// return false; +// } +// +// @Override +// public NestingKind getNestingKind() { +// return null; +// } +// +// @Override +// public Modifier getAccessLevel() { +// return null; +// } +// +// @Override +// public URI toUri() { +// return path.toUri(); +// } +// +// @Override +// public String getName() { +// return path.toString(); +// } +// +// @Override +// public InputStream openInputStream() throws IOException { +// return Files.newInputStream(path); +// } +// +// @Override +// public OutputStream openOutputStream() throws IOException { +// ensureParentDirectoriesExist(); +// return Files.newOutputStream(path); +// } +// +// @Override +// public Reader openReader(boolean ignoreEncodingErrors) throws IOException { +// CharsetDecoder decoder = fileManager.getDecoder(fileManager.getEncodingName(), ignoreEncodingErrors); +// return new InputStreamReader(openInputStream(), decoder); +// } +// +// @Override +// public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { +// CharBuffer cb = fileManager.getCachedContent(this); +// if (cb == null) { +// InputStream in = openInputStream(); +// try { +// ByteBuffer bb = fileManager.makeByteBuffer(in); +// JavaFileObject prev = fileManager.log.useSource(this); +// try { +// cb = fileManager.decode(bb, ignoreEncodingErrors); +// } finally { +// fileManager.log.useSource(prev); +// } +// fileManager.recycleByteBuffer(bb); +// if (!ignoreEncodingErrors) { +// fileManager.cache(this, cb); +// } +// } finally { +// in.close(); +// } +// } +// return cb; +// } +// +// @Override +// public Writer openWriter() throws IOException { +// ensureParentDirectoriesExist(); +// return new OutputStreamWriter(Files.newOutputStream(path), fileManager.getEncodingName()); +// } +// +// @Override +// public long getLastModified() { +// try { +// return Files.getLastModifiedTime(path).toMillis(); +// } catch (IOException e) { +// return -1; +// } +// } +// +// @Override +// public boolean delete() { +// try { +// Files.delete(path); +// return true; +// } catch (IOException e) { +// return false; +// } +// } +// +// public boolean isSameFile(PathFileObject other) { +// try { +// return Files.isSameFile(path, other.path); +// } catch (IOException e) { +// return false; +// } +// } +// +// @Override +// public boolean equals(Object other) { +// return (other instanceof PathFileObject && path.equals(((PathFileObject) other).path)); +// } +// +// @Override +// public int hashCode() { +// return path.hashCode(); +// } +// +// @Override +// public String toString() { +// return getClass().getSimpleName() + "[" + path + "]"; +// } +// +// private void ensureParentDirectoriesExist() throws IOException { +// Path parent = path.getParent(); +// if (parent != null) +// Files.createDirectories(parent); +// } +// +// private long size() { +// try { +// return Files.size(path); +// } catch (IOException e) { +// return -1; +// } +// } +// +// protected static String toBinaryName(Path relativePath) { +// return toBinaryName(relativePath.toString(), +// relativePath.getFileSystem().getSeparator()); +// } +// +// protected static String toBinaryName(String relativePath, String sep) { +// return removeExtension(relativePath).replace(sep, "."); +// } +// +// protected static String removeExtension(String fileName) { +// int lastDot = fileName.lastIndexOf("."); +// return (lastDot == -1 ? fileName : fileName.substring(0, lastDot)); +// } +//} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/parser/DocCommentScanner.java b/douyu-javac/src/main/java/com/sun/tools/javac/parser/DocCommentScanner.java new file mode 100644 index 0000000..e6d687d --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/parser/DocCommentScanner.java @@ -0,0 +1,420 @@ +/* + * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.parser; + +import java.nio.*; + +import com.sun.tools.javac.util.*; +import static com.sun.tools.javac.util.LayoutCharacters.*; + +/** An extension to the base lexical analyzer that captures + * and processes the contents of doc comments. It does so by + * translating Unicode escape sequences and by stripping the + * leading whitespace and starts from each line of the comment. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class DocCommentScanner extends Scanner { + + /** Create a scanner from the input buffer. buffer must implement + * array() and compact(), and remaining() must be less than limit(). + */ + protected DocCommentScanner(ScannerFactory fac, CharBuffer buffer) { + super(fac, buffer); + } + + /** Create a scanner from the input array. The array must have at + * least a single character of extra space. + */ + protected DocCommentScanner(ScannerFactory fac, char[] input, int inputLength) { + super(fac, input, inputLength); + } + + /** Starting position of the comment in original source + */ + private int pos; + + /** The comment input buffer, index of next chacter to be read, + * index of one past last character in buffer. + */ + private char[] buf; + private int bp; + private int buflen; + + /** The current character. + */ + private char ch; + + /** The column number position of the current character. + */ + private int col; + + /** The buffer index of the last converted Unicode character + */ + private int unicodeConversionBp = 0; + + /** + * Buffer for doc comment. + */ + private char[] docCommentBuffer = new char[1024]; + + /** + * Number of characters in doc comment buffer. + */ + private int docCommentCount; + + /** + * Translated and stripped contents of doc comment + */ + private String docComment = null; + + + /** Unconditionally expand the comment buffer. + */ + private void expandCommentBuffer() { + char[] newBuffer = new char[docCommentBuffer.length * 2]; + System.arraycopy(docCommentBuffer, 0, newBuffer, + 0, docCommentBuffer.length); + docCommentBuffer = newBuffer; + } + + /** Convert an ASCII digit from its base (8, 10, or 16) + * to its value. + */ + private int digit(int base) { + char c = ch; + int result = Character.digit(c, base); + if (result >= 0 && c > 0x7f) { + ch = "0123456789abcdef".charAt(result); + } + return result; + } + + /** Convert Unicode escape; bp points to initial '\' character + * (Spec 3.3). + */ + private void convertUnicode() { + if (ch == '\\' && unicodeConversionBp != bp) { + bp++; ch = buf[bp]; col++; + if (ch == 'u') { + do { + bp++; ch = buf[bp]; col++; + } while (ch == 'u'); + int limit = bp + 3; + if (limit < buflen) { + int d = digit(16); + int code = d; + while (bp < limit && d >= 0) { + bp++; ch = buf[bp]; col++; + d = digit(16); + code = (code << 4) + d; + } + if (d >= 0) { + ch = (char)code; + unicodeConversionBp = bp; + return; + } + } + // "illegal.Unicode.esc", reported by base scanner + } else { + bp--; + ch = '\\'; + col--; + } + } + } + + + /** Read next character. + */ + private void scanChar() { + bp++; + ch = buf[bp]; + switch (ch) { + case '\r': // return + col = 0; + break; + case '\n': // newline + if (bp == 0 || buf[bp-1] != '\r') { + col = 0; + } + break; + case '\t': // tab + col = (col / TabInc * TabInc) + TabInc; + break; + case '\\': // possible Unicode + col++; + convertUnicode(); + break; + default: + col++; + break; + } + } + + /** + * Read next character in doc comment, skipping over double '\' characters. + * If a double '\' is skipped, put in the buffer and update buffer count. + */ + private void scanDocCommentChar() { + scanChar(); + if (ch == '\\') { + if (buf[bp+1] == '\\' && unicodeConversionBp != bp) { + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ch; + bp++; col++; + } else { + convertUnicode(); + } + } + } + + /* Reset doc comment before reading each new token + */ + public void nextToken() { + docComment = null; + super.nextToken(); + } + + /** + * Returns the documentation string of the current token. + */ + public String docComment() { + return docComment; + } + + /** + * Process a doc comment and make the string content available. + * Strips leading whitespace and stars. + */ + @SuppressWarnings("fallthrough") + protected void processComment(CommentStyle style) { + if (style != CommentStyle.JAVADOC) { + return; + } + + pos = pos(); + buf = getRawCharacters(pos, endPos()); + buflen = buf.length; + bp = 0; + col = 0; + + docCommentCount = 0; + + boolean firstLine = true; + + // Skip over first slash + scanDocCommentChar(); + // Skip over first star + scanDocCommentChar(); + + // consume any number of stars + while (bp < buflen && ch == '*') { + scanDocCommentChar(); + } + // is the comment in the form /**/, /***/, /****/, etc. ? + if (bp < buflen && ch == '/') { + docComment = ""; + return; + } + + // skip a newline on the first line of the comment. + if (bp < buflen) { + if (ch == LF) { + scanDocCommentChar(); + firstLine = false; + } else if (ch == CR) { + scanDocCommentChar(); + if (ch == LF) { + scanDocCommentChar(); + firstLine = false; + } + } + } + + outerLoop: + + // The outerLoop processes the doc comment, looping once + // for each line. For each line, it first strips off + // whitespace, then it consumes any stars, then it + // puts the rest of the line into our buffer. + while (bp < buflen) { + + // The wsLoop consumes whitespace from the beginning + // of each line. + wsLoop: + + while (bp < buflen) { + switch(ch) { + case ' ': + scanDocCommentChar(); + break; + case '\t': + col = ((col - 1) / TabInc * TabInc) + TabInc; + scanDocCommentChar(); + break; + case FF: + col = 0; + scanDocCommentChar(); + break; +// Treat newline at beginning of line (blank line, no star) +// as comment text. Old Javadoc compatibility requires this. +/*---------------------------------* + case CR: // (Spec 3.4) + scanDocCommentChar(); + if (ch == LF) { + col = 0; + scanDocCommentChar(); + } + break; + case LF: // (Spec 3.4) + scanDocCommentChar(); + break; +*---------------------------------*/ + default: + // we've seen something that isn't whitespace; + // jump out. + break wsLoop; + } + } + + // Are there stars here? If so, consume them all + // and check for the end of comment. + if (ch == '*') { + // skip all of the stars + do { + scanDocCommentChar(); + } while (ch == '*'); + + // check for the closing slash. + if (ch == '/') { + // We're done with the doc comment + // scanChar() and breakout. + break outerLoop; + } + } else if (! firstLine) { + //The current line does not begin with a '*' so we will indent it. + for (int i = 1; i < col; i++) { + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ' '; + } + } + + // The textLoop processes the rest of the characters + // on the line, adding them to our buffer. + textLoop: + while (bp < buflen) { + switch (ch) { + case '*': + // Is this just a star? Or is this the + // end of a comment? + scanDocCommentChar(); + if (ch == '/') { + // This is the end of the comment, + // set ch and return our buffer. + break outerLoop; + } + // This is just an ordinary star. Add it to + // the buffer. + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = '*'; + break; + case ' ': + case '\t': + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ch; + scanDocCommentChar(); + break; + case FF: + scanDocCommentChar(); + break textLoop; // treat as end of line + case CR: // (Spec 3.4) + scanDocCommentChar(); + if (ch != LF) { + // Canonicalize CR-only line terminator to LF + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = (char)LF; + break textLoop; + } + /* fall through to LF case */ + case LF: // (Spec 3.4) + // We've seen a newline. Add it to our + // buffer and break out of this loop, + // starting fresh on a new line. + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ch; + scanDocCommentChar(); + break textLoop; + default: + // Add the character to our buffer. + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ch; + scanDocCommentChar(); + } + } // end textLoop + firstLine = false; + } // end outerLoop + + if (docCommentCount > 0) { + int i = docCommentCount - 1; + trailLoop: + while (i > -1) { + switch (docCommentBuffer[i]) { + case '*': + i--; + break; + default: + break trailLoop; + } + } + docCommentCount = i + 1; + + // Store the text of the doc comment + docComment = new String(docCommentBuffer, 0 , docCommentCount); + } else { + docComment = ""; + } + } + + /** Build a map for translating between line numbers and + * positions in the input. + * + * @return a LineMap */ + public Position.LineMap getLineMap() { + char[] buf = getRawCharacters(); + return Position.makeLineMap(buf, buf.length, true); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/parser/EndPosParser.java b/douyu-javac/src/main/java/com/sun/tools/javac/parser/EndPosParser.java new file mode 100644 index 0000000..715839b --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/parser/EndPosParser.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.parser; + +import java.util.Map; +import java.util.HashMap; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.TreeInfo; + +import static com.sun.tools.javac.tree.JCTree.*; + +/** + * This class is similar to Parser except that it stores ending + * positions for the tree nodes. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice.

+ */ +public class EndPosParser extends JavacParser { + + public EndPosParser(ParserFactory fac, Lexer S, boolean keepDocComments, boolean keepLineMap) { + super(fac, S, keepDocComments, keepLineMap); + this.S = S; + endPositions = new HashMap(); + } + + private Lexer S; + + /** A hashtable to store ending positions + * of source ranges indexed by the tree nodes. + * Defined only if option flag genEndPos is set. + */ + Map endPositions; + + /** {@inheritDoc} */ + @Override + protected void storeEnd(JCTree tree, int endpos) { + int errorEndPos = getErrorEndPos(); + endPositions.put(tree, errorEndPos > endpos ? errorEndPos : endpos); + } + + /** {@inheritDoc} */ + @Override + protected T to(T t) { + storeEnd(t, S.endPos()); + return t; + } + + /** {@inheritDoc} */ + @Override + protected T toP(T t) { + storeEnd(t, S.prevEndPos()); + return t; + } + + @Override + public JCCompilationUnit parseCompilationUnit() { + JCCompilationUnit t = super.parseCompilationUnit(); + t.endPositions = endPositions; + return t; + } + + /** {@inheritDoc} */ + @Override + JCExpression parExpression() { + int pos = S.pos(); + JCExpression t = super.parExpression(); + return toP(F.at(pos).Parens(t)); + } + + /** {@inheritDoc} */ + @Override + public int getEndPos(JCTree tree) { + return TreeInfo.getEndPos(tree, endPositions); + } + +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/parser/JavacParser.java b/douyu-javac/src/main/java/com/sun/tools/javac/parser/JavacParser.java new file mode 100644 index 0000000..d3ff404 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/parser/JavacParser.java @@ -0,0 +1,2990 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.parser; + +import java.util.*; + +import com.sun.tools.javac.tree.*; +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag; +import com.sun.tools.javac.util.List; +import static com.sun.tools.javac.util.ListBuffer.lb; + +import com.sun.tools.javac.tree.JCTree.*; + +import static com.sun.tools.javac.parser.Token.*; + +/** The parser maps a token sequence into an abstract syntax + * tree. It operates by recursive descent, with code derived + * systematically from an LL(1) grammar. For efficiency reasons, an + * operator precedence scheme is used for parsing binary operation + * expressions. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class JavacParser implements Parser { + + /** The number of precedence levels of infix operators. + */ + private static final int infixPrecedenceLevels = 10; + + /** The scanner used for lexical analysis. + */ + private Lexer S; + + /** The factory to be used for abstract syntax tree construction. + */ + protected TreeMaker F; + + /** The log to be used for error diagnostics. + */ + private Log log; + + /** The keyword table. */ + private Keywords keywords; + + /** The Source language setting. */ + private Source source; + + /** The name table. */ + private Names names; + + /** Construct a parser from a given scanner, tree factory and log. + */ + protected JavacParser(ParserFactory fac, + Lexer S, + boolean keepDocComments, + boolean keepLineMap) { + this.S = S; + S.nextToken(); // prime the pump + this.F = fac.F; + this.log = fac.log; + this.names = fac.names; + this.keywords = fac.keywords; + this.source = fac.source; + this.allowGenerics = source.allowGenerics(); + this.allowVarargs = source.allowVarargs(); + this.allowAsserts = source.allowAsserts(); + this.allowEnums = source.allowEnums(); + this.allowForeach = source.allowForeach(); + this.allowStaticImport = source.allowStaticImport(); + this.allowAnnotations = source.allowAnnotations(); + this.allowTWR = source.allowTryWithResources(); + this.allowDiamond = source.allowDiamond(); + this.allowMulticatch = source.allowMulticatch(); + this.keepDocComments = keepDocComments; + if (keepDocComments) + docComments = new HashMap(); + this.keepLineMap = keepLineMap; + this.errorTree = F.Erroneous(); + } + + /** Switch: Should generics be recognized? + */ + boolean allowGenerics; + + /** Switch: Should diamond operator be recognized? + */ + boolean allowDiamond; + + /** Switch: Should multicatch clause be accepted? + */ + boolean allowMulticatch; + + /** Switch: Should varargs be recognized? + */ + boolean allowVarargs; + + /** Switch: should we recognize assert statements, or just give a warning? + */ + boolean allowAsserts; + + /** Switch: should we recognize enums, or just give a warning? + */ + boolean allowEnums; + + /** Switch: should we recognize foreach? + */ + boolean allowForeach; + + /** Switch: should we recognize foreach? + */ + boolean allowStaticImport; + + /** Switch: should we recognize annotations? + */ + boolean allowAnnotations; + + /** Switch: should we recognize try-with-resources? + */ + boolean allowTWR; + + /** Switch: should we keep docComments? + */ + boolean keepDocComments; + + /** Switch: should we keep line table? + */ + boolean keepLineMap; + + /** When terms are parsed, the mode determines which is expected: + * mode = EXPR : an expression + * mode = TYPE : a type + * mode = NOPARAMS : no parameters allowed for type + * mode = TYPEARG : type argument + */ + static final int EXPR = 0x1; + static final int TYPE = 0x2; + static final int NOPARAMS = 0x4; + static final int TYPEARG = 0x8; + static final int DIAMOND = 0x10; + + /** The current mode. + */ + private int mode = 0; + + /** The mode of the term that was parsed last. + */ + private int lastmode = 0; + +/* ---------- error recovery -------------- */ + + private JCErroneous errorTree; + + /** Skip forward until a suitable stop token is found. + */ + private void skip(boolean stopAtImport, boolean stopAtMemberDecl, boolean stopAtIdentifier, boolean stopAtStatement) { + while (true) { + switch (S.token()) { + case SEMI: + S.nextToken(); + return; + case PUBLIC: + case FINAL: + case ABSTRACT: + case MONKEYS_AT: + case EOF: + case CLASS: + case INTERFACE: + case ENUM: + return; + case IMPORT: + if (stopAtImport) + return; + break; + case LBRACE: + case RBRACE: + case PRIVATE: + case PROTECTED: + case STATIC: + case TRANSIENT: + case NATIVE: + case VOLATILE: + case SYNCHRONIZED: + case STRICTFP: + case LT: + case BYTE: + case SHORT: + case CHAR: + case INT: + case LONG: + case FLOAT: + case DOUBLE: + case BOOLEAN: + case VOID: + if (stopAtMemberDecl) + return; + break; + case IDENTIFIER: + if (stopAtIdentifier) + return; + break; + case CASE: + case DEFAULT: + case IF: + case FOR: + case WHILE: + case DO: + case TRY: + case SWITCH: + case RETURN: + case THROW: + case BREAK: + case CONTINUE: + case ELSE: + case FINALLY: + case CATCH: + if (stopAtStatement) + return; + break; + } + S.nextToken(); + } + } + + private JCErroneous syntaxError(int pos, String key, Token... args) { + return syntaxError(pos, null, key, args); + } + + private JCErroneous syntaxError(int pos, List errs, String key, Token... args) { + setErrorEndPos(pos); + reportSyntaxError(pos, key, (Object[])args); + return toP(F.at(pos).Erroneous(errs)); + } + + private int errorPos = Position.NOPOS; + /** + * Report a syntax error at given position using the given + * argument unless one was already reported at the same position. + */ + private void reportSyntaxError(int pos, String key, Object... args) { + if (pos > S.errPos() || pos == Position.NOPOS) { + if (S.token() == EOF) + error(pos, "premature.eof"); + else + error(pos, key, args); + } + S.errPos(pos); + if (S.pos() == errorPos) + S.nextToken(); // guarantee progress + errorPos = S.pos(); + } + + + /** Generate a syntax error at current position unless one was already + * reported at the same position. + */ + private JCErroneous syntaxError(String key) { + return syntaxError(S.pos(), key); + } + + /** Generate a syntax error at current position unless one was + * already reported at the same position. + */ + private JCErroneous syntaxError(String key, Token arg) { + return syntaxError(S.pos(), key, arg); + } + + /** If next input token matches given token, skip it, otherwise report + * an error. + */ + public void accept(Token token) { + if (S.token() == token) { + S.nextToken(); + } else { + setErrorEndPos(S.pos()); + reportSyntaxError(S.prevEndPos(), "expected", token); + } + } + + /** Report an illegal start of expression/type error at given position. + */ + JCExpression illegal(int pos) { + setErrorEndPos(S.pos()); + if ((mode & EXPR) != 0) + return syntaxError(pos, "illegal.start.of.expr"); + else + return syntaxError(pos, "illegal.start.of.type"); + + } + + /** Report an illegal start of expression/type error at current position. + */ + JCExpression illegal() { + return illegal(S.pos()); + } + + /** Diagnose a modifier flag from the set, if any. */ + void checkNoMods(long mods) { + if (mods != 0) { + long lowestMod = mods & -mods; + error(S.pos(), "mod.not.allowed.here", + Flags.asFlagSet(lowestMod)); + } + } + +/* ---------- doc comments --------- */ + + /** A hashtable to store all documentation comments + * indexed by the tree nodes they refer to. + * defined only if option flag keepDocComment is set. + */ + Map docComments; + + /** Make an entry into docComments hashtable, + * provided flag keepDocComments is set and given doc comment is non-null. + * @param tree The tree to be used as index in the hashtable + * @param dc The doc comment to associate with the tree, or null. + */ + void attach(JCTree tree, String dc) { + if (keepDocComments && dc != null) { +// System.out.println("doc comment = ");System.out.println(dc);//DEBUG + docComments.put(tree, dc); + } + } + +/* -------- source positions ------- */ + + private int errorEndPos = -1; + + private void setErrorEndPos(int errPos) { + if (errPos > errorEndPos) + errorEndPos = errPos; + } + + protected int getErrorEndPos() { + return errorEndPos; + } + + /** + * Store ending position for a tree. + * @param tree The tree. + * @param endpos The ending position to associate with the tree. + */ + protected void storeEnd(JCTree tree, int endpos) {} + + /** + * Store ending position for a tree. The ending position should + * be the ending position of the current token. + * @param t The tree. + */ + protected T to(T t) { return t; } + + /** + * Store ending position for a tree. The ending position should + * be greater of the ending position of the previous token and errorEndPos. + * @param t The tree. + */ + protected T toP(T t) { return t; } + + /** Get the start position for a tree node. The start position is + * defined to be the position of the first character of the first + * token of the node's source text. + * @param tree The tree node + */ + public int getStartPos(JCTree tree) { + return TreeInfo.getStartPos(tree); + } + + /** + * Get the end position for a tree node. The end position is + * defined to be the position of the last character of the last + * token of the node's source text. Returns Position.NOPOS if end + * positions are not generated or the position is otherwise not + * found. + * @param tree The tree node + */ + public int getEndPos(JCTree tree) { + return Position.NOPOS; + } + + + +/* ---------- parsing -------------- */ + + /** + * Ident = IDENTIFIER + */ + Name ident() { + if (S.token() == IDENTIFIER) { + Name name = S.name(); + S.nextToken(); + return name; + } else if (S.token() == ASSERT) { + if (allowAsserts) { + error(S.pos(), "assert.as.identifier"); + S.nextToken(); + return names.error; + } else { + warning(S.pos(), "assert.as.identifier"); + Name name = S.name(); + S.nextToken(); + return name; + } + } else if (S.token() == ENUM) { + if (allowEnums) { + error(S.pos(), "enum.as.identifier"); + S.nextToken(); + return names.error; + } else { + warning(S.pos(), "enum.as.identifier"); + Name name = S.name(); + S.nextToken(); + return name; + } + } else { + accept(IDENTIFIER); + return names.error; + } +} + + /** + * Qualident = Ident { DOT Ident } + */ + public JCExpression qualident() { + JCExpression t = toP(F.at(S.pos()).Ident(ident())); + while (S.token() == DOT) { + int pos = S.pos(); + S.nextToken(); + t = toP(F.at(pos).Select(t, ident())); + } + return t; + } + + /** + * Literal = + * INTLITERAL + * | LONGLITERAL + * | FLOATLITERAL + * | DOUBLELITERAL + * | CHARLITERAL + * | STRINGLITERAL + * | TRUE + * | FALSE + * | NULL + */ + JCExpression literal(Name prefix) { + int pos = S.pos(); + JCExpression t = errorTree; + switch (S.token()) { + case INTLITERAL: + try { + t = F.at(pos).Literal( + TypeTags.INT, + Convert.string2int(strval(prefix), S.radix())); + } catch (NumberFormatException ex) { + error(S.pos(), "int.number.too.large", strval(prefix)); + } + break; + case LONGLITERAL: + try { + t = F.at(pos).Literal( + TypeTags.LONG, + new Long(Convert.string2long(strval(prefix), S.radix()))); + } catch (NumberFormatException ex) { + error(S.pos(), "int.number.too.large", strval(prefix)); + } + break; + case FLOATLITERAL: { + String proper = (S.radix() == 16 ? ("0x"+ S.stringVal()) : S.stringVal()); + Float n; + try { + n = Float.valueOf(proper); + } catch (NumberFormatException ex) { + // error already reported in scanner + n = Float.NaN; + } + if (n.floatValue() == 0.0f && !isZero(proper)) + error(S.pos(), "fp.number.too.small"); + else if (n.floatValue() == Float.POSITIVE_INFINITY) + error(S.pos(), "fp.number.too.large"); + else + t = F.at(pos).Literal(TypeTags.FLOAT, n); + break; + } + case DOUBLELITERAL: { + String proper = (S.radix() == 16 ? ("0x"+ S.stringVal()) : S.stringVal()); + Double n; + try { + n = Double.valueOf(proper); + } catch (NumberFormatException ex) { + // error already reported in scanner + n = Double.NaN; + } + if (n.doubleValue() == 0.0d && !isZero(proper)) + error(S.pos(), "fp.number.too.small"); + else if (n.doubleValue() == Double.POSITIVE_INFINITY) + error(S.pos(), "fp.number.too.large"); + else + t = F.at(pos).Literal(TypeTags.DOUBLE, n); + break; + } + case CHARLITERAL: + t = F.at(pos).Literal( + TypeTags.CHAR, + S.stringVal().charAt(0) + 0); + break; + case STRINGLITERAL: + t = F.at(pos).Literal( + TypeTags.CLASS, + S.stringVal()); + break; + case TRUE: case FALSE: + t = F.at(pos).Literal( + TypeTags.BOOLEAN, + (S.token() == TRUE ? 1 : 0)); + break; + case NULL: + t = F.at(pos).Literal( + TypeTags.BOT, + null); + break; + default: + Assert.error(); + } + if (t == errorTree) + t = F.at(pos).Erroneous(); + storeEnd(t, S.endPos()); + S.nextToken(); + return t; + } +//where + boolean isZero(String s) { + char[] cs = s.toCharArray(); + int base = ((cs.length > 1 && Character.toLowerCase(cs[1]) == 'x') ? 16 : 10); + int i = ((base==16) ? 2 : 0); + while (i < cs.length && (cs[i] == '0' || cs[i] == '.')) i++; + return !(i < cs.length && (Character.digit(cs[i], base) > 0)); + } + + String strval(Name prefix) { + String s = S.stringVal(); + return prefix.isEmpty() ? s : prefix + s; + } + + /** terms can be either expressions or types. + */ + public JCExpression parseExpression() { + return term(EXPR); + } + + public JCExpression parseType() { + return term(TYPE); + } + + JCExpression term(int newmode) { + int prevmode = mode; + mode = newmode; + JCExpression t = term(); + lastmode = mode; + mode = prevmode; + return t; + } + + /** + * Expression = Expression1 [ExpressionRest] + * ExpressionRest = [AssignmentOperator Expression1] + * AssignmentOperator = "=" | "+=" | "-=" | "*=" | "/=" | + * "&=" | "|=" | "^=" | + * "%=" | "<<=" | ">>=" | ">>>=" + * Type = Type1 + * TypeNoParams = TypeNoParams1 + * StatementExpression = Expression + * ConstantExpression = Expression + */ + JCExpression term() { + JCExpression t = term1(); + if ((mode & EXPR) != 0 && + S.token() == EQ || PLUSEQ.compareTo(S.token()) <= 0 && S.token().compareTo(GTGTGTEQ) <= 0) + return termRest(t); + else + return t; + } + + JCExpression termRest(JCExpression t) { + switch (S.token()) { + case EQ: { + int pos = S.pos(); + S.nextToken(); + mode = EXPR; + JCExpression t1 = term(); + return toP(F.at(pos).Assign(t, t1)); + } + case PLUSEQ: + case SUBEQ: + case STAREQ: + case SLASHEQ: + case PERCENTEQ: + case AMPEQ: + case BAREQ: + case CARETEQ: + case LTLTEQ: + case GTGTEQ: + case GTGTGTEQ: + int pos = S.pos(); + Token token = S.token(); + S.nextToken(); + mode = EXPR; + JCExpression t1 = term(); + return F.at(pos).Assignop(optag(token), t, t1); + default: + return t; + } + } + + /** Expression1 = Expression2 [Expression1Rest] + * Type1 = Type2 + * TypeNoParams1 = TypeNoParams2 + */ + JCExpression term1() { + JCExpression t = term2(); + if ((mode & EXPR) != 0 && S.token() == QUES) { + mode = EXPR; + return term1Rest(t); + } else { + return t; + } + } + + /** Expression1Rest = ["?" Expression ":" Expression1] + */ + JCExpression term1Rest(JCExpression t) { + if (S.token() == QUES) { + int pos = S.pos(); + S.nextToken(); + JCExpression t1 = term(); + accept(COLON); + JCExpression t2 = term1(); + return F.at(pos).Conditional(t, t1, t2); + } else { + return t; + } + } + + /** Expression2 = Expression3 [Expression2Rest] + * Type2 = Type3 + * TypeNoParams2 = TypeNoParams3 + */ + JCExpression term2() { + JCExpression t = term3(); + if ((mode & EXPR) != 0 && prec(S.token()) >= TreeInfo.orPrec) { + mode = EXPR; + return term2Rest(t, TreeInfo.orPrec); + } else { + return t; + } + } + + /* Expression2Rest = {infixop Expression3} + * | Expression3 instanceof Type + * infixop = "||" + * | "&&" + * | "|" + * | "^" + * | "&" + * | "==" | "!=" + * | "<" | ">" | "<=" | ">=" + * | "<<" | ">>" | ">>>" + * | "+" | "-" + * | "*" | "/" | "%" + */ + JCExpression term2Rest(JCExpression t, int minprec) { + List savedOd = odStackSupply.elems; + JCExpression[] odStack = newOdStack(); + List savedOp = opStackSupply.elems; + Token[] opStack = newOpStack(); + List savedPos = posStackSupply.elems; + int[] posStack = newPosStack(); + // optimization, was odStack = new Tree[...]; opStack = new Tree[...]; + int top = 0; + odStack[0] = t; + int startPos = S.pos(); + Token topOp = ERROR; + int topOpPos = Position.NOPOS; + while (prec(S.token()) >= minprec) { + posStack[top] = topOpPos; + opStack[top] = topOp; + top++; + topOp = S.token(); + topOpPos = S.pos(); + S.nextToken(); + odStack[top] = (topOp == INSTANCEOF) ? parseType() : term3(); + while (top > 0 && prec(topOp) >= prec(S.token())) { + odStack[top-1] = makeOp(topOpPos, topOp, odStack[top-1], + odStack[top]); + top--; + topOp = opStack[top]; + topOpPos = posStack[top]; + } + } + Assert.check(top == 0); + t = odStack[0]; + + if (t.getTag() == JCTree.PLUS) { + StringBuffer buf = foldStrings(t); + if (buf != null) { + t = toP(F.at(startPos).Literal(TypeTags.CLASS, buf.toString())); + } + } + + odStackSupply.elems = savedOd; // optimization + opStackSupply.elems = savedOp; // optimization + posStackSupply.elems = savedPos; // optimization + return t; + } +//where + /** Construct a binary or type test node. + */ + private JCExpression makeOp(int pos, + Token topOp, + JCExpression od1, + JCExpression od2) + { + if (topOp == INSTANCEOF) { + return F.at(pos).TypeTest(od1, od2); + } else { + return F.at(pos).Binary(optag(topOp), od1, od2); + } + } + /** If tree is a concatenation of string literals, replace it + * by a single literal representing the concatenated string. + */ + protected StringBuffer foldStrings(JCTree tree) { + List buf = List.nil(); + while (true) { + if (tree.getTag() == JCTree.LITERAL) { + JCLiteral lit = (JCLiteral) tree; + if (lit.typetag == TypeTags.CLASS) { + StringBuffer sbuf = + new StringBuffer((String)lit.value); + while (buf.nonEmpty()) { + sbuf.append(buf.head); + buf = buf.tail; + } + return sbuf; + } + } else if (tree.getTag() == JCTree.PLUS) { + JCBinary op = (JCBinary)tree; + if (op.rhs.getTag() == JCTree.LITERAL) { + JCLiteral lit = (JCLiteral) op.rhs; + if (lit.typetag == TypeTags.CLASS) { + buf = buf.prepend((String) lit.value); + tree = op.lhs; + continue; + } + } + } + return null; + } + } + + /** optimization: To save allocating a new operand/operator stack + * for every binary operation, we use supplys. + */ + ListBuffer odStackSupply = new ListBuffer(); + ListBuffer opStackSupply = new ListBuffer(); + ListBuffer posStackSupply = new ListBuffer(); + + private JCExpression[] newOdStack() { + if (odStackSupply.elems == odStackSupply.last) + odStackSupply.append(new JCExpression[infixPrecedenceLevels + 1]); + JCExpression[] odStack = odStackSupply.elems.head; + odStackSupply.elems = odStackSupply.elems.tail; + return odStack; + } + + private Token[] newOpStack() { + if (opStackSupply.elems == opStackSupply.last) + opStackSupply.append(new Token[infixPrecedenceLevels + 1]); + Token[] opStack = opStackSupply.elems.head; + opStackSupply.elems = opStackSupply.elems.tail; + return opStack; + } + + private int[] newPosStack() { + if (posStackSupply.elems == posStackSupply.last) + posStackSupply.append(new int[infixPrecedenceLevels + 1]); + int[] posStack = posStackSupply.elems.head; + posStackSupply.elems = posStackSupply.elems.tail; + return posStack; + } + + /** Expression3 = PrefixOp Expression3 + * | "(" Expr | TypeNoParams ")" Expression3 + * | Primary {Selector} {PostfixOp} + * Primary = "(" Expression ")" + * | Literal + * | [TypeArguments] THIS [Arguments] + * | [TypeArguments] SUPER SuperSuffix + * | NEW [TypeArguments] Creator + * | Ident { "." Ident } + * [ "[" ( "]" BracketsOpt "." CLASS | Expression "]" ) + * | Arguments + * | "." ( CLASS | THIS | [TypeArguments] SUPER Arguments | NEW [TypeArguments] InnerCreator ) + * ] + * | BasicType BracketsOpt "." CLASS + * PrefixOp = "++" | "--" | "!" | "~" | "+" | "-" + * PostfixOp = "++" | "--" + * Type3 = Ident { "." Ident } [TypeArguments] {TypeSelector} BracketsOpt + * | BasicType + * TypeNoParams3 = Ident { "." Ident } BracketsOpt + * Selector = "." [TypeArguments] Ident [Arguments] + * | "." THIS + * | "." [TypeArguments] SUPER SuperSuffix + * | "." NEW [TypeArguments] InnerCreator + * | "[" Expression "]" + * TypeSelector = "." Ident [TypeArguments] + * SuperSuffix = Arguments | "." Ident [Arguments] + */ + protected JCExpression term3() { + int pos = S.pos(); + JCExpression t; + List typeArgs = typeArgumentsOpt(EXPR); + switch (S.token()) { + case QUES: + if ((mode & TYPE) != 0 && (mode & (TYPEARG|NOPARAMS)) == TYPEARG) { + mode = TYPE; + return typeArgument(); + } else + return illegal(); + case PLUSPLUS: case SUBSUB: case BANG: case TILDE: case PLUS: case SUB: + if (typeArgs == null && (mode & EXPR) != 0) { + Token token = S.token(); + S.nextToken(); + mode = EXPR; + if (token == SUB && + (S.token() == INTLITERAL || S.token() == LONGLITERAL) && + S.radix() == 10) { + mode = EXPR; + t = literal(names.hyphen); + } else { + t = term3(); + return F.at(pos).Unary(unoptag(token), t); + } + } else return illegal(); + break; + case LPAREN: + if (typeArgs == null && (mode & EXPR) != 0) { + S.nextToken(); + mode = EXPR | TYPE | NOPARAMS; + t = term3(); + if ((mode & TYPE) != 0 && S.token() == LT) { + // Could be a cast to a parameterized type + int op = JCTree.LT; + int pos1 = S.pos(); + S.nextToken(); + mode &= (EXPR | TYPE); + mode |= TYPEARG; + JCExpression t1 = term3(); + if ((mode & TYPE) != 0 && + (S.token() == COMMA || S.token() == GT)) { + mode = TYPE; + ListBuffer args = new ListBuffer(); + args.append(t1); + while (S.token() == COMMA) { + S.nextToken(); + args.append(typeArgument()); + } + accept(GT); + t = toP(F.at(pos1).TypeApply(t, args.toList())); + checkGenerics(); + while (S.token() == DOT) { + S.nextToken(); + mode = TYPE; + t = toP(F.at(S.pos()).Select(t, ident())); + t = typeArgumentsOpt(t); + } + t = bracketsOpt(toP(t)); + } else if ((mode & EXPR) != 0) { + mode = EXPR; + JCExpression e = term2Rest(t1, TreeInfo.shiftPrec); + t = F.at(pos1).Binary(op, t, e); + t = termRest(term1Rest(term2Rest(t, TreeInfo.orPrec))); + } else { + accept(GT); + } + } + else { + t = termRest(term1Rest(term2Rest(t, TreeInfo.orPrec))); + } + accept(RPAREN); + lastmode = mode; + mode = EXPR; + if ((lastmode & EXPR) == 0) { + JCExpression t1 = term3(); + return F.at(pos).TypeCast(t, t1); + } else if ((lastmode & TYPE) != 0) { + switch (S.token()) { + /*case PLUSPLUS: case SUBSUB: */ + case BANG: case TILDE: + case LPAREN: case THIS: case SUPER: + case INTLITERAL: case LONGLITERAL: case FLOATLITERAL: + case DOUBLELITERAL: case CHARLITERAL: case STRINGLITERAL: + case TRUE: case FALSE: case NULL: + case NEW: case IDENTIFIER: case ASSERT: case ENUM: + case BYTE: case SHORT: case CHAR: case INT: + case LONG: case FLOAT: case DOUBLE: case BOOLEAN: case VOID: + JCExpression t1 = term3(); + return F.at(pos).TypeCast(t, t1); + } + } + } else return illegal(); + t = toP(F.at(pos).Parens(t)); + break; + case THIS: + if ((mode & EXPR) != 0) { + mode = EXPR; + t = to(F.at(pos).Ident(names._this)); + S.nextToken(); + if (typeArgs == null) + t = argumentsOpt(null, t); + else + t = arguments(typeArgs, t); + typeArgs = null; + } else return illegal(); + break; + case SUPER: + if ((mode & EXPR) != 0) { + mode = EXPR; + t = to(F.at(pos).Ident(names._super)); + t = superSuffix(typeArgs, t); + typeArgs = null; + } else return illegal(); + break; + case INTLITERAL: case LONGLITERAL: case FLOATLITERAL: case DOUBLELITERAL: + case CHARLITERAL: case STRINGLITERAL: + case TRUE: case FALSE: case NULL: + if (typeArgs == null && (mode & EXPR) != 0) { + mode = EXPR; + t = literal(names.empty); + } else return illegal(); + break; + case NEW: + if (typeArgs != null) return illegal(); + if ((mode & EXPR) != 0) { + mode = EXPR; + S.nextToken(); + if (S.token() == LT) typeArgs = typeArguments(false); + t = creator(pos, typeArgs); + typeArgs = null; + } else return illegal(); + break; + case IDENTIFIER: case ASSERT: case ENUM: + if (typeArgs != null) return illegal(); + t = toP(F.at(S.pos()).Ident(ident())); + loop: while (true) { + pos = S.pos(); + switch (S.token()) { + case LBRACKET: + S.nextToken(); + if (S.token() == RBRACKET) { + S.nextToken(); + t = bracketsOpt(t); + t = toP(F.at(pos).TypeArray(t)); + t = bracketsSuffix(t); + } else { + if ((mode & EXPR) != 0) { + mode = EXPR; + JCExpression t1 = term(); + t = to(F.at(pos).Indexed(t, t1)); + } + accept(RBRACKET); + } + break loop; + case LPAREN: + if ((mode & EXPR) != 0) { + mode = EXPR; + t = arguments(typeArgs, t); + typeArgs = null; + } + break loop; + case DOT: + S.nextToken(); + int oldmode = mode; + mode &= ~NOPARAMS; + typeArgs = typeArgumentsOpt(EXPR); + mode = oldmode; + if ((mode & EXPR) != 0) { + switch (S.token()) { + case CLASS: + if (typeArgs != null) return illegal(); + mode = EXPR; + t = to(F.at(pos).Select(t, names._class)); + S.nextToken(); + break loop; + case THIS: + if (typeArgs != null) return illegal(); + mode = EXPR; + t = to(F.at(pos).Select(t, names._this)); + S.nextToken(); + break loop; + case SUPER: + mode = EXPR; + t = to(F.at(pos).Select(t, names._super)); + t = superSuffix(typeArgs, t); + typeArgs = null; + break loop; + case NEW: + if (typeArgs != null) return illegal(); + mode = EXPR; + int pos1 = S.pos(); + S.nextToken(); + if (S.token() == LT) typeArgs = typeArguments(false); + t = innerCreator(pos1, typeArgs, t); + typeArgs = null; + break loop; + } + } + // typeArgs saved for next loop iteration. + t = toP(F.at(pos).Select(t, ident())); + break; + default: + break loop; + } + } + if (typeArgs != null) illegal(); + t = typeArgumentsOpt(t); + break; + case BYTE: case SHORT: case CHAR: case INT: case LONG: case FLOAT: + case DOUBLE: case BOOLEAN: + if (typeArgs != null) illegal(); + t = bracketsSuffix(bracketsOpt(basicType())); + break; + case VOID: + if (typeArgs != null) illegal(); + if ((mode & EXPR) != 0) { + S.nextToken(); + if (S.token() == DOT) { + JCPrimitiveTypeTree ti = toP(F.at(pos).TypeIdent(TypeTags.VOID)); + t = bracketsSuffix(ti); + } else { + return illegal(pos); + } + } else { + // Support the corner case of myMethodHandle.invoke() by passing + // a void type (like other primitive types) to the next phase. + // The error will be reported in Attr.attribTypes or Attr.visitApply. + JCPrimitiveTypeTree ti = to(F.at(pos).TypeIdent(TypeTags.VOID)); + S.nextToken(); + return ti; + //return illegal(); + } + break; + default: + return illegal(); + } + if (typeArgs != null) illegal(); + while (true) { + int pos1 = S.pos(); + if (S.token() == LBRACKET) { + S.nextToken(); + if ((mode & TYPE) != 0) { + int oldmode = mode; + mode = TYPE; + if (S.token() == RBRACKET) { + S.nextToken(); + t = bracketsOpt(t); + t = toP(F.at(pos1).TypeArray(t)); + return t; + } + mode = oldmode; + } + if ((mode & EXPR) != 0) { + mode = EXPR; + JCExpression t1 = term(); + t = to(F.at(pos1).Indexed(t, t1)); + } + accept(RBRACKET); + } else if (S.token() == DOT) { + S.nextToken(); + typeArgs = typeArgumentsOpt(EXPR); + if (S.token() == SUPER && (mode & EXPR) != 0) { + mode = EXPR; + t = to(F.at(pos1).Select(t, names._super)); + S.nextToken(); + t = arguments(typeArgs, t); + typeArgs = null; + } else if (S.token() == NEW && (mode & EXPR) != 0) { + if (typeArgs != null) return illegal(); + mode = EXPR; + int pos2 = S.pos(); + S.nextToken(); + if (S.token() == LT) typeArgs = typeArguments(false); + t = innerCreator(pos2, typeArgs, t); + typeArgs = null; + } else { + t = toP(F.at(pos1).Select(t, ident())); + t = argumentsOpt(typeArgs, typeArgumentsOpt(t)); + typeArgs = null; + } + } else { + break; + } + } + while ((S.token() == PLUSPLUS || S.token() == SUBSUB) && (mode & EXPR) != 0) { + mode = EXPR; + t = to(F.at(S.pos()).Unary( + S.token() == PLUSPLUS ? JCTree.POSTINC : JCTree.POSTDEC, t)); + S.nextToken(); + } + return toP(t); + } + + /** SuperSuffix = Arguments | "." [TypeArguments] Ident [Arguments] + */ + JCExpression superSuffix(List typeArgs, JCExpression t) { + S.nextToken(); + if (S.token() == LPAREN || typeArgs != null) { + t = arguments(typeArgs, t); + } else { + int pos = S.pos(); + accept(DOT); + typeArgs = (S.token() == LT) ? typeArguments(false) : null; + t = toP(F.at(pos).Select(t, ident())); + t = argumentsOpt(typeArgs, t); + } + return t; + } + + /** BasicType = BYTE | SHORT | CHAR | INT | LONG | FLOAT | DOUBLE | BOOLEAN + */ + JCPrimitiveTypeTree basicType() { + JCPrimitiveTypeTree t = to(F.at(S.pos()).TypeIdent(typetag(S.token()))); + S.nextToken(); + return t; + } + + /** ArgumentsOpt = [ Arguments ] + */ + JCExpression argumentsOpt(List typeArgs, JCExpression t) { + if ((mode & EXPR) != 0 && S.token() == LPAREN || typeArgs != null) { + mode = EXPR; + return arguments(typeArgs, t); + } else { + return t; + } + } + + /** Arguments = "(" [Expression { COMMA Expression }] ")" + */ + List arguments() { + ListBuffer args = lb(); + if (S.token() == LPAREN) { + S.nextToken(); + if (S.token() != RPAREN) { + args.append(parseExpression()); + while (S.token() == COMMA) { + S.nextToken(); + args.append(parseExpression()); + } + } + accept(RPAREN); + } else { + syntaxError(S.pos(), "expected", LPAREN); + } + return args.toList(); + } + + JCMethodInvocation arguments(List typeArgs, JCExpression t) { + int pos = S.pos(); + List args = arguments(); + return toP(F.at(pos).Apply(typeArgs, t, args)); + } + + /** TypeArgumentsOpt = [ TypeArguments ] + */ + JCExpression typeArgumentsOpt(JCExpression t) { + if (S.token() == LT && + (mode & TYPE) != 0 && + (mode & NOPARAMS) == 0) { + mode = TYPE; + checkGenerics(); + return typeArguments(t, false); + } else { + return t; + } + } + List typeArgumentsOpt() { + return typeArgumentsOpt(TYPE); + } + + List typeArgumentsOpt(int useMode) { + if (S.token() == LT) { + checkGenerics(); + if ((mode & useMode) == 0 || + (mode & NOPARAMS) != 0) { + illegal(); + } + mode = useMode; + return typeArguments(false); + } + return null; + } + + /** TypeArguments = "<" TypeArgument {"," TypeArgument} ">" + */ + List typeArguments(boolean diamondAllowed) { + if (S.token() == LT) { + S.nextToken(); + if (S.token() == GT && diamondAllowed) { + checkDiamond(); + mode |= DIAMOND; + S.nextToken(); + return List.nil(); + } else { + ListBuffer args = ListBuffer.lb(); + args.append(((mode & EXPR) == 0) ? typeArgument() : parseType()); + while (S.token() == COMMA) { + S.nextToken(); + args.append(((mode & EXPR) == 0) ? typeArgument() : parseType()); + } + switch (S.token()) { + case GTGTGTEQ: + S.token(GTGTEQ); + break; + case GTGTEQ: + S.token(GTEQ); + break; + case GTEQ: + S.token(EQ); + break; + case GTGTGT: + S.token(GTGT); + break; + case GTGT: + S.token(GT); + break; + default: + accept(GT); + break; + } + return args.toList(); + } + } else { + syntaxError(S.pos(), "expected", LT); + return List.nil(); + } + } + + /** TypeArgument = Type + * | "?" + * | "?" EXTENDS Type {"&" Type} + * | "?" SUPER Type + */ + JCExpression typeArgument() { + if (S.token() != QUES) return parseType(); + int pos = S.pos(); + S.nextToken(); + if (S.token() == EXTENDS) { + TypeBoundKind t = to(F.at(pos).TypeBoundKind(BoundKind.EXTENDS)); + S.nextToken(); + JCExpression bound = parseType(); + return F.at(pos).Wildcard(t, bound); + } else if (S.token() == SUPER) { + TypeBoundKind t = to(F.at(pos).TypeBoundKind(BoundKind.SUPER)); + S.nextToken(); + JCExpression bound = parseType(); + return F.at(pos).Wildcard(t, bound); + } else if (S.token() == IDENTIFIER) { + //error recovery + reportSyntaxError(S.prevEndPos(), "expected3", + GT, EXTENDS, SUPER); + TypeBoundKind t = F.at(Position.NOPOS).TypeBoundKind(BoundKind.UNBOUND); + JCExpression wc = toP(F.at(pos).Wildcard(t, null)); + JCIdent id = toP(F.at(S.pos()).Ident(ident())); + return F.at(pos).Erroneous(List.of(wc, id)); + } else { + TypeBoundKind t = toP(F.at(pos).TypeBoundKind(BoundKind.UNBOUND)); + return toP(F.at(pos).Wildcard(t, null)); + } + } + + JCTypeApply typeArguments(JCExpression t, boolean diamondAllowed) { + int pos = S.pos(); + List args = typeArguments(diamondAllowed); + return toP(F.at(pos).TypeApply(t, args)); + } + + /** BracketsOpt = {"[" "]"} + */ + private JCExpression bracketsOpt(JCExpression t) { + if (S.token() == LBRACKET) { + int pos = S.pos(); + S.nextToken(); + t = bracketsOptCont(t, pos); + F.at(pos); + } + return t; + } + + private JCArrayTypeTree bracketsOptCont(JCExpression t, int pos) { + accept(RBRACKET); + t = bracketsOpt(t); + return toP(F.at(pos).TypeArray(t)); + } + + /** BracketsSuffixExpr = "." CLASS + * BracketsSuffixType = + */ + JCExpression bracketsSuffix(JCExpression t) { + if ((mode & EXPR) != 0 && S.token() == DOT) { + mode = EXPR; + int pos = S.pos(); + S.nextToken(); + accept(CLASS); + if (S.pos() == errorEndPos) { + // error recovery + Name name = null; + if (S.token() == IDENTIFIER) { + name = S.name(); + S.nextToken(); + } else { + name = names.error; + } + t = F.at(pos).Erroneous(List.of(toP(F.at(pos).Select(t, name)))); + } else { + t = toP(F.at(pos).Select(t, names._class)); + } + } else if ((mode & TYPE) != 0) { + mode = TYPE; + } else { + syntaxError(S.pos(), "dot.class.expected"); + } + return t; + } + + /** Creator = Qualident [TypeArguments] ( ArrayCreatorRest | ClassCreatorRest ) + */ + JCExpression creator(int newpos, List typeArgs) { + switch (S.token()) { + case BYTE: case SHORT: case CHAR: case INT: case LONG: case FLOAT: + case DOUBLE: case BOOLEAN: + if (typeArgs == null) + return arrayCreatorRest(newpos, basicType()); + break; + default: + } + JCExpression t = qualident(); + int oldmode = mode; + mode = TYPE; + boolean diamondFound = false; + if (S.token() == LT) { + checkGenerics(); + t = typeArguments(t, true); + diamondFound = (mode & DIAMOND) != 0; + } + while (S.token() == DOT) { + if (diamondFound) { + //cannot select after a diamond + illegal(S.pos()); + } + int pos = S.pos(); + S.nextToken(); + t = toP(F.at(pos).Select(t, ident())); + if (S.token() == LT) { + checkGenerics(); + t = typeArguments(t, true); + diamondFound = (mode & DIAMOND) != 0; + } + } + mode = oldmode; + if (S.token() == LBRACKET) { + JCExpression e = arrayCreatorRest(newpos, t); + if (typeArgs != null) { + int pos = newpos; + if (!typeArgs.isEmpty() && typeArgs.head.pos != Position.NOPOS) { + // note: this should always happen but we should + // not rely on this as the parser is continuously + // modified to improve error recovery. + pos = typeArgs.head.pos; + } + setErrorEndPos(S.prevEndPos()); + reportSyntaxError(pos, "cannot.create.array.with.type.arguments"); + return toP(F.at(newpos).Erroneous(typeArgs.prepend(e))); + } + return e; + } else if (S.token() == LPAREN) { + return classCreatorRest(newpos, null, typeArgs, t); + } else { + reportSyntaxError(S.pos(), "expected2", + LPAREN, LBRACKET); + t = toP(F.at(newpos).NewClass(null, typeArgs, t, List.nil(), null)); + return toP(F.at(newpos).Erroneous(List.of(t))); + } + } + + /** InnerCreator = Ident [TypeArguments] ClassCreatorRest + */ + JCExpression innerCreator(int newpos, List typeArgs, JCExpression encl) { + JCExpression t = toP(F.at(S.pos()).Ident(ident())); + if (S.token() == LT) { + int oldmode = mode; + checkGenerics(); + t = typeArguments(t, true); + mode = oldmode; + } + return classCreatorRest(newpos, encl, typeArgs, t); + } + + /** ArrayCreatorRest = "[" ( "]" BracketsOpt ArrayInitializer + * | Expression "]" {"[" Expression "]"} BracketsOpt ) + */ + JCExpression arrayCreatorRest(int newpos, JCExpression elemtype) { + accept(LBRACKET); + if (S.token() == RBRACKET) { + accept(RBRACKET); + elemtype = bracketsOpt(elemtype); + if (S.token() == LBRACE) { + return arrayInitializer(newpos, elemtype); + } else { + return syntaxError(S.pos(), "array.dimension.missing"); + } + } else { + ListBuffer dims = new ListBuffer(); + dims.append(parseExpression()); + accept(RBRACKET); + while (S.token() == LBRACKET) { + int pos = S.pos(); + S.nextToken(); + if (S.token() == RBRACKET) { + elemtype = bracketsOptCont(elemtype, pos); + } else { + dims.append(parseExpression()); + accept(RBRACKET); + } + } + return toP(F.at(newpos).NewArray(elemtype, dims.toList(), null)); + } + } + + /** ClassCreatorRest = Arguments [ClassBody] + */ + JCNewClass classCreatorRest(int newpos, + JCExpression encl, + List typeArgs, + JCExpression t) + { + List args = arguments(); + JCClassDecl body = null; + if (S.token() == LBRACE) { + int pos = S.pos(); + List defs = classOrInterfaceBody(names.empty, false); + JCModifiers mods = F.at(Position.NOPOS).Modifiers(0); + body = toP(F.at(pos).AnonymousClassDef(mods, defs)); + } + return toP(F.at(newpos).NewClass(encl, typeArgs, t, args, body)); + } + + /** ArrayInitializer = "{" [VariableInitializer {"," VariableInitializer}] [","] "}" + */ + JCExpression arrayInitializer(int newpos, JCExpression t) { + accept(LBRACE); + ListBuffer elems = new ListBuffer(); + if (S.token() == COMMA) { + S.nextToken(); + } else if (S.token() != RBRACE) { + elems.append(variableInitializer()); + while (S.token() == COMMA) { + S.nextToken(); + if (S.token() == RBRACE) break; + elems.append(variableInitializer()); + } + } + accept(RBRACE); + return toP(F.at(newpos).NewArray(t, List.nil(), elems.toList())); + } + + /** VariableInitializer = ArrayInitializer | Expression + */ + public JCExpression variableInitializer() { + return S.token() == LBRACE ? arrayInitializer(S.pos(), null) : parseExpression(); + } + + /** ParExpression = "(" Expression ")" + */ + JCExpression parExpression() { + accept(LPAREN); + JCExpression t = parseExpression(); + accept(RPAREN); + return t; + } + + /** Block = "{" BlockStatements "}" + */ + JCBlock block(int pos, long flags) { + accept(LBRACE); + List stats = blockStatements(); + JCBlock t = F.at(pos).Block(flags, stats); + while (S.token() == CASE || S.token() == DEFAULT) { + syntaxError("orphaned", S.token()); + switchBlockStatementGroups(); + } + // the Block node has a field "endpos" for first char of last token, which is + // usually but not necessarily the last char of the last token. + t.endpos = S.pos(); + accept(RBRACE); + return toP(t); + } + + public JCBlock block() { + return block(S.pos(), 0); + } + + /** BlockStatements = { BlockStatement } + * BlockStatement = LocalVariableDeclarationStatement + * | ClassOrInterfaceOrEnumDeclaration + * | [Ident ":"] Statement + * LocalVariableDeclarationStatement + * = { FINAL | '@' Annotation } Type VariableDeclarators ";" + */ + @SuppressWarnings("fallthrough") + List blockStatements() { +//todo: skip to anchor on error(?) + int lastErrPos = -1; + ListBuffer stats = new ListBuffer(); + while (true) { + int pos = S.pos(); + switch (S.token()) { + case RBRACE: case CASE: case DEFAULT: case EOF: + return stats.toList(); + case LBRACE: case IF: case FOR: case WHILE: case DO: case TRY: + case SWITCH: case SYNCHRONIZED: case RETURN: case THROW: case BREAK: + case CONTINUE: case SEMI: case ELSE: case FINALLY: case CATCH: + stats.append(parseStatement()); + break; + case MONKEYS_AT: + case FINAL: { + String dc = S.docComment(); + JCModifiers mods = modifiersOpt(); + if (S.token() == INTERFACE || + S.token() == CLASS || + allowEnums && S.token() == ENUM) { + stats.append(classOrInterfaceOrEnumDeclaration(mods, dc)); + } else { + JCExpression t = parseType(); + stats.appendList(variableDeclarators(mods, t, + new ListBuffer())); + // A "LocalVariableDeclarationStatement" subsumes the terminating semicolon + storeEnd(stats.elems.last(), S.endPos()); + accept(SEMI); + } + break; + } + case ABSTRACT: case STRICTFP: { + String dc = S.docComment(); + JCModifiers mods = modifiersOpt(); + stats.append(classOrInterfaceOrEnumDeclaration(mods, dc)); + break; + } + case INTERFACE: + case CLASS: + stats.append(classOrInterfaceOrEnumDeclaration(modifiersOpt(), + S.docComment())); + break; + case ENUM: + case ASSERT: + if (allowEnums && S.token() == ENUM) { + error(S.pos(), "local.enum"); + stats. + append(classOrInterfaceOrEnumDeclaration(modifiersOpt(), + S.docComment())); + break; + } else if (allowAsserts && S.token() == ASSERT) { + stats.append(parseStatement()); + break; + } + /* fall through to default */ + default: + Name name = S.name(); + JCExpression t = term(EXPR | TYPE); + if (S.token() == COLON && t.getTag() == JCTree.IDENT) { + S.nextToken(); + JCStatement stat = parseStatement(); + stats.append(F.at(pos).Labelled(name, stat)); + } else if ((lastmode & TYPE) != 0 && + (S.token() == IDENTIFIER || + S.token() == ASSERT || + S.token() == ENUM)) { + pos = S.pos(); + JCModifiers mods = F.at(Position.NOPOS).Modifiers(0); + F.at(pos); + stats.appendList(variableDeclarators(mods, t, + new ListBuffer())); + // A "LocalVariableDeclarationStatement" subsumes the terminating semicolon + storeEnd(stats.elems.last(), S.endPos()); + accept(SEMI); + } else { + // This Exec is an "ExpressionStatement"; it subsumes the terminating semicolon + stats.append(to(F.at(pos).Exec(checkExprStat(t)))); + accept(SEMI); + } + } + + // error recovery + if (S.pos() == lastErrPos) + return stats.toList(); + if (S.pos() <= errorEndPos) { + skip(false, true, true, true); + lastErrPos = S.pos(); + } + + // ensure no dangling /** @deprecated */ active + S.resetDeprecatedFlag(); + } + } + + /** Statement = + * Block + * | IF ParExpression Statement [ELSE Statement] + * | FOR "(" ForInitOpt ";" [Expression] ";" ForUpdateOpt ")" Statement + * | FOR "(" FormalParameter : Expression ")" Statement + * | WHILE ParExpression Statement + * | DO Statement WHILE ParExpression ";" + * | TRY Block ( Catches | [Catches] FinallyPart ) + * | TRY "(" ResourceSpecification ";"opt ")" Block [Catches] [FinallyPart] + * | SWITCH ParExpression "{" SwitchBlockStatementGroups "}" + * | SYNCHRONIZED ParExpression Block + * | RETURN [Expression] ";" + * | THROW Expression ";" + * | BREAK [Ident] ";" + * | CONTINUE [Ident] ";" + * | ASSERT Expression [ ":" Expression ] ";" + * | ";" + * | ExpressionStatement + * | Ident ":" Statement + */ + @SuppressWarnings("fallthrough") + public JCStatement parseStatement() { + int pos = S.pos(); + switch (S.token()) { + case LBRACE: + return block(); + case IF: { + S.nextToken(); + JCExpression cond = parExpression(); + JCStatement thenpart = parseStatement(); + JCStatement elsepart = null; + if (S.token() == ELSE) { + S.nextToken(); + elsepart = parseStatement(); + } + return F.at(pos).If(cond, thenpart, elsepart); + } + case FOR: { + S.nextToken(); + accept(LPAREN); + List inits = S.token() == SEMI ? List.nil() : forInit(); + if (inits.length() == 1 && + inits.head.getTag() == JCTree.VARDEF && + ((JCVariableDecl) inits.head).init == null && + S.token() == COLON) { + checkForeach(); + JCVariableDecl var = (JCVariableDecl)inits.head; + accept(COLON); + JCExpression expr = parseExpression(); + accept(RPAREN); + JCStatement body = parseStatement(); + return F.at(pos).ForeachLoop(var, expr, body); + } else { + accept(SEMI); + JCExpression cond = S.token() == SEMI ? null : parseExpression(); + accept(SEMI); + List steps = S.token() == RPAREN ? List.nil() : forUpdate(); + accept(RPAREN); + JCStatement body = parseStatement(); + return F.at(pos).ForLoop(inits, cond, steps, body); + } + } + case WHILE: { + S.nextToken(); + JCExpression cond = parExpression(); + JCStatement body = parseStatement(); + return F.at(pos).WhileLoop(cond, body); + } + case DO: { + S.nextToken(); + JCStatement body = parseStatement(); + accept(WHILE); + JCExpression cond = parExpression(); + JCDoWhileLoop t = to(F.at(pos).DoLoop(body, cond)); + accept(SEMI); + return t; + } + case TRY: { + S.nextToken(); + List resources = List.nil(); + if (S.token() == LPAREN) { + checkTryWithResources(); + S.nextToken(); + resources = resources(); + accept(RPAREN); + } + JCBlock body = block(); + ListBuffer catchers = new ListBuffer(); + JCBlock finalizer = null; + if (S.token() == CATCH || S.token() == FINALLY) { + while (S.token() == CATCH) catchers.append(catchClause()); + if (S.token() == FINALLY) { + S.nextToken(); + finalizer = block(); + } + } else { + if (allowTWR) { + if (resources.isEmpty()) + error(pos, "try.without.catch.finally.or.resource.decls"); + } else + error(pos, "try.without.catch.or.finally"); + } + return F.at(pos).Try(resources, body, catchers.toList(), finalizer); + } + case SWITCH: { + S.nextToken(); + JCExpression selector = parExpression(); + accept(LBRACE); + List cases = switchBlockStatementGroups(); + JCSwitch t = to(F.at(pos).Switch(selector, cases)); + accept(RBRACE); + return t; + } + case SYNCHRONIZED: { + S.nextToken(); + JCExpression lock = parExpression(); + JCBlock body = block(); + return F.at(pos).Synchronized(lock, body); + } + case RETURN: { + S.nextToken(); + JCExpression result = S.token() == SEMI ? null : parseExpression(); + JCReturn t = to(F.at(pos).Return(result)); + accept(SEMI); + return t; + } + case THROW: { + S.nextToken(); + JCExpression exc = parseExpression(); + JCThrow t = to(F.at(pos).Throw(exc)); + accept(SEMI); + return t; + } + case BREAK: { + S.nextToken(); + Name label = (S.token() == IDENTIFIER || S.token() == ASSERT || S.token() == ENUM) ? ident() : null; + JCBreak t = to(F.at(pos).Break(label)); + accept(SEMI); + return t; + } + case CONTINUE: { + S.nextToken(); + Name label = (S.token() == IDENTIFIER || S.token() == ASSERT || S.token() == ENUM) ? ident() : null; + JCContinue t = to(F.at(pos).Continue(label)); + accept(SEMI); + return t; + } + case SEMI: + S.nextToken(); + return toP(F.at(pos).Skip()); + case ELSE: + return toP(F.Exec(syntaxError("else.without.if"))); + case FINALLY: + return toP(F.Exec(syntaxError("finally.without.try"))); + case CATCH: + return toP(F.Exec(syntaxError("catch.without.try"))); + case ASSERT: { + if (allowAsserts && S.token() == ASSERT) { + S.nextToken(); + JCExpression assertion = parseExpression(); + JCExpression message = null; + if (S.token() == COLON) { + S.nextToken(); + message = parseExpression(); + } + JCAssert t = to(F.at(pos).Assert(assertion, message)); + accept(SEMI); + return t; + } + /* else fall through to default case */ + } + case ENUM: + default: + Name name = S.name(); + JCExpression expr = parseExpression(); + if (S.token() == COLON && expr.getTag() == JCTree.IDENT) { + S.nextToken(); + JCStatement stat = parseStatement(); + return F.at(pos).Labelled(name, stat); + } else { + // This Exec is an "ExpressionStatement"; it subsumes the terminating semicolon + JCExpressionStatement stat = to(F.at(pos).Exec(checkExprStat(expr))); + accept(SEMI); + return stat; + } + } + } + + /** CatchClause = CATCH "(" FormalParameter ")" Block + */ + JCCatch catchClause() { + int pos = S.pos(); + accept(CATCH); + accept(LPAREN); + JCModifiers mods = optFinal(Flags.PARAMETER); + List catchTypes = catchTypes(); + JCExpression paramType = catchTypes.size() > 1 ? + toP(F.at(catchTypes.head.getStartPosition()).TypeUnion(catchTypes)) : + catchTypes.head; + JCVariableDecl formal = variableDeclaratorId(mods, paramType); + accept(RPAREN); + JCBlock body = block(); + return F.at(pos).Catch(formal, body); + } + + List catchTypes() { + ListBuffer catchTypes = ListBuffer.lb(); + catchTypes.add(parseType()); + while (S.token() == BAR) { + checkMulticatch(); + S.nextToken(); + catchTypes.add(qualident()); + } + return catchTypes.toList(); + } + + /** SwitchBlockStatementGroups = { SwitchBlockStatementGroup } + * SwitchBlockStatementGroup = SwitchLabel BlockStatements + * SwitchLabel = CASE ConstantExpression ":" | DEFAULT ":" + */ + List switchBlockStatementGroups() { + ListBuffer cases = new ListBuffer(); + while (true) { + int pos = S.pos(); + switch (S.token()) { + case CASE: { + S.nextToken(); + JCExpression pat = parseExpression(); + accept(COLON); + List stats = blockStatements(); + JCCase c = F.at(pos).Case(pat, stats); + if (stats.isEmpty()) + storeEnd(c, S.prevEndPos()); + cases.append(c); + break; + } + case DEFAULT: { + S.nextToken(); + accept(COLON); + List stats = blockStatements(); + JCCase c = F.at(pos).Case(null, stats); + if (stats.isEmpty()) + storeEnd(c, S.prevEndPos()); + cases.append(c); + break; + } + case RBRACE: case EOF: + return cases.toList(); + default: + S.nextToken(); // to ensure progress + syntaxError(pos, "expected3", + CASE, DEFAULT, RBRACE); + } + } + } + + /** MoreStatementExpressions = { COMMA StatementExpression } + */ + > T moreStatementExpressions(int pos, + JCExpression first, + T stats) { + // This Exec is a "StatementExpression"; it subsumes no terminating token + stats.append(toP(F.at(pos).Exec(checkExprStat(first)))); + while (S.token() == COMMA) { + S.nextToken(); + pos = S.pos(); + JCExpression t = parseExpression(); + // This Exec is a "StatementExpression"; it subsumes no terminating token + stats.append(toP(F.at(pos).Exec(checkExprStat(t)))); + } + return stats; + } + + /** ForInit = StatementExpression MoreStatementExpressions + * | { FINAL | '@' Annotation } Type VariableDeclarators + */ + List forInit() { + ListBuffer stats = lb(); + int pos = S.pos(); + if (S.token() == FINAL || S.token() == MONKEYS_AT) { + return variableDeclarators(optFinal(0), parseType(), stats).toList(); + } else { + JCExpression t = term(EXPR | TYPE); + if ((lastmode & TYPE) != 0 && + (S.token() == IDENTIFIER || S.token() == ASSERT || S.token() == ENUM)) + return variableDeclarators(modifiersOpt(), t, stats).toList(); + else + return moreStatementExpressions(pos, t, stats).toList(); + } + } + + /** ForUpdate = StatementExpression MoreStatementExpressions + */ + List forUpdate() { + return moreStatementExpressions(S.pos(), + parseExpression(), + new ListBuffer()).toList(); + } + + /** AnnotationsOpt = { '@' Annotation } + */ + List annotationsOpt() { + if (S.token() != MONKEYS_AT) return List.nil(); // optimization + ListBuffer buf = new ListBuffer(); + while (S.token() == MONKEYS_AT) { + int pos = S.pos(); + S.nextToken(); + buf.append(annotation(pos)); + } + return buf.toList(); + } + + /** ModifiersOpt = { Modifier } + * Modifier = PUBLIC | PROTECTED | PRIVATE | STATIC | ABSTRACT | FINAL + * | NATIVE | SYNCHRONIZED | TRANSIENT | VOLATILE | "@" + * | "@" Annotation + */ + JCModifiers modifiersOpt() { + return modifiersOpt(null); + } + JCModifiers modifiersOpt(JCModifiers partial) { + long flags; + ListBuffer annotations = new ListBuffer(); + int pos; + if (partial == null) { + flags = 0; + pos = S.pos(); + } else { + flags = partial.flags; + annotations.appendList(partial.annotations); + pos = partial.pos; + } + if (S.deprecatedFlag()) { + flags |= Flags.DEPRECATED; + S.resetDeprecatedFlag(); + } + int lastPos = Position.NOPOS; + loop: + while (true) { + long flag; + switch (S.token()) { + case PRIVATE : flag = Flags.PRIVATE; break; + case PROTECTED : flag = Flags.PROTECTED; break; + case PUBLIC : flag = Flags.PUBLIC; break; + case STATIC : flag = Flags.STATIC; break; + case TRANSIENT : flag = Flags.TRANSIENT; break; + case FINAL : flag = Flags.FINAL; break; + case ABSTRACT : flag = Flags.ABSTRACT; break; + case NATIVE : flag = Flags.NATIVE; break; + case VOLATILE : flag = Flags.VOLATILE; break; + case SYNCHRONIZED: flag = Flags.SYNCHRONIZED; break; + case STRICTFP : flag = Flags.STRICTFP; break; + case MONKEYS_AT : flag = Flags.ANNOTATION; break; + default: break loop; + } + if ((flags & flag) != 0) error(S.pos(), "repeated.modifier"); + lastPos = S.pos(); + S.nextToken(); + if (flag == Flags.ANNOTATION) { + checkAnnotations(); + if (S.token() != INTERFACE) { + JCAnnotation ann = annotation(lastPos); + // if first modifier is an annotation, set pos to annotation's. + if (flags == 0 && annotations.isEmpty()) + pos = ann.pos; + annotations.append(ann); + lastPos = ann.pos; + flag = 0; + } + } + flags |= flag; + } + switch (S.token()) { + case ENUM: flags |= Flags.ENUM; break; + case INTERFACE: flags |= Flags.INTERFACE; break; + default: break; + } + + /* A modifiers tree with no modifier tokens or annotations + * has no text position. */ + if ((flags & (Flags.ModifierFlags | Flags.ANNOTATION)) == 0 && annotations.isEmpty()) + pos = Position.NOPOS; + + JCModifiers mods = F.at(pos).Modifiers(flags, annotations.toList()); + if (pos != Position.NOPOS) + storeEnd(mods, S.prevEndPos()); + return mods; + } + + /** Annotation = "@" Qualident [ "(" AnnotationFieldValues ")" ] + * @param pos position of "@" token + */ + JCAnnotation annotation(int pos) { + // accept(AT); // AT consumed by caller + checkAnnotations(); + JCTree ident = qualident(); + List fieldValues = annotationFieldValuesOpt(); + JCAnnotation ann = F.at(pos).Annotation(ident, fieldValues); + storeEnd(ann, S.prevEndPos()); + return ann; + } + + List annotationFieldValuesOpt() { + return (S.token() == LPAREN) ? annotationFieldValues() : List.nil(); + } + + /** AnnotationFieldValues = "(" [ AnnotationFieldValue { "," AnnotationFieldValue } ] ")" */ + List annotationFieldValues() { + accept(LPAREN); + ListBuffer buf = new ListBuffer(); + if (S.token() != RPAREN) { + buf.append(annotationFieldValue()); + while (S.token() == COMMA) { + S.nextToken(); + buf.append(annotationFieldValue()); + } + } + accept(RPAREN); + return buf.toList(); + } + + /** AnnotationFieldValue = AnnotationValue + * | Identifier "=" AnnotationValue + */ + JCExpression annotationFieldValue() { + if (S.token() == IDENTIFIER) { + mode = EXPR; + JCExpression t1 = term1(); + if (t1.getTag() == JCTree.IDENT && S.token() == EQ) { + int pos = S.pos(); + accept(EQ); + JCExpression v = annotationValue(); + return toP(F.at(pos).Assign(t1, v)); + } else { + return t1; + } + } + return annotationValue(); + } + + /* AnnotationValue = ConditionalExpression + * | Annotation + * | "{" [ AnnotationValue { "," AnnotationValue } ] [","] "}" + */ + JCExpression annotationValue() { + int pos; + switch (S.token()) { + case MONKEYS_AT: + pos = S.pos(); + S.nextToken(); + return annotation(pos); + case LBRACE: + pos = S.pos(); + accept(LBRACE); + ListBuffer buf = new ListBuffer(); + if (S.token() != RBRACE) { + buf.append(annotationValue()); + while (S.token() == COMMA) { + S.nextToken(); + if (S.token() == RBRACE) break; + buf.append(annotationValue()); + } + } + accept(RBRACE); + return toP(F.at(pos).NewArray(null, List.nil(), buf.toList())); + default: + mode = EXPR; + return term1(); + } + } + + /** VariableDeclarators = VariableDeclarator { "," VariableDeclarator } + */ + public > T variableDeclarators(JCModifiers mods, + JCExpression type, + T vdefs) + { + return variableDeclaratorsRest(S.pos(), mods, type, ident(), false, null, vdefs); + } + + /** VariableDeclaratorsRest = VariableDeclaratorRest { "," VariableDeclarator } + * ConstantDeclaratorsRest = ConstantDeclaratorRest { "," ConstantDeclarator } + * + * @param reqInit Is an initializer always required? + * @param dc The documentation comment for the variable declarations, or null. + */ + > T variableDeclaratorsRest(int pos, + JCModifiers mods, + JCExpression type, + Name name, + boolean reqInit, + String dc, + T vdefs) + { + vdefs.append(variableDeclaratorRest(pos, mods, type, name, reqInit, dc)); + while (S.token() == COMMA) { + // All but last of multiple declarators subsume a comma + storeEnd((JCTree)vdefs.elems.last(), S.endPos()); + S.nextToken(); + vdefs.append(variableDeclarator(mods, type, reqInit, dc)); + } + return vdefs; + } + + /** VariableDeclarator = Ident VariableDeclaratorRest + * ConstantDeclarator = Ident ConstantDeclaratorRest + */ + JCVariableDecl variableDeclarator(JCModifiers mods, JCExpression type, boolean reqInit, String dc) { + return variableDeclaratorRest(S.pos(), mods, type, ident(), reqInit, dc); + } + + /** VariableDeclaratorRest = BracketsOpt ["=" VariableInitializer] + * ConstantDeclaratorRest = BracketsOpt "=" VariableInitializer + * + * @param reqInit Is an initializer always required? + * @param dc The documentation comment for the variable declarations, or null. + */ + JCVariableDecl variableDeclaratorRest(int pos, JCModifiers mods, JCExpression type, Name name, + boolean reqInit, String dc) { + type = bracketsOpt(type); + JCExpression init = null; + if (S.token() == EQ) { + S.nextToken(); + init = variableInitializer(); + } + else if (reqInit) syntaxError(S.pos(), "expected", EQ); + JCVariableDecl result = + toP(F.at(pos).VarDef(mods, name, type, init)); + attach(result, dc); + return result; + } + + /** VariableDeclaratorId = Ident BracketsOpt + */ + JCVariableDecl variableDeclaratorId(JCModifiers mods, JCExpression type) { + int pos = S.pos(); + Name name = ident(); + if ((mods.flags & Flags.VARARGS) != 0 && + S.token() == LBRACKET) { + log.error(S.pos(), "varargs.and.old.array.syntax"); + } + type = bracketsOpt(type); + return toP(F.at(pos).VarDef(mods, name, type, null)); + } + + /** Resources = Resource { ";" Resources } + */ + List resources() { + ListBuffer defs = new ListBuffer(); + defs.append(resource()); + while (S.token() == SEMI) { + // All but last of multiple declarators must subsume a semicolon + storeEnd(defs.elems.last(), S.endPos()); + int semiColonPos = S.pos(); + S.nextToken(); + if (S.token() == RPAREN) { // Optional trailing semicolon + // after last resource + break; + } + defs.append(resource()); + } + return defs.toList(); + } + + /** Resource = VariableModifiersOpt Type VariableDeclaratorId = Expression + */ + JCTree resource() { + return variableDeclaratorRest(S.pos(), optFinal(Flags.FINAL), + parseType(), ident(), true, null); + } + + /** CompilationUnit = [ { "@" Annotation } PACKAGE Qualident ";"] {ImportDeclaration} {TypeDeclaration} + */ + public JCTree.JCCompilationUnit parseCompilationUnit() { + int pos = S.pos(); + JCExpression pid = null; + String dc = S.docComment(); + JCModifiers mods = null; + List packageAnnotations = List.nil(); + if (S.token() == MONKEYS_AT) + mods = modifiersOpt(); + + if (S.token() == PACKAGE) { + if (mods != null) { + checkNoMods(mods.flags); + packageAnnotations = mods.annotations; + mods = null; + } + S.nextToken(); + pid = qualident(); + accept(SEMI); + } + ListBuffer defs = new ListBuffer(); + boolean checkForImports = true; + while (S.token() != EOF) { + if (S.pos() <= errorEndPos) { + // error recovery + skip(checkForImports, false, false, false); + if (S.token() == EOF) + break; + } + if (checkForImports && mods == null && S.token() == IMPORT) { + defs.append(importDeclaration()); + } else { + JCTree def = typeDeclaration(mods); + if (keepDocComments && dc != null && docComments.get(def) == dc) { + // If the first type declaration has consumed the first doc + // comment, then don't use it for the top level comment as well. + dc = null; + } + if (def instanceof JCExpressionStatement) + def = ((JCExpressionStatement)def).expr; + defs.append(def); + if (def instanceof JCClassDecl) + checkForImports = false; + mods = null; + } + } + JCTree.JCCompilationUnit toplevel = F.at(pos).TopLevel(packageAnnotations, pid, defs.toList()); + attach(toplevel, dc); + if (defs.elems.isEmpty()) + storeEnd(toplevel, S.prevEndPos()); + if (keepDocComments) + toplevel.docComments = docComments; + if (keepLineMap) + toplevel.lineMap = S.getLineMap(); + return toplevel; + } + + /** ImportDeclaration = IMPORT [ STATIC ] Ident { "." Ident } [ "." "*" ] ";" + */ + JCTree importDeclaration() { + int pos = S.pos(); + S.nextToken(); + boolean importStatic = false; + if (S.token() == STATIC) { + checkStaticImports(); + importStatic = true; + S.nextToken(); + } + JCExpression pid = toP(F.at(S.pos()).Ident(ident())); + do { + int pos1 = S.pos(); + accept(DOT); + if (S.token() == STAR) { + pid = to(F.at(pos1).Select(pid, names.asterisk)); + S.nextToken(); + break; + } else { + pid = toP(F.at(pos1).Select(pid, ident())); + } + } while (S.token() == DOT); + accept(SEMI); + return toP(F.at(pos).Import(pid, importStatic)); + } + + /** TypeDeclaration = ClassOrInterfaceOrEnumDeclaration + * | ";" + */ + JCTree typeDeclaration(JCModifiers mods) { + int pos = S.pos(); + if (mods == null && S.token() == SEMI) { + S.nextToken(); + return toP(F.at(pos).Skip()); + } else { + String dc = S.docComment(); + return classOrInterfaceOrEnumDeclaration(modifiersOpt(mods), dc); + } + } + + /** ClassOrInterfaceOrEnumDeclaration = ModifiersOpt + * (ClassDeclaration | InterfaceDeclaration | EnumDeclaration) + * @param mods Any modifiers starting the class or interface declaration + * @param dc The documentation comment for the class, or null. + */ + JCStatement classOrInterfaceOrEnumDeclaration(JCModifiers mods, String dc) { + if (S.token() == CLASS) { + return classDeclaration(mods, dc); + } else if (S.token() == INTERFACE) { + return interfaceDeclaration(mods, dc); + } else if (allowEnums) { + if (S.token() == ENUM) { + return enumDeclaration(mods, dc); + } else { + int pos = S.pos(); + List errs; + if (S.token() == IDENTIFIER) { + errs = List.of(mods, toP(F.at(pos).Ident(ident()))); + setErrorEndPos(S.pos()); + } else { + errs = List.of(mods); + } + return toP(F.Exec(syntaxError(pos, errs, "expected3", + CLASS, INTERFACE, ENUM))); + } + } else { + if (S.token() == ENUM) { + error(S.pos(), "enums.not.supported.in.source", source.name); + allowEnums = true; + return enumDeclaration(mods, dc); + } + int pos = S.pos(); + List errs; + if (S.token() == IDENTIFIER) { + errs = List.of(mods, toP(F.at(pos).Ident(ident()))); + setErrorEndPos(S.pos()); + } else { + errs = List.of(mods); + } + return toP(F.Exec(syntaxError(pos, errs, "expected2", + CLASS, INTERFACE))); + } + } + + /** ClassDeclaration = CLASS Ident TypeParametersOpt [EXTENDS Type] + * [IMPLEMENTS TypeList] ClassBody + * @param mods The modifiers starting the class declaration + * @param dc The documentation comment for the class, or null. + */ + JCClassDecl classDeclaration(JCModifiers mods, String dc) { + int pos = S.pos(); + accept(CLASS); + Name name = ident(); + + List typarams = typeParametersOpt(); + + JCExpression extending = null; + if (S.token() == EXTENDS) { + S.nextToken(); + extending = parseType(); + } + List implementing = List.nil(); + if (S.token() == IMPLEMENTS) { + S.nextToken(); + implementing = typeList(); + } + List defs = classOrInterfaceBody(name, false); + JCClassDecl result = toP(F.at(pos).ClassDef( + mods, name, typarams, extending, implementing, defs)); + attach(result, dc); + return result; + } + + /** InterfaceDeclaration = INTERFACE Ident TypeParametersOpt + * [EXTENDS TypeList] InterfaceBody + * @param mods The modifiers starting the interface declaration + * @param dc The documentation comment for the interface, or null. + */ + JCClassDecl interfaceDeclaration(JCModifiers mods, String dc) { + int pos = S.pos(); + accept(INTERFACE); + Name name = ident(); + + List typarams = typeParametersOpt(); + + List extending = List.nil(); + if (S.token() == EXTENDS) { + S.nextToken(); + extending = typeList(); + } + List defs = classOrInterfaceBody(name, true); + JCClassDecl result = toP(F.at(pos).ClassDef( + mods, name, typarams, null, extending, defs)); + attach(result, dc); + return result; + } + + /** EnumDeclaration = ENUM Ident [IMPLEMENTS TypeList] EnumBody + * @param mods The modifiers starting the enum declaration + * @param dc The documentation comment for the enum, or null. + */ + JCClassDecl enumDeclaration(JCModifiers mods, String dc) { + int pos = S.pos(); + accept(ENUM); + Name name = ident(); + + List implementing = List.nil(); + if (S.token() == IMPLEMENTS) { + S.nextToken(); + implementing = typeList(); + } + + List defs = enumBody(name); + mods.flags |= Flags.ENUM; + JCClassDecl result = toP(F.at(pos). + ClassDef(mods, name, List.nil(), + null, implementing, defs)); + attach(result, dc); + return result; + } + + /** EnumBody = "{" { EnumeratorDeclarationList } [","] + * [ ";" {ClassBodyDeclaration} ] "}" + */ + List enumBody(Name enumName) { + accept(LBRACE); + ListBuffer defs = new ListBuffer(); + if (S.token() == COMMA) { + S.nextToken(); + } else if (S.token() != RBRACE && S.token() != SEMI) { + defs.append(enumeratorDeclaration(enumName)); + while (S.token() == COMMA) { + S.nextToken(); + if (S.token() == RBRACE || S.token() == SEMI) break; + defs.append(enumeratorDeclaration(enumName)); + } + if (S.token() != SEMI && S.token() != RBRACE) { + defs.append(syntaxError(S.pos(), "expected3", + COMMA, RBRACE, SEMI)); + S.nextToken(); + } + } + if (S.token() == SEMI) { + S.nextToken(); + while (S.token() != RBRACE && S.token() != EOF) { + defs.appendList(classOrInterfaceBodyDeclaration(enumName, + false)); + if (S.pos() <= errorEndPos) { + // error recovery + skip(false, true, true, false); + } + } + } + accept(RBRACE); + return defs.toList(); + } + + /** EnumeratorDeclaration = AnnotationsOpt [TypeArguments] IDENTIFIER [ Arguments ] [ "{" ClassBody "}" ] + */ + JCTree enumeratorDeclaration(Name enumName) { + String dc = S.docComment(); + int flags = Flags.PUBLIC|Flags.STATIC|Flags.FINAL|Flags.ENUM; + if (S.deprecatedFlag()) { + flags |= Flags.DEPRECATED; + S.resetDeprecatedFlag(); + } + int pos = S.pos(); + List annotations = annotationsOpt(); + JCModifiers mods = F.at(annotations.isEmpty() ? Position.NOPOS : pos).Modifiers(flags, annotations); + List typeArgs = typeArgumentsOpt(); + int identPos = S.pos(); + Name name = ident(); + int createPos = S.pos(); + List args = (S.token() == LPAREN) + ? arguments() : List.nil(); + JCClassDecl body = null; + if (S.token() == LBRACE) { + JCModifiers mods1 = F.at(Position.NOPOS).Modifiers(Flags.ENUM | Flags.STATIC); + List defs = classOrInterfaceBody(names.empty, false); + body = toP(F.at(identPos).AnonymousClassDef(mods1, defs)); + } + if (args.isEmpty() && body == null) + createPos = identPos; + JCIdent ident = F.at(identPos).Ident(enumName); + JCNewClass create = F.at(createPos).NewClass(null, typeArgs, ident, args, body); + if (createPos != identPos) + storeEnd(create, S.prevEndPos()); + ident = F.at(identPos).Ident(enumName); + JCTree result = toP(F.at(pos).VarDef(mods, name, ident, create)); + attach(result, dc); + return result; + } + + /** TypeList = Type {"," Type} + */ + List typeList() { + ListBuffer ts = new ListBuffer(); + ts.append(parseType()); + while (S.token() == COMMA) { + S.nextToken(); + ts.append(parseType()); + } + return ts.toList(); + } + + /** ClassBody = "{" {ClassBodyDeclaration} "}" + * InterfaceBody = "{" {InterfaceBodyDeclaration} "}" + */ + List classOrInterfaceBody(Name className, boolean isInterface) { + accept(LBRACE); + if (S.pos() <= errorEndPos) { + // error recovery + skip(false, true, false, false); + if (S.token() == LBRACE) + S.nextToken(); + } + ListBuffer defs = new ListBuffer(); + while (S.token() != RBRACE && S.token() != EOF) { + defs.appendList(classOrInterfaceBodyDeclaration(className, isInterface)); + if (S.pos() <= errorEndPos) { + // error recovery + skip(false, true, true, false); + } + } + accept(RBRACE); + return defs.toList(); + } + + /** ClassBodyDeclaration = + * ";" + * | [STATIC] Block + * | ModifiersOpt + * ( Type Ident + * ( VariableDeclaratorsRest ";" | MethodDeclaratorRest ) + * | VOID Ident MethodDeclaratorRest + * | TypeParameters (Type | VOID) Ident MethodDeclaratorRest + * | Ident ConstructorDeclaratorRest + * | TypeParameters Ident ConstructorDeclaratorRest + * | ClassOrInterfaceOrEnumDeclaration + * ) + * InterfaceBodyDeclaration = + * ";" + * | ModifiersOpt Type Ident + * ( ConstantDeclaratorsRest | InterfaceMethodDeclaratorRest ";" ) + */ + List classOrInterfaceBodyDeclaration(Name className, boolean isInterface) { + if (S.token() == SEMI) { + S.nextToken(); + return List.nil(); + } else { + String dc = S.docComment(); + int pos = S.pos(); + JCModifiers mods = modifiersOpt(); + if (S.token() == CLASS || + S.token() == INTERFACE || + allowEnums && S.token() == ENUM) { + return List.of(classOrInterfaceOrEnumDeclaration(mods, dc)); + } else if (S.token() == LBRACE && !isInterface && + (mods.flags & Flags.StandardFlags & ~Flags.STATIC) == 0 && + mods.annotations.isEmpty()) { + return List.of(block(pos, mods.flags)); + } else { + pos = S.pos(); + List typarams = typeParametersOpt(); + // if there are type parameters but no modifiers, save the start + // position of the method in the modifiers. + if (typarams.nonEmpty() && mods.pos == Position.NOPOS) { + mods.pos = pos; + storeEnd(mods, pos); + } + Name name = S.name(); + pos = S.pos(); + JCExpression type; + boolean isVoid = S.token() == VOID; + if (isVoid) { + type = to(F.at(pos).TypeIdent(TypeTags.VOID)); + S.nextToken(); + } else { + type = parseType(); + } + if (S.token() == LPAREN && !isInterface && type.getTag() == JCTree.IDENT) { + if (isInterface || name != className) + error(pos, "invalid.meth.decl.ret.type.req"); + return List.of(methodDeclaratorRest( + pos, mods, null, names.init, typarams, + isInterface, true, dc)); + } else { + pos = S.pos(); + name = ident(); + if (S.token() == LPAREN) { + return List.of(methodDeclaratorRest( + pos, mods, type, name, typarams, + isInterface, isVoid, dc)); + } else if (!isVoid && typarams.isEmpty()) { + List defs = + variableDeclaratorsRest(pos, mods, type, name, isInterface, dc, + new ListBuffer()).toList(); + storeEnd(defs.last(), S.endPos()); + accept(SEMI); + return defs; + } else { + pos = S.pos(); + List err = isVoid + ? List.of(toP(F.at(pos).MethodDef(mods, name, type, typarams, + List.nil(), List.nil(), null, null))) + : null; + return List.of(syntaxError(S.pos(), err, "expected", LPAREN)); + } + } + } + } + } + + /** MethodDeclaratorRest = + * FormalParameters BracketsOpt [Throws TypeList] ( MethodBody | [DEFAULT AnnotationValue] ";") + * VoidMethodDeclaratorRest = + * FormalParameters [Throws TypeList] ( MethodBody | ";") + * InterfaceMethodDeclaratorRest = + * FormalParameters BracketsOpt [THROWS TypeList] ";" + * VoidInterfaceMethodDeclaratorRest = + * FormalParameters [THROWS TypeList] ";" + * ConstructorDeclaratorRest = + * "(" FormalParameterListOpt ")" [THROWS TypeList] MethodBody + */ + JCTree methodDeclaratorRest(int pos, + JCModifiers mods, + JCExpression type, + Name name, + List typarams, + boolean isInterface, boolean isVoid, + String dc) { + List params = formalParameters(); + if (!isVoid) type = bracketsOpt(type); + List thrown = List.nil(); + if (S.token() == THROWS) { + S.nextToken(); + thrown = qualidentList(); + } + JCBlock body = null; + JCExpression defaultValue; + if (S.token() == LBRACE) { + body = block(); + defaultValue = null; + } else { + if (S.token() == DEFAULT) { + accept(DEFAULT); + defaultValue = annotationValue(); + } else { + defaultValue = null; + } + accept(SEMI); + if (S.pos() <= errorEndPos) { + // error recovery + skip(false, true, false, false); + if (S.token() == LBRACE) { + body = block(); + } + } + } + + JCMethodDecl result = + toP(F.at(pos).MethodDef(mods, name, type, typarams, + params, thrown, + body, defaultValue)); + attach(result, dc); + return result; + } + + /** QualidentList = Qualident {"," Qualident} + */ + List qualidentList() { + ListBuffer ts = new ListBuffer(); + ts.append(qualident()); + while (S.token() == COMMA) { + S.nextToken(); + ts.append(qualident()); + } + return ts.toList(); + } + + /** TypeParametersOpt = ["<" TypeParameter {"," TypeParameter} ">"] + */ + List typeParametersOpt() { + if (S.token() == LT) { + checkGenerics(); + ListBuffer typarams = new ListBuffer(); + S.nextToken(); + typarams.append(typeParameter()); + while (S.token() == COMMA) { + S.nextToken(); + typarams.append(typeParameter()); + } + accept(GT); + return typarams.toList(); + } else { + return List.nil(); + } + } + + /** TypeParameter = TypeVariable [TypeParameterBound] + * TypeParameterBound = EXTENDS Type {"&" Type} + * TypeVariable = Ident + */ + JCTypeParameter typeParameter() { + int pos = S.pos(); + Name name = ident(); + ListBuffer bounds = new ListBuffer(); + if (S.token() == EXTENDS) { + S.nextToken(); + bounds.append(parseType()); + while (S.token() == AMP) { + S.nextToken(); + bounds.append(parseType()); + } + } + return toP(F.at(pos).TypeParameter(name, bounds.toList())); + } + + /** FormalParameters = "(" [ FormalParameterList ] ")" + * FormalParameterList = [ FormalParameterListNovarargs , ] LastFormalParameter + * FormalParameterListNovarargs = [ FormalParameterListNovarargs , ] FormalParameter + */ + List formalParameters() { + ListBuffer params = new ListBuffer(); + JCVariableDecl lastParam = null; + accept(LPAREN); + if (S.token() != RPAREN) { + params.append(lastParam = formalParameter()); + while ((lastParam.mods.flags & Flags.VARARGS) == 0 && S.token() == COMMA) { + S.nextToken(); + params.append(lastParam = formalParameter()); + } + } + accept(RPAREN); + return params.toList(); + } + + JCModifiers optFinal(long flags) { + JCModifiers mods = modifiersOpt(); + checkNoMods(mods.flags & ~(Flags.FINAL | Flags.DEPRECATED)); + mods.flags |= flags; + return mods; + } + + /** FormalParameter = { FINAL | '@' Annotation } Type VariableDeclaratorId + * LastFormalParameter = { FINAL | '@' Annotation } Type '...' Ident | FormalParameter + */ + JCVariableDecl formalParameter() { + JCModifiers mods = optFinal(Flags.PARAMETER); + JCExpression type = parseType(); + if (S.token() == ELLIPSIS) { + checkVarargs(); + mods.flags |= Flags.VARARGS; + type = to(F.at(S.pos()).TypeArray(type)); + S.nextToken(); + } + return variableDeclaratorId(mods, type); + } + +/* ---------- auxiliary methods -------------- */ + + void error(int pos, String key, Object ... args) { + log.error(DiagnosticFlag.SYNTAX, pos, key, args); + } + + void warning(int pos, String key, Object ... args) { + log.warning(pos, key, args); + } + + /** Check that given tree is a legal expression statement. + */ + protected JCExpression checkExprStat(JCExpression t) { + switch(t.getTag()) { + case JCTree.PREINC: case JCTree.PREDEC: + case JCTree.POSTINC: case JCTree.POSTDEC: + case JCTree.ASSIGN: + case JCTree.BITOR_ASG: case JCTree.BITXOR_ASG: case JCTree.BITAND_ASG: + case JCTree.SL_ASG: case JCTree.SR_ASG: case JCTree.USR_ASG: + case JCTree.PLUS_ASG: case JCTree.MINUS_ASG: + case JCTree.MUL_ASG: case JCTree.DIV_ASG: case JCTree.MOD_ASG: + case JCTree.APPLY: case JCTree.NEWCLASS: + case JCTree.ERRONEOUS: + return t; + default: + error(t.pos, "not.stmt"); + return F.at(t.pos).Erroneous(List.of(t)); + } + } + + /** Return precedence of operator represented by token, + * -1 if token is not a binary operator. @see TreeInfo.opPrec + */ + static int prec(Token token) { + int oc = optag(token); + return (oc >= 0) ? TreeInfo.opPrec(oc) : -1; + } + + /** + * Return the lesser of two positions, making allowance for either one + * being unset. + */ + static int earlier(int pos1, int pos2) { + if (pos1 == Position.NOPOS) + return pos2; + if (pos2 == Position.NOPOS) + return pos1; + return (pos1 < pos2 ? pos1 : pos2); + } + + /** Return operation tag of binary operator represented by token, + * -1 if token is not a binary operator. + */ + static int optag(Token token) { + switch (token) { + case BARBAR: + return JCTree.OR; + case AMPAMP: + return JCTree.AND; + case BAR: + return JCTree.BITOR; + case BAREQ: + return JCTree.BITOR_ASG; + case CARET: + return JCTree.BITXOR; + case CARETEQ: + return JCTree.BITXOR_ASG; + case AMP: + return JCTree.BITAND; + case AMPEQ: + return JCTree.BITAND_ASG; + case EQEQ: + return JCTree.EQ; + case BANGEQ: + return JCTree.NE; + case LT: + return JCTree.LT; + case GT: + return JCTree.GT; + case LTEQ: + return JCTree.LE; + case GTEQ: + return JCTree.GE; + case LTLT: + return JCTree.SL; + case LTLTEQ: + return JCTree.SL_ASG; + case GTGT: + return JCTree.SR; + case GTGTEQ: + return JCTree.SR_ASG; + case GTGTGT: + return JCTree.USR; + case GTGTGTEQ: + return JCTree.USR_ASG; + case PLUS: + return JCTree.PLUS; + case PLUSEQ: + return JCTree.PLUS_ASG; + case SUB: + return JCTree.MINUS; + case SUBEQ: + return JCTree.MINUS_ASG; + case STAR: + return JCTree.MUL; + case STAREQ: + return JCTree.MUL_ASG; + case SLASH: + return JCTree.DIV; + case SLASHEQ: + return JCTree.DIV_ASG; + case PERCENT: + return JCTree.MOD; + case PERCENTEQ: + return JCTree.MOD_ASG; + case INSTANCEOF: + return JCTree.TYPETEST; + default: + return -1; + } + } + + /** Return operation tag of unary operator represented by token, + * -1 if token is not a binary operator. + */ + static int unoptag(Token token) { + switch (token) { + case PLUS: + return JCTree.POS; + case SUB: + return JCTree.NEG; + case BANG: + return JCTree.NOT; + case TILDE: + return JCTree.COMPL; + case PLUSPLUS: + return JCTree.PREINC; + case SUBSUB: + return JCTree.PREDEC; + default: + return -1; + } + } + + /** Return type tag of basic type represented by token, + * -1 if token is not a basic type identifier. + */ + static int typetag(Token token) { + switch (token) { + case BYTE: + return TypeTags.BYTE; + case CHAR: + return TypeTags.CHAR; + case SHORT: + return TypeTags.SHORT; + case INT: + return TypeTags.INT; + case LONG: + return TypeTags.LONG; + case FLOAT: + return TypeTags.FLOAT; + case DOUBLE: + return TypeTags.DOUBLE; + case BOOLEAN: + return TypeTags.BOOLEAN; + default: + return -1; + } + } + + void checkGenerics() { + if (!allowGenerics) { + error(S.pos(), "generics.not.supported.in.source", source.name); + allowGenerics = true; + } + } + void checkVarargs() { + if (!allowVarargs) { + error(S.pos(), "varargs.not.supported.in.source", source.name); + allowVarargs = true; + } + } + void checkForeach() { + if (!allowForeach) { + error(S.pos(), "foreach.not.supported.in.source", source.name); + allowForeach = true; + } + } + void checkStaticImports() { + if (!allowStaticImport) { + error(S.pos(), "static.import.not.supported.in.source", source.name); + allowStaticImport = true; + } + } + void checkAnnotations() { + if (!allowAnnotations) { + error(S.pos(), "annotations.not.supported.in.source", source.name); + allowAnnotations = true; + } + } + void checkDiamond() { + if (!allowDiamond) { + error(S.pos(), "diamond.not.supported.in.source", source.name); + allowDiamond = true; + } + } + void checkMulticatch() { + if (!allowMulticatch) { + error(S.pos(), "multicatch.not.supported.in.source", source.name); + allowMulticatch = true; + } + } + void checkTryWithResources() { + if (!allowTWR) { + error(S.pos(), "try.with.resources.not.supported.in.source", source.name); + allowTWR = true; + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/parser/Keywords.java b/douyu-javac/src/main/java/com/sun/tools/javac/parser/Keywords.java new file mode 100644 index 0000000..43386bf --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/parser/Keywords.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.parser; + +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Log; +import com.sun.tools.javac.util.Name; +import com.sun.tools.javac.util.Names; + +import static com.sun.tools.javac.parser.Token.*; + +/** + * Map from Name to Token and Token to String. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Keywords { + public static final Context.Key keywordsKey = + new Context.Key(); + + public static Keywords instance(Context context) { + Keywords instance = context.get(keywordsKey); + if (instance == null) + instance = new Keywords(context); + return instance; + } + + private final Names names; + + protected Keywords(Context context) { + context.put(keywordsKey, this); + names = Names.instance(context); + + for (Token t : Token.values()) { + if (t.name != null) + enterKeyword(t.name, t); + else + tokenName[t.ordinal()] = null; + } + + key = new Token[maxKey+1]; + for (int i = 0; i <= maxKey; i++) key[i] = IDENTIFIER; + for (Token t : Token.values()) { + if (t.name != null) + key[tokenName[t.ordinal()].getIndex()] = t; + } + } + + + public Token key(Name name) { + return (name.getIndex() > maxKey) ? IDENTIFIER : key[name.getIndex()]; + } + + /** + * Keyword array. Maps name indices to Token. + */ + private final Token[] key; + + /** The number of the last entered keyword. + */ + private int maxKey = 0; + + /** The names of all tokens. + */ + private Name[] tokenName = new Name[Token.values().length]; + + private void enterKeyword(String s, Token token) { + Name n = names.fromString(s); + tokenName[token.ordinal()] = n; + if (n.getIndex() > maxKey) maxKey = n.getIndex(); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/parser/Lexer.java b/douyu-javac/src/main/java/com/sun/tools/javac/parser/Lexer.java new file mode 100644 index 0000000..90f6afe --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/parser/Lexer.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.parser; + +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.Position.LineMap; + +/** + * The lexical analyzer maps an input stream consisting of ASCII + * characters and Unicode escapes into a token sequence. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public interface Lexer { + + /** + * Has a @deprecated been encountered in last doc comment? + * This needs to be reset by client with resetDeprecatedFlag. + */ + boolean deprecatedFlag(); + + void resetDeprecatedFlag(); + + /** + * Returns the documentation string of the current token. + */ + String docComment(); + + /** + * Return the last character position of the current token. + */ + int endPos(); + + /** + * Return the position where a lexical error occurred; + */ + int errPos(); + + /** + * Set the position where a lexical error occurred; + */ + void errPos(int pos); + + /** + * Build a map for translating between line numbers and + * positions in the input. + * + * @return a LineMap + */ + LineMap getLineMap(); + + /** + * Returns a copy of the input buffer, up to its inputLength. + * Unicode escape sequences are not translated. + */ + char[] getRawCharacters(); + + /** + * Returns a copy of a character array subset of the input buffer. + * The returned array begins at the beginIndex and + * extends to the character at index endIndex - 1. + * Thus the length of the substring is endIndex-beginIndex. + * This behavior is like + * String.substring(beginIndex, endIndex). + * Unicode escape sequences are not translated. + * + * @param beginIndex the beginning index, inclusive. + * @param endIndex the ending index, exclusive. + * @throws IndexOutOfBounds if either offset is outside of the + * array bounds + */ + char[] getRawCharacters(int beginIndex, int endIndex); + + /** + * Return the name of an identifier or token for the current token. + */ + Name name(); + + /** + * Read token. + */ + void nextToken(); + + /** + * Return the current token's position: a 0-based + * offset from beginning of the raw input stream + * (before unicode translation) + */ + int pos(); + + /** + * Return the last character position of the previous token. + */ + int prevEndPos(); + + /** + * Return the radix of a numeric literal token. + */ + int radix(); + + /** + * The value of a literal token, recorded as a string. + * For integers, leading 0x and 'l' suffixes are suppressed. + */ + String stringVal(); + + /** + * Return the current token, set by nextToken(). + */ + Token token(); + + /** + * Sets the current token. + */ + void token(Token token); +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/parser/Parser.java b/douyu-javac/src/main/java/com/sun/tools/javac/parser/Parser.java new file mode 100644 index 0000000..5b70ad0 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/parser/Parser.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 1999, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +package com.sun.tools.javac.parser; + +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCStatement; + +/** + * Reads syntactic units from source code. + * Parsers are normally created from a ParserFactory. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public interface Parser { + /** + * Parse a compilation unit. + * @return a compilation unit + */ + JCCompilationUnit parseCompilationUnit(); + + /** + * Parse an expression. + * @return an expression + */ + JCExpression parseExpression(); + + /** + * Parse a statement. + * @return an expression + */ + JCStatement parseStatement(); + + /** + * Parse a type. + * @return an expression for a type + */ + JCExpression parseType(); +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/parser/ParserFactory.java b/douyu-javac/src/main/java/com/sun/tools/javac/parser/ParserFactory.java new file mode 100644 index 0000000..709bd5f --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/parser/ParserFactory.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.parser; + +import com.sun.tools.javac.code.Source; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Log; +import com.sun.tools.javac.util.Names; +import com.sun.tools.javac.util.Options; + +/** + * A factory for creating parsers. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class ParserFactory { + + /** The context key for the parser factory. */ + protected static final Context.Key parserFactoryKey = new Context.Key(); + + public static ParserFactory instance(Context context) { + ParserFactory instance = context.get(parserFactoryKey); + if (instance == null) { + instance = new ParserFactory(context); + } + return instance; + } + + final TreeMaker F; + final Log log; + final Keywords keywords; + final Source source; + final Names names; + final Options options; + final ScannerFactory scannerFactory; + + protected ParserFactory(Context context) { + super(); + context.put(parserFactoryKey, this); + this.F = TreeMaker.instance(context); + this.log = Log.instance(context); + this.names = Names.instance(context); + this.keywords = Keywords.instance(context); + this.source = Source.instance(context); + this.options = Options.instance(context); + this.scannerFactory = ScannerFactory.instance(context); + } + + public Parser newParser(CharSequence input, boolean keepDocComments, boolean keepEndPos, boolean keepLineMap) { + Lexer lexer = scannerFactory.newScanner(input, keepDocComments); + if (keepEndPos) { + return new EndPosParser(this, lexer, keepDocComments, keepLineMap); + } else { + return new JavacParser(this, lexer, keepDocComments, keepLineMap); + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/parser/Scanner.java b/douyu-javac/src/main/java/com/sun/tools/javac/parser/Scanner.java new file mode 100644 index 0000000..2f08975 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/parser/Scanner.java @@ -0,0 +1,1132 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.parser; + +import java.nio.*; + +import com.sun.tools.javac.code.Source; +import com.sun.tools.javac.file.JavacFileManager; +import com.sun.tools.javac.util.*; + + +import static com.sun.tools.javac.parser.Token.*; +import static com.sun.tools.javac.util.LayoutCharacters.*; + +/** The lexical analyzer maps an input stream consisting of + * ASCII characters and Unicode escapes into a token sequence. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Scanner implements Lexer { + + private static boolean scannerDebug = false; + + /* Output variables; set by nextToken(): + */ + + /** The token, set by nextToken(). + */ + private Token token; + + /** Allow hex floating-point literals. + */ + private boolean allowHexFloats; + + /** Allow binary literals. + */ + private boolean allowBinaryLiterals; + + /** Allow underscores in literals. + */ + private boolean allowUnderscoresInLiterals; + + /** The source language setting. + */ + private Source source; + + /** The token's position, 0-based offset from beginning of text. + */ + private int pos; + + /** Character position just after the last character of the token. + */ + private int endPos; + + /** The last character position of the previous token. + */ + private int prevEndPos; + + /** The position where a lexical error occurred; + */ + private int errPos = Position.NOPOS; + + /** The name of an identifier or token: + */ + private Name name; + + /** The radix of a numeric literal token. + */ + private int radix; + + /** Has a @deprecated been encountered in last doc comment? + * this needs to be reset by client. + */ + protected boolean deprecatedFlag = false; + + /** A character buffer for literals. + */ + private char[] sbuf = new char[128]; + private int sp; + + /** The input buffer, index of next chacter to be read, + * index of one past last character in buffer. + */ + private char[] buf; + private int bp; + private int buflen; + private int eofPos; + + /** The current character. + */ + private char ch; + + /** The buffer index of the last converted unicode character + */ + private int unicodeConversionBp = -1; + + /** The log to be used for error reporting. + */ + private final Log log; + + /** The name table. */ + private final Names names; + + /** The keyword table. */ + private final Keywords keywords; + + /** Common code for constructors. */ + private Scanner(ScannerFactory fac) { + log = fac.log; + names = fac.names; + keywords = fac.keywords; + source = fac.source; + allowBinaryLiterals = source.allowBinaryLiterals(); + allowHexFloats = source.allowHexFloats(); + allowUnderscoresInLiterals = source.allowUnderscoresInLiterals(); + } + + private static final boolean hexFloatsWork = hexFloatsWork(); + private static boolean hexFloatsWork() { + try { + Float.valueOf("0x1.0p1"); + return true; + } catch (NumberFormatException ex) { + return false; + } + } + + /** Create a scanner from the input buffer. buffer must implement + * array() and compact(), and remaining() must be less than limit(). + */ + protected Scanner(ScannerFactory fac, CharBuffer buffer) { + this(fac, JavacFileManager.toArray(buffer), buffer.limit()); + } + + /** + * Create a scanner from the input array. This method might + * modify the array. To avoid copying the input array, ensure + * that {@code inputLength < input.length} or + * {@code input[input.length -1]} is a white space character. + * + * @param fac the factory which created this Scanner + * @param input the input, might be modified + * @param inputLength the size of the input. + * Must be positive and less than or equal to input.length. + */ + protected Scanner(ScannerFactory fac, char[] input, int inputLength) { + this(fac); + eofPos = inputLength; + if (inputLength == input.length) { + if (input.length > 0 && Character.isWhitespace(input[input.length - 1])) { + inputLength--; + } else { + char[] newInput = new char[inputLength + 1]; + System.arraycopy(input, 0, newInput, 0, input.length); + input = newInput; + } + } + buf = input; + buflen = inputLength; + buf[buflen] = EOI; + bp = -1; + scanChar(); + } + + /** Report an error at the given position using the provided arguments. + */ + private void lexError(int pos, String key, Object... args) { + log.error(pos, key, args); + token = ERROR; + errPos = pos; + } + + /** Report an error at the current token position using the provided + * arguments. + */ + private void lexError(String key, Object... args) { + lexError(pos, key, args); + } + + /** Convert an ASCII digit from its base (8, 10, or 16) + * to its value. + */ + private int digit(int base) { + char c = ch; + int result = Character.digit(c, base); + if (result >= 0 && c > 0x7f) { + lexError(pos+1, "illegal.nonascii.digit"); + ch = "0123456789abcdef".charAt(result); + } + return result; + } + + /** Convert unicode escape; bp points to initial '\' character + * (Spec 3.3). + */ + private void convertUnicode() { + if (ch == '\\' && unicodeConversionBp != bp) { + bp++; ch = buf[bp]; + if (ch == 'u') { + do { + bp++; ch = buf[bp]; + } while (ch == 'u'); + int limit = bp + 3; + if (limit < buflen) { + int d = digit(16); + int code = d; + while (bp < limit && d >= 0) { + bp++; ch = buf[bp]; + d = digit(16); + code = (code << 4) + d; + } + if (d >= 0) { + ch = (char)code; + unicodeConversionBp = bp; + return; + } + } + lexError(bp, "illegal.unicode.esc"); + } else { + bp--; + ch = '\\'; + } + } + } + + /** Read next character. + */ + private void scanChar() { + ch = buf[++bp]; + if (ch == '\\') { + convertUnicode(); + } + } + + /** Read next character in comment, skipping over double '\' characters. + */ + private void scanCommentChar() { + scanChar(); + if (ch == '\\') { + if (buf[bp+1] == '\\' && unicodeConversionBp != bp) { + bp++; + } else { + convertUnicode(); + } + } + } + + /** Append a character to sbuf. + */ + private void putChar(char ch) { + if (sp == sbuf.length) { + char[] newsbuf = new char[sbuf.length * 2]; + System.arraycopy(sbuf, 0, newsbuf, 0, sbuf.length); + sbuf = newsbuf; + } + sbuf[sp++] = ch; + } + + /** Read next character in character or string literal and copy into sbuf. + */ + private void scanLitChar() { + if (ch == '\\') { + if (buf[bp+1] == '\\' && unicodeConversionBp != bp) { + bp++; + putChar('\\'); + scanChar(); + } else { + scanChar(); + switch (ch) { + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + char leadch = ch; + int oct = digit(8); + scanChar(); + if ('0' <= ch && ch <= '7') { + oct = oct * 8 + digit(8); + scanChar(); + if (leadch <= '3' && '0' <= ch && ch <= '7') { + oct = oct * 8 + digit(8); + scanChar(); + } + } + putChar((char)oct); + break; + case 'b': + putChar('\b'); scanChar(); break; + case 't': + putChar('\t'); scanChar(); break; + case 'n': + putChar('\n'); scanChar(); break; + case 'f': + putChar('\f'); scanChar(); break; + case 'r': + putChar('\r'); scanChar(); break; + case '\'': + putChar('\''); scanChar(); break; + case '\"': + putChar('\"'); scanChar(); break; + case '\\': + putChar('\\'); scanChar(); break; + default: + lexError(bp, "illegal.esc.char"); + } + } + } else if (bp != buflen) { + putChar(ch); scanChar(); + } + } + + private void scanDigits(int digitRadix) { + char saveCh; + int savePos; + do { + if (ch != '_') { + putChar(ch); + } else { + if (!allowUnderscoresInLiterals) { + lexError("unsupported.underscore.lit", source.name); + allowUnderscoresInLiterals = true; + } + } + saveCh = ch; + savePos = bp; + scanChar(); + } while (digit(digitRadix) >= 0 || ch == '_'); + if (saveCh == '_') + lexError(savePos, "illegal.underscore"); + } + + /** Read fractional part of hexadecimal floating point number. + */ + private void scanHexExponentAndSuffix() { + if (ch == 'p' || ch == 'P') { + putChar(ch); + scanChar(); + skipIllegalUnderscores(); + if (ch == '+' || ch == '-') { + putChar(ch); + scanChar(); + } + skipIllegalUnderscores(); + if ('0' <= ch && ch <= '9') { + scanDigits(10); + if (!allowHexFloats) { + lexError("unsupported.fp.lit", source.name); + allowHexFloats = true; + } + else if (!hexFloatsWork) + lexError("unsupported.cross.fp.lit"); + } else + lexError("malformed.fp.lit"); + } else { + lexError("malformed.fp.lit"); + } + if (ch == 'f' || ch == 'F') { + putChar(ch); + scanChar(); + token = FLOATLITERAL; + } else { + if (ch == 'd' || ch == 'D') { + putChar(ch); + scanChar(); + } + token = DOUBLELITERAL; + } + } + + /** Read fractional part of floating point number. + */ + private void scanFraction() { + skipIllegalUnderscores(); + if ('0' <= ch && ch <= '9') { + scanDigits(10); + } + int sp1 = sp; + if (ch == 'e' || ch == 'E') { + putChar(ch); + scanChar(); + skipIllegalUnderscores(); + if (ch == '+' || ch == '-') { + putChar(ch); + scanChar(); + } + skipIllegalUnderscores(); + if ('0' <= ch && ch <= '9') { + scanDigits(10); + return; + } + lexError("malformed.fp.lit"); + sp = sp1; + } + } + + /** Read fractional part and 'd' or 'f' suffix of floating point number. + */ + private void scanFractionAndSuffix() { + this.radix = 10; + scanFraction(); + if (ch == 'f' || ch == 'F') { + putChar(ch); + scanChar(); + token = FLOATLITERAL; + } else { + if (ch == 'd' || ch == 'D') { + putChar(ch); + scanChar(); + } + token = DOUBLELITERAL; + } + } + + /** Read fractional part and 'd' or 'f' suffix of floating point number. + */ + private void scanHexFractionAndSuffix(boolean seendigit) { + this.radix = 16; + Assert.check(ch == '.'); + putChar(ch); + scanChar(); + skipIllegalUnderscores(); + if (digit(16) >= 0) { + seendigit = true; + scanDigits(16); + } + if (!seendigit) + lexError("invalid.hex.number"); + else + scanHexExponentAndSuffix(); + } + + private void skipIllegalUnderscores() { + if (ch == '_') { + lexError(bp, "illegal.underscore"); + while (ch == '_') + scanChar(); + } + } + + /** Read a number. + * @param radix The radix of the number; one of 2, j8, 10, 16. + */ + private void scanNumber(int radix) { + this.radix = radix; + // for octal, allow base-10 digit in case it's a float literal + int digitRadix = (radix == 8 ? 10 : radix); + boolean seendigit = false; + if (digit(digitRadix) >= 0) { + seendigit = true; + scanDigits(digitRadix); + } + if (radix == 16 && ch == '.') { + scanHexFractionAndSuffix(seendigit); + } else if (seendigit && radix == 16 && (ch == 'p' || ch == 'P')) { + scanHexExponentAndSuffix(); + } else if (digitRadix == 10 && ch == '.') { + putChar(ch); + scanChar(); + scanFractionAndSuffix(); + } else if (digitRadix == 10 && + (ch == 'e' || ch == 'E' || + ch == 'f' || ch == 'F' || + ch == 'd' || ch == 'D')) { + scanFractionAndSuffix(); + } else { + if (ch == 'l' || ch == 'L') { + scanChar(); + token = LONGLITERAL; + } else { + token = INTLITERAL; + } + } + } + + /** Read an identifier. + */ + private void scanIdent() { + boolean isJavaIdentifierPart; + char high; + do { + if (sp == sbuf.length) putChar(ch); else sbuf[sp++] = ch; + // optimization, was: putChar(ch); + + scanChar(); + switch (ch) { + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'I': case 'J': + case 'K': case 'L': case 'M': case 'N': case 'O': + case 'P': case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': case 'Y': + case 'Z': + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': case 'g': case 'h': case 'i': case 'j': + case 'k': case 'l': case 'm': case 'n': case 'o': + case 'p': case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': case 'y': + case 'z': + case '$': case '_': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case '\u0000': case '\u0001': case '\u0002': case '\u0003': + case '\u0004': case '\u0005': case '\u0006': case '\u0007': + case '\u0008': case '\u000E': case '\u000F': case '\u0010': + case '\u0011': case '\u0012': case '\u0013': case '\u0014': + case '\u0015': case '\u0016': case '\u0017': + case '\u0018': case '\u0019': case '\u001B': + case '\u007F': + break; + case '\u001A': // EOI is also a legal identifier part + if (bp >= buflen) { + name = names.fromChars(sbuf, 0, sp); + token = keywords.key(name); + return; + } + break; + default: + if (ch < '\u0080') { + // all ASCII range chars already handled, above + isJavaIdentifierPart = false; + } else { + high = scanSurrogates(); + if (high != 0) { + if (sp == sbuf.length) { + putChar(high); + } else { + sbuf[sp++] = high; + } + isJavaIdentifierPart = Character.isJavaIdentifierPart( + Character.toCodePoint(high, ch)); + } else { + isJavaIdentifierPart = Character.isJavaIdentifierPart(ch); + } + } + if (!isJavaIdentifierPart) { + name = names.fromChars(sbuf, 0, sp); + token = keywords.key(name); + return; + } + } + } while (true); + } + + /** Are surrogates supported? + */ + final static boolean surrogatesSupported = surrogatesSupported(); + private static boolean surrogatesSupported() { + try { + Character.isHighSurrogate('a'); + return true; + } catch (NoSuchMethodError ex) { + return false; + } + } + + /** Scan surrogate pairs. If 'ch' is a high surrogate and + * the next character is a low surrogate, then put the low + * surrogate in 'ch', and return the high surrogate. + * otherwise, just return 0. + */ + private char scanSurrogates() { + if (surrogatesSupported && Character.isHighSurrogate(ch)) { + char high = ch; + + scanChar(); + + if (Character.isLowSurrogate(ch)) { + return high; + } + + ch = high; + } + + return 0; + } + + /** Return true if ch can be part of an operator. + */ + private boolean isSpecial(char ch) { + switch (ch) { + case '!': case '%': case '&': case '*': case '?': + case '+': case '-': case ':': case '<': case '=': + case '>': case '^': case '|': case '~': + case '@': + return true; + default: + return false; + } + } + + /** Read longest possible sequence of special characters and convert + * to token. + */ + private void scanOperator() { + while (true) { + putChar(ch); + Name newname = names.fromChars(sbuf, 0, sp); + if (keywords.key(newname) == IDENTIFIER) { + sp--; + break; + } + name = newname; + token = keywords.key(newname); + scanChar(); + if (!isSpecial(ch)) break; + } + } + + /** + * Scan a documention comment; determine if a deprecated tag is present. + * Called once the initial /, * have been skipped, positioned at the second * + * (which is treated as the beginning of the first line). + * Stops positioned at the closing '/'. + */ + @SuppressWarnings("fallthrough") + private void scanDocComment() { + boolean deprecatedPrefix = false; + + forEachLine: + while (bp < buflen) { + + // Skip optional WhiteSpace at beginning of line + while (bp < buflen && (ch == ' ' || ch == '\t' || ch == FF)) { + scanCommentChar(); + } + + // Skip optional consecutive Stars + while (bp < buflen && ch == '*') { + scanCommentChar(); + if (ch == '/') { + return; + } + } + + // Skip optional WhiteSpace after Stars + while (bp < buflen && (ch == ' ' || ch == '\t' || ch == FF)) { + scanCommentChar(); + } + + deprecatedPrefix = false; + // At beginning of line in the JavaDoc sense. + if (bp < buflen && ch == '@' && !deprecatedFlag) { + scanCommentChar(); + if (bp < buflen && ch == 'd') { + scanCommentChar(); + if (bp < buflen && ch == 'e') { + scanCommentChar(); + if (bp < buflen && ch == 'p') { + scanCommentChar(); + if (bp < buflen && ch == 'r') { + scanCommentChar(); + if (bp < buflen && ch == 'e') { + scanCommentChar(); + if (bp < buflen && ch == 'c') { + scanCommentChar(); + if (bp < buflen && ch == 'a') { + scanCommentChar(); + if (bp < buflen && ch == 't') { + scanCommentChar(); + if (bp < buflen && ch == 'e') { + scanCommentChar(); + if (bp < buflen && ch == 'd') { + deprecatedPrefix = true; + scanCommentChar(); + }}}}}}}}}}} + if (deprecatedPrefix && bp < buflen) { + if (Character.isWhitespace(ch)) { + deprecatedFlag = true; + } else if (ch == '*') { + scanCommentChar(); + if (ch == '/') { + deprecatedFlag = true; + return; + } + } + } + + // Skip rest of line + while (bp < buflen) { + switch (ch) { + case '*': + scanCommentChar(); + if (ch == '/') { + return; + } + break; + case CR: // (Spec 3.4) + scanCommentChar(); + if (ch != LF) { + continue forEachLine; + } + /* fall through to LF case */ + case LF: // (Spec 3.4) + scanCommentChar(); + continue forEachLine; + default: + scanCommentChar(); + } + } // rest of line + } // forEachLine + return; + } + + /** The value of a literal token, recorded as a string. + * For integers, leading 0x and 'l' suffixes are suppressed. + */ + public String stringVal() { + return new String(sbuf, 0, sp); + } + + /** Read token. + */ + public void nextToken() { + + try { + prevEndPos = endPos; + sp = 0; + + while (true) { + pos = bp; + switch (ch) { + case ' ': // (Spec 3.6) + case '\t': // (Spec 3.6) + case FF: // (Spec 3.6) + do { + scanChar(); + } while (ch == ' ' || ch == '\t' || ch == FF); + endPos = bp; + processWhiteSpace(); + break; + case LF: // (Spec 3.4) + scanChar(); + endPos = bp; + processLineTerminator(); + break; + case CR: // (Spec 3.4) + scanChar(); + if (ch == LF) { + scanChar(); + } + endPos = bp; + processLineTerminator(); + break; + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'I': case 'J': + case 'K': case 'L': case 'M': case 'N': case 'O': + case 'P': case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': case 'Y': + case 'Z': + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': case 'g': case 'h': case 'i': case 'j': + case 'k': case 'l': case 'm': case 'n': case 'o': + case 'p': case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': case 'y': + case 'z': + case '$': case '_': + scanIdent(); + return; + case '0': + scanChar(); + if (ch == 'x' || ch == 'X') { + scanChar(); + skipIllegalUnderscores(); + if (ch == '.') { + scanHexFractionAndSuffix(false); + } else if (digit(16) < 0) { + lexError("invalid.hex.number"); + } else { + scanNumber(16); + } + } else if (ch == 'b' || ch == 'B') { + if (!allowBinaryLiterals) { + lexError("unsupported.binary.lit", source.name); + allowBinaryLiterals = true; + } + scanChar(); + skipIllegalUnderscores(); + if (digit(2) < 0) { + lexError("invalid.binary.number"); + } else { + scanNumber(2); + } + } else { + putChar('0'); + if (ch == '_') { + int savePos = bp; + do { + scanChar(); + } while (ch == '_'); + if (digit(10) < 0) { + lexError(savePos, "illegal.underscore"); + } + } + scanNumber(8); + } + return; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + scanNumber(10); + return; + case '.': + scanChar(); + if ('0' <= ch && ch <= '9') { + putChar('.'); + scanFractionAndSuffix(); + } else if (ch == '.') { + putChar('.'); putChar('.'); + scanChar(); + if (ch == '.') { + scanChar(); + putChar('.'); + token = ELLIPSIS; + } else { + lexError("malformed.fp.lit"); + } + } else { + token = DOT; + } + return; + case ',': + scanChar(); token = COMMA; return; + case ';': + scanChar(); token = SEMI; return; + case '(': + scanChar(); token = LPAREN; return; + case ')': + scanChar(); token = RPAREN; return; + case '[': + scanChar(); token = LBRACKET; return; + case ']': + scanChar(); token = RBRACKET; return; + case '{': + scanChar(); token = LBRACE; return; + case '}': + scanChar(); token = RBRACE; return; + case '/': + scanChar(); + if (ch == '/') { + do { + scanCommentChar(); + } while (ch != CR && ch != LF && bp < buflen); + if (bp < buflen) { + endPos = bp; + processComment(CommentStyle.LINE); + } + break; + } else if (ch == '*') { + scanChar(); + CommentStyle style; + if (ch == '*') { + style = CommentStyle.JAVADOC; + scanDocComment(); + } else { + style = CommentStyle.BLOCK; + while (bp < buflen) { + if (ch == '*') { + scanChar(); + if (ch == '/') break; + } else { + scanCommentChar(); + } + } + } + if (ch == '/') { + scanChar(); + endPos = bp; + processComment(style); + break; + } else { + lexError("unclosed.comment"); + return; + } + } else if (ch == '=') { + name = names.slashequals; + token = SLASHEQ; + scanChar(); + } else { + name = names.slash; + token = SLASH; + } + return; + case '\'': + scanChar(); + if (ch == '\'') { + lexError("empty.char.lit"); + } else { + if (ch == CR || ch == LF) + lexError(pos, "illegal.line.end.in.char.lit"); + scanLitChar(); + if (ch == '\'') { + scanChar(); + token = CHARLITERAL; + } else { + lexError(pos, "unclosed.char.lit"); + } + } + return; + case '\"': + scanChar(); + while (ch != '\"' && ch != CR && ch != LF && bp < buflen) + scanLitChar(); + if (ch == '\"') { + token = STRINGLITERAL; + scanChar(); + } else { + lexError(pos, "unclosed.str.lit"); + } + return; + default: + if (isSpecial(ch)) { + scanOperator(); + } else { + boolean isJavaIdentifierStart; + if (ch < '\u0080') { + // all ASCII range chars already handled, above + isJavaIdentifierStart = false; + } else { + char high = scanSurrogates(); + if (high != 0) { + if (sp == sbuf.length) { + putChar(high); + } else { + sbuf[sp++] = high; + } + + isJavaIdentifierStart = Character.isJavaIdentifierStart( + Character.toCodePoint(high, ch)); + } else { + isJavaIdentifierStart = Character.isJavaIdentifierStart(ch); + } + } + if (isJavaIdentifierStart) { + scanIdent(); + } else if (bp == buflen || ch == EOI && bp+1 == buflen) { // JLS 3.5 + token = EOF; + pos = bp = eofPos; + } else { + lexError("illegal.char", String.valueOf((int)ch)); + scanChar(); + } + } + return; + } + } + } finally { + endPos = bp; + if (scannerDebug) + System.out.println("nextToken(" + pos + + "," + endPos + ")=|" + + new String(getRawCharacters(pos, endPos)) + + "|"); + } + } + + /** Return the current token, set by nextToken(). + */ + public Token token() { + return token; + } + + /** Sets the current token. + */ + public void token(Token token) { + this.token = token; + } + + /** Return the current token's position: a 0-based + * offset from beginning of the raw input stream + * (before unicode translation) + */ + public int pos() { + return pos; + } + + /** Return the last character position of the current token. + */ + public int endPos() { + return endPos; + } + + /** Return the last character position of the previous token. + */ + public int prevEndPos() { + return prevEndPos; + } + + /** Return the position where a lexical error occurred; + */ + public int errPos() { + return errPos; + } + + /** Set the position where a lexical error occurred; + */ + public void errPos(int pos) { + errPos = pos; + } + + /** Return the name of an identifier or token for the current token. + */ + public Name name() { + return name; + } + + /** Return the radix of a numeric literal token. + */ + public int radix() { + return radix; + } + + /** Has a @deprecated been encountered in last doc comment? + * This needs to be reset by client with resetDeprecatedFlag. + */ + public boolean deprecatedFlag() { + return deprecatedFlag; + } + + public void resetDeprecatedFlag() { + deprecatedFlag = false; + } + + /** + * Returns the documentation string of the current token. + */ + public String docComment() { + return null; + } + + /** + * Returns a copy of the input buffer, up to its inputLength. + * Unicode escape sequences are not translated. + */ + public char[] getRawCharacters() { + char[] chars = new char[buflen]; + System.arraycopy(buf, 0, chars, 0, buflen); + return chars; + } + + /** + * Returns a copy of a character array subset of the input buffer. + * The returned array begins at the beginIndex and + * extends to the character at index endIndex - 1. + * Thus the length of the substring is endIndex-beginIndex. + * This behavior is like + * String.substring(beginIndex, endIndex). + * Unicode escape sequences are not translated. + * + * @param beginIndex the beginning index, inclusive. + * @param endIndex the ending index, exclusive. + * @throws IndexOutOfBounds if either offset is outside of the + * array bounds + */ + public char[] getRawCharacters(int beginIndex, int endIndex) { + int length = endIndex - beginIndex; + char[] chars = new char[length]; + System.arraycopy(buf, beginIndex, chars, 0, length); + return chars; + } + + public enum CommentStyle { + LINE, + BLOCK, + JAVADOC, + } + + /** + * Called when a complete comment has been scanned. pos and endPos + * will mark the comment boundary. + */ + protected void processComment(CommentStyle style) { + if (scannerDebug) + System.out.println("processComment(" + pos + + "," + endPos + "," + style + ")=|" + + new String(getRawCharacters(pos, endPos)) + + "|"); + } + + /** + * Called when a complete whitespace run has been scanned. pos and endPos + * will mark the whitespace boundary. + */ + protected void processWhiteSpace() { + if (scannerDebug) + System.out.println("processWhitespace(" + pos + + "," + endPos + ")=|" + + new String(getRawCharacters(pos, endPos)) + + "|"); + } + + /** + * Called when a line terminator has been processed. + */ + protected void processLineTerminator() { + if (scannerDebug) + System.out.println("processTerminator(" + pos + + "," + endPos + ")=|" + + new String(getRawCharacters(pos, endPos)) + + "|"); + } + + /** Build a map for translating between line numbers and + * positions in the input. + * + * @return a LineMap */ + public Position.LineMap getLineMap() { + return Position.makeLineMap(buf, buflen, false); + } + +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/parser/ScannerFactory.java b/douyu-javac/src/main/java/com/sun/tools/javac/parser/ScannerFactory.java new file mode 100644 index 0000000..86c9bb2 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/parser/ScannerFactory.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.parser; + +import java.nio.CharBuffer; + +import com.sun.tools.javac.code.Source; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Log; +import com.sun.tools.javac.util.Names; + + +/** + * A factory for creating scanners. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own + * risk. This code and its internal interfaces are subject to change + * or deletion without notice. + */ +public class ScannerFactory { + /** The context key for the scanner factory. */ + public static final Context.Key scannerFactoryKey = + new Context.Key(); + + /** Get the Factory instance for this context. */ + public static ScannerFactory instance(Context context) { + ScannerFactory instance = context.get(scannerFactoryKey); + if (instance == null) + instance = new ScannerFactory(context); + return instance; + } + + final Log log; + final Names names; + final Source source; + final Keywords keywords; + + /** Create a new scanner factory. */ + protected ScannerFactory(Context context) { + context.put(scannerFactoryKey, this); + this.log = Log.instance(context); + this.names = Names.instance(context); + this.source = Source.instance(context); + this.keywords = Keywords.instance(context); + } + + public Scanner newScanner(CharSequence input, boolean keepDocComments) { + if (input instanceof CharBuffer) { + CharBuffer buf = (CharBuffer) input; + if (keepDocComments) + return new DocCommentScanner(this, buf); + else + return new Scanner(this, buf); + } else { + char[] array = input.toString().toCharArray(); + return newScanner(array, array.length, keepDocComments); + } + } + + public Scanner newScanner(char[] input, int inputLength, boolean keepDocComments) { + if (keepDocComments) + return new DocCommentScanner(this, input, inputLength); + else + return new Scanner(this, input, inputLength); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/parser/Token.java b/douyu-javac/src/main/java/com/sun/tools/javac/parser/Token.java new file mode 100644 index 0000000..8fec6f8 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/parser/Token.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 1999, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.parser; + +import java.util.Locale; + +import com.sun.tools.javac.api.Formattable; +import com.sun.tools.javac.api.Messages; + +/** An interface that defines codes for Java source tokens + * returned from lexical analysis. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public enum Token implements Formattable { + EOF, + ERROR, + IDENTIFIER, + ABSTRACT("abstract"), + ASSERT("assert"), + BOOLEAN("boolean"), + BREAK("break"), + BYTE("byte"), + CASE("case"), + CATCH("catch"), + CHAR("char"), + CLASS("class"), + CONST("const"), + CONTINUE("continue"), + DEFAULT("default"), + DO("do"), + DOUBLE("double"), + ELSE("else"), + ENUM("enum"), + EXTENDS("extends"), + FINAL("final"), + FINALLY("finally"), + FLOAT("float"), + FOR("for"), + GOTO("goto"), + IF("if"), + IMPLEMENTS("implements"), + IMPORT("import"), + INSTANCEOF("instanceof"), + INT("int"), + INTERFACE("interface"), + LONG("long"), + NATIVE("native"), + NEW("new"), + PACKAGE("package"), + PRIVATE("private"), + PROTECTED("protected"), + PUBLIC("public"), + RETURN("return"), + SHORT("short"), + STATIC("static"), + STRICTFP("strictfp"), + SUPER("super"), + SWITCH("switch"), + SYNCHRONIZED("synchronized"), + THIS("this"), + THROW("throw"), + THROWS("throws"), + TRANSIENT("transient"), + TRY("try"), + VOID("void"), + VOLATILE("volatile"), + WHILE("while"), + INTLITERAL, + LONGLITERAL, + FLOATLITERAL, + DOUBLELITERAL, + CHARLITERAL, + STRINGLITERAL, + TRUE("true"), + FALSE("false"), + NULL("null"), + LPAREN("("), + RPAREN(")"), + LBRACE("{"), + RBRACE("}"), + LBRACKET("["), + RBRACKET("]"), + SEMI(";"), + COMMA(","), + DOT("."), + ELLIPSIS("..."), + EQ("="), + GT(">"), + LT("<"), + BANG("!"), + TILDE("~"), + QUES("?"), + COLON(":"), + EQEQ("=="), + LTEQ("<="), + GTEQ(">="), + BANGEQ("!="), + AMPAMP("&&"), + BARBAR("||"), + PLUSPLUS("++"), + SUBSUB("--"), + PLUS("+"), + SUB("-"), + STAR("*"), + SLASH("/"), + AMP("&"), + BAR("|"), + CARET("^"), + PERCENT("%"), + LTLT("<<"), + GTGT(">>"), + GTGTGT(">>>"), + PLUSEQ("+="), + SUBEQ("-="), + STAREQ("*="), + SLASHEQ("/="), + AMPEQ("&="), + BAREQ("|="), + CARETEQ("^="), + PERCENTEQ("%="), + LTLTEQ("<<="), + GTGTEQ(">>="), + GTGTGTEQ(">>>="), + MONKEYS_AT("@"), + CUSTOM; + + Token() { + this(null); + } + Token(String name) { + this.name = name; + } + + public final String name; + + public String toString() { + switch (this) { + case IDENTIFIER: + return "token.identifier"; + case CHARLITERAL: + return "token.character"; + case STRINGLITERAL: + return "token.string"; + case INTLITERAL: + return "token.integer"; + case LONGLITERAL: + return "token.long-integer"; + case FLOATLITERAL: + return "token.float"; + case DOUBLELITERAL: + return "token.double"; + case ERROR: + return "token.bad-symbol"; + case EOF: + return "token.end-of-input"; + case DOT: case COMMA: case SEMI: case LPAREN: case RPAREN: + case LBRACKET: case RBRACKET: case LBRACE: case RBRACE: + return "'" + name + "'"; + default: + return name; + } + } + + public String getKind() { + return "Token"; + } + + public String toString(Locale locale, Messages messages) { + return name != null ? toString() : messages.getLocalizedString(locale, "compiler.misc." + toString()); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/processing/ActionParameterType.java b/douyu-javac/src/main/java/com/sun/tools/javac/processing/ActionParameterType.java new file mode 100644 index 0000000..967441b --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/processing/ActionParameterType.java @@ -0,0 +1,63 @@ +//package com.sun.tools.javac.processing; +// +//import java.util.HashMap; +//import java.util.Map; +// +//import javax.lang.model.element.VariableElement; +// +//public abstract class ActionParameterType { +// public abstract String genCode(String name); +// +// public static Map parameterTypes = new HashMap(); +// static { +// parameterTypes.put("douyu.mvc.Context", new ContextType()); +// +// RequestType requestType = new RequestType(); +// parameterTypes.put("javax.servlet.ServletRequest", requestType); +// parameterTypes.put("javax.servlet.http.HttpServletRequest", requestType); +// +// ResponseType responseType = new ResponseType(); +// parameterTypes.put("javax.servlet.ServletResponse", responseType); +// parameterTypes.put("javax.servlet.http.HttpServletResponse", responseType); +// +// StringType stringType = new StringType(); +// parameterTypes.put("java.lang.String", stringType); +// parameterTypes.put("String", stringType); +// } +// +// public static String parse(VariableElement var) { +// String type = var.asType().toString(); +// ActionParameterType apt = parameterTypes.get(type); +// String name = var.getSimpleName().toString(); +// return apt.genCode(name); +// } +// +// public static class ContextType extends ActionParameterType { +// @Override +// public String genCode(String name) { +// return "this"; +// } +// } +// +// public static class RequestType extends ActionParameterType { +// @Override +// public String genCode(String name) { +// return "this"; +// } +// } +// +// public static class ResponseType extends ActionParameterType { +// @Override +// public String genCode(String name) { +// return "this"; +// } +// } +// +// public static class StringType extends ActionParameterType { +// @Override +// public String genCode(String name) { +// return "request.getParameter(\"" + name + "\")"; +// } +// } +// +//} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/processing/AnnotationProcessingError.java b/douyu-javac/src/main/java/com/sun/tools/javac/processing/AnnotationProcessingError.java new file mode 100644 index 0000000..9382038 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/processing/AnnotationProcessingError.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.processing; + + +/** + * Error thrown for problems encountered during annotation processing. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class AnnotationProcessingError extends Error { + static final long serialVersionUID = 305337707019230790L; + AnnotationProcessingError(Throwable cause) { + super(cause); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/processing/ControllerProcessor.java b/douyu-javac/src/main/java/com/sun/tools/javac/processing/ControllerProcessor.java new file mode 100644 index 0000000..bce6c55 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/processing/ControllerProcessor.java @@ -0,0 +1,613 @@ +package com.sun.tools.javac.processing; + +import java.io.IOException; +import java.io.Writer; +import java.util.List; +import java.util.Set; +import java.util.Stack; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Filer; +import javax.annotation.processing.Messager; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.NestingKind; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.SimpleElementVisitor7; +import javax.lang.model.SourceVersion; +import javax.tools.JavaFileObject; + +import douyu.http.HttpMethod; +import douyu.mvc.Action; +import douyu.mvc.Async; +import douyu.mvc.Controller; +import douyu.mvc.Model; + +/** + * + * @author ZHH + * + */ + +// 如果用这个,则在java1.6中会出错 +// 见com.sun.tools.javac.processing.JavacProcessingEnvironment.ProcessorState.checkSourceVersionCompatibility +// Override掉超类的getSupportedSourceVersion()更合适一些 +// @SupportedSourceVersion(SourceVersion.RELEASE_7) +@SupportedAnnotationTypes("douyu.mvc.Controller") +// @SupportedAnnotationTypes("*") +public class ControllerProcessor extends AbstractProcessor { + + private static String SUFFIX = "$DOUYU"; + private static String CONTROLLER_VAR_NAME = "_c"; + + private static int varCount = 1; + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + + @Override + public boolean process(Set tes, RoundEnvironment renv) { + // JavacProcessingEnvironment处理到最后一个Round时,我们就不再管它了。 + if (tes == null || tes.size() == 0) + return false; + + // renv.getRootElements()返回的Element并不是都有@Controller的 + // 按理说用@SupportedAnnotationTypes指明我的Processor只处理哪些注解,javac就应该返回带有@Controller注解的Element, + // 但是实际情况并不是这样,所以这里要再判断一下。 + for (Element element : renv.getRootElements()) { + ElementKind kind = element.getKind(); + if (kind != ElementKind.CLASS) // 有可能是package-info.java中指定的PackageElement(也就是PackageSymbol) + continue; + // 只处理最顶层的类 + NestingKind nestingKind = ((TypeElement) element).getNestingKind(); + if (nestingKind != NestingKind.TOP_LEVEL) + continue; + + Controller controller = element.getAnnotation(Controller.class); + if (controller == null) + continue; + + varCount = 1; // 在开发阶段,如果不断更改源文件,会不断重新编译,这个值如果不清零,会一直加 + + String className = ((TypeElement) element).getQualifiedName().toString() + SUFFIX; + new ControllerElementVisitor(controller, processingEnv).visit(element).writeTo(className); + } + + return true; + } + + private static class ControllerElementVisitor extends SimpleElementVisitor7 { + private final ProcessingEnvironment processingEnv; + private final Elements elementUtils; + private final Messager messager; + private final Filer filer; + private final Controller controller; + private final PrettyPrinter p = new PrettyPrinter(); + + private final PrettyPrinter p_isAsyncAction = new PrettyPrinter(2); + + boolean isAsyncController; + + public ControllerElementVisitor(Controller controller, ProcessingEnvironment processingEnv) { + this.processingEnv = processingEnv; + this.elementUtils = processingEnv.getElementUtils(); + this.messager = processingEnv.getMessager(); + this.filer = processingEnv.getFiler(); + this.controller = controller; + } + + public void writeTo(String className) { + try { + JavaFileObject jfo = filer.createSourceFile(className, (Element[]) null); + Writer w = jfo.openWriter(); + w.write(p.toString()); + // 在com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing中会调用filer.warnIfUnclosedFiles() + // 所以必需要close,否则JavacProcessingEnvironment会出错 + w.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public ControllerElementVisitor visitType(TypeElement e, Void v) { + // 带有@Controller的类必需是public的 + if (!e.getModifiers().contains(Modifier.PUBLIC)) { + messager.printMessage(javax.tools.Diagnostic.Kind.ERROR, "class with @Controller must be 'public'", e); + return this; + } + + isAsyncController = e.getAnnotation(Async.class) != null; + + List enclosedElements = e.getEnclosedElements(); + + // 带有@Controller的类必需有一个public的默认构造函数 + boolean find = false; + for (Element element : enclosedElements) { + if (element.getKind() == ElementKind.CONSTRUCTOR && element.getModifiers().contains(Modifier.PUBLIC) + && ((ExecutableElement) element).getParameters().size() == 0) { + find = true; + break; + } + } + + if (!find) { + messager.printMessage(javax.tools.Diagnostic.Kind.ERROR, + "class with @Controller must contain a 'public' 0-parameter constructor", e); + return this; + } + + PackageElement pkg = elementUtils.getPackageOf(e); + if (!pkg.isUnnamed()) { + p.println("package " + pkg.getQualifiedName() + ";"); + p.println(); + } + + String simpleName = e.getSimpleName().toString(); + p.print("public class ").append(simpleName).append(SUFFIX).append( + " extends org.douyu.mvc.DouyuContext {"); + p.println(); + + p.tab++; + p.print("private static ").append(simpleName).append(" ").append(CONTROLLER_VAR_NAME).append(" = new ") + .append(simpleName).append("();"); + p.println(); + p.println(); + + p.println("protected void executeAction() throws Exception {"); + p.tab++; + + p.println("String p;"); + p.print("if(actionName == null) actionName = \"").append(controller.defaultAction()).append("\";"); + p.println(); + p.println(); + + for (Element element : enclosedElements) { + if (element.getKind() == ElementKind.METHOD) + this.visit(element); + } + + p.println("else {"); + p.tab++; + p.println("response.sendError(404, request.getRequestURI());"); + p.tab--; + p.println("}"); + + p.tab--; + p.println("}"); + + if(isAsyncController) { + p.println("public boolean isAsyncAction(String actionName) {return true;}"); + } else { + String str = p_isAsyncAction.toString(); + if(str.length()>0) { + p.println("public boolean isAsyncAction(String actionName) {"); + p.tab++; + p.print("if(actionName == null) actionName = \"").append(controller.defaultAction()).append("\";"); + p.println(); + p.printNoAlign(str); + p.println(); + p.println("return false;"); + p.tab--; + p.println("}"); + } + } + p.tab--; + p.println("}"); + return this; + } + + private boolean isFirstAction = true; + + @Override + public ControllerElementVisitor visitExecutable(ExecutableElement e, Void v) { + ElementKind kind = e.getKind(); + if (kind != ElementKind.METHOD) + return this; + + Set modifiers = e.getModifiers(); + if (!modifiers.contains(Modifier.PUBLIC) || modifiers.contains(Modifier.STATIC)) + return this; + + HttpMethod[] httpMethods = null; + + boolean isAsyncAction = !isAsyncController && e.getAnnotation(Async.class) != null; + if(isAsyncAction) { + p_isAsyncAction.print("if(actionName.equals(\"").append(e.getSimpleName()).append("\")) return true;"); + p_isAsyncAction.println(); + } + + Action action = e.getAnnotation(Action.class); + if (action != null) { + httpMethods = action.httpMethods(); + } + if (httpMethods == null || httpMethods.length == 0) { + httpMethods = controller.httpMethods(); + } + StringBuilder methods = new StringBuilder("checkHttpMethods("); + String sep = ""; + if (httpMethods != null && httpMethods.length > 0) { + for (HttpMethod hm : httpMethods) { + methods.append(sep).append("\"").append(hm.name()).append("\""); + sep = ","; + } + } + + methods.append(");"); + if (isFirstAction) { + p.print("if(actionName.equals(\"").append(e.getSimpleName()).append("\")) {"); + isFirstAction = false; + } else { + p.print("else if(actionName.equals(\"").append(e.getSimpleName()).append("\")) {"); + } + p.println(); + p.tab++; + p.println(methods); + + printMethodParameters(p, CONTROLLER_VAR_NAME, e, processingEnv, null); + + p.tab--; + p.println("}"); + return this; + } + + @Override + public ControllerElementVisitor visitVariable(VariableElement e, Void v) { + return this; + } + + @Override + public ControllerElementVisitor visitTypeParameter(TypeParameterElement e, Void v) { + return this; + } + + @Override + public ControllerElementVisitor visitPackage(PackageElement e, Void v) { + return this; + } + + @Override + protected ControllerElementVisitor defaultAction(Element e, Void v) { + return this; + } + } + + private static class ModelElementVisitor extends SimpleElementVisitor7 { + private final ProcessingEnvironment processingEnv; + private final Messager messager; + private final PrettyPrinter p; + private final String prefixModelVarName; + private final String invokerModelVarName; + + public ModelElementVisitor(PrettyPrinter p, String prefixModelVarName, String invokerModelVarName, + ProcessingEnvironment processingEnv) { + this.processingEnv = processingEnv; + this.messager = processingEnv.getMessager(); + this.p = p; + this.prefixModelVarName = prefixModelVarName; + this.invokerModelVarName = invokerModelVarName; + } + + @Override + public ModelElementVisitor visitType(TypeElement e, Void v) { + // 带有@Model的类必需是public的 + if (!e.getModifiers().contains(Modifier.PUBLIC)) { + messager.printMessage(javax.tools.Diagnostic.Kind.ERROR, "class with @Model must be 'public'", e); + return this; + } + + List enclosedElements = e.getEnclosedElements(); + + // 带有@Model的类必需有一个public的默认构造函数 + boolean find = false; + for (Element element : enclosedElements) { + if (element.getKind() == ElementKind.CONSTRUCTOR && element.getModifiers().contains(Modifier.PUBLIC) + && ((ExecutableElement) element).getParameters().size() == 0) { + find = true; + break; + } + } + + if (!find) { + messager.printMessage(javax.tools.Diagnostic.Kind.ERROR, + "class with @Model must contain a 'public' 0-parameter constructor", e); + return this; + } + + String qualifiedName = e.getQualifiedName().toString(); + p.print(qualifiedName).append(" ").append(invokerModelVarName).append(" = new ").append(qualifiedName) + .append("();"); + p.println(); + + for (Element element : enclosedElements) { + if (element.getKind() == ElementKind.METHOD) { + ExecutableElement method = (ExecutableElement) element; + Set modifiers = method.getModifiers(); + if (!modifiers.contains(Modifier.PUBLIC) || modifiers.contains(Modifier.STATIC)) + continue; + + if (method.getReturnType().getKind() != TypeKind.VOID) + continue; + + if (method.getParameters().size() == 0) + continue; + + if (!method.getSimpleName().toString().startsWith("set")) + continue; + + printMethodParameters(p, invokerModelVarName, method, processingEnv, prefixModelVarName); + } + } + return this; + } + + @Override + public ModelElementVisitor visitExecutable(ExecutableElement e, Void v) { + return this; + } + + @Override + public ModelElementVisitor visitVariable(VariableElement e, Void v) { + return this; + } + + @Override + public ModelElementVisitor visitTypeParameter(TypeParameterElement e, Void v) { + return this; + } + + @Override + public ModelElementVisitor visitPackage(PackageElement e, Void v) { + return this; + } + + @Override + protected ModelElementVisitor defaultAction(Element e, Void v) { + return this; + } + } + + // TODO 是否考虑重构,做成ActionParameterType类那样吗? + // 难点是下面每个if语句并没有统一规律,不好生成字符串。 + private static void printMethodParameters(PrettyPrinter p, String invoker, ExecutableElement e, + ProcessingEnvironment processingEnv, String varPrefix) { + List vars = e.getParameters(); + if (vars == null || vars.size() == 0) { + p.print(invoker).append(".").append(e.getSimpleName()).append("();"); + p.println(); + } else { + String sep = ""; + StringBuilder s = new StringBuilder(); + + TypeMirror varType; + String type; + String name; + + for (VariableElement var : vars) { + varType = var.asType(); + type = varType.toString(); + name = var.getSimpleName().toString(); + if (varPrefix != null) + name = varPrefix + "." + name; + + if ("douyu.mvc.Context".equals(type) || "douyu.mvc.ModelManager".equals(type) + || "douyu.mvc.ViewManager".equals(type) || "douyu.mvc.ControllerManager".equals(type)) { + s.append(sep).append("this"); + } else if ("douyu.http.HttpRequest".equals(type)) { + s.append(sep).append("request"); + } else if ("douyu.http.HttpResponse".equals(type)) { + s.append(sep).append("response"); + } else if ("java.io.PrintWriter".equals(type) || "java.io.Writer".equals(type)) { + s.append(sep).append("response.getWriter()"); + } else if ("java.lang.String".equals(type)) { + s.append(sep).append("request.getParameter(\""); + s.append(name).append("\")"); + } else if ("int".equals(type)) { + p.print("int ").append("v").append(varCount).append(" = 0;"); + p.println(); + p.print("p = request.getParameter(\"").append(name).append("\");"); + p.println(); + + p.print("if(p != null) v").append(varCount).append(" = Integer.parseInt(p);"); + p.println(); + p.println(); + + s.append(sep).append("v").append(varCount); + varCount++; + } else if ("long".equals(type)) { + p.print("long ").append("v").append(varCount).append(" = 0L;"); + p.println(); + p.print("p = request.getParameter(\"").append(name).append("\");"); + p.println(); + + p.print("if(p != null) v").append(varCount).append(" = Long.parseLong(p);"); + p.println(); + p.println(); + + s.append(sep).append("v").append(varCount); + varCount++; + } else if ("float".equals(type)) { + p.print("float ").append("v").append(varCount).append(" = 0.0F;"); + p.println(); + p.print("p = request.getParameter(\"").append(name).append("\");"); + p.println(); + + p.print("if(p != null) v").append(varCount).append(" = Float.parseFloat(p);"); + p.println(); + p.println(); + + s.append(sep).append("v").append(varCount); + varCount++; + } else if ("double".equals(type)) { + p.print("double ").append("v").append(varCount).append(" = 0.0D;"); + p.println(); + p.print("p = request.getParameter(\"").append(name).append("\");"); + p.println(); + + p.print("if(p != null) v").append(varCount).append(" = Double.parseDouble(p);"); + p.println(); + p.println(); + + s.append(sep).append("v").append(varCount); + varCount++; + } else if ("boolean".equals(type)) { + p.print("boolean ").append("v").append(varCount).append(" = false;"); + p.println(); + p.print("p = request.getParameter(\"").append(name).append("\");"); + p.println(); + + p.print("if(p != null) v").append(varCount).append(" = Boolean.parseBoolean(p);"); + p.println(); + p.println(); + + s.append(sep).append("v").append(varCount); + varCount++; + } else if ("byte".equals(type)) { + p.print("byte ").append("v").append(varCount).append(" = 0;"); + p.println(); + p.print("p = request.getParameter(\"").append(name).append("\");"); + p.println(); + + p.print("if(p != null) v").append(varCount).append(" = Byte.parseByte(p);"); + p.println(); + p.println(); + + s.append(sep).append("v").append(varCount); + varCount++; + } else if ("short".equals(type)) { + p.print("short ").append("v").append(varCount).append(" = 0;"); + p.println(); + p.print("p = request.getParameter(\"").append(name).append("\");"); + p.println(); + + p.print("if(p != null) v").append(varCount).append(" = Short.parseShort(p);"); + p.println(); + p.println(); + + s.append(sep).append("v").append(varCount); + varCount++; + } else if ("char".equals(type)) { + p.print("char ").append("v").append(varCount).append(" = 0;"); + p.println(); + + p.print("p = request.getParameter(\"").append(name).append("\");"); + p.println(); + + p.print("if(p != null && p.length() > 0) v").append(varCount).append(" = p.charAt(0);"); + p.println(); + p.println(); + + s.append(sep).append("v").append(varCount); + varCount++; + } else if ("java.lang.Integer".equals(type) || "java.lang.Long".equals(type) + || "java.lang.Float".equals(type) || "java.lang.Double".equals(type) + || "java.lang.Boolean".equals(type) || "java.lang.Byte".equals(type) + || "java.lang.Short".equals(type)) { + + type = type.substring(type.lastIndexOf('.') + 1); + + p.print(type).append(" v").append(varCount).append(" = null;"); + p.println(); + + p.print("p = request.getParameter(\"").append(name).append("\");"); + p.println(); + + p.print("if(p != null) v").append(varCount).append(" = ").append(type).append(".valueOf(p);"); + p.println(); + p.println(); + + s.append(sep).append("v").append(varCount); + varCount++; + } else if ("java.lang.Character".equals(type)) { + p.print("Character v").append(varCount).append(" = null;"); + p.println(); + + p.print("p = request.getParameter(\"").append(name).append("\");"); + p.println(); + + p.print("if(p != null && p.length() > 0) v").append(varCount).append(" = p.charAt(0);"); + p.println(); + p.println(); + + s.append(sep).append("v").append(varCount); + varCount++; + } else if (varType.getKind() == TypeKind.ARRAY + && "java.lang.String".equals(((ArrayType) varType).getComponentType().toString())) { + + s.append(sep).append("request.getParameterValues(\"").append(name).append("\")"); + + } else if ("douyu.http.UploadedFile".equals(type)) { + s.append(sep).append("request.getUploadedFile(\"").append(name).append("\")"); + + } else if (varType.getKind() == TypeKind.ARRAY + && "douyu.http.UploadedFile".equals(((ArrayType) varType).getComponentType().toString())) { + + s.append(sep).append("request.getUploadedFiles()"); + } else { + // 只注入带有@Model的类,其他的都注入null + if (varType.getKind() == TypeKind.DECLARED) { + Element element = ((DeclaredType) varType).asElement(); + + if (element.getAnnotation(Model.class) != null) { + String invokerModelVarName; + + // 防止循环setXXX + // 如A.setXXX(B b), B.setYYY(A a) + // 出现这种情况时,B直接用A的引用 + int index = modelElementStack.indexOf(element); + if (index != -1) { + invokerModelVarName = modelVarNameStack.get(index); + } else { + invokerModelVarName = "v" + varCount; + varCount++; + + modelElementStack.push(element); + modelVarNameStack.push(invokerModelVarName); + + try { + new ModelElementVisitor(p, name, invokerModelVarName, processingEnv).visit(element); + } finally { + modelElementStack.pop(); + modelVarNameStack.pop(); + } + } + s.append(sep).append(invokerModelVarName); + } else { + s.append(sep).append("null"); + } + // TODO 支持Model数组 + // } else if (varType.getKind() == TypeKind.ARRAY + // && ((ArrayType) varType).getComponentType().getKind() + // == TypeKind.DECLARED) { + // Element element = ((DeclaredType) ((ArrayType) + // varType).getComponentType()).asElement(); + } else { + s.append(sep).append("null"); + } + } + + sep = ","; + } + p.print(invoker).append(".").append(e.getSimpleName()).append("(").append(s).append(");"); + p.println(); + } + } + + private static Stack modelElementStack = new Stack(); + private static Stack modelVarNameStack = new Stack(); +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/processing/JavacFiler.java b/douyu-javac/src/main/java/com/sun/tools/javac/processing/JavacFiler.java new file mode 100644 index 0000000..1f712e2 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/processing/JavacFiler.java @@ -0,0 +1,627 @@ +/* + * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.processing; + +import java.io.Closeable; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.FilterOutputStream; +import java.io.Reader; +import java.io.Writer; +import java.io.FilterWriter; +import java.io.PrintWriter; +import java.io.IOException; +import java.util.*; + +import static java.util.Collections.*; + +import javax.annotation.processing.*; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.NestingKind; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Element; +import javax.tools.*; +import javax.tools.JavaFileManager.Location; + +import static javax.tools.StandardLocation.SOURCE_OUTPUT; +import static javax.tools.StandardLocation.CLASS_OUTPUT; + +import com.sun.tools.javac.code.Lint; +import com.sun.tools.javac.util.*; + +import static com.sun.tools.javac.code.Lint.LintCategory.PROCESSING; + +/** + * The FilerImplementation class must maintain a number of + * constraints. First, multiple attempts to open the same path within + * the same invocation of the tool results in an IOException being + * thrown. For example, trying to open the same source file twice: + * + *

+ * createSourceFile("foo.Bar")
+ * ...
+ * createSourceFile("foo.Bar")
+ * 
+ * + * is disallowed as is opening a text file that happens to have + * the same name as a source file: + * + *
+ * createSourceFile("foo.Bar")
+ * ...
+ * createTextFile(SOURCE_TREE, "foo", new File("Bar"), null)
+ * 
+ * + *

Additionally, creating a source file that corresponds to an + * already created class file (or vice versa) also results in an + * IOException since each type can only be created once. However, if + * the Filer is used to create a text file named *.java that happens + * to correspond to an existing class file, a warning is *not* + * generated. Similarly, a warning is not generated for a binary file + * named *.class and an existing source file. + * + *

The reason for this difference is that source files and class + * files are registered with the tool and can get passed on as + * declarations to the next round of processing. Files that are just + * named *.java and *.class are not processed in that manner; although + * having extra source files and class files on the source path and + * class path can alter the behavior of the tool and any final + * compile. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class JavacFiler implements Filer, Closeable { + // TODO: Implement different transaction model for updating the + // Filer's record keeping on file close. + + private static final String ALREADY_OPENED = + "Output stream or writer has already been opened."; + private static final String NOT_FOR_READING = + "FileObject was not opened for reading."; + private static final String NOT_FOR_WRITING = + "FileObject was not opened for writing."; + + /** + * Wrap a JavaFileObject to manage writing by the Filer. + */ + private class FilerOutputFileObject extends ForwardingFileObject { + private boolean opened = false; + private String name; + + FilerOutputFileObject(String name, FileObject fileObject) { + super(fileObject); + this.name = name; + } + + @Override + public synchronized OutputStream openOutputStream() throws IOException { + if (opened) + throw new IOException(ALREADY_OPENED); + opened = true; + return new FilerOutputStream(name, fileObject); + } + + @Override + public synchronized Writer openWriter() throws IOException { + if (opened) + throw new IOException(ALREADY_OPENED); + opened = true; + return new FilerWriter(name, fileObject); + } + + // Three anti-literacy methods + @Override + public InputStream openInputStream() throws IOException { + throw new IllegalStateException(NOT_FOR_READING); + } + + @Override + public Reader openReader(boolean ignoreEncodingErrors) throws IOException { + throw new IllegalStateException(NOT_FOR_READING); + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + throw new IllegalStateException(NOT_FOR_READING); + } + + @Override + public boolean delete() { + return false; + } + } + + private class FilerOutputJavaFileObject extends FilerOutputFileObject implements JavaFileObject { + private final JavaFileObject javaFileObject; + FilerOutputJavaFileObject(String name, JavaFileObject javaFileObject) { + super(name, javaFileObject); + this.javaFileObject = javaFileObject; + } + + public JavaFileObject.Kind getKind() { + return javaFileObject.getKind(); + } + + public boolean isNameCompatible(String simpleName, + JavaFileObject.Kind kind) { + return javaFileObject.isNameCompatible(simpleName, kind); + } + + public NestingKind getNestingKind() { + return javaFileObject.getNestingKind(); + } + + public Modifier getAccessLevel() { + return javaFileObject.getAccessLevel(); + } + } + + /** + * Wrap a JavaFileObject to manage reading by the Filer. + */ + private class FilerInputFileObject extends ForwardingFileObject { + FilerInputFileObject(FileObject fileObject) { + super(fileObject); + } + + @Override + public OutputStream openOutputStream() throws IOException { + throw new IllegalStateException(NOT_FOR_WRITING); + } + + @Override + public Writer openWriter() throws IOException { + throw new IllegalStateException(NOT_FOR_WRITING); + } + + @Override + public boolean delete() { + return false; + } + } + + private class FilerInputJavaFileObject extends FilerInputFileObject implements JavaFileObject { + private final JavaFileObject javaFileObject; + FilerInputJavaFileObject(JavaFileObject javaFileObject) { + super(javaFileObject); + this.javaFileObject = javaFileObject; + } + + public JavaFileObject.Kind getKind() { + return javaFileObject.getKind(); + } + + public boolean isNameCompatible(String simpleName, + JavaFileObject.Kind kind) { + return javaFileObject.isNameCompatible(simpleName, kind); + } + + public NestingKind getNestingKind() { + return javaFileObject.getNestingKind(); + } + + public Modifier getAccessLevel() { + return javaFileObject.getAccessLevel(); + } + } + + + /** + * Wrap a {@code OutputStream} returned from the {@code + * JavaFileManager} to properly register source or class files + * when they are closed. + */ + private class FilerOutputStream extends FilterOutputStream { + String typeName; + FileObject fileObject; + boolean closed = false; + + /** + * @param typeName name of class or {@code null} if just a + * binary file + */ + FilerOutputStream(String typeName, FileObject fileObject) throws IOException { + super(fileObject.openOutputStream()); + this.typeName = typeName; + this.fileObject = fileObject; + } + + public synchronized void close() throws IOException { + if (!closed) { + closed = true; + /* + * If an IOException occurs when closing the underlying + * stream, still try to process the file. + */ + + closeFileObject(typeName, fileObject); + out.close(); + } + } + } + + /** + * Wrap a {@code Writer} returned from the {@code JavaFileManager} + * to properly register source or class files when they are + * closed. + */ + private class FilerWriter extends FilterWriter { + String typeName; + FileObject fileObject; + boolean closed = false; + + /** + * @param fileObject the fileObject to be written to + * @param typeName name of source file or {@code null} if just a + * text file + */ + FilerWriter(String typeName, FileObject fileObject) throws IOException { + super(fileObject.openWriter()); + this.typeName = typeName; + this.fileObject = fileObject; + } + + public synchronized void close() throws IOException { + if (!closed) { + closed = true; + /* + * If an IOException occurs when closing the underlying + * Writer, still try to process the file. + */ + + closeFileObject(typeName, fileObject); + out.close(); + } + } + } + + JavaFileManager fileManager; + Log log; + Context context; + boolean lastRound; + + private final boolean lint; + + /** + * Logical names of all created files. This set must be + * synchronized. + */ + private final Set fileObjectHistory; + + /** + * Names of types that have had files created but not closed. + */ + private final Set openTypeNames; + + /** + * Names of source files closed in this round. This set must be + * synchronized. Its iterators should preserve insertion order. + */ + private Set generatedSourceNames; + + /** + * Names and class files of the class files closed in this round. + * This set must be synchronized. Its iterators should preserve + * insertion order. + */ + private final Map generatedClasses; + + /** + * JavaFileObjects for source files closed in this round. This + * set must be synchronized. Its iterators should preserve + * insertion order. + */ + private Set generatedSourceFileObjects; + + /** + * Names of all created source files. Its iterators should + * preserve insertion order. + */ + private final Set aggregateGeneratedSourceNames; + + /** + * Names of all created class files. Its iterators should + * preserve insertion order. + */ + private final Set aggregateGeneratedClassNames; + + + JavacFiler(Context context) { + this.context = context; + fileManager = context.get(JavaFileManager.class); + + log = Log.instance(context); + + fileObjectHistory = synchronizedSet(new LinkedHashSet()); + generatedSourceNames = synchronizedSet(new LinkedHashSet()); + generatedSourceFileObjects = synchronizedSet(new LinkedHashSet()); + + generatedClasses = synchronizedMap(new LinkedHashMap()); + + openTypeNames = synchronizedSet(new LinkedHashSet()); + + aggregateGeneratedSourceNames = new LinkedHashSet(); + aggregateGeneratedClassNames = new LinkedHashSet(); + + lint = (Lint.instance(context)).isEnabled(PROCESSING); + } + + public JavaFileObject createSourceFile(CharSequence name, + Element... originatingElements) throws IOException { + return createSourceOrClassFile(true, name.toString()); + } + + public JavaFileObject createClassFile(CharSequence name, + Element... originatingElements) throws IOException { + return createSourceOrClassFile(false, name.toString()); + } + + private JavaFileObject createSourceOrClassFile(boolean isSourceFile, String name) throws IOException { + if (lint) { + int periodIndex = name.lastIndexOf("."); + if (periodIndex != -1) { + String base = name.substring(periodIndex); + String extn = (isSourceFile ? ".java" : ".class"); + if (base.equals(extn)) + log.warning("proc.suspicious.class.name", name, extn); + } + } + checkNameAndExistence(name, isSourceFile); + Location loc = (isSourceFile ? SOURCE_OUTPUT : CLASS_OUTPUT); + JavaFileObject.Kind kind = (isSourceFile ? + JavaFileObject.Kind.SOURCE : + JavaFileObject.Kind.CLASS); + + JavaFileObject fileObject = + fileManager.getJavaFileForOutput(loc, name, kind, null); + checkFileReopening(fileObject, true); + + if (lastRound) + log.warning("proc.file.create.last.round", name); + + if (isSourceFile) + aggregateGeneratedSourceNames.add(name); + else + aggregateGeneratedClassNames.add(name); + openTypeNames.add(name); + + return new FilerOutputJavaFileObject(name, fileObject); + } + + public FileObject createResource(JavaFileManager.Location location, + CharSequence pkg, + CharSequence relativeName, + Element... originatingElements) throws IOException { + locationCheck(location); + + String strPkg = pkg.toString(); + if (strPkg.length() > 0) + checkName(strPkg); + + FileObject fileObject = + fileManager.getFileForOutput(location, strPkg, + relativeName.toString(), null); + checkFileReopening(fileObject, true); + + if (fileObject instanceof JavaFileObject) + return new FilerOutputJavaFileObject(null, (JavaFileObject)fileObject); + else + return new FilerOutputFileObject(null, fileObject); + } + + private void locationCheck(JavaFileManager.Location location) { + if (location instanceof StandardLocation) { + StandardLocation stdLoc = (StandardLocation) location; + if (!stdLoc.isOutputLocation()) + throw new IllegalArgumentException("Resource creation not supported in location " + + stdLoc); + } + } + + public FileObject getResource(JavaFileManager.Location location, + CharSequence pkg, + CharSequence relativeName) throws IOException { + String strPkg = pkg.toString(); + if (strPkg.length() > 0) + checkName(strPkg); + + // TODO: Only support reading resources in selected output + // locations? Only allow reading of non-source, non-class + // files from the supported input locations? + FileObject fileObject = fileManager.getFileForInput(location, + pkg.toString(), + relativeName.toString()); + if (fileObject == null) { + String name = (pkg.length() == 0) + ? relativeName.toString() : (pkg + "/" + relativeName); + throw new FileNotFoundException(name); + } + + // If the path was already opened for writing, throw an exception. + checkFileReopening(fileObject, false); + return new FilerInputFileObject(fileObject); + } + + private void checkName(String name) throws FilerException { + checkName(name, false); + } + + private void checkName(String name, boolean allowUnnamedPackageInfo) throws FilerException { + if (!SourceVersion.isName(name) && !isPackageInfo(name, allowUnnamedPackageInfo)) { + if (lint) + log.warning("proc.illegal.file.name", name); + throw new FilerException("Illegal name " + name); + } + } + + private boolean isPackageInfo(String name, boolean allowUnnamedPackageInfo) { + // Is the name of the form "package-info" or + // "foo.bar.package-info"? + final String PKG_INFO = "package-info"; + int periodIndex = name.lastIndexOf("."); + if (periodIndex == -1) { + return allowUnnamedPackageInfo ? name.equals(PKG_INFO) : false; + } else { + // "foo.bar.package-info." illegal + String prefix = name.substring(0, periodIndex); + String simple = name.substring(periodIndex+1); + return SourceVersion.isName(prefix) && simple.equals(PKG_INFO); + } + } + + private void checkNameAndExistence(String typename, boolean allowUnnamedPackageInfo) throws FilerException { + // TODO: Check if type already exists on source or class path? + // If so, use warning message key proc.type.already.exists + checkName(typename, allowUnnamedPackageInfo); + if (aggregateGeneratedSourceNames.contains(typename) || + aggregateGeneratedClassNames.contains(typename)) { + if (lint) + log.warning("proc.type.recreate", typename); + throw new FilerException("Attempt to recreate a file for type " + typename); + } + } + + /** + * Check to see if the file has already been opened; if so, throw + * an exception, otherwise add it to the set of files. + */ + private void checkFileReopening(FileObject fileObject, boolean addToHistory) throws FilerException { + for(FileObject veteran : fileObjectHistory) { + if (fileManager.isSameFile(veteran, fileObject)) { + if (lint) + log.warning("proc.file.reopening", fileObject.getName()); + throw new FilerException("Attempt to reopen a file for path " + fileObject.getName()); + } + } + if (addToHistory) + fileObjectHistory.add(fileObject); + } + + public boolean newFiles() { + return (!generatedSourceNames.isEmpty()) + || (!generatedClasses.isEmpty()); + } + + public Set getGeneratedSourceNames() { + return generatedSourceNames; + } + + public Set getGeneratedSourceFileObjects() { + return generatedSourceFileObjects; + } + + public Map getGeneratedClasses() { + return generatedClasses; + } + + public void warnIfUnclosedFiles() { + if (!openTypeNames.isEmpty()) + log.warning("proc.unclosed.type.files", openTypeNames.toString()); + } + + /** + * Update internal state for a new round. + */ + public void newRound(Context context) { + this.context = context; + this.log = Log.instance(context); + clearRoundState(); + } + + void setLastRound(boolean lastRound) { + this.lastRound = lastRound; + } + + public void close() { + clearRoundState(); + // Cross-round state + fileObjectHistory.clear(); + openTypeNames.clear(); + aggregateGeneratedSourceNames.clear(); + aggregateGeneratedClassNames.clear(); + } + + private void clearRoundState() { + generatedSourceNames.clear(); + generatedSourceFileObjects.clear(); + generatedClasses.clear(); + } + + /** + * Debugging function to display internal state. + */ + public void displayState() { + PrintWriter xout = context.get(Log.outKey); + xout.println("File Object History : " + fileObjectHistory); + xout.println("Open Type Names : " + openTypeNames); + xout.println("Gen. Src Names : " + generatedSourceNames); + xout.println("Gen. Cls Names : " + generatedClasses.keySet()); + xout.println("Agg. Gen. Src Names : " + aggregateGeneratedSourceNames); + xout.println("Agg. Gen. Cls Names : " + aggregateGeneratedClassNames); + } + + public String toString() { + return "javac Filer"; + } + + /** + * Upon close, register files opened by create{Source, Class}File + * for annotation processing. + */ + private void closeFileObject(String typeName, FileObject fileObject) { + /* + * If typeName is non-null, the file object was opened as a + * source or class file by the user. If a file was opened as + * a resource, typeName will be null and the file is *not* + * subject to annotation processing. + */ + if ((typeName != null)) { + if (!(fileObject instanceof JavaFileObject)) + throw new AssertionError("JavaFileOject not found for " + fileObject); + JavaFileObject javaFileObject = (JavaFileObject)fileObject; + switch(javaFileObject.getKind()) { + case SOURCE: + generatedSourceNames.add(typeName); + generatedSourceFileObjects.add(javaFileObject); + openTypeNames.remove(typeName); + break; + + case CLASS: + generatedClasses.put(typeName, javaFileObject); + openTypeNames.remove(typeName); + break; + + default: + break; + } + } + } + +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/processing/JavacMessager.java b/douyu-javac/src/main/java/com/sun/tools/javac/processing/JavacMessager.java new file mode 100644 index 0000000..eeeb095 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/processing/JavacMessager.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.processing; + +import com.sun.tools.javac.model.JavacElements; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.*; +import javax.lang.model.element.*; +import javax.tools.JavaFileObject; +import javax.tools.Diagnostic; +import javax.annotation.processing.*; + +/** + * An implementation of the Messager built on top of log. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class JavacMessager implements Messager { + Log log; + JavacProcessingEnvironment processingEnv; + int errorCount = 0; + int warningCount = 0; + + JavacMessager(Context context, JavacProcessingEnvironment processingEnv) { + log = Log.instance(context); + this.processingEnv = processingEnv; + } + + // processingEnv.getElementUtils() + + public void printMessage(Diagnostic.Kind kind, CharSequence msg) { + printMessage(kind, msg, null, null, null); + } + + public void printMessage(Diagnostic.Kind kind, CharSequence msg, + Element e) { + printMessage(kind, msg, e, null, null); + } + + /** + * Prints a message of the specified kind at the location of the + * annotation mirror of the annotated element. + * + * @param kind the kind of message + * @param msg the message, or an empty string if none + * @param e the annotated element + * @param a the annotation to use as a position hint + */ + public void printMessage(Diagnostic.Kind kind, CharSequence msg, + Element e, AnnotationMirror a) { + printMessage(kind, msg, e, a, null); + } + + /** + * Prints a message of the specified kind at the location of the + * annotation value inside the annotation mirror of the annotated + * element. + * + * @param kind the kind of message + * @param msg the message, or an empty string if none + * @param e the annotated element + * @param a the annotation containing the annotaiton value + * @param v the annotation value to use as a position hint + */ + public void printMessage(Diagnostic.Kind kind, CharSequence msg, + Element e, AnnotationMirror a, AnnotationValue v) { + JavaFileObject oldSource = null; + JavaFileObject newSource = null; + JCDiagnostic.DiagnosticPosition pos = null; + JavacElements elemUtils = processingEnv.getElementUtils(); + Pair treeTop = elemUtils.getTreeAndTopLevel(e, a, v); + if (treeTop != null) { + newSource = treeTop.snd.sourcefile; + if (newSource != null) { + oldSource = log.useSource(newSource); + pos = treeTop.fst.pos(); + } + } + try { + switch (kind) { + case ERROR: + errorCount++; + boolean prev = log.multipleErrors; + log.multipleErrors = true; + try { + log.error(pos, "proc.messager", msg.toString()); + } finally { + log.multipleErrors = prev; + } + break; + + case WARNING: + warningCount++; + log.warning(pos, "proc.messager", msg.toString()); + break; + + case MANDATORY_WARNING: + warningCount++; + log.mandatoryWarning(pos, "proc.messager", msg.toString()); + break; + + default: + log.note(pos, "proc.messager", msg.toString()); + break; + } + } finally { + if (oldSource != null) + log.useSource(oldSource); + } + } + + /** + * Prints an error message. + * Equivalent to {@code printError(null, msg)}. + * @param msg the message, or an empty string if none + */ + public void printError(String msg) { + printMessage(Diagnostic.Kind.ERROR, msg); + } + + /** + * Prints a warning message. + * Equivalent to {@code printWarning(null, msg)}. + * @param msg the message, or an empty string if none + */ + public void printWarning(String msg) { + printMessage(Diagnostic.Kind.WARNING, msg); + } + + /** + * Prints a notice. + * @param msg the message, or an empty string if none + */ + public void printNotice(String msg) { + printMessage(Diagnostic.Kind.NOTE, msg); + } + + public boolean errorRaised() { + return errorCount > 0; + } + + public int errorCount() { + return errorCount; + } + + public int warningCount() { + return warningCount; + } + + public void newRound(Context context) { + log = Log.instance(context); + errorCount = 0; + } + + public String toString() { + return "javac Messager"; + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/processing/JavacProcessingEnvironment.java b/douyu-javac/src/main/java/com/sun/tools/javac/processing/JavacProcessingEnvironment.java new file mode 100644 index 0000000..00ad623 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/processing/JavacProcessingEnvironment.java @@ -0,0 +1,1530 @@ +/* + * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.processing; + +import java.lang.reflect.*; +import java.util.*; +import java.util.regex.*; + +import java.net.URL; +import java.io.Closeable; +import java.io.File; +import java.io.PrintWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.net.MalformedURLException; + +import javax.annotation.processing.*; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.PackageElement; +import javax.lang.model.util.*; +import javax.tools.JavaFileManager; +import javax.tools.StandardJavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.DiagnosticListener; + +import com.sun.source.util.TaskEvent; +import com.sun.source.util.TaskListener; +import com.sun.tools.javac.api.JavacTaskImpl; +import com.sun.tools.javac.api.JavacTrees; +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.file.FSInfo; +import com.sun.tools.javac.file.JavacFileManager; +import com.sun.tools.javac.jvm.*; +import com.sun.tools.javac.jvm.ClassReader.BadClassFile; +import com.sun.tools.javac.main.JavaCompiler; +import com.sun.tools.javac.main.JavaCompiler.CompileState; +import com.sun.tools.javac.model.JavacElements; +import com.sun.tools.javac.model.JavacTypes; +import com.sun.tools.javac.parser.*; +import com.sun.tools.javac.tree.*; +import com.sun.tools.javac.tree.JCTree.*; +import com.sun.tools.javac.util.Abort; +import com.sun.tools.javac.util.Assert; +import com.sun.tools.javac.util.ClientCodeException; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Convert; +import com.sun.tools.javac.util.FatalError; +import com.sun.tools.javac.util.JCDiagnostic; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Log; +import com.sun.tools.javac.util.JavacMessages; +import com.sun.tools.javac.util.Name; +import com.sun.tools.javac.util.Names; +import com.sun.tools.javac.util.Options; + +import static javax.tools.StandardLocation.*; +import static com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag.*; +import static com.sun.tools.javac.main.OptionName.*; +import static com.sun.tools.javac.code.Lint.LintCategory.PROCESSING; + +/** + * Objects of this class hold and manage the state needed to support + * annotation processing. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class JavacProcessingEnvironment implements ProcessingEnvironment, Closeable { + Options options; + + private final boolean printProcessorInfo; + private final boolean printRounds; + private final boolean verbose; + private final boolean lint; + private final boolean procOnly; + private final boolean fatalErrors; + private final boolean werror; + private final boolean showResolveErrors; + private boolean foundTypeProcessors; + + private final JavacFiler filer; + private final JavacMessager messager; + private final JavacElements elementUtils; + private final JavacTypes typeUtils; + + /** + * Holds relevant state history of which processors have been + * used. + */ + private DiscoveredProcessors discoveredProcs; + + /** + * Map of processor-specific options. + */ + private final Map processorOptions; + + /** + */ + private final Set unmatchedProcessorOptions; + + /** + * Annotations implicitly processed and claimed by javac. + */ + private final Set platformAnnotations; + + /** + * Set of packages given on command line. + */ + private Set specifiedPackages = Collections.emptySet(); + + /** The log to be used for error reporting. + */ + Log log; + + /** Diagnostic factory. + */ + JCDiagnostic.Factory diags; + + /** + * Source level of the compile. + */ + Source source; + + private ClassLoader processorClassLoader; + + /** + * JavacMessages object used for localization + */ + private JavacMessages messages; + + private Context context; + + public JavacProcessingEnvironment(Context context, Iterable processors) { + this.context = context; + log = Log.instance(context); + source = Source.instance(context); + diags = JCDiagnostic.Factory.instance(context); + options = Options.instance(context); + printProcessorInfo = options.isSet(XPRINTPROCESSORINFO); + printRounds = options.isSet(XPRINTROUNDS); + verbose = options.isSet(VERBOSE); + lint = Lint.instance(context).isEnabled(PROCESSING); + procOnly = options.isSet(PROC, "only") || options.isSet(XPRINT); + fatalErrors = options.isSet("fatalEnterError"); + showResolveErrors = options.isSet("showResolveErrors"); + werror = options.isSet(WERROR); + platformAnnotations = initPlatformAnnotations(); + foundTypeProcessors = false; + + // Initialize services before any processors are initialized + // in case processors use them. + filer = new JavacFiler(context); + messager = new JavacMessager(context, this); + elementUtils = JavacElements.instance(context); + typeUtils = JavacTypes.instance(context); + processorOptions = initProcessorOptions(context); + unmatchedProcessorOptions = initUnmatchedProcessorOptions(); + messages = JavacMessages.instance(context); + initProcessorIterator(context, processors); + } + + private Set initPlatformAnnotations() { + Set platformAnnotations = new HashSet(); + platformAnnotations.add("java.lang.Deprecated"); + platformAnnotations.add("java.lang.Override"); + platformAnnotations.add("java.lang.SuppressWarnings"); + platformAnnotations.add("java.lang.annotation.Documented"); + platformAnnotations.add("java.lang.annotation.Inherited"); + platformAnnotations.add("java.lang.annotation.Retention"); + platformAnnotations.add("java.lang.annotation.Target"); + return Collections.unmodifiableSet(platformAnnotations); + } + + private void initProcessorIterator(Context context, Iterable processors) { + Log log = Log.instance(context); + Iterator processorIterator; + + if (options.isSet(XPRINT)) { + try { + Processor processor = PrintingProcessor.class.newInstance(); + processorIterator = List.of(processor).iterator(); + } catch (Throwable t) { + AssertionError assertError = + new AssertionError("Problem instantiating PrintingProcessor."); + assertError.initCause(t); + throw assertError; + } + } else if (processors != null) { + processorIterator = processors.iterator(); + } else { + String processorNames = options.get(PROCESSOR); + JavaFileManager fileManager = context.get(JavaFileManager.class); + try { + // If processorpath is not explicitly set, use the classpath. + processorClassLoader = fileManager.hasLocation(ANNOTATION_PROCESSOR_PATH) + ? fileManager.getClassLoader(ANNOTATION_PROCESSOR_PATH) + : fileManager.getClassLoader(CLASS_PATH); + + /* + * If the "-processor" option is used, search the appropriate + * path for the named class. Otherwise, use a service + * provider mechanism to create the processor iterator. + */ + if (processorNames != null) { + processorIterator = new NameProcessIterator(processorNames, processorClassLoader, log); + } else { + processorIterator = new ServiceIterator(processorClassLoader, log); + } + } catch (SecurityException e) { + /* + * A security exception will occur if we can't create a classloader. + * Ignore the exception if, with hindsight, we didn't need it anyway + * (i.e. no processor was specified either explicitly, or implicitly, + * in service configuration file.) Otherwise, we cannot continue. + */ + processorIterator = handleServiceLoaderUnavailability("proc.cant.create.loader", e); + } + } + discoveredProcs = new DiscoveredProcessors(processorIterator); + } + + /** + * Returns an empty processor iterator if no processors are on the + * relevant path, otherwise if processors are present, logs an + * error. Called when a service loader is unavailable for some + * reason, either because a service loader class cannot be found + * or because a security policy prevents class loaders from being + * created. + * + * @param key The resource key to use to log an error message + * @param e If non-null, pass this exception to Abort + */ + private Iterator handleServiceLoaderUnavailability(String key, Exception e) { + JavaFileManager fileManager = context.get(JavaFileManager.class); + + if (fileManager instanceof JavacFileManager) { + StandardJavaFileManager standardFileManager = (JavacFileManager) fileManager; + Iterable workingPath = fileManager.hasLocation(ANNOTATION_PROCESSOR_PATH) + ? standardFileManager.getLocation(ANNOTATION_PROCESSOR_PATH) + : standardFileManager.getLocation(CLASS_PATH); + + if (needClassLoader(options.get(PROCESSOR), workingPath) ) + handleException(key, e); + + } else { + handleException(key, e); + } + + java.util.List pl = Collections.emptyList(); + return pl.iterator(); + } + + /** + * Handle a security exception thrown during initializing the + * Processor iterator. + */ + private void handleException(String key, Exception e) { + if (e != null) { + log.error(key, e.getLocalizedMessage()); + throw new Abort(e); + } else { + log.error(key); + throw new Abort(); + } + } + + /** + * Use a service loader appropriate for the platform to provide an + * iterator over annotations processors. If + * java.util.ServiceLoader is present use it, otherwise, use + * sun.misc.Service, otherwise fail if a loader is needed. + */ + private class ServiceIterator implements Iterator { + // The to-be-wrapped iterator. + private Iterator iterator; + private Log log; + private Class loaderClass; + private boolean jusl; + private Object loader; + + ServiceIterator(ClassLoader classLoader, Log log) { + String loadMethodName; + + this.log = log; + try { + try { + loaderClass = Class.forName("java.util.ServiceLoader"); + loadMethodName = "load"; + jusl = true; + } catch (ClassNotFoundException cnfe) { + try { + loaderClass = Class.forName("sun.misc.Service"); + loadMethodName = "providers"; + jusl = false; + } catch (ClassNotFoundException cnfe2) { + // Fail softly if a loader is not actually needed. + this.iterator = handleServiceLoaderUnavailability("proc.no.service", + null); + return; + } + } + + // java.util.ServiceLoader.load or sun.misc.Service.providers + Method loadMethod = loaderClass.getMethod(loadMethodName, + Class.class, + ClassLoader.class); + + Object result = loadMethod.invoke(null, + Processor.class, + classLoader); + + // For java.util.ServiceLoader, we have to call another + // method to get the iterator. + if (jusl) { + loader = result; // Store ServiceLoader to call reload later + Method m = loaderClass.getMethod("iterator"); + result = m.invoke(result); // serviceLoader.iterator(); + } + + // The result should now be an iterator. + this.iterator = (Iterator) result; + } catch (Throwable t) { + log.error("proc.service.problem"); + throw new Abort(t); + } + } + + public boolean hasNext() { + try { + return iterator.hasNext(); + } catch (Throwable t) { + if ("ServiceConfigurationError". + equals(t.getClass().getSimpleName())) { + log.error("proc.bad.config.file", t.getLocalizedMessage()); + } + throw new Abort(t); + } + } + + public Processor next() { + try { + return (Processor)(iterator.next()); + } catch (Throwable t) { + if ("ServiceConfigurationError". + equals(t.getClass().getSimpleName())) { + log.error("proc.bad.config.file", t.getLocalizedMessage()); + } else { + log.error("proc.processor.constructor.error", t.getLocalizedMessage()); + } + throw new Abort(t); + } + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public void close() { + if (jusl) { + try { + // Call java.util.ServiceLoader.reload + Method reloadMethod = loaderClass.getMethod("reload"); + reloadMethod.invoke(loader); + } catch(Exception e) { + ; // Ignore problems during a call to reload. + } + } + } + } + + + private static class NameProcessIterator implements Iterator { + Processor nextProc = null; + Iterator names; + ClassLoader processorCL; + Log log; + + NameProcessIterator(String names, ClassLoader processorCL, Log log) { + this.names = Arrays.asList(names.split(",")).iterator(); + this.processorCL = processorCL; + this.log = log; + } + + public boolean hasNext() { + if (nextProc != null) + return true; + else { + if (!names.hasNext()) + return false; + else { + String processorName = names.next(); + + Processor processor; + try { + try { + processor = + (Processor) (processorCL.loadClass(processorName).newInstance()); + } catch (ClassNotFoundException cnfe) { + log.error("proc.processor.not.found", processorName); + return false; + } catch (ClassCastException cce) { + log.error("proc.processor.wrong.type", processorName); + return false; + } catch (Exception e ) { + log.error("proc.processor.cant.instantiate", processorName); + return false; + } + } catch(ClientCodeException e) { + throw e; + } catch(Throwable t) { + throw new AnnotationProcessingError(t); + } + nextProc = processor; + return true; + } + + } + } + + public Processor next() { + if (hasNext()) { + Processor p = nextProc; + nextProc = null; + return p; + } else + throw new NoSuchElementException(); + } + + public void remove () { + throw new UnsupportedOperationException(); + } + } + + public boolean atLeastOneProcessor() { + return discoveredProcs.iterator().hasNext(); + } + + private Map initProcessorOptions(Context context) { + Options options = Options.instance(context); + Set keySet = options.keySet(); + Map tempOptions = new LinkedHashMap(); + + for(String key : keySet) { + if (key.startsWith("-A") && key.length() > 2) { + int sepIndex = key.indexOf('='); + String candidateKey = null; + String candidateValue = null; + + if (sepIndex == -1) + candidateKey = key.substring(2); + else if (sepIndex >= 3) { + candidateKey = key.substring(2, sepIndex); + candidateValue = (sepIndex < key.length()-1)? + key.substring(sepIndex+1) : null; + } + tempOptions.put(candidateKey, candidateValue); + } + } + + return Collections.unmodifiableMap(tempOptions); + } + + private Set initUnmatchedProcessorOptions() { + Set unmatchedProcessorOptions = new HashSet(); + unmatchedProcessorOptions.addAll(processorOptions.keySet()); + return unmatchedProcessorOptions; + } + + /** + * State about how a processor has been used by the tool. If a + * processor has been used on a prior round, its process method is + * called on all subsequent rounds, perhaps with an empty set of + * annotations to process. The {@code annotatedSupported} method + * caches the supported annotation information from the first (and + * only) getSupportedAnnotationTypes call to the processor. + */ + static class ProcessorState { + public Processor processor; + public boolean contributed; + private ArrayList supportedAnnotationPatterns; + private ArrayList supportedOptionNames; + + ProcessorState(Processor p, Log log, Source source, ProcessingEnvironment env) { + processor = p; + contributed = false; + + try { + processor.init(env); + + checkSourceVersionCompatibility(source, log); + + supportedAnnotationPatterns = new ArrayList(); + for (String importString : processor.getSupportedAnnotationTypes()) { + supportedAnnotationPatterns.add(importStringToPattern(importString, + processor, + log)); + } + + supportedOptionNames = new ArrayList(); + for (String optionName : processor.getSupportedOptions() ) { + if (checkOptionName(optionName, log)) + supportedOptionNames.add(optionName); + } + + } catch (ClientCodeException e) { + throw e; + } catch (Throwable t) { + throw new AnnotationProcessingError(t); + } + } + + /** + * Checks whether or not a processor's source version is + * compatible with the compilation source version. The + * processor's source version needs to be greater than or + * equal to the source version of the compile. + */ + private void checkSourceVersionCompatibility(Source source, Log log) { + SourceVersion procSourceVersion = processor.getSupportedSourceVersion(); + + if (procSourceVersion.compareTo(Source.toSourceVersion(source)) < 0 ) { + log.warning("proc.processor.incompatible.source.version", + procSourceVersion, + processor.getClass().getName(), + source.name); + } + } + + private boolean checkOptionName(String optionName, Log log) { + boolean valid = isValidOptionName(optionName); + if (!valid) + log.error("proc.processor.bad.option.name", + optionName, + processor.getClass().getName()); + return valid; + } + + public boolean annotationSupported(String annotationName) { + for(Pattern p: supportedAnnotationPatterns) { + if (p.matcher(annotationName).matches()) + return true; + } + return false; + } + + /** + * Remove options that are matched by this processor. + */ + public void removeSupportedOptions(Set unmatchedProcessorOptions) { + unmatchedProcessorOptions.removeAll(supportedOptionNames); + } + } + + // TODO: These two classes can probably be rewritten better... + /** + * This class holds information about the processors that have + * been discoverd so far as well as the means to discover more, if + * necessary. A single iterator should be used per round of + * annotation processing. The iterator first visits already + * discovered processors then fails over to the service provider + * mechanism if additional queries are made. + */ + class DiscoveredProcessors implements Iterable { + + class ProcessorStateIterator implements Iterator { + DiscoveredProcessors psi; + Iterator innerIter; + boolean onProcInterator; + + ProcessorStateIterator(DiscoveredProcessors psi) { + this.psi = psi; + this.innerIter = psi.procStateList.iterator(); + this.onProcInterator = false; + } + + public ProcessorState next() { + if (!onProcInterator) { + if (innerIter.hasNext()) + return innerIter.next(); + else + onProcInterator = true; + } + + if (psi.processorIterator.hasNext()) { + ProcessorState ps = new ProcessorState(psi.processorIterator.next(), + log, source, JavacProcessingEnvironment.this); + psi.procStateList.add(ps); + return ps; + } else + throw new NoSuchElementException(); + } + + public boolean hasNext() { + if (onProcInterator) + return psi.processorIterator.hasNext(); + else + return innerIter.hasNext() || psi.processorIterator.hasNext(); + } + + public void remove () { + throw new UnsupportedOperationException(); + } + + /** + * Run all remaining processors on the procStateList that + * have not already run this round with an empty set of + * annotations. + */ + public void runContributingProcs(RoundEnvironment re) { + if (!onProcInterator) { + Set emptyTypeElements = Collections.emptySet(); + while(innerIter.hasNext()) { + ProcessorState ps = innerIter.next(); + if (ps.contributed) + callProcessor(ps.processor, emptyTypeElements, re); + } + } + } + } + + Iterator processorIterator; + ArrayList procStateList; + + public ProcessorStateIterator iterator() { + return new ProcessorStateIterator(this); + } + + DiscoveredProcessors(Iterator processorIterator) { + this.processorIterator = processorIterator; + this.procStateList = new ArrayList(); + } + + /** + * Free jar files, etc. if using a service loader. + */ + public void close() { + if (processorIterator != null && + processorIterator instanceof ServiceIterator) { + ((ServiceIterator) processorIterator).close(); + } + } + } + + private void discoverAndRunProcs(Context context, + Set annotationsPresent, + List topLevelClasses, + List packageInfoFiles) { + Map unmatchedAnnotations = + new HashMap(annotationsPresent.size()); + + for(TypeElement a : annotationsPresent) { + unmatchedAnnotations.put(a.getQualifiedName().toString(), + a); + } + + // Give "*" processors a chance to match + if (unmatchedAnnotations.size() == 0) + unmatchedAnnotations.put("", null); + + DiscoveredProcessors.ProcessorStateIterator psi = discoveredProcs.iterator(); + // TODO: Create proper argument values; need past round + // information to fill in this constructor. Note that the 1 + // st round of processing could be the last round if there + // were parse errors on the initial source files; however, we + // are not doing processing in that case. + + Set rootElements = new LinkedHashSet(); + rootElements.addAll(topLevelClasses); + rootElements.addAll(packageInfoFiles); + rootElements = Collections.unmodifiableSet(rootElements); + + RoundEnvironment renv = new JavacRoundEnvironment(false, + false, + rootElements, + JavacProcessingEnvironment.this); + + while(unmatchedAnnotations.size() > 0 && psi.hasNext() ) { + ProcessorState ps = psi.next(); + Set matchedNames = new HashSet(); + Set typeElements = new LinkedHashSet(); + + for (Map.Entry entry: unmatchedAnnotations.entrySet()) { + String unmatchedAnnotationName = entry.getKey(); + if (ps.annotationSupported(unmatchedAnnotationName) ) { + matchedNames.add(unmatchedAnnotationName); + TypeElement te = entry.getValue(); + if (te != null) + typeElements.add(te); + } + } + + if (matchedNames.size() > 0 || ps.contributed) { + boolean processingResult = callProcessor(ps.processor, typeElements, renv); + ps.contributed = true; + ps.removeSupportedOptions(unmatchedProcessorOptions); + + if (printProcessorInfo || verbose) { + log.printNoteLines("x.print.processor.info", + ps.processor.getClass().getName(), + matchedNames.toString(), + processingResult); + } + + if (processingResult) { + unmatchedAnnotations.keySet().removeAll(matchedNames); + } + + } + } + unmatchedAnnotations.remove(""); + + if (lint && unmatchedAnnotations.size() > 0) { + // Remove annotations processed by javac + unmatchedAnnotations.keySet().removeAll(platformAnnotations); + if (unmatchedAnnotations.size() > 0) { + log = Log.instance(context); + log.warning("proc.annotations.without.processors", + unmatchedAnnotations.keySet()); + } + } + + // Run contributing processors that haven't run yet + psi.runContributingProcs(renv); + + // Debugging + if (options.isSet("displayFilerState")) + filer.displayState(); + } + + /** + * Computes the set of annotations on the symbol in question. + * Leave class public for external testing purposes. + */ + public static class ComputeAnnotationSet extends + ElementScanner7, Set> { + final Elements elements; + + public ComputeAnnotationSet(Elements elements) { + super(); + this.elements = elements; + } + + @Override + public Set visitPackage(PackageElement e, Set p) { + // Don't scan enclosed elements of a package + return p; + } + + @Override + public Set scan(Element e, Set p) { + for (AnnotationMirror annotationMirror : + elements.getAllAnnotationMirrors(e) ) { + Element e2 = annotationMirror.getAnnotationType().asElement(); + p.add((TypeElement) e2); + } + return super.scan(e, p); + } + } + + private boolean callProcessor(Processor proc, + Set tes, + RoundEnvironment renv) { + try { + return proc.process(tes, renv); + } catch (BadClassFile ex) { + log.error("proc.cant.access.1", ex.sym, ex.getDetailValue()); + return false; + } catch (CompletionFailure ex) { + StringWriter out = new StringWriter(); + ex.printStackTrace(new PrintWriter(out)); + log.error("proc.cant.access", ex.sym, ex.getDetailValue(), out.toString()); + return false; + } catch (ClientCodeException e) { + throw e; + } catch (Throwable t) { + throw new AnnotationProcessingError(t); + } + } + + /** + * Helper object for a single round of annotation processing. + */ + class Round { + /** The round number. */ + final int number; + /** The context for the round. */ + final Context context; + /** The compiler for the round. */ + final JavaCompiler compiler; + /** The log for the round. */ + final Log log; + + /** The ASTs to be compiled. */ + List roots; + /** The classes to be compiler that have were generated. */ + Map genClassFiles; + + /** The set of annotations to be processed this round. */ + Set annotationsPresent; + /** The set of top level classes to be processed this round. */ + List topLevelClasses; + /** The set of package-info files to be processed this round. */ + List packageInfoFiles; + + /** The number of Messager errors generated in this round. */ + int nMessagerErrors; + + /** Create a round (common code). */ + private Round(Context context, int number, int priorErrors, int priorWarnings) { + this.context = context; + this.number = number; + + compiler = JavaCompiler.instance(context); + log = Log.instance(context); + log.nerrors = priorErrors; + log.nwarnings += priorWarnings; + log.deferDiagnostics = true; + + // the following is for the benefit of JavacProcessingEnvironment.getContext() + JavacProcessingEnvironment.this.context = context; + + // the following will be populated as needed + topLevelClasses = List.nil(); + packageInfoFiles = List.nil(); + } + + /** Create the first round. */ + Round(Context context, List roots, List classSymbols) { + this(context, 1, 0, 0); + this.roots = roots; + genClassFiles = new HashMap(); + + compiler.todo.clear(); // free the compiler's resources + + // The reverse() in the following line is to maintain behavioural + // compatibility with the previous revision of the code. Strictly speaking, + // it should not be necessary, but a javah golden file test fails without it. + topLevelClasses = + getTopLevelClasses(roots).prependList(classSymbols.reverse()); + + packageInfoFiles = getPackageInfoFiles(roots); + + findAnnotationsPresent(); + } + + /** Create a new round. */ + private Round(Round prev, + Set newSourceFiles, Map newClassFiles) { + this(prev.nextContext(), + prev.number+1, + prev.nMessagerErrors, + prev.compiler.log.nwarnings); + this.genClassFiles = prev.genClassFiles; + + List parsedFiles = compiler.parseFiles(newSourceFiles); + roots = cleanTrees(prev.roots).appendList(parsedFiles); + + // Check for errors after parsing + if (unrecoverableError()) + return; + + enterClassFiles(genClassFiles); + List newClasses = enterClassFiles(newClassFiles); + genClassFiles.putAll(newClassFiles); + enterTrees(roots); + + if (unrecoverableError()) + return; + + topLevelClasses = join( + getTopLevelClasses(parsedFiles), + getTopLevelClassesFromClasses(newClasses)); + + packageInfoFiles = join( + getPackageInfoFiles(parsedFiles), + getPackageInfoFilesFromClasses(newClasses)); + + findAnnotationsPresent(); + } + + /** Create the next round to be used. */ + Round next(Set newSourceFiles, Map newClassFiles) { + try { + return new Round(this, newSourceFiles, newClassFiles); + } finally { + compiler.close(false); + } + } + + /** Create the compiler to be used for the final compilation. */ + JavaCompiler finalCompiler(boolean errorStatus) { + try { + JavaCompiler c = JavaCompiler.instance(nextContext()); + c.log.nwarnings += compiler.log.nwarnings; + if (errorStatus) { + c.log.nerrors += compiler.log.nerrors; + } + return c; + } finally { + compiler.close(false); + } + } + + /** Return the number of errors found so far in this round. + * This may include uncoverable errors, such as parse errors, + * and transient errors, such as missing symbols. */ + int errorCount() { + return compiler.errorCount(); + } + + /** Return the number of warnings found so far in this round. */ + int warningCount() { + return compiler.warningCount(); + } + + /** Return whether or not an unrecoverable error has occurred. */ + boolean unrecoverableError() { + if (messager.errorRaised()) + return true; + + for (JCDiagnostic d: log.deferredDiagnostics) { + switch (d.getKind()) { + case WARNING: + if (werror) + return true; + break; + + case ERROR: + if (fatalErrors || !d.isFlagSet(RECOVERABLE)) + return true; + break; + } + } + + return false; + } + + /** Find the set of annotations present in the set of top level + * classes and package info files to be processed this round. */ + void findAnnotationsPresent() { + ComputeAnnotationSet annotationComputer = new ComputeAnnotationSet(elementUtils); + // Use annotation processing to compute the set of annotations present + annotationsPresent = new LinkedHashSet(); + for (ClassSymbol classSym : topLevelClasses) + annotationComputer.scan(classSym, annotationsPresent); + for (PackageSymbol pkgSym : packageInfoFiles) + annotationComputer.scan(pkgSym, annotationsPresent); + } + + /** Enter a set of generated class files. */ + private List enterClassFiles(Map classFiles) { + ClassReader reader = ClassReader.instance(context); + Names names = Names.instance(context); + List list = List.nil(); + + for (Map.Entry entry : classFiles.entrySet()) { + Name name = names.fromString(entry.getKey()); + JavaFileObject file = entry.getValue(); + if (file.getKind() != JavaFileObject.Kind.CLASS) + throw new AssertionError(file); + ClassSymbol cs; + if (isPkgInfo(file, JavaFileObject.Kind.CLASS)) { + Name packageName = Convert.packagePart(name); + PackageSymbol p = reader.enterPackage(packageName); + if (p.package_info == null) + p.package_info = reader.enterClass(Convert.shortName(name), p); + cs = p.package_info; + if (cs.classfile == null) + cs.classfile = file; + } else + cs = reader.enterClass(name, file); + list = list.prepend(cs); + } + return list.reverse(); + } + + /** Enter a set of syntax trees. */ + private void enterTrees(List roots) { + compiler.enterTrees(roots); + } + + /** Run a processing round. */ + void run(boolean lastRound, boolean errorStatus) { + printRoundInfo(lastRound); + + TaskListener taskListener = context.get(TaskListener.class); + if (taskListener != null) + taskListener.started(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING_ROUND)); + + try { + if (lastRound) { + filer.setLastRound(true); + Set emptyRootElements = Collections.emptySet(); // immutable + RoundEnvironment renv = new JavacRoundEnvironment(true, + errorStatus, + emptyRootElements, + JavacProcessingEnvironment.this); + discoveredProcs.iterator().runContributingProcs(renv); + } else { + discoverAndRunProcs(context, annotationsPresent, topLevelClasses, packageInfoFiles); + } + } finally { + if (taskListener != null) + taskListener.finished(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING_ROUND)); + } + + nMessagerErrors = messager.errorCount(); + } + + void showDiagnostics(boolean showAll) { + Set kinds = EnumSet.allOf(JCDiagnostic.Kind.class); + if (!showAll) { + // suppress errors, which are all presumed to be transient resolve errors + kinds.remove(JCDiagnostic.Kind.ERROR); + } + log.reportDeferredDiagnostics(kinds); + } + + /** Print info about this round. */ + private void printRoundInfo(boolean lastRound) { + if (printRounds || verbose) { + List tlc = lastRound ? List.nil() : topLevelClasses; + Set ap = lastRound ? Collections.emptySet() : annotationsPresent; + log.printNoteLines("x.print.rounds", + number, + "{" + tlc.toString(", ") + "}", + ap, + lastRound); + } + } + + /** Get the context for the next round of processing. + * Important values are propogated from round to round; + * other values are implicitly reset. + */ + private Context nextContext() { + Context next = new Context(context); + + Options options = Options.instance(context); + Assert.checkNonNull(options); + next.put(Options.optionsKey, options); + + PrintWriter out = context.get(Log.outKey); + Assert.checkNonNull(out); + next.put(Log.outKey, out); + Locale locale = context.get(Locale.class); + if (locale != null) + next.put(Locale.class, locale); + Assert.checkNonNull(messages); + next.put(JavacMessages.messagesKey, messages); + + final boolean shareNames = true; + if (shareNames) { + Names names = Names.instance(context); + Assert.checkNonNull(names); + next.put(Names.namesKey, names); + } + + DiagnosticListener dl = context.get(DiagnosticListener.class); + if (dl != null) + next.put(DiagnosticListener.class, dl); + + TaskListener tl = context.get(TaskListener.class); + if (tl != null) + next.put(TaskListener.class, tl); + + FSInfo fsInfo = context.get(FSInfo.class); + if (fsInfo != null) + next.put(FSInfo.class, fsInfo); + + JavaFileManager jfm = context.get(JavaFileManager.class); + Assert.checkNonNull(jfm); + next.put(JavaFileManager.class, jfm); + if (jfm instanceof JavacFileManager) { + ((JavacFileManager)jfm).setContext(next); + } + + Names names = Names.instance(context); + Assert.checkNonNull(names); + next.put(Names.namesKey, names); + + Keywords keywords = Keywords.instance(context); + Assert.checkNonNull(keywords); + next.put(Keywords.keywordsKey, keywords); + + JavaCompiler oldCompiler = JavaCompiler.instance(context); + JavaCompiler nextCompiler = JavaCompiler.instance(next); + nextCompiler.initRound(oldCompiler); + + filer.newRound(next); + messager.newRound(next); + elementUtils.setContext(next); + typeUtils.setContext(next); + + JavacTaskImpl task = context.get(JavacTaskImpl.class); + if (task != null) { + next.put(JavacTaskImpl.class, task); + task.updateContext(next); + } + + JavacTrees trees = context.get(JavacTrees.class); + if (trees != null) { + next.put(JavacTrees.class, trees); + trees.updateContext(next); + } + + context.clear(); + return next; + } + } + + + // TODO: internal catch clauses?; catch and rethrow an annotation + // processing error + public JavaCompiler doProcessing(Context context, + List roots, + List classSymbols, + Iterable pckSymbols) { + + TaskListener taskListener = context.get(TaskListener.class); + log = Log.instance(context); + + Set specifiedPackages = new LinkedHashSet(); + for (PackageSymbol psym : pckSymbols) + specifiedPackages.add(psym); + this.specifiedPackages = Collections.unmodifiableSet(specifiedPackages); + + Round round = new Round(context, roots, classSymbols); + + boolean errorStatus; + boolean moreToDo; + do { + // Run processors for round n + round.run(false, false); + + // Processors for round n have run to completion. + // Check for errors and whether there is more work to do. + errorStatus = round.unrecoverableError(); + moreToDo = moreToDo(); + + round.showDiagnostics(errorStatus || showResolveErrors); + + // Set up next round. + // Copy mutable collections returned from filer. + round = round.next( + new LinkedHashSet(filer.getGeneratedSourceFileObjects()), + new LinkedHashMap(filer.getGeneratedClasses())); + + // Check for errors during setup. + if (round.unrecoverableError()) + errorStatus = true; + + } while (moreToDo && !errorStatus); + + // run last round + round.run(true, errorStatus); + round.showDiagnostics(true); + + filer.warnIfUnclosedFiles(); + warnIfUnmatchedOptions(); + + /* + * If an annotation processor raises an error in a round, + * that round runs to completion and one last round occurs. + * The last round may also occur because no more source or + * class files have been generated. Therefore, if an error + * was raised on either of the last *two* rounds, the compile + * should exit with a nonzero exit code. The current value of + * errorStatus holds whether or not an error was raised on the + * second to last round; errorRaised() gives the error status + * of the last round. + */ + if (messager.errorRaised() + || werror && round.warningCount() > 0 && round.errorCount() > 0) + errorStatus = true; + + Set newSourceFiles = + new LinkedHashSet(filer.getGeneratedSourceFileObjects()); + roots = cleanTrees(round.roots); + + JavaCompiler compiler = round.finalCompiler(errorStatus); + + if (newSourceFiles.size() > 0) + roots = roots.appendList(compiler.parseFiles(newSourceFiles)); + + errorStatus = errorStatus || (compiler.errorCount() > 0); + + // Free resources + this.close(); + + if (taskListener != null) + taskListener.finished(new TaskEvent(TaskEvent.Kind.ANNOTATION_PROCESSING)); + + if (errorStatus) { + if (compiler.errorCount() == 0) + compiler.log.nerrors++; + return compiler; + } + + if (procOnly && !foundTypeProcessors) { + compiler.todo.clear(); + } else { + if (procOnly && foundTypeProcessors) + compiler.shouldStopPolicy = CompileState.FLOW; + + compiler.enterTrees(roots); + } + + return compiler; + } + + private void warnIfUnmatchedOptions() { + if (!unmatchedProcessorOptions.isEmpty()) { + log.warning("proc.unmatched.processor.options", unmatchedProcessorOptions.toString()); + } + } + + /** + * Free resources related to annotation processing. + */ + public void close() { + filer.close(); + if (discoveredProcs != null) // Make calling close idempotent + discoveredProcs.close(); + discoveredProcs = null; + if (processorClassLoader != null && processorClassLoader instanceof Closeable) { + try { + ((Closeable) processorClassLoader).close(); + } catch (IOException e) { + JCDiagnostic msg = diags.fragment("fatal.err.cant.close.loader"); + throw new FatalError(msg, e); + } + } + } + + private List getTopLevelClasses(List units) { + List classes = List.nil(); + for (JCCompilationUnit unit : units) { + for (JCTree node : unit.defs) { + if (node.getTag() == JCTree.CLASSDEF) { + ClassSymbol sym = ((JCClassDecl) node).sym; + Assert.checkNonNull(sym); + classes = classes.prepend(sym); + } + } + } + return classes.reverse(); + } + + private List getTopLevelClassesFromClasses(List syms) { + List classes = List.nil(); + for (ClassSymbol sym : syms) { + if (!isPkgInfo(sym)) { + classes = classes.prepend(sym); + } + } + return classes.reverse(); + } + + private List getPackageInfoFiles(List units) { + List packages = List.nil(); + for (JCCompilationUnit unit : units) { + if (isPkgInfo(unit.sourcefile, JavaFileObject.Kind.SOURCE)) { + packages = packages.prepend(unit.packge); + } + } + return packages.reverse(); + } + + private List getPackageInfoFilesFromClasses(List syms) { + List packages = List.nil(); + for (ClassSymbol sym : syms) { + if (isPkgInfo(sym)) { + packages = packages.prepend((PackageSymbol) sym.owner); + } + } + return packages.reverse(); + } + + // avoid unchecked warning from use of varargs + private static List join(List list1, List list2) { + return list1.appendList(list2); + } + + private boolean isPkgInfo(JavaFileObject fo, JavaFileObject.Kind kind) { + return fo.isNameCompatible("package-info", kind); + } + + private boolean isPkgInfo(ClassSymbol sym) { + return isPkgInfo(sym.classfile, JavaFileObject.Kind.CLASS) && (sym.packge().package_info == sym); + } + + /* + * Called retroactively to determine if a class loader was required, + * after we have failed to create one. + */ + private boolean needClassLoader(String procNames, Iterable workingpath) { + if (procNames != null) + return true; + + String procPath; + URL[] urls = new URL[1]; + for(File pathElement : workingpath) { + try { + urls[0] = pathElement.toURI().toURL(); + if (ServiceProxy.hasService(Processor.class, urls)) + return true; + } catch (MalformedURLException ex) { + throw new AssertionError(ex); + } + catch (ServiceProxy.ServiceConfigurationError e) { + log.error("proc.bad.config.file", e.getLocalizedMessage()); + return true; + } + } + + return false; + } + + private static List cleanTrees(List nodes) { + for (T node : nodes) + treeCleaner.scan(node); + return nodes; + } + + private static TreeScanner treeCleaner = new TreeScanner() { + public void scan(JCTree node) { + super.scan(node); + if (node != null) + node.type = null; + } + public void visitTopLevel(JCCompilationUnit node) { + node.packge = null; + super.visitTopLevel(node); + } + public void visitClassDef(JCClassDecl node) { + node.sym = null; + super.visitClassDef(node); + } + public void visitMethodDef(JCMethodDecl node) { + node.sym = null; + super.visitMethodDef(node); + } + public void visitVarDef(JCVariableDecl node) { + node.sym = null; + super.visitVarDef(node); + } + public void visitNewClass(JCNewClass node) { + node.constructor = null; + super.visitNewClass(node); + } + public void visitAssignop(JCAssignOp node) { + node.operator = null; + super.visitAssignop(node); + } + public void visitUnary(JCUnary node) { + node.operator = null; + super.visitUnary(node); + } + public void visitBinary(JCBinary node) { + node.operator = null; + super.visitBinary(node); + } + public void visitSelect(JCFieldAccess node) { + node.sym = null; + super.visitSelect(node); + } + public void visitIdent(JCIdent node) { + node.sym = null; + super.visitIdent(node); + } + }; + + + private boolean moreToDo() { + return filer.newFiles(); + } + + /** + * {@inheritdoc} + * + * Command line options suitable for presenting to annotation + * processors. "-Afoo=bar" should be "-Afoo" => "bar". + */ + public Map getOptions() { + return processorOptions; + } + + public Messager getMessager() { + return messager; + } + + public Filer getFiler() { + return filer; + } + + public JavacElements getElementUtils() { + return elementUtils; + } + + public JavacTypes getTypeUtils() { + return typeUtils; + } + + public SourceVersion getSourceVersion() { + return Source.toSourceVersion(source); + } + + public Locale getLocale() { + return messages.getCurrentLocale(); + } + + public Set getSpecifiedPackages() { + return specifiedPackages; + } + + private static final Pattern allMatches = Pattern.compile(".*"); + public static final Pattern noMatches = Pattern.compile("(\\P{all})+"); + + /** + * Convert import-style string for supported annotations into a + * regex matching that string. If the string is a valid + * import-style string, return a regex that won't match anything. + */ + private static Pattern importStringToPattern(String s, Processor p, Log log) { + if (isValidImportString(s)) { + return validImportStringToPattern(s); + } else { + log.warning("proc.malformed.supported.string", s, p.getClass().getName()); + return noMatches; // won't match any valid identifier + } + } + + /** + * Return true if the argument string is a valid import-style + * string specifying claimed annotations; return false otherwise. + */ + public static boolean isValidImportString(String s) { + if (s.equals("*")) + return true; + + boolean valid = true; + String t = s; + int index = t.indexOf('*'); + + if (index != -1) { + // '*' must be last character... + if (index == t.length() -1) { + // ... any and preceding character must be '.' + if ( index-1 >= 0 ) { + valid = t.charAt(index-1) == '.'; + // Strip off ".*$" for identifier checks + t = t.substring(0, t.length()-2); + } + } else + return false; + } + + // Verify string is off the form (javaId \.)+ or javaId + if (valid) { + String[] javaIds = t.split("\\.", t.length()+2); + for(String javaId: javaIds) + valid &= SourceVersion.isIdentifier(javaId); + } + return valid; + } + + public static Pattern validImportStringToPattern(String s) { + if (s.equals("*")) { + return allMatches; + } else { + String s_prime = s.replace(".", "\\."); + + if (s_prime.endsWith("*")) { + s_prime = s_prime.substring(0, s_prime.length() - 1) + ".+"; + } + + return Pattern.compile(s_prime); + } + } + + /** + * For internal use only. This method will be + * removed without warning. + */ + public Context getContext() { + return context; + } + + public String toString() { + return "javac ProcessingEnvironment"; + } + + public static boolean isValidOptionName(String optionName) { + for(String s : optionName.split("\\.", -1)) { + if (!SourceVersion.isIdentifier(s)) + return false; + } + return true; + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/processing/JavacRoundEnvironment.java b/douyu-javac/src/main/java/com/sun/tools/javac/processing/JavacRoundEnvironment.java new file mode 100644 index 0000000..1131c67 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/processing/JavacRoundEnvironment.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.processing; + +import java.lang.annotation.Annotation; +import com.sun.tools.javac.tree.JCTree.*; +import javax.annotation.processing.*; +import javax.lang.model.element.*; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.*; +import java.util.*; + +/** + * Object providing state about a prior round of annotation processing. + * + *

The methods in this class do not take type annotations into account, + * as target types, not java elements. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class JavacRoundEnvironment implements RoundEnvironment { + // Default equals and hashCode methods are okay. + + private final boolean processingOver; + private final boolean errorRaised; + private final ProcessingEnvironment processingEnv; + + // Caller must pass in an immutable set + private final Set rootElements; + + JavacRoundEnvironment(boolean processingOver, + boolean errorRaised, + Set rootElements, + ProcessingEnvironment processingEnv) { + this.processingOver = processingOver; + this.errorRaised = errorRaised; + this.rootElements = rootElements; + this.processingEnv = processingEnv; + } + + public String toString() { + return String.format("[errorRaised=%b, rootElements=%s, processingOver=%b]", + errorRaised, + rootElements, + processingOver); + } + + public boolean processingOver() { + return processingOver; + } + + /** + * Returns {@code true} if an error was raised in the prior round + * of processing; returns {@code false} otherwise. + * + * @return {@code true} if an error was raised in the prior round + * of processing; returns {@code false} otherwise. + */ + public boolean errorRaised() { + return errorRaised; + } + + /** + * Returns the type elements specified by the prior round. + * + * @return the types elements specified by the prior round, or an + * empty set if there were none + */ + public Set getRootElements() { + return rootElements; + } + + private static final String NOT_AN_ANNOTATION_TYPE = + "The argument does not represent an annotation type: "; + + /** + * Returns the elements annotated with the given annotation type. + * Only type elements included in this round of annotation + * processing, or declarations of members, parameters, or type + * parameters declared within those, are returned. Included type + * elements are {@linkplain #getSpecifiedTypeElements specified + * types} and any types nested within them. + * + * @param a annotation type being requested + * @return the elements annotated with the given annotation type, + * or an empty set if there are none + */ + public Set getElementsAnnotatedWith(TypeElement a) { + Set result = Collections.emptySet(); + Types typeUtil = processingEnv.getTypeUtils(); + if (a.getKind() != ElementKind.ANNOTATION_TYPE) + throw new IllegalArgumentException(NOT_AN_ANNOTATION_TYPE + a); + + DeclaredType annotationTypeElement; + TypeMirror tm = a.asType(); + if ( tm instanceof DeclaredType ) + annotationTypeElement = (DeclaredType) a.asType(); + else + throw new AssertionError("Bad implementation type for " + tm); + + ElementScanner7, DeclaredType> scanner = + new AnnotationSetScanner(result, typeUtil); + + for (Element element : rootElements) + result = scanner.scan(element, annotationTypeElement); + + return result; + } + + // Could be written as a local class inside getElementsAnnotatedWith + private class AnnotationSetScanner extends + ElementScanner7, DeclaredType> { + // Insertion-order preserving set + Set annotatedElements = new LinkedHashSet(); + Types typeUtil; + + AnnotationSetScanner(Set defaultSet, Types typeUtil) { + super(defaultSet); + this.typeUtil = typeUtil; + } + + @Override + public Set scan(Element e, DeclaredType p) { + java.util.List annotationMirrors = + processingEnv.getElementUtils().getAllAnnotationMirrors(e); + for (AnnotationMirror annotationMirror : annotationMirrors) { + if (typeUtil.isSameType(annotationMirror.getAnnotationType(), p)) + annotatedElements.add(e); + } + e.accept(this, p); + return annotatedElements; + } + + } + + /** + * {@inheritdoc} + */ + public Set getElementsAnnotatedWith(Class a) { + if (!a.isAnnotation()) + throw new IllegalArgumentException(NOT_AN_ANNOTATION_TYPE + a); + String name = a.getCanonicalName(); + if (name == null) + return Collections.emptySet(); + else { + TypeElement annotationType = processingEnv.getElementUtils().getTypeElement(name); + if (annotationType == null) + return Collections.emptySet(); + else + return getElementsAnnotatedWith(annotationType); + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/processing/PrettyPrinter.java b/douyu-javac/src/main/java/com/sun/tools/javac/processing/PrettyPrinter.java new file mode 100644 index 0000000..c91dcc3 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/processing/PrettyPrinter.java @@ -0,0 +1,85 @@ +package com.sun.tools.javac.processing; + +/** + * + * @author ZHH + * + */ +public class PrettyPrinter { + public int tab=0; + public StringBuilder buf; + private int oldTab = 0; + + + public PrettyPrinter(StringBuilder buf) { + this.buf = buf; + } + public PrettyPrinter(int tab) { + this.buf = new StringBuilder(); + this.tab = tab; + + this.oldTab = tab; + } + public PrettyPrinter() { + this.buf = new StringBuilder(); + } + + public void reset() { + buf = new StringBuilder(); + tab = oldTab; + } + + public StringBuilder print(Object s) { + align(); + buf.append(s); + return buf; + } + public StringBuilder printNoAlign(Object s) { + buf.append(s); + return buf; + } + + public StringBuilder print(int t,Object s) { + T(t); + print(s); + return buf; + } + + public StringBuilder println() { + buf.append(lineSep); + return buf; + } + + public StringBuilder println(Object s) { + print(s); + println(); + return buf; + } + + public StringBuilder println(int t,Object s) { + T(t); + println(s); + return buf; + } + + + //打印tab + public StringBuilder T(int count) { + for(int i=0;iThis is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +@SupportedAnnotationTypes("*") +// TODO: Change to version 7 based visitors when available +@SupportedSourceVersion(SourceVersion.RELEASE_7) +public class PrintingProcessor extends AbstractProcessor { + PrintWriter writer; + + public PrintingProcessor() { + super(); + writer = new PrintWriter(System.out); + } + + public void setWriter(Writer w) { + writer = new PrintWriter(w); + } + + @Override + public boolean process(Set tes, + RoundEnvironment renv) { + + for(Element element : renv.getRootElements()) { + print(element); + } + + // Just print the elements, nothing more to do. + return true; + } + + void print(Element element) { + new PrintingElementVisitor(writer, processingEnv.getElementUtils()). + visit(element).flush(); + } + + /** + * Used for the -Xprint option and called by Elements.printElements + */ + public static class PrintingElementVisitor + extends SimpleElementVisitor7 { + int indentation; // Indentation level; + final PrintWriter writer; + final Elements elementUtils; + + public PrintingElementVisitor(Writer w, Elements elementUtils) { + super(); + this.writer = new PrintWriter(w); + this.elementUtils = elementUtils; + indentation = 0; + } + + @Override + protected PrintingElementVisitor defaultAction(Element e, Boolean newLine) { + if (newLine != null && newLine) + writer.println(); + printDocComment(e); + printModifiers(e); + return this; + } + + @Override + public PrintingElementVisitor visitExecutable(ExecutableElement e, Boolean p) { + ElementKind kind = e.getKind(); + + if (kind != STATIC_INIT && + kind != INSTANCE_INIT) { + Element enclosing = e.getEnclosingElement(); + + // Don't print out the constructor of an anonymous class + if (kind == CONSTRUCTOR && + enclosing != null && + NestingKind.ANONYMOUS == + // Use an anonymous class to determine anonymity! + (new SimpleElementVisitor7() { + @Override + public NestingKind visitType(TypeElement e, Void p) { + return e.getNestingKind(); + } + }).visit(enclosing)) + return this; + + defaultAction(e, true); + printFormalTypeParameters(e, true); + + switch(kind) { + case CONSTRUCTOR: + // Print out simple name of the class + writer.print(e.getEnclosingElement().getSimpleName()); + break; + + case METHOD: + writer.print(e.getReturnType().toString()); + writer.print(" "); + writer.print(e.getSimpleName().toString()); + break; + } + + writer.print("("); + printParameters(e); + writer.print(")"); + AnnotationValue defaultValue = e.getDefaultValue(); + if (defaultValue != null) + writer.print(" default " + defaultValue); + + printThrows(e); + writer.println(";"); + } + return this; + } + + + @Override + public PrintingElementVisitor visitType(TypeElement e, Boolean p) { + ElementKind kind = e.getKind(); + NestingKind nestingKind = e.getNestingKind(); + + if (NestingKind.ANONYMOUS == nestingKind) { + // Print out an anonymous class in the style of a + // class instance creation expression rather than a + // class declaration. + writer.print("new "); + + // If the anonymous class implements an interface + // print that name, otherwise print the superclass. + List interfaces = e.getInterfaces(); + if (!interfaces.isEmpty()) + writer.print(interfaces.get(0)); + else + writer.print(e.getSuperclass()); + + writer.print("("); + // Anonymous classes that implement an interface can't + // have any constructor arguments. + if (interfaces.isEmpty()) { + // Print out the parameter list from the sole + // constructor. For now, don't try to elide any + // synthetic parameters by determining if the + // anonymous class is in a static context, etc. + List constructors = + ElementFilter.constructorsIn(e.getEnclosedElements()); + + if (!constructors.isEmpty()) + printParameters(constructors.get(0)); + } + writer.print(")"); + } else { + if (nestingKind == TOP_LEVEL) { + PackageElement pkg = elementUtils.getPackageOf(e); + if (!pkg.isUnnamed()) + writer.print("package " + pkg.getQualifiedName() + ";\n"); + } + + defaultAction(e, true); + + switch(kind) { + case ANNOTATION_TYPE: + writer.print("@interface"); + break; + default: + writer.print(kind.toString().toLowerCase()); + } + writer.print(" "); + writer.print(e.getSimpleName()); + + printFormalTypeParameters(e, false); + + // Print superclass information if informative + if (kind == CLASS) { + TypeMirror supertype = e.getSuperclass(); + if (supertype.getKind() != TypeKind.NONE) { + TypeElement e2 = (TypeElement) + ((DeclaredType) supertype).asElement(); + if (e2.getSuperclass().getKind() != TypeKind.NONE) + writer.print(" extends " + supertype); + } + } + + printInterfaces(e); + } + writer.println(" {"); + indentation++; + + if (kind == ENUM) { + List enclosedElements = + new ArrayList(e.getEnclosedElements()); + // Handle any enum constants specially before other entities. + List enumConstants = new ArrayList(); + for(Element element : enclosedElements) { + if (element.getKind() == ENUM_CONSTANT) + enumConstants.add(element); + } + if (!enumConstants.isEmpty()) { + int i; + for(i = 0; i < enumConstants.size()-1; i++) { + this.visit(enumConstants.get(i), true); + writer.print(","); + } + this.visit(enumConstants.get(i), true); + writer.println(";\n"); + + enclosedElements.removeAll(enumConstants); + } + + for(Element element : enclosedElements) + this.visit(element); + } else { + for(Element element : e.getEnclosedElements()) + this.visit(element); + } + + indentation--; + indent(); + writer.println("}"); + return this; + } + + @Override + public PrintingElementVisitor visitVariable(VariableElement e, Boolean newLine) { + ElementKind kind = e.getKind(); + defaultAction(e, newLine); + + if (kind == ENUM_CONSTANT) + writer.print(e.getSimpleName()); + else { + writer.print(e.asType().toString() + " " + e.getSimpleName() ); + Object constantValue = e.getConstantValue(); + if (constantValue != null) { + writer.print(" = "); + writer.print(elementUtils.getConstantExpression(constantValue)); + } + writer.println(";"); + } + return this; + } + + @Override + public PrintingElementVisitor visitTypeParameter(TypeParameterElement e, Boolean p) { + writer.print(e.getSimpleName()); + return this; + } + + // Should we do more here? + @Override + public PrintingElementVisitor visitPackage(PackageElement e, Boolean p) { + defaultAction(e, false); + if (!e.isUnnamed()) + writer.println("package " + e.getQualifiedName() + ";"); + else + writer.println("// Unnamed package"); + return this; + } + + public void flush() { + writer.flush(); + } + + private void printDocComment(Element e) { + String docComment = elementUtils.getDocComment(e); + + if (docComment != null) { + // Break comment into lines + java.util.StringTokenizer st = new StringTokenizer(docComment, + "\n\r"); + indent(); + writer.println("/**"); + + while(st.hasMoreTokens()) { + indent(); + writer.print(" *"); + writer.println(st.nextToken()); + } + + indent(); + writer.println(" */"); + } + } + + private void printModifiers(Element e) { + ElementKind kind = e.getKind(); + if (kind == PARAMETER) { + printAnnotationsInline(e); + } else { + printAnnotations(e); + indent(); + } + + if (kind == ENUM_CONSTANT) + return; + + Set modifiers = new LinkedHashSet(); + modifiers.addAll(e.getModifiers()); + + switch (kind) { + case ANNOTATION_TYPE: + case INTERFACE: + modifiers.remove(Modifier.ABSTRACT); + break; + + case ENUM: + modifiers.remove(Modifier.FINAL); + modifiers.remove(Modifier.ABSTRACT); + break; + + case METHOD: + case FIELD: + Element enclosingElement = e.getEnclosingElement(); + if (enclosingElement != null && + enclosingElement.getKind().isInterface()) { + modifiers.remove(Modifier.PUBLIC); + modifiers.remove(Modifier.ABSTRACT); // only for methods + modifiers.remove(Modifier.STATIC); // only for fields + modifiers.remove(Modifier.FINAL); // only for fields + } + break; + + } + + for(Modifier m: modifiers) { + writer.print(m.toString() + " "); + } + } + + private void printFormalTypeParameters(Parameterizable e, + boolean pad) { + List typeParams = e.getTypeParameters(); + if (typeParams.size() > 0) { + writer.print("<"); + + boolean first = true; + for(TypeParameterElement tpe: typeParams) { + if (!first) + writer.print(", "); + printAnnotationsInline(tpe); + writer.print(tpe.toString()); + first = false; + } + + writer.print(">"); + if (pad) + writer.print(" "); + } + } + + private void printAnnotationsInline(Element e) { + List annots = e.getAnnotationMirrors(); + for(AnnotationMirror annotationMirror : annots) { + writer.print(annotationMirror); + writer.print(" "); + } + } + + private void printAnnotations(Element e) { + List annots = e.getAnnotationMirrors(); + for(AnnotationMirror annotationMirror : annots) { + indent(); + writer.println(annotationMirror); + } + } + + // TODO: Refactor + private void printParameters(ExecutableElement e) { + List parameters = e.getParameters(); + int size = parameters.size(); + + switch (size) { + case 0: + break; + + case 1: + for(VariableElement parameter: parameters) { + printModifiers(parameter); + + if (e.isVarArgs() ) { + TypeMirror tm = parameter.asType(); + if (tm.getKind() != TypeKind.ARRAY) + throw new AssertionError("Var-args parameter is not an array type: " + tm); + writer.print((ArrayType.class.cast(tm)).getComponentType() ); + writer.print("..."); + } else + writer.print(parameter.asType()); + writer.print(" " + parameter.getSimpleName()); + } + break; + + default: + { + int i = 1; + for(VariableElement parameter: parameters) { + if (i == 2) + indentation++; + + if (i > 1) + indent(); + + printModifiers(parameter); + + if (i == size && e.isVarArgs() ) { + TypeMirror tm = parameter.asType(); + if (tm.getKind() != TypeKind.ARRAY) + throw new AssertionError("Var-args parameter is not an array type: " + tm); + writer.print((ArrayType.class.cast(tm)).getComponentType() ); + + writer.print("..."); + } else + writer.print(parameter.asType()); + writer.print(" " + parameter.getSimpleName()); + + if (i < size) + writer.println(","); + + i++; + } + + if (parameters.size() >= 2) + indentation--; + } + break; + } + } + + private void printInterfaces(TypeElement e) { + ElementKind kind = e.getKind(); + + if(kind != ANNOTATION_TYPE) { + List interfaces = e.getInterfaces(); + if (interfaces.size() > 0) { + writer.print((kind.isClass() ? " implements" : " extends")); + + boolean first = true; + for(TypeMirror interf: interfaces) { + if (!first) + writer.print(","); + writer.print(" "); + writer.print(interf.toString()); + first = false; + } + } + } + } + + private void printThrows(ExecutableElement e) { + List thrownTypes = e.getThrownTypes(); + final int size = thrownTypes.size(); + if (size != 0) { + writer.print(" throws"); + + int i = 1; + for(TypeMirror thrownType: thrownTypes) { + if (i == 1) + writer.print(" "); + + if (i == 2) + indentation++; + + if (i >= 2) + indent(); + + writer.print(thrownType); + + if (i != size) + writer.println(", "); + + i++; + } + + if (size >= 2) + indentation--; + } + } + + private static final String [] spaces = { + "", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " " + }; + + private void indent() { + int indentation = this.indentation; + if (indentation < 0) + return; + final int maxIndex = spaces.length - 1; + + while (indentation > maxIndex) { + writer.print(spaces[maxIndex]); + indentation -= maxIndex; + } + writer.print(spaces[indentation]); + } + + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/processing/ServiceProxy.java b/douyu-javac/src/main/java/com/sun/tools/javac/processing/ServiceProxy.java new file mode 100644 index 0000000..e3cd7ad --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/processing/ServiceProxy.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2006, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.processing; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Utility class to determine if a service can be found on the + * path that might be used to create a class loader. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + */ +// based on sun.misc.Service +class ServiceProxy { + static class ServiceConfigurationError extends Error { + static final long serialVersionUID = 7732091036771098303L; + + ServiceConfigurationError(String msg) { + super(msg); + } + } + + private static final String prefix = "META-INF/services/"; + + private static void fail(Class service, String msg) + throws ServiceConfigurationError { + throw new ServiceConfigurationError(service.getName() + ": " + msg); + } + + private static void fail(Class service, URL u, int line, String msg) + throws ServiceConfigurationError { + fail(service, u + ":" + line + ": " + msg); + } + + /** + * Parse the content of the given URL as a provider-configuration file. + * + * @param service + * The service class for which providers are being sought; + * used to construct error detail strings + * + * @param url + * The URL naming the configuration file to be parsed + * + * @return true if the name of a service is found + * + * @throws ServiceConfigurationError + * If an I/O error occurs while reading from the given URL, or + * if a configuration-file format error is detected + */ + private static boolean parse(Class service, URL u) throws ServiceConfigurationError { + InputStream in = null; + BufferedReader r = null; + try { + in = u.openStream(); + r = new BufferedReader(new InputStreamReader(in, "utf-8")); + int lc = 1; + String ln; + while ((ln = r.readLine()) != null) { + int ci = ln.indexOf('#'); + if (ci >= 0) ln = ln.substring(0, ci); + ln = ln.trim(); + int n = ln.length(); + if (n != 0) { + if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) + fail(service, u, lc, "Illegal configuration-file syntax"); + int cp = ln.codePointAt(0); + if (!Character.isJavaIdentifierStart(cp)) + fail(service, u, lc, "Illegal provider-class name: " + ln); + for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) { + cp = ln.codePointAt(i); + if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) + fail(service, u, lc, "Illegal provider-class name: " + ln); + } + return true; + } + } + } catch (FileNotFoundException x) { + return false; + } catch (IOException x) { + fail(service, ": " + x); + } finally { + try { + if (r != null) r.close(); + } catch (IOException y) { + fail(service, ": " + y); + } + try { + if (in != null) in.close(); + } catch (IOException y) { + fail(service, ": " + y); + } + } + return false; + } + + /** + * Return true if a description for at least one service is found in the + * service configuration files in the given URLs. + */ + public static boolean hasService(Class service, URL[] urls) + throws ServiceConfigurationError { + for (URL url: urls) { + try { + String fullName = prefix + service.getName(); + URL u = new URL(url, fullName); + boolean found = parse(service, u); + if (found) + return true; + } catch (MalformedURLException e) { + // should not happen; ignore it if it does + } + } + return false; + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/services/javax.tools.JavaCompilerTool b/douyu-javac/src/main/java/com/sun/tools/javac/services/javax.tools.JavaCompilerTool new file mode 100644 index 0000000..f4791f2 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/services/javax.tools.JavaCompilerTool @@ -0,0 +1 @@ +com.sun.tools.javac.api.Tool diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/sym/CreateSymbols.java b/douyu-javac/src/main/java/com/sun/tools/javac/sym/CreateSymbols.java new file mode 100644 index 0000000..9ee550e --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/sym/CreateSymbols.java @@ -0,0 +1,493 @@ +/* + * Copyright (c) 2006, 2008, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.sym; + +import com.sun.tools.javac.api.JavacTaskImpl; +import com.sun.tools.javac.code.Kinds; +import com.sun.tools.javac.code.Scope; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Attribute; +import com.sun.tools.javac.code.Symtab; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.jvm.ClassReader; +import com.sun.tools.javac.jvm.ClassWriter; +import com.sun.tools.javac.jvm.Pool; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Pair; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.ResourceBundle; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedOptions; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileManager.Location; +import javax.tools.JavaFileObject; +import static javax.tools.JavaFileObject.Kind.CLASS; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; + +/** + * Used to generate a "symbol file" representing rt.jar that only + * includes supported or legacy proprietary API. Valid annotation + * processor options: + * + *

+ *
com.sun.tools.javac.sym.Jar
+ *
Specifies the location of rt.jar.
+ *
com.sun.tools.javac.sym.Dest
+ *
Specifies the destination directory.
+ *
+ * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own + * risk. This code and its internal interfaces are subject to change + * or deletion without notice.

+ * + * @author Peter von der Ah\u00e9 + */ +@SupportedOptions({"com.sun.tools.javac.sym.Jar","com.sun.tools.javac.sym.Dest"}) +@SupportedAnnotationTypes("*") +public class CreateSymbols extends AbstractProcessor { + + static Set getLegacyPackages() { + ResourceBundle legacyBundle + = ResourceBundle.getBundle("com.sun.tools.javac.resources.legacy"); + Set keys = new HashSet(); + for (Enumeration e = legacyBundle.getKeys(); e.hasMoreElements(); ) + keys.add(e.nextElement()); + return keys; + } + + public boolean process(Set tes, RoundEnvironment renv) { + try { + if (renv.processingOver()) + createSymbols(); + } catch (IOException e) { + processingEnv.getMessager() + .printMessage(Diagnostic.Kind.ERROR, e.getLocalizedMessage()); + } catch (Throwable t) { + Throwable cause = t.getCause(); + if (cause == null) + cause = t; + processingEnv.getMessager() + .printMessage(Diagnostic.Kind.ERROR, cause.getLocalizedMessage()); + } + return true; + } + + void createSymbols() throws IOException { + Set legacy = getLegacyPackages(); + Set legacyProprietary = getLegacyPackages(); + Set documented = new HashSet(); + Set packages = + ((JavacProcessingEnvironment)processingEnv).getSpecifiedPackages(); + String jarName = processingEnv.getOptions().get("com.sun.tools.javac.sym.Jar"); + if (jarName == null) + throw new RuntimeException("Must use -Acom.sun.tools.javac.sym.Jar=LOCATION_OF_JAR"); + String destName = processingEnv.getOptions().get("com.sun.tools.javac.sym.Dest"); + if (destName == null) + throw new RuntimeException("Must use -Acom.sun.tools.javac.sym.Dest=LOCATION_OF_JAR"); + + for (PackageSymbol psym : packages) { + String name = psym.getQualifiedName().toString(); + legacyProprietary.remove(name); + documented.add(name); + } + + JavaCompiler tool = ToolProvider.getSystemJavaCompiler(); + StandardJavaFileManager fm = tool.getStandardFileManager(null, null, null); + Location jarLocation = StandardLocation.locationFor(jarName); + File jarFile = new File(jarName); + fm.setLocation(jarLocation, List.of(jarFile)); + fm.setLocation(StandardLocation.CLASS_PATH, List.nil()); + fm.setLocation(StandardLocation.SOURCE_PATH, List.nil()); + { + ArrayList bootClassPath = new ArrayList(); + bootClassPath.add(jarFile); + for (File path : fm.getLocation(StandardLocation.PLATFORM_CLASS_PATH)) { + if (!new File(path.getName()).equals(new File("rt.jar"))) + bootClassPath.add(path); + } + System.err.println("Using boot class path = " + bootClassPath); + fm.setLocation(StandardLocation.PLATFORM_CLASS_PATH, bootClassPath); + } + // System.out.println(fm.getLocation(StandardLocation.PLATFORM_CLASS_PATH)); + File destDir = new File(destName); + if (!destDir.exists()) + if (!destDir.mkdirs()) + throw new RuntimeException("Could not create " + destDir); + fm.setLocation(StandardLocation.CLASS_OUTPUT, List.of(destDir)); + Set hiddenPackages = new HashSet(); + Set crisp = new HashSet(); + List options = List.of("-XDdev"); + // options = options.prepend("-doe"); + // options = options.prepend("-verbose"); + JavacTaskImpl task = (JavacTaskImpl) + tool.getTask(null, fm, null, options, null, null); + com.sun.tools.javac.main.JavaCompiler compiler = + com.sun.tools.javac.main.JavaCompiler.instance(task.getContext()); + ClassReader reader = ClassReader.instance(task.getContext()); + ClassWriter writer = ClassWriter.instance(task.getContext()); + Symtab syms = Symtab.instance(task.getContext()); + Attribute.Compound proprietary = + new Attribute.Compound(syms.proprietaryType, + List.>nil()); + + Type.moreInfo = true; + Pool pool = new Pool(); + for (JavaFileObject file : fm.list(jarLocation, "", EnumSet.of(CLASS), true)) { + String className = fm.inferBinaryName(jarLocation, file); + int index = className.lastIndexOf('.'); + String pckName = index == -1 ? "" : className.substring(0, index); + boolean addLegacyAnnotation = false; + if (documented.contains(pckName)) { + if (!legacy.contains(pckName)) + crisp.add(pckName); + // System.out.println("Documented: " + className); + } else if (legacyProprietary.contains(pckName)) { + addLegacyAnnotation = true; + // System.out.println("Legacy proprietary: " + className); + } else { + // System.out.println("Hidden " + className); + hiddenPackages.add(pckName); + continue; + } + TypeSymbol sym = (TypeSymbol)compiler.resolveIdent(className); + if (sym.kind != Kinds.TYP) { + if (className.indexOf('$') < 0) { + System.err.println("Ignoring (other) " + className + " : " + sym); + System.err.println(" " + sym.getClass().getSimpleName() + " " + sym.type); + } + continue; + } + sym.complete(); + if (sym.getEnclosingElement().getKind() != ElementKind.PACKAGE) { + System.err.println("Ignoring (bad) " + sym.getQualifiedName()); + continue; + } + ClassSymbol cs = (ClassSymbol) sym; + if (addLegacyAnnotation) { + cs.attributes_field = (cs.attributes_field == null) + ? List.of(proprietary) + : cs.attributes_field.prepend(proprietary); + } + writeClass(pool, cs, writer); + } + + if (false) { + for (String pckName : crisp) + System.out.println("Crisp: " + pckName); + for (String pckName : hiddenPackages) + System.out.println("Hidden: " + pckName); + for (String pckName : legacyProprietary) + System.out.println("Legacy proprietary: " + pckName); + for (String pckName : documented) + System.out.println("Documented: " + pckName); + } + } + + void writeClass(final Pool pool, final ClassSymbol cs, final ClassWriter writer) + throws IOException + { + try { + pool.reset(); + cs.pool = pool; + writer.writeClass(cs); + for (Scope.Entry e = cs.members().elems; e != null; e = e.sibling) { + if (e.sym.kind == Kinds.TYP) { + ClassSymbol nestedClass = (ClassSymbol)e.sym; + nestedClass.complete(); + writeClass(pool, nestedClass, writer); + } + } + } catch (ClassWriter.StringOverflow ex) { + throw new RuntimeException(ex); + } catch (ClassWriter.PoolOverflow ex) { + throw new RuntimeException(ex); + } + } + + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + + // used for debugging + public static void main(String... args) { + String rt_jar = args[0]; + String dest = args[1]; + args = new String[] { + "-Xbootclasspath:" + rt_jar, + "-XDprocess.packages", + "-proc:only", + "-processor", + "com.sun.tools.javac.sym.CreateSymbols", + "-Acom.sun.tools.javac.sym.Jar=" + rt_jar, + "-Acom.sun.tools.javac.sym.Dest=" + dest, + // + "java.applet", + "java.awt", + "java.awt.color", + "java.awt.datatransfer", + "java.awt.dnd", + "java.awt.event", + "java.awt.font", + "java.awt.geom", + "java.awt.im", + "java.awt.im.spi", + "java.awt.image", + "java.awt.image.renderable", + "java.awt.print", + "java.beans", + "java.beans.beancontext", + "java.io", + "java.lang", + "java.lang.annotation", + "java.lang.instrument", + "java.lang.management", + "java.lang.ref", + "java.lang.reflect", + "java.math", + "java.net", + "java.nio", + "java.nio.channels", + "java.nio.channels.spi", + "java.nio.charset", + "java.nio.charset.spi", + "java.rmi", + "java.rmi.activation", + "java.rmi.dgc", + "java.rmi.registry", + "java.rmi.server", + "java.security", + "java.security.acl", + "java.security.cert", + "java.security.interfaces", + "java.security.spec", + "java.sql", + "java.text", + "java.text.spi", + "java.util", + "java.util.concurrent", + "java.util.concurrent.atomic", + "java.util.concurrent.locks", + "java.util.jar", + "java.util.logging", + "java.util.prefs", + "java.util.regex", + "java.util.spi", + "java.util.zip", + "javax.accessibility", + "javax.activation", + "javax.activity", + "javax.annotation", + "javax.annotation.processing", + "javax.crypto", + "javax.crypto.interfaces", + "javax.crypto.spec", + "javax.imageio", + "javax.imageio.event", + "javax.imageio.metadata", + "javax.imageio.plugins.jpeg", + "javax.imageio.plugins.bmp", + "javax.imageio.spi", + "javax.imageio.stream", + "javax.jws", + "javax.jws.soap", + "javax.lang.model", + "javax.lang.model.element", + "javax.lang.model.type", + "javax.lang.model.util", + "javax.management", + "javax.management.loading", + "javax.management.monitor", + "javax.management.relation", + "javax.management.openmbean", + "javax.management.timer", + "javax.management.modelmbean", + "javax.management.remote", + "javax.management.remote.rmi", + "javax.naming", + "javax.naming.directory", + "javax.naming.event", + "javax.naming.ldap", + "javax.naming.spi", + "javax.net", + "javax.net.ssl", + "javax.print", + "javax.print.attribute", + "javax.print.attribute.standard", + "javax.print.event", + "javax.rmi", + "javax.rmi.CORBA", + "javax.rmi.ssl", + "javax.script", + "javax.security.auth", + "javax.security.auth.callback", + "javax.security.auth.kerberos", + "javax.security.auth.login", + "javax.security.auth.spi", + "javax.security.auth.x500", + "javax.security.cert", + "javax.security.sasl", + "javax.sound.sampled", + "javax.sound.sampled.spi", + "javax.sound.midi", + "javax.sound.midi.spi", + "javax.sql", + "javax.sql.rowset", + "javax.sql.rowset.serial", + "javax.sql.rowset.spi", + "javax.swing", + "javax.swing.border", + "javax.swing.colorchooser", + "javax.swing.filechooser", + "javax.swing.event", + "javax.swing.table", + "javax.swing.text", + "javax.swing.text.html", + "javax.swing.text.html.parser", + "javax.swing.text.rtf", + "javax.swing.tree", + "javax.swing.undo", + "javax.swing.plaf", + "javax.swing.plaf.basic", + "javax.swing.plaf.metal", + "javax.swing.plaf.multi", + "javax.swing.plaf.synth", + "javax.tools", + "javax.transaction", + "javax.transaction.xa", + "javax.xml.parsers", + "javax.xml.bind", + "javax.xml.bind.annotation", + "javax.xml.bind.annotation.adapters", + "javax.xml.bind.attachment", + "javax.xml.bind.helpers", + "javax.xml.bind.util", + "javax.xml.soap", + "javax.xml.ws", + "javax.xml.ws.handler", + "javax.xml.ws.handler.soap", + "javax.xml.ws.http", + "javax.xml.ws.soap", + "javax.xml.ws.spi", + "javax.xml.transform", + "javax.xml.transform.sax", + "javax.xml.transform.dom", + "javax.xml.transform.stax", + "javax.xml.transform.stream", + "javax.xml", + "javax.xml.crypto", + "javax.xml.crypto.dom", + "javax.xml.crypto.dsig", + "javax.xml.crypto.dsig.dom", + "javax.xml.crypto.dsig.keyinfo", + "javax.xml.crypto.dsig.spec", + "javax.xml.datatype", + "javax.xml.validation", + "javax.xml.namespace", + "javax.xml.xpath", + "javax.xml.stream", + "javax.xml.stream.events", + "javax.xml.stream.util", + "org.ietf.jgss", + "org.omg.CORBA", + "org.omg.CORBA.DynAnyPackage", + "org.omg.CORBA.ORBPackage", + "org.omg.CORBA.TypeCodePackage", + "org.omg.stub.java.rmi", + "org.omg.CORBA.portable", + "org.omg.CORBA_2_3", + "org.omg.CORBA_2_3.portable", + "org.omg.CosNaming", + "org.omg.CosNaming.NamingContextExtPackage", + "org.omg.CosNaming.NamingContextPackage", + "org.omg.SendingContext", + "org.omg.PortableServer", + "org.omg.PortableServer.CurrentPackage", + "org.omg.PortableServer.POAPackage", + "org.omg.PortableServer.POAManagerPackage", + "org.omg.PortableServer.ServantLocatorPackage", + "org.omg.PortableServer.portable", + "org.omg.PortableInterceptor", + "org.omg.PortableInterceptor.ORBInitInfoPackage", + "org.omg.Messaging", + "org.omg.IOP", + "org.omg.IOP.CodecFactoryPackage", + "org.omg.IOP.CodecPackage", + "org.omg.Dynamic", + "org.omg.DynamicAny", + "org.omg.DynamicAny.DynAnyPackage", + "org.omg.DynamicAny.DynAnyFactoryPackage", + "org.w3c.dom", + "org.w3c.dom.events", + "org.w3c.dom.bootstrap", + "org.w3c.dom.ls", + "org.xml.sax", + "org.xml.sax.ext", + "org.xml.sax.helpers", + "com.sun.java.browser.dom", + "org.w3c.dom", + "org.w3c.dom.bootstrap", + "org.w3c.dom.ls", + "org.w3c.dom.ranges", + "org.w3c.dom.traversal", + "org.w3c.dom.html", + "org.w3c.dom.stylesheets", + "org.w3c.dom.css", + "org.w3c.dom.events", + "org.w3c.dom.views", + "com.sun.management", + "com.sun.security.auth", + "com.sun.security.auth.callback", + "com.sun.security.auth.login", + "com.sun.security.auth.module", + "com.sun.security.jgss", + "com.sun.net.httpserver", + "com.sun.net.httpserver.spi", + "javax.smartcardio" + // + }; + com.sun.tools.javac.Main.compile(args); + } + +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/tree/JCTree.java b/douyu-javac/src/main/java/com/sun/tools/javac/tree/JCTree.java new file mode 100644 index 0000000..bade408 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/tree/JCTree.java @@ -0,0 +1,2242 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.tree; + +import java.util.*; + +import java.io.IOException; +import java.io.StringWriter; +import javax.lang.model.element.Modifier; +import javax.lang.model.type.TypeKind; +import javax.tools.JavaFileObject; + +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Scope.*; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.source.tree.*; + +import static com.sun.tools.javac.code.BoundKind.*; + +/** + * Root class for abstract syntax tree nodes. It provides definitions + * for specific tree nodes as subclasses nested inside. + * + *

Each subclass is highly standardized. It generally contains + * only tree fields for the syntactic subcomponents of the node. Some + * classes that represent identifier uses or definitions also define a + * Symbol field that denotes the represented identifier. Classes for + * non-local jumps also carry the jump target as a field. The root + * class Tree itself defines fields for the tree's type and position. + * No other fields are kept in a tree node; instead parameters are + * passed to methods accessing the node. + * + *

Except for the methods defined by com.sun.source, the only + * method defined in subclasses is `visit' which applies a given + * visitor to the tree. The actual tree processing is done by visitor + * classes in other packages. The abstract class Visitor, as well as + * an Factory interface for trees, are defined as inner classes in + * Tree. + * + *

To avoid ambiguities with the Tree API in com.sun.source all sub + * classes should, by convention, start with JC (javac). + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @see TreeMaker + * @see TreeInfo + * @see TreeTranslator + * @see Pretty + */ +public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition { + + /* Tree tag values, identifying kinds of trees */ + + /** Toplevel nodes, of type TopLevel, representing entire source files. + */ + public static final int TOPLEVEL = 1; + + /** Import clauses, of type Import. + */ + public static final int IMPORT = TOPLEVEL + 1; + + /** Class definitions, of type ClassDef. + */ + public static final int CLASSDEF = IMPORT + 1; + + /** Method definitions, of type MethodDef. + */ + public static final int METHODDEF = CLASSDEF + 1; + + /** Variable definitions, of type VarDef. + */ + public static final int VARDEF = METHODDEF + 1; + + /** The no-op statement ";", of type Skip + */ + public static final int SKIP = VARDEF + 1; + + /** Blocks, of type Block. + */ + public static final int BLOCK = SKIP + 1; + + /** Do-while loops, of type DoLoop. + */ + public static final int DOLOOP = BLOCK + 1; + + /** While-loops, of type WhileLoop. + */ + public static final int WHILELOOP = DOLOOP + 1; + + /** For-loops, of type ForLoop. + */ + public static final int FORLOOP = WHILELOOP + 1; + + /** Foreach-loops, of type ForeachLoop. + */ + public static final int FOREACHLOOP = FORLOOP + 1; + + /** Labelled statements, of type Labelled. + */ + public static final int LABELLED = FOREACHLOOP + 1; + + /** Switch statements, of type Switch. + */ + public static final int SWITCH = LABELLED + 1; + + /** Case parts in switch statements, of type Case. + */ + public static final int CASE = SWITCH + 1; + + /** Synchronized statements, of type Synchonized. + */ + public static final int SYNCHRONIZED = CASE + 1; + + /** Try statements, of type Try. + */ + public static final int TRY = SYNCHRONIZED + 1; + + /** Catch clauses in try statements, of type Catch. + */ + public static final int CATCH = TRY + 1; + + /** Conditional expressions, of type Conditional. + */ + public static final int CONDEXPR = CATCH + 1; + + /** Conditional statements, of type If. + */ + public static final int IF = CONDEXPR + 1; + + /** Expression statements, of type Exec. + */ + public static final int EXEC = IF + 1; + + /** Break statements, of type Break. + */ + public static final int BREAK = EXEC + 1; + + /** Continue statements, of type Continue. + */ + public static final int CONTINUE = BREAK + 1; + + /** Return statements, of type Return. + */ + public static final int RETURN = CONTINUE + 1; + + /** Throw statements, of type Throw. + */ + public static final int THROW = RETURN + 1; + + /** Assert statements, of type Assert. + */ + public static final int ASSERT = THROW + 1; + + /** Method invocation expressions, of type Apply. + */ + public static final int APPLY = ASSERT + 1; + + /** Class instance creation expressions, of type NewClass. + */ + public static final int NEWCLASS = APPLY + 1; + + /** Array creation expressions, of type NewArray. + */ + public static final int NEWARRAY = NEWCLASS + 1; + + /** Parenthesized subexpressions, of type Parens. + */ + public static final int PARENS = NEWARRAY + 1; + + /** Assignment expressions, of type Assign. + */ + public static final int ASSIGN = PARENS + 1; + + /** Type cast expressions, of type TypeCast. + */ + public static final int TYPECAST = ASSIGN + 1; + + /** Type test expressions, of type TypeTest. + */ + public static final int TYPETEST = TYPECAST + 1; + + /** Indexed array expressions, of type Indexed. + */ + public static final int INDEXED = TYPETEST + 1; + + /** Selections, of type Select. + */ + public static final int SELECT = INDEXED + 1; + + /** Simple identifiers, of type Ident. + */ + public static final int IDENT = SELECT + 1; + + /** Literals, of type Literal. + */ + public static final int LITERAL = IDENT + 1; + + /** Basic type identifiers, of type TypeIdent. + */ + public static final int TYPEIDENT = LITERAL + 1; + + /** Array types, of type TypeArray. + */ + public static final int TYPEARRAY = TYPEIDENT + 1; + + /** Parameterized types, of type TypeApply. + */ + public static final int TYPEAPPLY = TYPEARRAY + 1; + + /** Union types, of type TypeUnion + */ + public static final int TYPEUNION = TYPEAPPLY + 1; + + /** Formal type parameters, of type TypeParameter. + */ + public static final int TYPEPARAMETER = TYPEUNION + 1; + + /** Type argument. + */ + public static final int WILDCARD = TYPEPARAMETER + 1; + + /** Bound kind: extends, super, exact, or unbound + */ + public static final int TYPEBOUNDKIND = WILDCARD + 1; + + /** metadata: Annotation. + */ + public static final int ANNOTATION = TYPEBOUNDKIND + 1; + + /** metadata: Modifiers + */ + public static final int MODIFIERS = ANNOTATION + 1; + + public static final int ANNOTATED_TYPE = MODIFIERS + 1; + + /** Error trees, of type Erroneous. + */ + public static final int ERRONEOUS = ANNOTATED_TYPE + 1; + + /** Unary operators, of type Unary. + */ + public static final int POS = ERRONEOUS + 1; // + + public static final int NEG = POS + 1; // - + public static final int NOT = NEG + 1; // ! + public static final int COMPL = NOT + 1; // ~ + public static final int PREINC = COMPL + 1; // ++ _ + public static final int PREDEC = PREINC + 1; // -- _ + public static final int POSTINC = PREDEC + 1; // _ ++ + public static final int POSTDEC = POSTINC + 1; // _ -- + + /** unary operator for null reference checks, only used internally. + */ + public static final int NULLCHK = POSTDEC + 1; + + /** Binary operators, of type Binary. + */ + public static final int OR = NULLCHK + 1; // || + public static final int AND = OR + 1; // && + public static final int BITOR = AND + 1; // | + public static final int BITXOR = BITOR + 1; // ^ + public static final int BITAND = BITXOR + 1; // & + public static final int EQ = BITAND + 1; // == + public static final int NE = EQ + 1; // != + public static final int LT = NE + 1; // < + public static final int GT = LT + 1; // > + public static final int LE = GT + 1; // <= + public static final int GE = LE + 1; // >= + public static final int SL = GE + 1; // << + public static final int SR = SL + 1; // >> + public static final int USR = SR + 1; // >>> + public static final int PLUS = USR + 1; // + + public static final int MINUS = PLUS + 1; // - + public static final int MUL = MINUS + 1; // * + public static final int DIV = MUL + 1; // / + public static final int MOD = DIV + 1; // % + + /** Assignment operators, of type Assignop. + */ + public static final int BITOR_ASG = MOD + 1; // |= + public static final int BITXOR_ASG = BITOR_ASG + 1; // ^= + public static final int BITAND_ASG = BITXOR_ASG + 1; // &= + + public static final int SL_ASG = SL + BITOR_ASG - BITOR; // <<= + public static final int SR_ASG = SL_ASG + 1; // >>= + public static final int USR_ASG = SR_ASG + 1; // >>>= + public static final int PLUS_ASG = USR_ASG + 1; // += + public static final int MINUS_ASG = PLUS_ASG + 1; // -= + public static final int MUL_ASG = MINUS_ASG + 1; // *= + public static final int DIV_ASG = MUL_ASG + 1; // /= + public static final int MOD_ASG = DIV_ASG + 1; // %= + + /** A synthetic let expression, of type LetExpr. + */ + public static final int LETEXPR = MOD_ASG + 1; // ala scheme + + + /** The offset between assignment operators and normal operators. + */ + public static final int ASGOffset = BITOR_ASG - BITOR; + + /* The (encoded) position in the source file. @see util.Position. + */ + public int pos; + + /* The type of this node. + */ + public Type type; + + /* The tag of this node -- one of the constants declared above. + */ + public abstract int getTag(); + + /** Convert a tree to a pretty-printed string. */ + @Override + public String toString() { + StringWriter s = new StringWriter(); + try { + new Pretty(s, false).printExpr(this); + } + catch (IOException e) { + // should never happen, because StringWriter is defined + // never to throw any IOExceptions + throw new AssertionError(e); + } + return s.toString(); + } + + /** Set position field and return this tree. + */ + public JCTree setPos(int pos) { + this.pos = pos; + return this; + } + + /** Set type field and return this tree. + */ + public JCTree setType(Type type) { + this.type = type; + return this; + } + + /** Visit this tree with a given visitor. + */ + public abstract void accept(Visitor v); + + public abstract R accept(TreeVisitor v, D d); + + /** Return a shallow copy of this tree. + */ + @Override + public Object clone() { + try { + return super.clone(); + } catch(CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + + /** Get a default position for this tree node. + */ + public DiagnosticPosition pos() { + return this; + } + + // for default DiagnosticPosition + public JCTree getTree() { + return this; + } + + // for default DiagnosticPosition + public int getStartPosition() { + return TreeInfo.getStartPos(this); + } + + // for default DiagnosticPosition + public int getPreferredPosition() { + return pos; + } + + // for default DiagnosticPosition + public int getEndPosition(Map endPosTable) { + return TreeInfo.getEndPos(this, endPosTable); + } + + /** + * Everything in one source file is kept in a TopLevel structure. + * @param pid The tree representing the package clause. + * @param sourcefile The source file name. + * @param defs All definitions in this file (ClassDef, Import, and Skip) + * @param packge The package it belongs to. + * @param namedImportScope A scope for all named imports. + * @param starImportScope A scope for all import-on-demands. + * @param lineMap Line starting positions, defined only + * if option -g is set. + * @param docComments A hashtable that stores all documentation comments + * indexed by the tree nodes they refer to. + * defined only if option -s is set. + * @param endPositions A hashtable that stores ending positions of source + * ranges indexed by the tree nodes they belong to. + * Defined only if option -Xjcov is set. + */ + public static class JCCompilationUnit extends JCTree implements CompilationUnitTree { + public List packageAnnotations; + public JCExpression pid; + public List defs; + public JavaFileObject sourcefile; + public PackageSymbol packge; + public ImportScope namedImportScope; + public StarImportScope starImportScope; + public long flags; + public Position.LineMap lineMap = null; + public Map docComments = null; + public Map endPositions = null; + protected JCCompilationUnit(List packageAnnotations, + JCExpression pid, + List defs, + JavaFileObject sourcefile, + PackageSymbol packge, + ImportScope namedImportScope, + StarImportScope starImportScope) { + this.packageAnnotations = packageAnnotations; + this.pid = pid; + this.defs = defs; + this.sourcefile = sourcefile; + this.packge = packge; + this.namedImportScope = namedImportScope; + this.starImportScope = starImportScope; + } + @Override + public void accept(Visitor v) { v.visitTopLevel(this); } + + public Kind getKind() { return Kind.COMPILATION_UNIT; } + public List getPackageAnnotations() { + return packageAnnotations; + } + public List getImports() { + ListBuffer imports = new ListBuffer(); + for (JCTree tree : defs) { + int tag = tree.getTag(); + if (tag == IMPORT) + imports.append((JCImport)tree); + else if (tag != SKIP) + break; + } + return imports.toList(); + } + public JCExpression getPackageName() { return pid; } + public JavaFileObject getSourceFile() { + return sourcefile; + } + public Position.LineMap getLineMap() { + return lineMap; + } + public List getTypeDecls() { + List typeDefs; + for (typeDefs = defs; !typeDefs.isEmpty(); typeDefs = typeDefs.tail) + if (typeDefs.head.getTag() != IMPORT) + break; + return typeDefs; + } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitCompilationUnit(this, d); + } + + @Override + public int getTag() { + return TOPLEVEL; + } + } + + /** + * An import clause. + * @param qualid The imported class(es). + */ + public static class JCImport extends JCTree implements ImportTree { + public boolean staticImport; + public JCTree qualid; + protected JCImport(JCTree qualid, boolean importStatic) { + this.qualid = qualid; + this.staticImport = importStatic; + } + @Override + public void accept(Visitor v) { v.visitImport(this); } + + public boolean isStatic() { return staticImport; } + public JCTree getQualifiedIdentifier() { return qualid; } + + public Kind getKind() { return Kind.IMPORT; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitImport(this, d); + } + + @Override + public int getTag() { + return IMPORT; + } + } + + public static abstract class JCStatement extends JCTree implements StatementTree { + @Override + public JCStatement setType(Type type) { + super.setType(type); + return this; + } + @Override + public JCStatement setPos(int pos) { + super.setPos(pos); + return this; + } + } + + public static abstract class JCExpression extends JCTree implements ExpressionTree { + @Override + public JCExpression setType(Type type) { + super.setType(type); + return this; + } + @Override + public JCExpression setPos(int pos) { + super.setPos(pos); + return this; + } + } + + /** + * A class definition. + * @param modifiers the modifiers + * @param name the name of the class + * @param typarams formal class parameters + * @param extending the classes this class extends + * @param implementing the interfaces implemented by this class + * @param defs all variables and methods defined in this class + * @param sym the symbol + */ + public static class JCClassDecl extends JCStatement implements ClassTree { + public JCModifiers mods; + public Name name; + public List typarams; + public JCExpression extending; + public List implementing; + public List defs; + public ClassSymbol sym; + protected JCClassDecl(JCModifiers mods, + Name name, + List typarams, + JCExpression extending, + List implementing, + List defs, + ClassSymbol sym) + { + this.mods = mods; + this.name = name; + this.typarams = typarams; + this.extending = extending; + this.implementing = implementing; + this.defs = defs; + this.sym = sym; + } + @Override + public void accept(Visitor v) { v.visitClassDef(this); } + + public Kind getKind() { + if ((mods.flags & Flags.ANNOTATION) != 0) + return Kind.ANNOTATION_TYPE; + else if ((mods.flags & Flags.INTERFACE) != 0) + return Kind.INTERFACE; + else if ((mods.flags & Flags.ENUM) != 0) + return Kind.ENUM; + else + return Kind.CLASS; + } + + public JCModifiers getModifiers() { return mods; } + public Name getSimpleName() { return name; } + public List getTypeParameters() { + return typarams; + } + public JCTree getExtendsClause() { return extending; } + public List getImplementsClause() { + return implementing; + } + public List getMembers() { + return defs; + } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitClass(this, d); + } + + @Override + public int getTag() { + return CLASSDEF; + } + } + + /** + * A method definition. + * @param modifiers method modifiers + * @param name method name + * @param restype type of method return value + * @param typarams type parameters + * @param params value parameters + * @param thrown exceptions thrown by this method + * @param stats statements in the method + * @param sym method symbol + */ + public static class JCMethodDecl extends JCTree implements MethodTree { + public JCModifiers mods; + public Name name; + public JCExpression restype; + public List typarams; + public List params; + public List thrown; + public JCBlock body; + public JCExpression defaultValue; // for annotation types + public MethodSymbol sym; + protected JCMethodDecl(JCModifiers mods, + Name name, + JCExpression restype, + List typarams, + List params, + List thrown, + JCBlock body, + JCExpression defaultValue, + MethodSymbol sym) + { + this.mods = mods; + this.name = name; + this.restype = restype; + this.typarams = typarams; + this.params = params; + this.thrown = thrown; + this.body = body; + this.defaultValue = defaultValue; + this.sym = sym; + } + @Override + public void accept(Visitor v) { v.visitMethodDef(this); } + + public Kind getKind() { return Kind.METHOD; } + public JCModifiers getModifiers() { return mods; } + public Name getName() { return name; } + public JCTree getReturnType() { return restype; } + public List getTypeParameters() { + return typarams; + } + public List getParameters() { + return params; + } + public List getThrows() { + return thrown; + } + public JCBlock getBody() { return body; } + public JCTree getDefaultValue() { // for annotation types + return defaultValue; + } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitMethod(this, d); + } + + @Override + public int getTag() { + return METHODDEF; + } + } + + /** + * A variable definition. + * @param modifiers variable modifiers + * @param name variable name + * @param vartype type of the variable + * @param init variables initial value + * @param sym symbol + */ + public static class JCVariableDecl extends JCStatement implements VariableTree { + public JCModifiers mods; + public Name name; + public JCExpression vartype; + public JCExpression init; + public VarSymbol sym; + protected JCVariableDecl(JCModifiers mods, + Name name, + JCExpression vartype, + JCExpression init, + VarSymbol sym) { + this.mods = mods; + this.name = name; + this.vartype = vartype; + this.init = init; + this.sym = sym; + } + @Override + public void accept(Visitor v) { v.visitVarDef(this); } + + public Kind getKind() { return Kind.VARIABLE; } + public JCModifiers getModifiers() { return mods; } + public Name getName() { return name; } + public JCTree getType() { return vartype; } + public JCExpression getInitializer() { + return init; + } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitVariable(this, d); + } + + @Override + public int getTag() { + return VARDEF; + } + } + + /** + * A no-op statement ";". + */ + public static class JCSkip extends JCStatement implements EmptyStatementTree { + protected JCSkip() { + } + @Override + public void accept(Visitor v) { v.visitSkip(this); } + + public Kind getKind() { return Kind.EMPTY_STATEMENT; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitEmptyStatement(this, d); + } + + @Override + public int getTag() { + return SKIP; + } + } + + /** + * A statement block. + * @param stats statements + * @param flags flags + */ + public static class JCBlock extends JCStatement implements BlockTree { + public long flags; + public List stats; + /** Position of closing brace, optional. */ + public int endpos = Position.NOPOS; + protected JCBlock(long flags, List stats) { + this.stats = stats; + this.flags = flags; + } + @Override + public void accept(Visitor v) { v.visitBlock(this); } + + public Kind getKind() { return Kind.BLOCK; } + public List getStatements() { + return stats; + } + public boolean isStatic() { return (flags & Flags.STATIC) != 0; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitBlock(this, d); + } + + @Override + public int getTag() { + return BLOCK; + } + } + + /** + * A do loop + */ + public static class JCDoWhileLoop extends JCStatement implements DoWhileLoopTree { + public JCStatement body; + public JCExpression cond; + protected JCDoWhileLoop(JCStatement body, JCExpression cond) { + this.body = body; + this.cond = cond; + } + @Override + public void accept(Visitor v) { v.visitDoLoop(this); } + + public Kind getKind() { return Kind.DO_WHILE_LOOP; } + public JCExpression getCondition() { return cond; } + public JCStatement getStatement() { return body; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitDoWhileLoop(this, d); + } + + @Override + public int getTag() { + return DOLOOP; + } + } + + /** + * A while loop + */ + public static class JCWhileLoop extends JCStatement implements WhileLoopTree { + public JCExpression cond; + public JCStatement body; + protected JCWhileLoop(JCExpression cond, JCStatement body) { + this.cond = cond; + this.body = body; + } + @Override + public void accept(Visitor v) { v.visitWhileLoop(this); } + + public Kind getKind() { return Kind.WHILE_LOOP; } + public JCExpression getCondition() { return cond; } + public JCStatement getStatement() { return body; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitWhileLoop(this, d); + } + + @Override + public int getTag() { + return WHILELOOP; + } + } + + /** + * A for loop. + */ + public static class JCForLoop extends JCStatement implements ForLoopTree { + public List init; + public JCExpression cond; + public List step; + public JCStatement body; + protected JCForLoop(List init, + JCExpression cond, + List update, + JCStatement body) + { + this.init = init; + this.cond = cond; + this.step = update; + this.body = body; + } + @Override + public void accept(Visitor v) { v.visitForLoop(this); } + + public Kind getKind() { return Kind.FOR_LOOP; } + public JCExpression getCondition() { return cond; } + public JCStatement getStatement() { return body; } + public List getInitializer() { + return init; + } + public List getUpdate() { + return step; + } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitForLoop(this, d); + } + + @Override + public int getTag() { + return FORLOOP; + } + } + + /** + * The enhanced for loop. + */ + public static class JCEnhancedForLoop extends JCStatement implements EnhancedForLoopTree { + public JCVariableDecl var; + public JCExpression expr; + public JCStatement body; + protected JCEnhancedForLoop(JCVariableDecl var, JCExpression expr, JCStatement body) { + this.var = var; + this.expr = expr; + this.body = body; + } + @Override + public void accept(Visitor v) { v.visitForeachLoop(this); } + + public Kind getKind() { return Kind.ENHANCED_FOR_LOOP; } + public JCVariableDecl getVariable() { return var; } + public JCExpression getExpression() { return expr; } + public JCStatement getStatement() { return body; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitEnhancedForLoop(this, d); + } + @Override + public int getTag() { + return FOREACHLOOP; + } + } + + /** + * A labelled expression or statement. + */ + public static class JCLabeledStatement extends JCStatement implements LabeledStatementTree { + public Name label; + public JCStatement body; + protected JCLabeledStatement(Name label, JCStatement body) { + this.label = label; + this.body = body; + } + @Override + public void accept(Visitor v) { v.visitLabelled(this); } + public Kind getKind() { return Kind.LABELED_STATEMENT; } + public Name getLabel() { return label; } + public JCStatement getStatement() { return body; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitLabeledStatement(this, d); + } + @Override + public int getTag() { + return LABELLED; + } + } + + /** + * A "switch ( ) { }" construction. + */ + public static class JCSwitch extends JCStatement implements SwitchTree { + public JCExpression selector; + public List cases; + protected JCSwitch(JCExpression selector, List cases) { + this.selector = selector; + this.cases = cases; + } + @Override + public void accept(Visitor v) { v.visitSwitch(this); } + + public Kind getKind() { return Kind.SWITCH; } + public JCExpression getExpression() { return selector; } + public List getCases() { return cases; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitSwitch(this, d); + } + @Override + public int getTag() { + return SWITCH; + } + } + + /** + * A "case :" of a switch. + */ + public static class JCCase extends JCStatement implements CaseTree { + public JCExpression pat; + public List stats; + protected JCCase(JCExpression pat, List stats) { + this.pat = pat; + this.stats = stats; + } + @Override + public void accept(Visitor v) { v.visitCase(this); } + + public Kind getKind() { return Kind.CASE; } + public JCExpression getExpression() { return pat; } + public List getStatements() { return stats; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitCase(this, d); + } + @Override + public int getTag() { + return CASE; + } + } + + /** + * A synchronized block. + */ + public static class JCSynchronized extends JCStatement implements SynchronizedTree { + public JCExpression lock; + public JCBlock body; + protected JCSynchronized(JCExpression lock, JCBlock body) { + this.lock = lock; + this.body = body; + } + @Override + public void accept(Visitor v) { v.visitSynchronized(this); } + + public Kind getKind() { return Kind.SYNCHRONIZED; } + public JCExpression getExpression() { return lock; } + public JCBlock getBlock() { return body; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitSynchronized(this, d); + } + @Override + public int getTag() { + return SYNCHRONIZED; + } + } + + /** + * A "try { } catch ( ) { } finally { }" block. + */ + public static class JCTry extends JCStatement implements TryTree { + public JCBlock body; + public List catchers; + public JCBlock finalizer; + public List resources; + protected JCTry(List resources, + JCBlock body, + List catchers, + JCBlock finalizer) { + this.body = body; + this.catchers = catchers; + this.finalizer = finalizer; + this.resources = resources; + } + @Override + public void accept(Visitor v) { v.visitTry(this); } + + public Kind getKind() { return Kind.TRY; } + public JCBlock getBlock() { return body; } + public List getCatches() { + return catchers; + } + public JCBlock getFinallyBlock() { return finalizer; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitTry(this, d); + } + @Override + public List getResources() { + return resources; + } + @Override + public int getTag() { + return TRY; + } + } + + /** + * A catch block. + */ + public static class JCCatch extends JCTree implements CatchTree { + public JCVariableDecl param; + public JCBlock body; + protected JCCatch(JCVariableDecl param, JCBlock body) { + this.param = param; + this.body = body; + } + @Override + public void accept(Visitor v) { v.visitCatch(this); } + + public Kind getKind() { return Kind.CATCH; } + public JCVariableDecl getParameter() { return param; } + public JCBlock getBlock() { return body; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitCatch(this, d); + } + @Override + public int getTag() { + return CATCH; + } + } + + /** + * A ( ) ? ( ) : ( ) conditional expression + */ + public static class JCConditional extends JCExpression implements ConditionalExpressionTree { + public JCExpression cond; + public JCExpression truepart; + public JCExpression falsepart; + protected JCConditional(JCExpression cond, + JCExpression truepart, + JCExpression falsepart) + { + this.cond = cond; + this.truepart = truepart; + this.falsepart = falsepart; + } + @Override + public void accept(Visitor v) { v.visitConditional(this); } + + public Kind getKind() { return Kind.CONDITIONAL_EXPRESSION; } + public JCExpression getCondition() { return cond; } + public JCExpression getTrueExpression() { return truepart; } + public JCExpression getFalseExpression() { return falsepart; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitConditionalExpression(this, d); + } + @Override + public int getTag() { + return CONDEXPR; + } + } + + /** + * An "if ( ) { } else { }" block + */ + public static class JCIf extends JCStatement implements IfTree { + public JCExpression cond; + public JCStatement thenpart; + public JCStatement elsepart; + protected JCIf(JCExpression cond, + JCStatement thenpart, + JCStatement elsepart) + { + this.cond = cond; + this.thenpart = thenpart; + this.elsepart = elsepart; + } + @Override + public void accept(Visitor v) { v.visitIf(this); } + + public Kind getKind() { return Kind.IF; } + public JCExpression getCondition() { return cond; } + public JCStatement getThenStatement() { return thenpart; } + public JCStatement getElseStatement() { return elsepart; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitIf(this, d); + } + @Override + public int getTag() { + return IF; + } + } + + /** + * an expression statement + * @param expr expression structure + */ + public static class JCExpressionStatement extends JCStatement implements ExpressionStatementTree { + public JCExpression expr; + protected JCExpressionStatement(JCExpression expr) + { + this.expr = expr; + } + @Override + public void accept(Visitor v) { v.visitExec(this); } + + public Kind getKind() { return Kind.EXPRESSION_STATEMENT; } + public JCExpression getExpression() { return expr; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitExpressionStatement(this, d); + } + @Override + public int getTag() { + return EXEC; + } + } + + /** + * A break from a loop or switch. + */ + public static class JCBreak extends JCStatement implements BreakTree { + public Name label; + public JCTree target; + protected JCBreak(Name label, JCTree target) { + this.label = label; + this.target = target; + } + @Override + public void accept(Visitor v) { v.visitBreak(this); } + + public Kind getKind() { return Kind.BREAK; } + public Name getLabel() { return label; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitBreak(this, d); + } + @Override + public int getTag() { + return BREAK; + } + } + + /** + * A continue of a loop. + */ + public static class JCContinue extends JCStatement implements ContinueTree { + public Name label; + public JCTree target; + protected JCContinue(Name label, JCTree target) { + this.label = label; + this.target = target; + } + @Override + public void accept(Visitor v) { v.visitContinue(this); } + + public Kind getKind() { return Kind.CONTINUE; } + public Name getLabel() { return label; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitContinue(this, d); + } + @Override + public int getTag() { + return CONTINUE; + } + } + + /** + * A return statement. + */ + public static class JCReturn extends JCStatement implements ReturnTree { + public JCExpression expr; + protected JCReturn(JCExpression expr) { + this.expr = expr; + } + @Override + public void accept(Visitor v) { v.visitReturn(this); } + + public Kind getKind() { return Kind.RETURN; } + public JCExpression getExpression() { return expr; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitReturn(this, d); + } + @Override + public int getTag() { + return RETURN; + } + } + + /** + * A throw statement. + */ + public static class JCThrow extends JCStatement implements ThrowTree { + public JCExpression expr; + protected JCThrow(JCTree expr) { + this.expr = (JCExpression)expr; + } + @Override + public void accept(Visitor v) { v.visitThrow(this); } + + public Kind getKind() { return Kind.THROW; } + public JCExpression getExpression() { return expr; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitThrow(this, d); + } + @Override + public int getTag() { + return THROW; + } + } + + /** + * An assert statement. + */ + public static class JCAssert extends JCStatement implements AssertTree { + public JCExpression cond; + public JCExpression detail; + protected JCAssert(JCExpression cond, JCExpression detail) { + this.cond = cond; + this.detail = detail; + } + @Override + public void accept(Visitor v) { v.visitAssert(this); } + + public Kind getKind() { return Kind.ASSERT; } + public JCExpression getCondition() { return cond; } + public JCExpression getDetail() { return detail; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitAssert(this, d); + } + @Override + public int getTag() { + return ASSERT; + } + } + + /** + * A method invocation + */ + public static class JCMethodInvocation extends JCExpression implements MethodInvocationTree { + public List typeargs; + public JCExpression meth; + public List args; + public Type varargsElement; + protected JCMethodInvocation(List typeargs, + JCExpression meth, + List args) + { + this.typeargs = (typeargs == null) ? List.nil() + : typeargs; + this.meth = meth; + this.args = args; + } + @Override + public void accept(Visitor v) { v.visitApply(this); } + + public Kind getKind() { return Kind.METHOD_INVOCATION; } + public List getTypeArguments() { + return typeargs; + } + public JCExpression getMethodSelect() { return meth; } + public List getArguments() { + return args; + } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitMethodInvocation(this, d); + } + @Override + public JCMethodInvocation setType(Type type) { + super.setType(type); + return this; + } + @Override + public int getTag() { + return(APPLY); + } + } + + /** + * A new(...) operation. + */ + public static class JCNewClass extends JCExpression implements NewClassTree { + public JCExpression encl; + public List typeargs; + public JCExpression clazz; + public List args; + public JCClassDecl def; + public Symbol constructor; + public Type varargsElement; + public Type constructorType; + protected JCNewClass(JCExpression encl, + List typeargs, + JCExpression clazz, + List args, + JCClassDecl def) + { + this.encl = encl; + this.typeargs = (typeargs == null) ? List.nil() + : typeargs; + this.clazz = clazz; + this.args = args; + this.def = def; + } + @Override + public void accept(Visitor v) { v.visitNewClass(this); } + + public Kind getKind() { return Kind.NEW_CLASS; } + public JCExpression getEnclosingExpression() { // expr.new C< ... > ( ... ) + return encl; + } + public List getTypeArguments() { + return typeargs; + } + public JCExpression getIdentifier() { return clazz; } + public List getArguments() { + return args; + } + public JCClassDecl getClassBody() { return def; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitNewClass(this, d); + } + @Override + public int getTag() { + return NEWCLASS; + } + } + + /** + * A new[...] operation. + */ + public static class JCNewArray extends JCExpression implements NewArrayTree { + public JCExpression elemtype; + public List dims; + public List elems; + protected JCNewArray(JCExpression elemtype, + List dims, + List elems) + { + this.elemtype = elemtype; + this.dims = dims; + this.elems = elems; + } + @Override + public void accept(Visitor v) { v.visitNewArray(this); } + + public Kind getKind() { return Kind.NEW_ARRAY; } + public JCExpression getType() { return elemtype; } + public List getDimensions() { + return dims; + } + public List getInitializers() { + return elems; + } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitNewArray(this, d); + } + @Override + public int getTag() { + return NEWARRAY; + } + } + + /** + * A parenthesized subexpression ( ... ) + */ + public static class JCParens extends JCExpression implements ParenthesizedTree { + public JCExpression expr; + protected JCParens(JCExpression expr) { + this.expr = expr; + } + @Override + public void accept(Visitor v) { v.visitParens(this); } + + public Kind getKind() { return Kind.PARENTHESIZED; } + public JCExpression getExpression() { return expr; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitParenthesized(this, d); + } + @Override + public int getTag() { + return PARENS; + } + } + + /** + * A assignment with "=". + */ + public static class JCAssign extends JCExpression implements AssignmentTree { + public JCExpression lhs; + public JCExpression rhs; + protected JCAssign(JCExpression lhs, JCExpression rhs) { + this.lhs = lhs; + this.rhs = rhs; + } + @Override + public void accept(Visitor v) { v.visitAssign(this); } + + public Kind getKind() { return Kind.ASSIGNMENT; } + public JCExpression getVariable() { return lhs; } + public JCExpression getExpression() { return rhs; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitAssignment(this, d); + } + @Override + public int getTag() { + return ASSIGN; + } + } + + /** + * An assignment with "+=", "|=" ... + */ + public static class JCAssignOp extends JCExpression implements CompoundAssignmentTree { + private int opcode; + public JCExpression lhs; + public JCExpression rhs; + public Symbol operator; + protected JCAssignOp(int opcode, JCTree lhs, JCTree rhs, Symbol operator) { + this.opcode = opcode; + this.lhs = (JCExpression)lhs; + this.rhs = (JCExpression)rhs; + this.operator = operator; + } + @Override + public void accept(Visitor v) { v.visitAssignop(this); } + + public Kind getKind() { return TreeInfo.tagToKind(getTag()); } + public JCExpression getVariable() { return lhs; } + public JCExpression getExpression() { return rhs; } + public Symbol getOperator() { + return operator; + } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitCompoundAssignment(this, d); + } + @Override + public int getTag() { + return opcode; + } + } + + /** + * A unary operation. + */ + public static class JCUnary extends JCExpression implements UnaryTree { + private int opcode; + public JCExpression arg; + public Symbol operator; + protected JCUnary(int opcode, JCExpression arg) { + this.opcode = opcode; + this.arg = arg; + } + @Override + public void accept(Visitor v) { v.visitUnary(this); } + + public Kind getKind() { return TreeInfo.tagToKind(getTag()); } + public JCExpression getExpression() { return arg; } + public Symbol getOperator() { + return operator; + } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitUnary(this, d); + } + @Override + public int getTag() { + return opcode; + } + + public void setTag(int tag) { + opcode = tag; + } + } + + /** + * A binary operation. + */ + public static class JCBinary extends JCExpression implements BinaryTree { + private int opcode; + public JCExpression lhs; + public JCExpression rhs; + public Symbol operator; + protected JCBinary(int opcode, + JCExpression lhs, + JCExpression rhs, + Symbol operator) { + this.opcode = opcode; + this.lhs = lhs; + this.rhs = rhs; + this.operator = operator; + } + @Override + public void accept(Visitor v) { v.visitBinary(this); } + + public Kind getKind() { return TreeInfo.tagToKind(getTag()); } + public JCExpression getLeftOperand() { return lhs; } + public JCExpression getRightOperand() { return rhs; } + public Symbol getOperator() { + return operator; + } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitBinary(this, d); + } + @Override + public int getTag() { + return opcode; + } + } + + /** + * A type cast. + */ + public static class JCTypeCast extends JCExpression implements TypeCastTree { + public JCTree clazz; + public JCExpression expr; + protected JCTypeCast(JCTree clazz, JCExpression expr) { + this.clazz = clazz; + this.expr = expr; + } + @Override + public void accept(Visitor v) { v.visitTypeCast(this); } + + public Kind getKind() { return Kind.TYPE_CAST; } + public JCTree getType() { return clazz; } + public JCExpression getExpression() { return expr; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitTypeCast(this, d); + } + @Override + public int getTag() { + return TYPECAST; + } + } + + /** + * A type test. + */ + public static class JCInstanceOf extends JCExpression implements InstanceOfTree { + public JCExpression expr; + public JCTree clazz; + protected JCInstanceOf(JCExpression expr, JCTree clazz) { + this.expr = expr; + this.clazz = clazz; + } + @Override + public void accept(Visitor v) { v.visitTypeTest(this); } + + public Kind getKind() { return Kind.INSTANCE_OF; } + public JCTree getType() { return clazz; } + public JCExpression getExpression() { return expr; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitInstanceOf(this, d); + } + @Override + public int getTag() { + return TYPETEST; + } + } + + /** + * An array selection + */ + public static class JCArrayAccess extends JCExpression implements ArrayAccessTree { + public JCExpression indexed; + public JCExpression index; + protected JCArrayAccess(JCExpression indexed, JCExpression index) { + this.indexed = indexed; + this.index = index; + } + @Override + public void accept(Visitor v) { v.visitIndexed(this); } + + public Kind getKind() { return Kind.ARRAY_ACCESS; } + public JCExpression getExpression() { return indexed; } + public JCExpression getIndex() { return index; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitArrayAccess(this, d); + } + @Override + public int getTag() { + return INDEXED; + } + } + + /** + * Selects through packages and classes + * @param selected selected Tree hierarchie + * @param selector name of field to select thru + * @param sym symbol of the selected class + */ + public static class JCFieldAccess extends JCExpression implements MemberSelectTree { + public JCExpression selected; + public Name name; + public Symbol sym; + protected JCFieldAccess(JCExpression selected, Name name, Symbol sym) { + this.selected = selected; + this.name = name; + this.sym = sym; + } + @Override + public void accept(Visitor v) { v.visitSelect(this); } + + public Kind getKind() { return Kind.MEMBER_SELECT; } + public JCExpression getExpression() { return selected; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitMemberSelect(this, d); + } + public Name getIdentifier() { return name; } + @Override + public int getTag() { + return SELECT; + } + } + + /** + * An identifier + * @param idname the name + * @param sym the symbol + */ + public static class JCIdent extends JCExpression implements IdentifierTree { + public Name name; + public Symbol sym; + protected JCIdent(Name name, Symbol sym) { + this.name = name; + this.sym = sym; + } + @Override + public void accept(Visitor v) { v.visitIdent(this); } + + public Kind getKind() { return Kind.IDENTIFIER; } + public Name getName() { return name; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitIdentifier(this, d); + } + public int getTag() { + return IDENT; + } + } + + /** + * A constant value given literally. + * @param value value representation + */ + public static class JCLiteral extends JCExpression implements LiteralTree { + public int typetag; + public Object value; + protected JCLiteral(int typetag, Object value) { + this.typetag = typetag; + this.value = value; + } + @Override + public void accept(Visitor v) { v.visitLiteral(this); } + + public Kind getKind() { + switch (typetag) { + case TypeTags.INT: + return Kind.INT_LITERAL; + case TypeTags.LONG: + return Kind.LONG_LITERAL; + case TypeTags.FLOAT: + return Kind.FLOAT_LITERAL; + case TypeTags.DOUBLE: + return Kind.DOUBLE_LITERAL; + case TypeTags.BOOLEAN: + return Kind.BOOLEAN_LITERAL; + case TypeTags.CHAR: + return Kind.CHAR_LITERAL; + case TypeTags.CLASS: + return Kind.STRING_LITERAL; + case TypeTags.BOT: + return Kind.NULL_LITERAL; + default: + throw new AssertionError("unknown literal kind " + this); + } + } + public Object getValue() { + switch (typetag) { + case TypeTags.BOOLEAN: + int bi = (Integer) value; + return (bi != 0); + case TypeTags.CHAR: + int ci = (Integer) value; + char c = (char) ci; + if (c != ci) + throw new AssertionError("bad value for char literal"); + return c; + default: + return value; + } + } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitLiteral(this, d); + } + @Override + public JCLiteral setType(Type type) { + super.setType(type); + return this; + } + @Override + public int getTag() { + return LITERAL; + } + } + + /** + * Identifies a basic type. + * @param tag the basic type id + * @see TypeTags + */ + public static class JCPrimitiveTypeTree extends JCExpression implements PrimitiveTypeTree { + public int typetag; + protected JCPrimitiveTypeTree(int typetag) { + this.typetag = typetag; + } + @Override + public void accept(Visitor v) { v.visitTypeIdent(this); } + + public Kind getKind() { return Kind.PRIMITIVE_TYPE; } + public TypeKind getPrimitiveTypeKind() { + switch (typetag) { + case TypeTags.BOOLEAN: + return TypeKind.BOOLEAN; + case TypeTags.BYTE: + return TypeKind.BYTE; + case TypeTags.SHORT: + return TypeKind.SHORT; + case TypeTags.INT: + return TypeKind.INT; + case TypeTags.LONG: + return TypeKind.LONG; + case TypeTags.CHAR: + return TypeKind.CHAR; + case TypeTags.FLOAT: + return TypeKind.FLOAT; + case TypeTags.DOUBLE: + return TypeKind.DOUBLE; + case TypeTags.VOID: + return TypeKind.VOID; + default: + throw new AssertionError("unknown primitive type " + this); + } + } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitPrimitiveType(this, d); + } + @Override + public int getTag() { + return TYPEIDENT; + } + } + + /** + * An array type, A[] + */ + public static class JCArrayTypeTree extends JCExpression implements ArrayTypeTree { + public JCExpression elemtype; + protected JCArrayTypeTree(JCExpression elemtype) { + this.elemtype = elemtype; + } + @Override + public void accept(Visitor v) { v.visitTypeArray(this); } + + public Kind getKind() { return Kind.ARRAY_TYPE; } + public JCTree getType() { return elemtype; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitArrayType(this, d); + } + @Override + public int getTag() { + return TYPEARRAY; + } + } + + /** + * A parameterized type, T<...> + */ + public static class JCTypeApply extends JCExpression implements ParameterizedTypeTree { + public JCExpression clazz; + public List arguments; + protected JCTypeApply(JCExpression clazz, List arguments) { + this.clazz = clazz; + this.arguments = arguments; + } + @Override + public void accept(Visitor v) { v.visitTypeApply(this); } + + public Kind getKind() { return Kind.PARAMETERIZED_TYPE; } + public JCTree getType() { return clazz; } + public List getTypeArguments() { + return arguments; + } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitParameterizedType(this, d); + } + @Override + public int getTag() { + return TYPEAPPLY; + } + } + + /** + * A union type, T1 | T2 | ... Tn (used in multicatch statements) + */ + public static class JCTypeUnion extends JCExpression implements UnionTypeTree { + + public List alternatives; + + protected JCTypeUnion(List components) { + this.alternatives = components; + } + @Override + public void accept(Visitor v) { v.visitTypeUnion(this); } + + public Kind getKind() { return Kind.UNION_TYPE; } + + public List getTypeAlternatives() { + return alternatives; + } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitUnionType(this, d); + } + @Override + public int getTag() { + return TYPEUNION; + } + } + + /** + * A formal class parameter. + * @param name name + * @param bounds bounds + */ + public static class JCTypeParameter extends JCTree implements TypeParameterTree { + public Name name; + public List bounds; + protected JCTypeParameter(Name name, List bounds) { + this.name = name; + this.bounds = bounds; + } + @Override + public void accept(Visitor v) { v.visitTypeParameter(this); } + + public Kind getKind() { return Kind.TYPE_PARAMETER; } + public Name getName() { return name; } + public List getBounds() { + return bounds; + } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitTypeParameter(this, d); + } + @Override + public int getTag() { + return TYPEPARAMETER; + } + } + + public static class JCWildcard extends JCExpression implements WildcardTree { + public TypeBoundKind kind; + public JCTree inner; + protected JCWildcard(TypeBoundKind kind, JCTree inner) { + kind.getClass(); // null-check + this.kind = kind; + this.inner = inner; + } + @Override + public void accept(Visitor v) { v.visitWildcard(this); } + + public Kind getKind() { + switch (kind.kind) { + case UNBOUND: + return Kind.UNBOUNDED_WILDCARD; + case EXTENDS: + return Kind.EXTENDS_WILDCARD; + case SUPER: + return Kind.SUPER_WILDCARD; + default: + throw new AssertionError("Unknown wildcard bound " + kind); + } + } + public JCTree getBound() { return inner; } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitWildcard(this, d); + } + @Override + public int getTag() { + return WILDCARD; + } + } + + public static class TypeBoundKind extends JCTree { + public BoundKind kind; + protected TypeBoundKind(BoundKind kind) { + this.kind = kind; + } + @Override + public void accept(Visitor v) { v.visitTypeBoundKind(this); } + + public Kind getKind() { + throw new AssertionError("TypeBoundKind is not part of a public API"); + } + @Override + public R accept(TreeVisitor v, D d) { + throw new AssertionError("TypeBoundKind is not part of a public API"); + } + @Override + public int getTag() { + return TYPEBOUNDKIND; + } + } + + public static class JCAnnotation extends JCExpression implements AnnotationTree { + public JCTree annotationType; + public List args; + protected JCAnnotation(JCTree annotationType, List args) { + this.annotationType = annotationType; + this.args = args; + } + @Override + public void accept(Visitor v) { v.visitAnnotation(this); } + + public Kind getKind() { return Kind.ANNOTATION; } + public JCTree getAnnotationType() { return annotationType; } + public List getArguments() { + return args; + } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitAnnotation(this, d); + } + @Override + public int getTag() { + return ANNOTATION; + } + } + + public static class JCModifiers extends JCTree implements com.sun.source.tree.ModifiersTree { + public long flags; + public List annotations; + protected JCModifiers(long flags, List annotations) { + this.flags = flags; + this.annotations = annotations; + } + @Override + public void accept(Visitor v) { v.visitModifiers(this); } + + public Kind getKind() { return Kind.MODIFIERS; } + public Set getFlags() { + return Flags.asModifierSet(flags); + } + public List getAnnotations() { + return annotations; + } + @Override + public R accept(TreeVisitor v, D d) { + return v.visitModifiers(this, d); + } + @Override + public int getTag() { + return MODIFIERS; + } + } + + public static class JCErroneous extends JCExpression + implements com.sun.source.tree.ErroneousTree { + public List errs; + protected JCErroneous(List errs) { + this.errs = errs; + } + @Override + public void accept(Visitor v) { v.visitErroneous(this); } + + public Kind getKind() { return Kind.ERRONEOUS; } + + public List getErrorTrees() { + return errs; + } + + @Override + public R accept(TreeVisitor v, D d) { + return v.visitErroneous(this, d); + } + @Override + public int getTag() { + return ERRONEOUS; + } + } + + /** (let int x = 3; in x+2) */ + public static class LetExpr extends JCExpression { + public List defs; + public JCTree expr; + protected LetExpr(List defs, JCTree expr) { + this.defs = defs; + this.expr = expr; + } + @Override + public void accept(Visitor v) { v.visitLetExpr(this); } + + public Kind getKind() { + throw new AssertionError("LetExpr is not part of a public API"); + } + @Override + public R accept(TreeVisitor v, D d) { + throw new AssertionError("LetExpr is not part of a public API"); + } + @Override + public int getTag() { + return LETEXPR; + } + } + + /** An interface for tree factories + */ + public interface Factory { + JCCompilationUnit TopLevel(List packageAnnotations, + JCExpression pid, + List defs); + JCImport Import(JCTree qualid, boolean staticImport); + JCClassDecl ClassDef(JCModifiers mods, + Name name, + List typarams, + JCExpression extending, + List implementing, + List defs); + JCMethodDecl MethodDef(JCModifiers mods, + Name name, + JCExpression restype, + List typarams, + List params, + List thrown, + JCBlock body, + JCExpression defaultValue); + JCVariableDecl VarDef(JCModifiers mods, + Name name, + JCExpression vartype, + JCExpression init); + JCSkip Skip(); + JCBlock Block(long flags, List stats); + JCDoWhileLoop DoLoop(JCStatement body, JCExpression cond); + JCWhileLoop WhileLoop(JCExpression cond, JCStatement body); + JCForLoop ForLoop(List init, + JCExpression cond, + List step, + JCStatement body); + JCEnhancedForLoop ForeachLoop(JCVariableDecl var, JCExpression expr, JCStatement body); + JCLabeledStatement Labelled(Name label, JCStatement body); + JCSwitch Switch(JCExpression selector, List cases); + JCCase Case(JCExpression pat, List stats); + JCSynchronized Synchronized(JCExpression lock, JCBlock body); + JCTry Try(JCBlock body, List catchers, JCBlock finalizer); + JCTry Try(List resources, + JCBlock body, + List catchers, + JCBlock finalizer); + JCCatch Catch(JCVariableDecl param, JCBlock body); + JCConditional Conditional(JCExpression cond, + JCExpression thenpart, + JCExpression elsepart); + JCIf If(JCExpression cond, JCStatement thenpart, JCStatement elsepart); + JCExpressionStatement Exec(JCExpression expr); + JCBreak Break(Name label); + JCContinue Continue(Name label); + JCReturn Return(JCExpression expr); + JCThrow Throw(JCTree expr); + JCAssert Assert(JCExpression cond, JCExpression detail); + JCMethodInvocation Apply(List typeargs, + JCExpression fn, + List args); + JCNewClass NewClass(JCExpression encl, + List typeargs, + JCExpression clazz, + List args, + JCClassDecl def); + JCNewArray NewArray(JCExpression elemtype, + List dims, + List elems); + JCParens Parens(JCExpression expr); + JCAssign Assign(JCExpression lhs, JCExpression rhs); + JCAssignOp Assignop(int opcode, JCTree lhs, JCTree rhs); + JCUnary Unary(int opcode, JCExpression arg); + JCBinary Binary(int opcode, JCExpression lhs, JCExpression rhs); + JCTypeCast TypeCast(JCTree expr, JCExpression type); + JCInstanceOf TypeTest(JCExpression expr, JCTree clazz); + JCArrayAccess Indexed(JCExpression indexed, JCExpression index); + JCFieldAccess Select(JCExpression selected, Name selector); + JCIdent Ident(Name idname); + JCLiteral Literal(int tag, Object value); + JCPrimitiveTypeTree TypeIdent(int typetag); + JCArrayTypeTree TypeArray(JCExpression elemtype); + JCTypeApply TypeApply(JCExpression clazz, List arguments); + JCTypeParameter TypeParameter(Name name, List bounds); + JCWildcard Wildcard(TypeBoundKind kind, JCTree type); + TypeBoundKind TypeBoundKind(BoundKind kind); + JCAnnotation Annotation(JCTree annotationType, List args); + JCModifiers Modifiers(long flags, List annotations); + JCErroneous Erroneous(List errs); + LetExpr LetExpr(List defs, JCTree expr); + } + + /** A generic visitor class for trees. + */ + public static abstract class Visitor { + public void visitTopLevel(JCCompilationUnit that) { visitTree(that); } + public void visitImport(JCImport that) { visitTree(that); } + public void visitClassDef(JCClassDecl that) { visitTree(that); } + public void visitMethodDef(JCMethodDecl that) { visitTree(that); } + public void visitVarDef(JCVariableDecl that) { visitTree(that); } + public void visitSkip(JCSkip that) { visitTree(that); } + public void visitBlock(JCBlock that) { visitTree(that); } + public void visitDoLoop(JCDoWhileLoop that) { visitTree(that); } + public void visitWhileLoop(JCWhileLoop that) { visitTree(that); } + public void visitForLoop(JCForLoop that) { visitTree(that); } + public void visitForeachLoop(JCEnhancedForLoop that) { visitTree(that); } + public void visitLabelled(JCLabeledStatement that) { visitTree(that); } + public void visitSwitch(JCSwitch that) { visitTree(that); } + public void visitCase(JCCase that) { visitTree(that); } + public void visitSynchronized(JCSynchronized that) { visitTree(that); } + public void visitTry(JCTry that) { visitTree(that); } + public void visitCatch(JCCatch that) { visitTree(that); } + public void visitConditional(JCConditional that) { visitTree(that); } + public void visitIf(JCIf that) { visitTree(that); } + public void visitExec(JCExpressionStatement that) { visitTree(that); } + public void visitBreak(JCBreak that) { visitTree(that); } + public void visitContinue(JCContinue that) { visitTree(that); } + public void visitReturn(JCReturn that) { visitTree(that); } + public void visitThrow(JCThrow that) { visitTree(that); } + public void visitAssert(JCAssert that) { visitTree(that); } + public void visitApply(JCMethodInvocation that) { visitTree(that); } + public void visitNewClass(JCNewClass that) { visitTree(that); } + public void visitNewArray(JCNewArray that) { visitTree(that); } + public void visitParens(JCParens that) { visitTree(that); } + public void visitAssign(JCAssign that) { visitTree(that); } + public void visitAssignop(JCAssignOp that) { visitTree(that); } + public void visitUnary(JCUnary that) { visitTree(that); } + public void visitBinary(JCBinary that) { visitTree(that); } + public void visitTypeCast(JCTypeCast that) { visitTree(that); } + public void visitTypeTest(JCInstanceOf that) { visitTree(that); } + public void visitIndexed(JCArrayAccess that) { visitTree(that); } + public void visitSelect(JCFieldAccess that) { visitTree(that); } + public void visitIdent(JCIdent that) { visitTree(that); } + public void visitLiteral(JCLiteral that) { visitTree(that); } + public void visitTypeIdent(JCPrimitiveTypeTree that) { visitTree(that); } + public void visitTypeArray(JCArrayTypeTree that) { visitTree(that); } + public void visitTypeApply(JCTypeApply that) { visitTree(that); } + public void visitTypeUnion(JCTypeUnion that) { visitTree(that); } + public void visitTypeParameter(JCTypeParameter that) { visitTree(that); } + public void visitWildcard(JCWildcard that) { visitTree(that); } + public void visitTypeBoundKind(TypeBoundKind that) { visitTree(that); } + public void visitAnnotation(JCAnnotation that) { visitTree(that); } + public void visitModifiers(JCModifiers that) { visitTree(that); } + public void visitErroneous(JCErroneous that) { visitTree(that); } + public void visitLetExpr(LetExpr that) { visitTree(that); } + + public void visitTree(JCTree that) { Assert.error(); } + } + +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/tree/Pretty.java b/douyu-javac/src/main/java/com/sun/tools/javac/tree/Pretty.java new file mode 100644 index 0000000..a332cde --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/tree/Pretty.java @@ -0,0 +1,1258 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.tree; + +import java.io.*; +import java.util.*; + +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.code.*; + +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.tree.JCTree.*; + +import static com.sun.tools.javac.code.Flags.*; + +/** Prints out a tree as an indented Java source program. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Pretty extends JCTree.Visitor { + + public Pretty(Writer out, boolean sourceOutput) { + this.out = out; + this.sourceOutput = sourceOutput; + } + + /** Set when we are producing source output. If we're not + * producing source output, we can sometimes give more detail in + * the output even though that detail would not be valid java + * source. + */ + private final boolean sourceOutput; + + /** The output stream on which trees are printed. + */ + Writer out; + + /** Indentation width (can be reassigned from outside). + */ + public int width = 4; + + /** The current left margin. + */ + int lmargin = 0; + + /** The enclosing class name. + */ + Name enclClassName; + + /** A hashtable mapping trees to their documentation comments + * (can be null) + */ + Map docComments = null; + + /** Align code to be indented to left margin. + */ + void align() throws IOException { + for (int i = 0; i < lmargin; i++) out.write(" "); + } + + /** Increase left margin by indentation width. + */ + void indent() { + lmargin = lmargin + width; + } + + /** Decrease left margin by indentation width. + */ + void undent() { + lmargin = lmargin - width; + } + + /** Enter a new precedence level. Emit a `(' if new precedence level + * is less than precedence level so far. + * @param contextPrec The precedence level in force so far. + * @param ownPrec The new precedence level. + */ + void open(int contextPrec, int ownPrec) throws IOException { + if (ownPrec < contextPrec) out.write("("); + } + + /** Leave precedence level. Emit a `(' if inner precedence level + * is less than precedence level we revert to. + * @param contextPrec The precedence level we revert to. + * @param ownPrec The inner precedence level. + */ + void close(int contextPrec, int ownPrec) throws IOException { + if (ownPrec < contextPrec) out.write(")"); + } + + /** Print string, replacing all non-ascii character with unicode escapes. + */ + public void print(Object s) throws IOException { + out.write(Convert.escapeUnicode(s.toString())); + } + + /** Print new line. + */ + public void println() throws IOException { + out.write(lineSep); + } + + String lineSep = System.getProperty("line.separator"); + + /************************************************************************** + * Traversal methods + *************************************************************************/ + + /** Exception to propogate IOException through visitXXX methods */ + private static class UncheckedIOException extends Error { + static final long serialVersionUID = -4032692679158424751L; + UncheckedIOException(IOException e) { + super(e.getMessage(), e); + } + } + + /** Visitor argument: the current precedence level. + */ + int prec; + + /** Visitor method: print expression tree. + * @param prec The current precedence level. + */ + public void printExpr(JCTree tree, int prec) throws IOException { + int prevPrec = this.prec; + try { + this.prec = prec; + if (tree == null) print("/*missing*/"); + else { + tree.accept(this); + } + } catch (UncheckedIOException ex) { + IOException e = new IOException(ex.getMessage()); + e.initCause(ex); + throw e; + } finally { + this.prec = prevPrec; + } + } + + /** Derived visitor method: print expression tree at minimum precedence level + * for expression. + */ + public void printExpr(JCTree tree) throws IOException { + printExpr(tree, TreeInfo.noPrec); + } + + /** Derived visitor method: print statement tree. + */ + public void printStat(JCTree tree) throws IOException { + printExpr(tree, TreeInfo.notExpression); + } + + /** Derived visitor method: print list of expression trees, separated by given string. + * @param sep the separator string + */ + public void printExprs(List trees, String sep) throws IOException { + if (trees.nonEmpty()) { + printExpr(trees.head); + for (List l = trees.tail; l.nonEmpty(); l = l.tail) { + print(sep); + printExpr(l.head); + } + } + } + + /** Derived visitor method: print list of expression trees, separated by commas. + */ + public void printExprs(List trees) throws IOException { + printExprs(trees, ", "); + } + + /** Derived visitor method: print list of statements, each on a separate line. + */ + public void printStats(List trees) throws IOException { + for (List l = trees; l.nonEmpty(); l = l.tail) { + align(); + printStat(l.head); + println(); + } + } + + /** Print a set of modifiers. + */ + public void printFlags(long flags) throws IOException { + if ((flags & SYNTHETIC) != 0) print("/*synthetic*/ "); + print(TreeInfo.flagNames(flags)); + if ((flags & StandardFlags) != 0) print(" "); + if ((flags & ANNOTATION) != 0) print("@"); + } + + public void printAnnotations(List trees) throws IOException { + for (List l = trees; l.nonEmpty(); l = l.tail) { + printStat(l.head); + println(); + align(); + } + } + + /** Print documentation comment, if it exists + * @param tree The tree for which a documentation comment should be printed. + */ + public void printDocComment(JCTree tree) throws IOException { + if (docComments != null) { + String dc = docComments.get(tree); + if (dc != null) { + print("/**"); println(); + int pos = 0; + int endpos = lineEndPos(dc, pos); + while (pos < dc.length()) { + align(); + print(" *"); + if (pos < dc.length() && dc.charAt(pos) > ' ') print(" "); + print(dc.substring(pos, endpos)); println(); + pos = endpos + 1; + endpos = lineEndPos(dc, pos); + } + align(); print(" */"); println(); + align(); + } + } + } +//where + static int lineEndPos(String s, int start) { + int pos = s.indexOf('\n', start); + if (pos < 0) pos = s.length(); + return pos; + } + + /** If type parameter list is non-empty, print it enclosed in "<...>" brackets. + */ + public void printTypeParameters(List trees) throws IOException { + if (trees.nonEmpty()) { + print("<"); + printExprs(trees); + print(">"); + } + } + + /** Print a block. + */ + public void printBlock(List stats) throws IOException { + print("{"); + println(); + indent(); + printStats(stats); + undent(); + align(); + print("}"); + } + + /** Print a block. + */ + public void printEnumBody(List stats) throws IOException { + print("{"); + println(); + indent(); + boolean first = true; + for (List l = stats; l.nonEmpty(); l = l.tail) { + if (isEnumerator(l.head)) { + if (!first) { + print(","); + println(); + } + align(); + printStat(l.head); + first = false; + } + } + print(";"); + println(); + for (List l = stats; l.nonEmpty(); l = l.tail) { + if (!isEnumerator(l.head)) { + align(); + printStat(l.head); + println(); + } + } + undent(); + align(); + print("}"); + } + + /** Is the given tree an enumerator definition? */ + boolean isEnumerator(JCTree t) { + return t.getTag() == JCTree.VARDEF && (((JCVariableDecl) t).mods.flags & ENUM) != 0; + } + + /** Print unit consisting of package clause and import statements in toplevel, + * followed by class definition. if class definition == null, + * print all definitions in toplevel. + * @param tree The toplevel tree + * @param cdef The class definition, which is assumed to be part of the + * toplevel tree. + */ + public void printUnit(JCCompilationUnit tree, JCClassDecl cdef) throws IOException { + docComments = tree.docComments; + printDocComment(tree); + if (tree.pid != null) { + print("package "); + printExpr(tree.pid); + print(";"); + println(); + } + boolean firstImport = true; + for (List l = tree.defs; + l.nonEmpty() && (cdef == null || l.head.getTag() == JCTree.IMPORT); + l = l.tail) { + if (l.head.getTag() == JCTree.IMPORT) { + JCImport imp = (JCImport)l.head; + Name name = TreeInfo.name(imp.qualid); + if (name == name.table.names.asterisk || + cdef == null || + isUsed(TreeInfo.symbol(imp.qualid), cdef)) { + if (firstImport) { + firstImport = false; + println(); + } + printStat(imp); + } + } else { + printStat(l.head); + } + } + if (cdef != null) { + printStat(cdef); + println(); + } + } + // where + boolean isUsed(final Symbol t, JCTree cdef) { + class UsedVisitor extends TreeScanner { + public void scan(JCTree tree) { + if (tree!=null && !result) tree.accept(this); + } + boolean result = false; + public void visitIdent(JCIdent tree) { + if (tree.sym == t) result = true; + } + } + UsedVisitor v = new UsedVisitor(); + v.scan(cdef); + return v.result; + } + + /************************************************************************** + * Visitor methods + *************************************************************************/ + + public void visitTopLevel(JCCompilationUnit tree) { + try { + printUnit(tree, null); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitImport(JCImport tree) { + try { + print("import "); + if (tree.staticImport) print("static "); + printExpr(tree.qualid); + print(";"); + println(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitClassDef(JCClassDecl tree) { + try { + println(); align(); + printDocComment(tree); + printAnnotations(tree.mods.annotations); + printFlags(tree.mods.flags & ~INTERFACE); + Name enclClassNamePrev = enclClassName; + enclClassName = tree.name; + if ((tree.mods.flags & INTERFACE) != 0) { + print("interface " + tree.name); + printTypeParameters(tree.typarams); + if (tree.implementing.nonEmpty()) { + print(" extends "); + printExprs(tree.implementing); + } + } else { + if ((tree.mods.flags & ENUM) != 0) + print("enum " + tree.name); + else + print("class " + tree.name); + printTypeParameters(tree.typarams); + if (tree.extending != null) { + print(" extends "); + printExpr(tree.extending); + } + if (tree.implementing.nonEmpty()) { + print(" implements "); + printExprs(tree.implementing); + } + } + print(" "); + if ((tree.mods.flags & ENUM) != 0) { + printEnumBody(tree.defs); + } else { + printBlock(tree.defs); + } + enclClassName = enclClassNamePrev; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitMethodDef(JCMethodDecl tree) { + try { + // when producing source output, omit anonymous constructors + if (tree.name == tree.name.table.names.init && + enclClassName == null && + sourceOutput) return; + println(); align(); + printDocComment(tree); + printExpr(tree.mods); + printTypeParameters(tree.typarams); + if (tree.name == tree.name.table.names.init) { + print(enclClassName != null ? enclClassName : tree.name); + } else { + printExpr(tree.restype); + print(" " + tree.name); + } + print("("); + printExprs(tree.params); + print(")"); + if (tree.thrown.nonEmpty()) { + print(" throws "); + printExprs(tree.thrown); + } + if (tree.defaultValue != null) { + print(" default "); + printExpr(tree.defaultValue); + } + if (tree.body != null) { + print(" "); + printStat(tree.body); + } else { + print(";"); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitVarDef(JCVariableDecl tree) { + try { + if (docComments != null && docComments.get(tree) != null) { + println(); align(); + } + printDocComment(tree); + if ((tree.mods.flags & ENUM) != 0) { + print("/*public static final*/ "); + print(tree.name); + if (tree.init != null) { + if (sourceOutput && tree.init.getTag() == JCTree.NEWCLASS) { + print(" /*enum*/ "); + JCNewClass init = (JCNewClass) tree.init; + if (init.args != null && init.args.nonEmpty()) { + print("("); + print(init.args); + print(")"); + } + if (init.def != null && init.def.defs != null) { + print(" "); + printBlock(init.def.defs); + } + return; + } + print(" /* = "); + printExpr(tree.init); + print(" */"); + } + } else { + printExpr(tree.mods); + if ((tree.mods.flags & VARARGS) != 0) { + printExpr(((JCArrayTypeTree) tree.vartype).elemtype); + print("... " + tree.name); + } else { + printExpr(tree.vartype); + print(" " + tree.name); + } + if (tree.init != null) { + print(" = "); + printExpr(tree.init); + } + if (prec == TreeInfo.notExpression) print(";"); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitSkip(JCSkip tree) { + try { + print(";"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitBlock(JCBlock tree) { + try { + printFlags(tree.flags); + printBlock(tree.stats); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitDoLoop(JCDoWhileLoop tree) { + try { + print("do "); + printStat(tree.body); + align(); + print(" while "); + if (tree.cond.getTag() == JCTree.PARENS) { + printExpr(tree.cond); + } else { + print("("); + printExpr(tree.cond); + print(")"); + } + print(";"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitWhileLoop(JCWhileLoop tree) { + try { + print("while "); + if (tree.cond.getTag() == JCTree.PARENS) { + printExpr(tree.cond); + } else { + print("("); + printExpr(tree.cond); + print(")"); + } + print(" "); + printStat(tree.body); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitForLoop(JCForLoop tree) { + try { + print("for ("); + if (tree.init.nonEmpty()) { + if (tree.init.head.getTag() == JCTree.VARDEF) { + printExpr(tree.init.head); + for (List l = tree.init.tail; l.nonEmpty(); l = l.tail) { + JCVariableDecl vdef = (JCVariableDecl)l.head; + print(", " + vdef.name + " = "); + printExpr(vdef.init); + } + } else { + printExprs(tree.init); + } + } + print("; "); + if (tree.cond != null) printExpr(tree.cond); + print("; "); + printExprs(tree.step); + print(") "); + printStat(tree.body); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitForeachLoop(JCEnhancedForLoop tree) { + try { + print("for ("); + printExpr(tree.var); + print(" : "); + printExpr(tree.expr); + print(") "); + printStat(tree.body); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitLabelled(JCLabeledStatement tree) { + try { + print(tree.label + ": "); + printStat(tree.body); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitSwitch(JCSwitch tree) { + try { + print("switch "); + if (tree.selector.getTag() == JCTree.PARENS) { + printExpr(tree.selector); + } else { + print("("); + printExpr(tree.selector); + print(")"); + } + print(" {"); + println(); + printStats(tree.cases); + align(); + print("}"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitCase(JCCase tree) { + try { + if (tree.pat == null) { + print("default"); + } else { + print("case "); + printExpr(tree.pat); + } + print(": "); + println(); + indent(); + printStats(tree.stats); + undent(); + align(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitSynchronized(JCSynchronized tree) { + try { + print("synchronized "); + if (tree.lock.getTag() == JCTree.PARENS) { + printExpr(tree.lock); + } else { + print("("); + printExpr(tree.lock); + print(")"); + } + print(" "); + printStat(tree.body); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitTry(JCTry tree) { + try { + print("try "); + if (tree.resources.nonEmpty()) { + print("("); + boolean first = true; + for (JCTree var : tree.resources) { + if (!first) { + println(); + indent(); + } + printStat(var); + first = false; + } + print(") "); + } + printStat(tree.body); + for (List l = tree.catchers; l.nonEmpty(); l = l.tail) { + printStat(l.head); + } + if (tree.finalizer != null) { + print(" finally "); + printStat(tree.finalizer); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitCatch(JCCatch tree) { + try { + print(" catch ("); + printExpr(tree.param); + print(") "); + printStat(tree.body); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitConditional(JCConditional tree) { + try { + open(prec, TreeInfo.condPrec); + printExpr(tree.cond, TreeInfo.condPrec); + print(" ? "); + printExpr(tree.truepart, TreeInfo.condPrec); + print(" : "); + printExpr(tree.falsepart, TreeInfo.condPrec); + close(prec, TreeInfo.condPrec); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitIf(JCIf tree) { + try { + print("if "); + if (tree.cond.getTag() == JCTree.PARENS) { + printExpr(tree.cond); + } else { + print("("); + printExpr(tree.cond); + print(")"); + } + print(" "); + printStat(tree.thenpart); + if (tree.elsepart != null) { + print(" else "); + printStat(tree.elsepart); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitExec(JCExpressionStatement tree) { + try { + printExpr(tree.expr); + if (prec == TreeInfo.notExpression) print(";"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitBreak(JCBreak tree) { + try { + print("break"); + if (tree.label != null) print(" " + tree.label); + print(";"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitContinue(JCContinue tree) { + try { + print("continue"); + if (tree.label != null) print(" " + tree.label); + print(";"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitReturn(JCReturn tree) { + try { + print("return"); + if (tree.expr != null) { + print(" "); + printExpr(tree.expr); + } + print(";"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitThrow(JCThrow tree) { + try { + print("throw "); + printExpr(tree.expr); + print(";"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitAssert(JCAssert tree) { + try { + print("assert "); + printExpr(tree.cond); + if (tree.detail != null) { + print(" : "); + printExpr(tree.detail); + } + print(";"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitApply(JCMethodInvocation tree) { + try { + if (!tree.typeargs.isEmpty()) { + if (tree.meth.getTag() == JCTree.SELECT) { + JCFieldAccess left = (JCFieldAccess)tree.meth; + printExpr(left.selected); + print(".<"); + printExprs(tree.typeargs); + print(">" + left.name); + } else { + print("<"); + printExprs(tree.typeargs); + print(">"); + printExpr(tree.meth); + } + } else { + printExpr(tree.meth); + } + print("("); + printExprs(tree.args); + print(")"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitNewClass(JCNewClass tree) { + try { + if (tree.encl != null) { + printExpr(tree.encl); + print("."); + } + print("new "); + if (!tree.typeargs.isEmpty()) { + print("<"); + printExprs(tree.typeargs); + print(">"); + } + printExpr(tree.clazz); + print("("); + printExprs(tree.args); + print(")"); + if (tree.def != null) { + Name enclClassNamePrev = enclClassName; + enclClassName = + tree.def.name != null ? tree.def.name : + tree.type != null && tree.type.tsym.name != tree.type.tsym.name.table.names.empty + ? tree.type.tsym.name : null; + if ((tree.def.mods.flags & Flags.ENUM) != 0) print("/*enum*/"); + printBlock(tree.def.defs); + enclClassName = enclClassNamePrev; + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitNewArray(JCNewArray tree) { + try { + if (tree.elemtype != null) { + print("new "); + JCTree elem = tree.elemtype; + if (elem.getTag() == JCTree.TYPEARRAY) + printBaseElementType((JCArrayTypeTree) elem); + else + printExpr(elem); + for (List l = tree.dims; l.nonEmpty(); l = l.tail) { + print("["); + printExpr(l.head); + print("]"); + } + if (elem instanceof JCArrayTypeTree) + printBrackets((JCArrayTypeTree) elem); + } + if (tree.elems != null) { + if (tree.elemtype != null) print("[]"); + print("{"); + printExprs(tree.elems); + print("}"); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitParens(JCParens tree) { + try { + print("("); + printExpr(tree.expr); + print(")"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitAssign(JCAssign tree) { + try { + open(prec, TreeInfo.assignPrec); + printExpr(tree.lhs, TreeInfo.assignPrec + 1); + print(" = "); + printExpr(tree.rhs, TreeInfo.assignPrec); + close(prec, TreeInfo.assignPrec); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public String operatorName(int tag) { + switch(tag) { + case JCTree.POS: return "+"; + case JCTree.NEG: return "-"; + case JCTree.NOT: return "!"; + case JCTree.COMPL: return "~"; + case JCTree.PREINC: return "++"; + case JCTree.PREDEC: return "--"; + case JCTree.POSTINC: return "++"; + case JCTree.POSTDEC: return "--"; + case JCTree.NULLCHK: return "<*nullchk*>"; + case JCTree.OR: return "||"; + case JCTree.AND: return "&&"; + case JCTree.EQ: return "=="; + case JCTree.NE: return "!="; + case JCTree.LT: return "<"; + case JCTree.GT: return ">"; + case JCTree.LE: return "<="; + case JCTree.GE: return ">="; + case JCTree.BITOR: return "|"; + case JCTree.BITXOR: return "^"; + case JCTree.BITAND: return "&"; + case JCTree.SL: return "<<"; + case JCTree.SR: return ">>"; + case JCTree.USR: return ">>>"; + case JCTree.PLUS: return "+"; + case JCTree.MINUS: return "-"; + case JCTree.MUL: return "*"; + case JCTree.DIV: return "/"; + case JCTree.MOD: return "%"; + default: throw new Error(); + } + } + + public void visitAssignop(JCAssignOp tree) { + try { + open(prec, TreeInfo.assignopPrec); + printExpr(tree.lhs, TreeInfo.assignopPrec + 1); + print(" " + operatorName(tree.getTag() - JCTree.ASGOffset) + "= "); + printExpr(tree.rhs, TreeInfo.assignopPrec); + close(prec, TreeInfo.assignopPrec); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitUnary(JCUnary tree) { + try { + int ownprec = TreeInfo.opPrec(tree.getTag()); + String opname = operatorName(tree.getTag()); + open(prec, ownprec); + if (tree.getTag() <= JCTree.PREDEC) { + print(opname); + printExpr(tree.arg, ownprec); + } else { + printExpr(tree.arg, ownprec); + print(opname); + } + close(prec, ownprec); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitBinary(JCBinary tree) { + try { + int ownprec = TreeInfo.opPrec(tree.getTag()); + String opname = operatorName(tree.getTag()); + open(prec, ownprec); + printExpr(tree.lhs, ownprec); + print(" " + opname + " "); + printExpr(tree.rhs, ownprec + 1); + close(prec, ownprec); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitTypeCast(JCTypeCast tree) { + try { + open(prec, TreeInfo.prefixPrec); + print("("); + printExpr(tree.clazz); + print(")"); + printExpr(tree.expr, TreeInfo.prefixPrec); + close(prec, TreeInfo.prefixPrec); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitTypeTest(JCInstanceOf tree) { + try { + open(prec, TreeInfo.ordPrec); + printExpr(tree.expr, TreeInfo.ordPrec); + print(" instanceof "); + printExpr(tree.clazz, TreeInfo.ordPrec + 1); + close(prec, TreeInfo.ordPrec); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitIndexed(JCArrayAccess tree) { + try { + printExpr(tree.indexed, TreeInfo.postfixPrec); + print("["); + printExpr(tree.index); + print("]"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitSelect(JCFieldAccess tree) { + try { + printExpr(tree.selected, TreeInfo.postfixPrec); + print("." + tree.name); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitIdent(JCIdent tree) { + try { + print(tree.name); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitLiteral(JCLiteral tree) { + try { + switch (tree.typetag) { + case TypeTags.INT: + print(tree.value.toString()); + break; + case TypeTags.LONG: + print(tree.value + "L"); + break; + case TypeTags.FLOAT: + print(tree.value + "F"); + break; + case TypeTags.DOUBLE: + print(tree.value.toString()); + break; + case TypeTags.CHAR: + print("\'" + + Convert.quote( + String.valueOf((char)((Number)tree.value).intValue())) + + "\'"); + break; + case TypeTags.BOOLEAN: + print(((Number)tree.value).intValue() == 1 ? "true" : "false"); + break; + case TypeTags.BOT: + print("null"); + break; + default: + print("\"" + Convert.quote(tree.value.toString()) + "\""); + break; + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitTypeIdent(JCPrimitiveTypeTree tree) { + try { + switch(tree.typetag) { + case TypeTags.BYTE: + print("byte"); + break; + case TypeTags.CHAR: + print("char"); + break; + case TypeTags.SHORT: + print("short"); + break; + case TypeTags.INT: + print("int"); + break; + case TypeTags.LONG: + print("long"); + break; + case TypeTags.FLOAT: + print("float"); + break; + case TypeTags.DOUBLE: + print("double"); + break; + case TypeTags.BOOLEAN: + print("boolean"); + break; + case TypeTags.VOID: + print("void"); + break; + default: + print("error"); + break; + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitTypeArray(JCArrayTypeTree tree) { + try { + printBaseElementType(tree); + printBrackets(tree); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + // Prints the inner element type of a nested array + private void printBaseElementType(JCTree tree) throws IOException { + printExpr(TreeInfo.innermostType(tree)); + } + + // prints the brackets of a nested array in reverse order + private void printBrackets(JCArrayTypeTree tree) throws IOException { + JCTree elem; + while (true) { + elem = tree.elemtype; + print("[]"); + if (elem.getTag() != JCTree.TYPEARRAY) break; + tree = (JCArrayTypeTree) elem; + } + } + + public void visitTypeApply(JCTypeApply tree) { + try { + printExpr(tree.clazz); + print("<"); + printExprs(tree.arguments); + print(">"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitTypeUnion(JCTypeUnion tree) { + try { + printExprs(tree.alternatives, " | "); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitTypeParameter(JCTypeParameter tree) { + try { + print(tree.name); + if (tree.bounds.nonEmpty()) { + print(" extends "); + printExprs(tree.bounds, " & "); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public void visitWildcard(JCWildcard tree) { + try { + print(tree.kind); + if (tree.kind.kind != BoundKind.UNBOUND) + printExpr(tree.inner); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public void visitTypeBoundKind(TypeBoundKind tree) { + try { + print(String.valueOf(tree.kind)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitErroneous(JCErroneous tree) { + try { + print("(ERROR)"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitLetExpr(LetExpr tree) { + try { + print("(let " + tree.defs + " in " + tree.expr + ")"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitModifiers(JCModifiers mods) { + try { + printAnnotations(mods.annotations); + printFlags(mods.flags); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitAnnotation(JCAnnotation tree) { + try { + print("@"); + printExpr(tree.annotationType); + print("("); + printExprs(tree.args); + print(")"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void visitTree(JCTree tree) { + try { + print("(UNKNOWN: " + tree + ")"); + println(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/tree/TreeCopier.java b/douyu-javac/src/main/java/com/sun/tools/javac/tree/TreeCopier.java new file mode 100644 index 0000000..4b96be6 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/tree/TreeCopier.java @@ -0,0 +1,420 @@ +/* + * Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.tree; + +import com.sun.source.tree.*; +import com.sun.tools.javac.tree.JCTree.*; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; + +/** + * Creates a copy of a tree, using a given TreeMaker. + * Names, literal values, etc are shared with the original. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class TreeCopier

implements TreeVisitor { + private TreeMaker M; + + /** Creates a new instance of TreeCopier */ + public TreeCopier(TreeMaker M) { + this.M = M; + } + + public T copy(T tree) { + return copy(tree, null); + } + + @SuppressWarnings("unchecked") + public T copy(T tree, P p) { + if (tree == null) + return null; + return (T) (tree.accept(this, p)); + } + + public List copy(List trees) { + return copy(trees, null); + } + + public List copy(List trees, P p) { + if (trees == null) + return null; + ListBuffer lb = new ListBuffer(); + for (T tree: trees) + lb.append(copy(tree, p)); + return lb.toList(); + } + + public JCTree visitAnnotation(AnnotationTree node, P p) { + JCAnnotation t = (JCAnnotation) node; + JCTree annotationType = copy(t.annotationType, p); + List args = copy(t.args, p); + return M.at(t.pos).Annotation(annotationType, args); + } + + public JCTree visitAssert(AssertTree node, P p) { + JCAssert t = (JCAssert) node; + JCExpression cond = copy(t.cond, p); + JCExpression detail = copy(t.detail, p); + return M.at(t.pos).Assert(cond, detail); + } + + public JCTree visitAssignment(AssignmentTree node, P p) { + JCAssign t = (JCAssign) node; + JCExpression lhs = copy(t.lhs, p); + JCExpression rhs = copy(t.rhs, p); + return M.at(t.pos).Assign(lhs, rhs); + } + + public JCTree visitCompoundAssignment(CompoundAssignmentTree node, P p) { + JCAssignOp t = (JCAssignOp) node; + JCTree lhs = copy(t.lhs, p); + JCTree rhs = copy(t.rhs, p); + return M.at(t.pos).Assignop(t.getTag(), lhs, rhs); + } + + public JCTree visitBinary(BinaryTree node, P p) { + JCBinary t = (JCBinary) node; + JCExpression lhs = copy(t.lhs, p); + JCExpression rhs = copy(t.rhs, p); + return M.at(t.pos).Binary(t.getTag(), lhs, rhs); + } + + public JCTree visitBlock(BlockTree node, P p) { + JCBlock t = (JCBlock) node; + List stats = copy(t.stats, p); + return M.at(t.pos).Block(t.flags, stats); + } + + public JCTree visitBreak(BreakTree node, P p) { + JCBreak t = (JCBreak) node; + return M.at(t.pos).Break(t.label); + } + + public JCTree visitCase(CaseTree node, P p) { + JCCase t = (JCCase) node; + JCExpression pat = copy(t.pat, p); + List stats = copy(t.stats, p); + return M.at(t.pos).Case(pat, stats); + } + + public JCTree visitCatch(CatchTree node, P p) { + JCCatch t = (JCCatch) node; + JCVariableDecl param = copy(t.param, p); + JCBlock body = copy(t.body, p); + return M.at(t.pos).Catch(param, body); + } + + public JCTree visitClass(ClassTree node, P p) { + JCClassDecl t = (JCClassDecl) node; + JCModifiers mods = copy(t.mods, p); + List typarams = copy(t.typarams, p); + JCExpression extending = copy(t.extending, p); + List implementing = copy(t.implementing, p); + List defs = copy(t.defs, p); + return M.at(t.pos).ClassDef(mods, t.name, typarams, extending, implementing, defs); + } + + public JCTree visitConditionalExpression(ConditionalExpressionTree node, P p) { + JCConditional t = (JCConditional) node; + JCExpression cond = copy(t.cond, p); + JCExpression truepart = copy(t.truepart, p); + JCExpression falsepart = copy(t.falsepart, p); + return M.at(t.pos).Conditional(cond, truepart, falsepart); + } + + public JCTree visitContinue(ContinueTree node, P p) { + JCContinue t = (JCContinue) node; + return M.at(t.pos).Continue(t.label); + } + + public JCTree visitDoWhileLoop(DoWhileLoopTree node, P p) { + JCDoWhileLoop t = (JCDoWhileLoop) node; + JCStatement body = copy(t.body, p); + JCExpression cond = copy(t.cond, p); + return M.at(t.pos).DoLoop(body, cond); + } + + public JCTree visitErroneous(ErroneousTree node, P p) { + JCErroneous t = (JCErroneous) node; + List errs = copy(t.errs, p); + return M.at(t.pos).Erroneous(errs); + } + + public JCTree visitExpressionStatement(ExpressionStatementTree node, P p) { + JCExpressionStatement t = (JCExpressionStatement) node; + JCExpression expr = copy(t.expr, p); + return M.at(t.pos).Exec(expr); + } + + public JCTree visitEnhancedForLoop(EnhancedForLoopTree node, P p) { + JCEnhancedForLoop t = (JCEnhancedForLoop) node; + JCVariableDecl var = copy(t.var, p); + JCExpression expr = copy(t.expr, p); + JCStatement body = copy(t.body, p); + return M.at(t.pos).ForeachLoop(var, expr, body); + } + + public JCTree visitForLoop(ForLoopTree node, P p) { + JCForLoop t = (JCForLoop) node; + List init = copy(t.init, p); + JCExpression cond = copy(t.cond, p); + List step = copy(t.step, p); + JCStatement body = copy(t.body, p); + return M.at(t.pos).ForLoop(init, cond, step, body); + } + + public JCTree visitIdentifier(IdentifierTree node, P p) { + JCIdent t = (JCIdent) node; + return M.at(t.pos).Ident(t.name); + } + + public JCTree visitIf(IfTree node, P p) { + JCIf t = (JCIf) node; + JCExpression cond = copy(t.cond, p); + JCStatement thenpart = copy(t.thenpart, p); + JCStatement elsepart = copy(t.elsepart, p); + return M.at(t.pos).If(cond, thenpart, elsepart); + } + + public JCTree visitImport(ImportTree node, P p) { + JCImport t = (JCImport) node; + JCTree qualid = copy(t.qualid, p); + return M.at(t.pos).Import(qualid, t.staticImport); + } + + public JCTree visitArrayAccess(ArrayAccessTree node, P p) { + JCArrayAccess t = (JCArrayAccess) node; + JCExpression indexed = copy(t.indexed, p); + JCExpression index = copy(t.index, p); + return M.at(t.pos).Indexed(indexed, index); + } + + public JCTree visitLabeledStatement(LabeledStatementTree node, P p) { + JCLabeledStatement t = (JCLabeledStatement) node; + JCStatement body = copy(t.body, p); + return M.at(t.pos).Labelled(t.label, t.body); + } + + public JCTree visitLiteral(LiteralTree node, P p) { + JCLiteral t = (JCLiteral) node; + return M.at(t.pos).Literal(t.typetag, t.value); + } + + public JCTree visitMethod(MethodTree node, P p) { + JCMethodDecl t = (JCMethodDecl) node; + JCModifiers mods = copy(t.mods, p); + JCExpression restype = copy(t.restype, p); + List typarams = copy(t.typarams, p); + List params = copy(t.params, p); + List thrown = copy(t.thrown, p); + JCBlock body = copy(t.body, p); + JCExpression defaultValue = copy(t.defaultValue, p); + return M.at(t.pos).MethodDef(mods, t.name, restype, typarams, params, thrown, body, defaultValue); + } + + public JCTree visitMethodInvocation(MethodInvocationTree node, P p) { + JCMethodInvocation t = (JCMethodInvocation) node; + List typeargs = copy(t.typeargs, p); + JCExpression meth = copy(t.meth, p); + List args = copy(t.args, p); + return M.at(t.pos).Apply(typeargs, meth, args); + } + + public JCTree visitModifiers(ModifiersTree node, P p) { + JCModifiers t = (JCModifiers) node; + List annotations = copy(t.annotations, p); + return M.at(t.pos).Modifiers(t.flags, annotations); + } + + public JCTree visitNewArray(NewArrayTree node, P p) { + JCNewArray t = (JCNewArray) node; + JCExpression elemtype = copy(t.elemtype, p); + List dims = copy(t.dims, p); + List elems = copy(t.elems, p); + return M.at(t.pos).NewArray(elemtype, dims, elems); + } + + public JCTree visitNewClass(NewClassTree node, P p) { + JCNewClass t = (JCNewClass) node; + JCExpression encl = copy(t.encl, p); + List typeargs = copy(t.typeargs, p); + JCExpression clazz = copy(t.clazz, p); + List args = copy(t.args, p); + JCClassDecl def = copy(t.def, p); + return M.at(t.pos).NewClass(encl, typeargs, clazz, args, def); + } + + public JCTree visitParenthesized(ParenthesizedTree node, P p) { + JCParens t = (JCParens) node; + JCExpression expr = copy(t.expr, p); + return M.at(t.pos).Parens(expr); + } + + public JCTree visitReturn(ReturnTree node, P p) { + JCReturn t = (JCReturn) node; + JCExpression expr = copy(t.expr, p); + return M.at(t.pos).Return(expr); + } + + public JCTree visitMemberSelect(MemberSelectTree node, P p) { + JCFieldAccess t = (JCFieldAccess) node; + JCExpression selected = copy(t.selected, p); + return M.at(t.pos).Select(selected, t.name); + } + + public JCTree visitEmptyStatement(EmptyStatementTree node, P p) { + JCSkip t = (JCSkip) node; + return M.at(t.pos).Skip(); + } + + public JCTree visitSwitch(SwitchTree node, P p) { + JCSwitch t = (JCSwitch) node; + JCExpression selector = copy(t.selector, p); + List cases = copy(t.cases, p); + return M.at(t.pos).Switch(selector, cases); + } + + public JCTree visitSynchronized(SynchronizedTree node, P p) { + JCSynchronized t = (JCSynchronized) node; + JCExpression lock = copy(t.lock, p); + JCBlock body = copy(t.body, p); + return M.at(t.pos).Synchronized(lock, body); + } + + public JCTree visitThrow(ThrowTree node, P p) { + JCThrow t = (JCThrow) node; + JCTree expr = copy(t.expr, p); + return M.at(t.pos).Throw(expr); + } + + public JCTree visitCompilationUnit(CompilationUnitTree node, P p) { + JCCompilationUnit t = (JCCompilationUnit) node; + List packageAnnotations = copy(t.packageAnnotations, p); + JCExpression pid = copy(t.pid, p); + List defs = copy(t.defs, p); + return M.at(t.pos).TopLevel(packageAnnotations, pid, defs); + } + + public JCTree visitTry(TryTree node, P p) { + JCTry t = (JCTry) node; + List resources = copy(t.resources, p); + JCBlock body = copy(t.body, p); + List catchers = copy(t.catchers, p); + JCBlock finalizer = copy(t.finalizer, p); + return M.at(t.pos).Try(resources, body, catchers, finalizer); + } + + public JCTree visitParameterizedType(ParameterizedTypeTree node, P p) { + JCTypeApply t = (JCTypeApply) node; + JCExpression clazz = copy(t.clazz, p); + List arguments = copy(t.arguments, p); + return M.at(t.pos).TypeApply(clazz, arguments); + } + + public JCTree visitUnionType(UnionTypeTree node, P p) { + JCTypeUnion t = (JCTypeUnion) node; + List components = copy(t.alternatives, p); + return M.at(t.pos).TypeUnion(components); + } + + public JCTree visitArrayType(ArrayTypeTree node, P p) { + JCArrayTypeTree t = (JCArrayTypeTree) node; + JCExpression elemtype = copy(t.elemtype, p); + return M.at(t.pos).TypeArray(elemtype); + } + + public JCTree visitTypeCast(TypeCastTree node, P p) { + JCTypeCast t = (JCTypeCast) node; + JCTree clazz = copy(t.clazz, p); + JCExpression expr = copy(t.expr, p); + return M.at(t.pos).TypeCast(clazz, expr); + } + + public JCTree visitPrimitiveType(PrimitiveTypeTree node, P p) { + JCPrimitiveTypeTree t = (JCPrimitiveTypeTree) node; + return M.at(t.pos).TypeIdent(t.typetag); + } + + public JCTree visitTypeParameter(TypeParameterTree node, P p) { + JCTypeParameter t = (JCTypeParameter) node; + List bounds = copy(t.bounds, p); + return M.at(t.pos).TypeParameter(t.name, bounds); + } + + public JCTree visitInstanceOf(InstanceOfTree node, P p) { + JCInstanceOf t = (JCInstanceOf) node; + JCExpression expr = copy(t.expr, p); + JCTree clazz = copy(t.clazz, p); + return M.at(t.pos).TypeTest(expr, clazz); + } + + public JCTree visitUnary(UnaryTree node, P p) { + JCUnary t = (JCUnary) node; + JCExpression arg = copy(t.arg, p); + return M.at(t.pos).Unary(t.getTag(), arg); + } + + public JCTree visitVariable(VariableTree node, P p) { + JCVariableDecl t = (JCVariableDecl) node; + JCModifiers mods = copy(t.mods, p); + JCExpression vartype = copy(t.vartype, p); + JCExpression init = copy(t.init, p); + return M.at(t.pos).VarDef(mods, t.name, vartype, init); + } + + public JCTree visitWhileLoop(WhileLoopTree node, P p) { + JCWhileLoop t = (JCWhileLoop) node; + JCStatement body = copy(t.body, p); + JCExpression cond = copy(t.cond, p); + return M.at(t.pos).WhileLoop(cond, body); + } + + public JCTree visitWildcard(WildcardTree node, P p) { + JCWildcard t = (JCWildcard) node; + TypeBoundKind kind = M.at(t.kind.pos).TypeBoundKind(t.kind.kind); + JCTree inner = copy(t.inner, p); + return M.at(t.pos).Wildcard(kind, inner); + } + + public JCTree visitOther(Tree node, P p) { + JCTree tree = (JCTree) node; + switch (tree.getTag()) { + case JCTree.LETEXPR: { + LetExpr t = (LetExpr) node; + List defs = copy(t.defs, p); + JCTree expr = copy(t.expr, p); + return M.at(t.pos).LetExpr(defs, expr); + } + default: + throw new AssertionError("unknown tree tag: " + tree.getTag()); + } + } + +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/tree/TreeInfo.java b/douyu-javac/src/main/java/com/sun/tools/javac/tree/TreeInfo.java new file mode 100644 index 0000000..c37a561 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/tree/TreeInfo.java @@ -0,0 +1,927 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.tree; + +import com.sun.source.tree.Tree; +import com.sun.tools.javac.comp.AttrContext; +import com.sun.tools.javac.comp.Env; +import java.util.Map; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.tree.JCTree.*; + +import static com.sun.tools.javac.code.Flags.*; + +/** Utility class containing inspector methods for trees. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class TreeInfo { + protected static final Context.Key treeInfoKey = + new Context.Key(); + + public static TreeInfo instance(Context context) { + TreeInfo instance = context.get(treeInfoKey); + if (instance == null) + instance = new TreeInfo(context); + return instance; + } + + /** The names of all operators. + */ + private Name[] opname = new Name[JCTree.MOD - JCTree.POS + 1]; + + private TreeInfo(Context context) { + context.put(treeInfoKey, this); + + Names names = Names.instance(context); + opname[JCTree.POS - JCTree.POS] = names.fromString("+"); + opname[JCTree.NEG - JCTree.POS] = names.hyphen; + opname[JCTree.NOT - JCTree.POS] = names.fromString("!"); + opname[JCTree.COMPL - JCTree.POS] = names.fromString("~"); + opname[JCTree.PREINC - JCTree.POS] = names.fromString("++"); + opname[JCTree.PREDEC - JCTree.POS] = names.fromString("--"); + opname[JCTree.POSTINC - JCTree.POS] = names.fromString("++"); + opname[JCTree.POSTDEC - JCTree.POS] = names.fromString("--"); + opname[JCTree.NULLCHK - JCTree.POS] = names.fromString("<*nullchk*>"); + opname[JCTree.OR - JCTree.POS] = names.fromString("||"); + opname[JCTree.AND - JCTree.POS] = names.fromString("&&"); + opname[JCTree.EQ - JCTree.POS] = names.fromString("=="); + opname[JCTree.NE - JCTree.POS] = names.fromString("!="); + opname[JCTree.LT - JCTree.POS] = names.fromString("<"); + opname[JCTree.GT - JCTree.POS] = names.fromString(">"); + opname[JCTree.LE - JCTree.POS] = names.fromString("<="); + opname[JCTree.GE - JCTree.POS] = names.fromString(">="); + opname[JCTree.BITOR - JCTree.POS] = names.fromString("|"); + opname[JCTree.BITXOR - JCTree.POS] = names.fromString("^"); + opname[JCTree.BITAND - JCTree.POS] = names.fromString("&"); + opname[JCTree.SL - JCTree.POS] = names.fromString("<<"); + opname[JCTree.SR - JCTree.POS] = names.fromString(">>"); + opname[JCTree.USR - JCTree.POS] = names.fromString(">>>"); + opname[JCTree.PLUS - JCTree.POS] = names.fromString("+"); + opname[JCTree.MINUS - JCTree.POS] = names.hyphen; + opname[JCTree.MUL - JCTree.POS] = names.asterisk; + opname[JCTree.DIV - JCTree.POS] = names.slash; + opname[JCTree.MOD - JCTree.POS] = names.fromString("%"); + } + + + /** Return name of operator with given tree tag. + */ + public Name operatorName(int tag) { + return opname[tag - JCTree.POS]; + } + + /** Is tree a constructor declaration? + */ + public static boolean isConstructor(JCTree tree) { + if (tree.getTag() == JCTree.METHODDEF) { + Name name = ((JCMethodDecl) tree).name; + return name == name.table.names.init; + } else { + return false; + } + } + + /** Is there a constructor declaration in the given list of trees? + */ + public static boolean hasConstructors(List trees) { + for (List l = trees; l.nonEmpty(); l = l.tail) + if (isConstructor(l.head)) return true; + return false; + } + + public static boolean isMultiCatch(JCCatch catchClause) { + return catchClause.param.vartype.getTag() == JCTree.TYPEUNION; + } + + /** Is statement an initializer for a synthetic field? + */ + public static boolean isSyntheticInit(JCTree stat) { + if (stat.getTag() == JCTree.EXEC) { + JCExpressionStatement exec = (JCExpressionStatement)stat; + if (exec.expr.getTag() == JCTree.ASSIGN) { + JCAssign assign = (JCAssign)exec.expr; + if (assign.lhs.getTag() == JCTree.SELECT) { + JCFieldAccess select = (JCFieldAccess)assign.lhs; + if (select.sym != null && + (select.sym.flags() & SYNTHETIC) != 0) { + Name selected = name(select.selected); + if (selected != null && selected == selected.table.names._this) + return true; + } + } + } + } + return false; + } + + /** If the expression is a method call, return the method name, null + * otherwise. */ + public static Name calledMethodName(JCTree tree) { + if (tree.getTag() == JCTree.EXEC) { + JCExpressionStatement exec = (JCExpressionStatement)tree; + if (exec.expr.getTag() == JCTree.APPLY) { + Name mname = TreeInfo.name(((JCMethodInvocation) exec.expr).meth); + return mname; + } + } + return null; + } + + /** Is this a call to this or super? + */ + public static boolean isSelfCall(JCTree tree) { + Name name = calledMethodName(tree); + if (name != null) { + Names names = name.table.names; + return name==names._this || name==names._super; + } else { + return false; + } + } + + /** Is this a call to super? + */ + public static boolean isSuperCall(JCTree tree) { + Name name = calledMethodName(tree); + if (name != null) { + Names names = name.table.names; + return name==names._super; + } else { + return false; + } + } + + /** Is this a constructor whose first (non-synthetic) statement is not + * of the form this(...)? + */ + public static boolean isInitialConstructor(JCTree tree) { + JCMethodInvocation app = firstConstructorCall(tree); + if (app == null) return false; + Name meth = name(app.meth); + return meth == null || meth != meth.table.names._this; + } + + /** Return the first call in a constructor definition. */ + public static JCMethodInvocation firstConstructorCall(JCTree tree) { + if (tree.getTag() != JCTree.METHODDEF) return null; + JCMethodDecl md = (JCMethodDecl) tree; + Names names = md.name.table.names; + if (md.name != names.init) return null; + if (md.body == null) return null; + List stats = md.body.stats; + // Synthetic initializations can appear before the super call. + while (stats.nonEmpty() && isSyntheticInit(stats.head)) + stats = stats.tail; + if (stats.isEmpty()) return null; + if (stats.head.getTag() != JCTree.EXEC) return null; + JCExpressionStatement exec = (JCExpressionStatement) stats.head; + if (exec.expr.getTag() != JCTree.APPLY) return null; + return (JCMethodInvocation)exec.expr; + } + + /** Return true if a tree represents a diamond new expr. */ + public static boolean isDiamond(JCTree tree) { + switch(tree.getTag()) { + case JCTree.TYPEAPPLY: return ((JCTypeApply)tree).getTypeArguments().isEmpty(); + case JCTree.NEWCLASS: return isDiamond(((JCNewClass)tree).clazz); + default: return false; + } + } + + /** Return true if a tree represents the null literal. */ + public static boolean isNull(JCTree tree) { + if (tree.getTag() != JCTree.LITERAL) + return false; + JCLiteral lit = (JCLiteral) tree; + return (lit.typetag == TypeTags.BOT); + } + + /** The position of the first statement in a block, or the position of + * the block itself if it is empty. + */ + public static int firstStatPos(JCTree tree) { + if (tree.getTag() == JCTree.BLOCK && ((JCBlock) tree).stats.nonEmpty()) + return ((JCBlock) tree).stats.head.pos; + else + return tree.pos; + } + + /** The end position of given tree, if it is a block with + * defined endpos. + */ + public static int endPos(JCTree tree) { + if (tree.getTag() == JCTree.BLOCK && ((JCBlock) tree).endpos != Position.NOPOS) + return ((JCBlock) tree).endpos; + else if (tree.getTag() == JCTree.SYNCHRONIZED) + return endPos(((JCSynchronized) tree).body); + else if (tree.getTag() == JCTree.TRY) { + JCTry t = (JCTry) tree; + return endPos((t.finalizer != null) + ? t.finalizer + : t.catchers.last().body); + } else + return tree.pos; + } + + + /** Get the start position for a tree node. The start position is + * defined to be the position of the first character of the first + * token of the node's source text. + * @param tree The tree node + */ + public static int getStartPos(JCTree tree) { + if (tree == null) + return Position.NOPOS; + + switch(tree.getTag()) { + case(JCTree.APPLY): + return getStartPos(((JCMethodInvocation) tree).meth); + case(JCTree.ASSIGN): + return getStartPos(((JCAssign) tree).lhs); + case(JCTree.BITOR_ASG): case(JCTree.BITXOR_ASG): case(JCTree.BITAND_ASG): + case(JCTree.SL_ASG): case(JCTree.SR_ASG): case(JCTree.USR_ASG): + case(JCTree.PLUS_ASG): case(JCTree.MINUS_ASG): case(JCTree.MUL_ASG): + case(JCTree.DIV_ASG): case(JCTree.MOD_ASG): + return getStartPos(((JCAssignOp) tree).lhs); + case(JCTree.OR): case(JCTree.AND): case(JCTree.BITOR): + case(JCTree.BITXOR): case(JCTree.BITAND): case(JCTree.EQ): + case(JCTree.NE): case(JCTree.LT): case(JCTree.GT): + case(JCTree.LE): case(JCTree.GE): case(JCTree.SL): + case(JCTree.SR): case(JCTree.USR): case(JCTree.PLUS): + case(JCTree.MINUS): case(JCTree.MUL): case(JCTree.DIV): + case(JCTree.MOD): + return getStartPos(((JCBinary) tree).lhs); + case(JCTree.CLASSDEF): { + JCClassDecl node = (JCClassDecl)tree; + if (node.mods.pos != Position.NOPOS) + return node.mods.pos; + break; + } + case(JCTree.CONDEXPR): + return getStartPos(((JCConditional) tree).cond); + case(JCTree.EXEC): + return getStartPos(((JCExpressionStatement) tree).expr); + case(JCTree.INDEXED): + return getStartPos(((JCArrayAccess) tree).indexed); + case(JCTree.METHODDEF): { + JCMethodDecl node = (JCMethodDecl)tree; + if (node.mods.pos != Position.NOPOS) + return node.mods.pos; + if (node.typarams.nonEmpty()) // List.nil() used for no typarams + return getStartPos(node.typarams.head); + return node.restype == null ? node.pos : getStartPos(node.restype); + } + case(JCTree.SELECT): + return getStartPos(((JCFieldAccess) tree).selected); + case(JCTree.TYPEAPPLY): + return getStartPos(((JCTypeApply) tree).clazz); + case(JCTree.TYPEARRAY): + return getStartPos(((JCArrayTypeTree) tree).elemtype); + case(JCTree.TYPETEST): + return getStartPos(((JCInstanceOf) tree).expr); + case(JCTree.POSTINC): + case(JCTree.POSTDEC): + return getStartPos(((JCUnary) tree).arg); + case(JCTree.NEWCLASS): { + JCNewClass node = (JCNewClass)tree; + if (node.encl != null) + return getStartPos(node.encl); + break; + } + case(JCTree.VARDEF): { + JCVariableDecl node = (JCVariableDecl)tree; + if (node.mods.pos != Position.NOPOS) { + return node.mods.pos; + } else { + return getStartPos(node.vartype); + } + } + case(JCTree.ERRONEOUS): { + JCErroneous node = (JCErroneous)tree; + if (node.errs != null && node.errs.nonEmpty()) + return getStartPos(node.errs.head); + } + } + return tree.pos; + } + + /** The end position of given tree, given a table of end positions generated by the parser + */ + public static int getEndPos(JCTree tree, Map endPositions) { + if (tree == null) + return Position.NOPOS; + + if (endPositions == null) { + // fall back on limited info in the tree + return endPos(tree); + } + + Integer mapPos = endPositions.get(tree); + if (mapPos != null) + return mapPos; + + switch(tree.getTag()) { + case(JCTree.BITOR_ASG): case(JCTree.BITXOR_ASG): case(JCTree.BITAND_ASG): + case(JCTree.SL_ASG): case(JCTree.SR_ASG): case(JCTree.USR_ASG): + case(JCTree.PLUS_ASG): case(JCTree.MINUS_ASG): case(JCTree.MUL_ASG): + case(JCTree.DIV_ASG): case(JCTree.MOD_ASG): + return getEndPos(((JCAssignOp) tree).rhs, endPositions); + case(JCTree.OR): case(JCTree.AND): case(JCTree.BITOR): + case(JCTree.BITXOR): case(JCTree.BITAND): case(JCTree.EQ): + case(JCTree.NE): case(JCTree.LT): case(JCTree.GT): + case(JCTree.LE): case(JCTree.GE): case(JCTree.SL): + case(JCTree.SR): case(JCTree.USR): case(JCTree.PLUS): + case(JCTree.MINUS): case(JCTree.MUL): case(JCTree.DIV): + case(JCTree.MOD): + return getEndPos(((JCBinary) tree).rhs, endPositions); + case(JCTree.CASE): + return getEndPos(((JCCase) tree).stats.last(), endPositions); + case(JCTree.CATCH): + return getEndPos(((JCCatch) tree).body, endPositions); + case(JCTree.CONDEXPR): + return getEndPos(((JCConditional) tree).falsepart, endPositions); + case(JCTree.FORLOOP): + return getEndPos(((JCForLoop) tree).body, endPositions); + case(JCTree.FOREACHLOOP): + return getEndPos(((JCEnhancedForLoop) tree).body, endPositions); + case(JCTree.IF): { + JCIf node = (JCIf)tree; + if (node.elsepart == null) { + return getEndPos(node.thenpart, endPositions); + } else { + return getEndPos(node.elsepart, endPositions); + } + } + case(JCTree.LABELLED): + return getEndPos(((JCLabeledStatement) tree).body, endPositions); + case(JCTree.MODIFIERS): + return getEndPos(((JCModifiers) tree).annotations.last(), endPositions); + case(JCTree.SYNCHRONIZED): + return getEndPos(((JCSynchronized) tree).body, endPositions); + case(JCTree.TOPLEVEL): + return getEndPos(((JCCompilationUnit) tree).defs.last(), endPositions); + case(JCTree.TRY): { + JCTry node = (JCTry)tree; + if (node.finalizer != null) { + return getEndPos(node.finalizer, endPositions); + } else if (!node.catchers.isEmpty()) { + return getEndPos(node.catchers.last(), endPositions); + } else { + return getEndPos(node.body, endPositions); + } + } + case(JCTree.WILDCARD): + return getEndPos(((JCWildcard) tree).inner, endPositions); + case(JCTree.TYPECAST): + return getEndPos(((JCTypeCast) tree).expr, endPositions); + case(JCTree.TYPETEST): + return getEndPos(((JCInstanceOf) tree).clazz, endPositions); + case(JCTree.POS): + case(JCTree.NEG): + case(JCTree.NOT): + case(JCTree.COMPL): + case(JCTree.PREINC): + case(JCTree.PREDEC): + return getEndPos(((JCUnary) tree).arg, endPositions); + case(JCTree.WHILELOOP): + return getEndPos(((JCWhileLoop) tree).body, endPositions); + case(JCTree.ERRONEOUS): { + JCErroneous node = (JCErroneous)tree; + if (node.errs != null && node.errs.nonEmpty()) + return getEndPos(node.errs.last(), endPositions); + } + } + return Position.NOPOS; + } + + + /** A DiagnosticPosition with the preferred position set to the + * end position of given tree, if it is a block with + * defined endpos. + */ + public static DiagnosticPosition diagEndPos(final JCTree tree) { + final int endPos = TreeInfo.endPos(tree); + return new DiagnosticPosition() { + public JCTree getTree() { return tree; } + public int getStartPosition() { return TreeInfo.getStartPos(tree); } + public int getPreferredPosition() { return endPos; } + public int getEndPosition(Map endPosTable) { + return TreeInfo.getEndPos(tree, endPosTable); + } + }; + } + + /** The position of the finalizer of given try/synchronized statement. + */ + public static int finalizerPos(JCTree tree) { + if (tree.getTag() == JCTree.TRY) { + JCTry t = (JCTry) tree; + Assert.checkNonNull(t.finalizer); + return firstStatPos(t.finalizer); + } else if (tree.getTag() == JCTree.SYNCHRONIZED) { + return endPos(((JCSynchronized) tree).body); + } else { + throw new AssertionError(); + } + } + + /** Find the position for reporting an error about a symbol, where + * that symbol is defined somewhere in the given tree. */ + public static int positionFor(final Symbol sym, final JCTree tree) { + JCTree decl = declarationFor(sym, tree); + return ((decl != null) ? decl : tree).pos; + } + + /** Find the position for reporting an error about a symbol, where + * that symbol is defined somewhere in the given tree. */ + public static DiagnosticPosition diagnosticPositionFor(final Symbol sym, final JCTree tree) { + JCTree decl = declarationFor(sym, tree); + return ((decl != null) ? decl : tree).pos(); + } + + /** Find the declaration for a symbol, where + * that symbol is defined somewhere in the given tree. */ + public static JCTree declarationFor(final Symbol sym, final JCTree tree) { + class DeclScanner extends TreeScanner { + JCTree result = null; + public void scan(JCTree tree) { + if (tree!=null && result==null) + tree.accept(this); + } + public void visitTopLevel(JCCompilationUnit that) { + if (that.packge == sym) result = that; + else super.visitTopLevel(that); + } + public void visitClassDef(JCClassDecl that) { + if (that.sym == sym) result = that; + else super.visitClassDef(that); + } + public void visitMethodDef(JCMethodDecl that) { + if (that.sym == sym) result = that; + else super.visitMethodDef(that); + } + public void visitVarDef(JCVariableDecl that) { + if (that.sym == sym) result = that; + else super.visitVarDef(that); + } + public void visitTypeParameter(JCTypeParameter that) { + if (that.type != null && that.type.tsym == sym) result = that; + else super.visitTypeParameter(that); + } + } + DeclScanner s = new DeclScanner(); + tree.accept(s); + return s.result; + } + + public static Env scopeFor(JCTree node, JCCompilationUnit unit) { + return scopeFor(pathFor(node, unit)); + } + + public static Env scopeFor(List path) { + // TODO: not implemented yet + throw new UnsupportedOperationException("not implemented yet"); + } + + public static List pathFor(final JCTree node, final JCCompilationUnit unit) { + class Result extends Error { + static final long serialVersionUID = -5942088234594905625L; + List path; + Result(List path) { + this.path = path; + } + } + class PathFinder extends TreeScanner { + List path = List.nil(); + public void scan(JCTree tree) { + if (tree != null) { + path = path.prepend(tree); + if (tree == node) + throw new Result(path); + super.scan(tree); + path = path.tail; + } + } + } + try { + new PathFinder().scan(unit); + } catch (Result result) { + return result.path; + } + return List.nil(); + } + + /** Return the statement referenced by a label. + * If the label refers to a loop or switch, return that switch + * otherwise return the labelled statement itself + */ + public static JCTree referencedStatement(JCLabeledStatement tree) { + JCTree t = tree; + do t = ((JCLabeledStatement) t).body; + while (t.getTag() == JCTree.LABELLED); + switch (t.getTag()) { + case JCTree.DOLOOP: case JCTree.WHILELOOP: case JCTree.FORLOOP: case JCTree.FOREACHLOOP: case JCTree.SWITCH: + return t; + default: + return tree; + } + } + + /** Skip parens and return the enclosed expression + */ + public static JCExpression skipParens(JCExpression tree) { + while (tree.getTag() == JCTree.PARENS) { + tree = ((JCParens) tree).expr; + } + return tree; + } + + /** Skip parens and return the enclosed expression + */ + public static JCTree skipParens(JCTree tree) { + if (tree.getTag() == JCTree.PARENS) + return skipParens((JCParens)tree); + else + return tree; + } + + /** Return the types of a list of trees. + */ + public static List types(List trees) { + ListBuffer ts = new ListBuffer(); + for (List l = trees; l.nonEmpty(); l = l.tail) + ts.append(l.head.type); + return ts.toList(); + } + + /** If this tree is an identifier or a field or a parameterized type, + * return its name, otherwise return null. + */ + public static Name name(JCTree tree) { + switch (tree.getTag()) { + case JCTree.IDENT: + return ((JCIdent) tree).name; + case JCTree.SELECT: + return ((JCFieldAccess) tree).name; + case JCTree.TYPEAPPLY: + return name(((JCTypeApply) tree).clazz); + default: + return null; + } + } + + /** If this tree is a qualified identifier, its return fully qualified name, + * otherwise return null. + */ + public static Name fullName(JCTree tree) { + tree = skipParens(tree); + switch (tree.getTag()) { + case JCTree.IDENT: + return ((JCIdent) tree).name; + case JCTree.SELECT: + Name sname = fullName(((JCFieldAccess) tree).selected); + return sname == null ? null : sname.append('.', name(tree)); + default: + return null; + } + } + + public static Symbol symbolFor(JCTree node) { + node = skipParens(node); + switch (node.getTag()) { + case JCTree.CLASSDEF: + return ((JCClassDecl) node).sym; + case JCTree.METHODDEF: + return ((JCMethodDecl) node).sym; + case JCTree.VARDEF: + return ((JCVariableDecl) node).sym; + default: + return null; + } + } + + public static boolean isDeclaration(JCTree node) { + node = skipParens(node); + switch (node.getTag()) { + case JCTree.CLASSDEF: + case JCTree.METHODDEF: + case JCTree.VARDEF: + return true; + default: + return false; + } + } + + /** If this tree is an identifier or a field, return its symbol, + * otherwise return null. + */ + public static Symbol symbol(JCTree tree) { + tree = skipParens(tree); + switch (tree.getTag()) { + case JCTree.IDENT: + return ((JCIdent) tree).sym; + case JCTree.SELECT: + return ((JCFieldAccess) tree).sym; + case JCTree.TYPEAPPLY: + return symbol(((JCTypeApply) tree).clazz); + default: + return null; + } + } + + /** Return true if this is a nonstatic selection. */ + public static boolean nonstaticSelect(JCTree tree) { + tree = skipParens(tree); + if (tree.getTag() != JCTree.SELECT) return false; + JCFieldAccess s = (JCFieldAccess) tree; + Symbol e = symbol(s.selected); + return e == null || (e.kind != Kinds.PCK && e.kind != Kinds.TYP); + } + + /** If this tree is an identifier or a field, set its symbol, otherwise skip. + */ + public static void setSymbol(JCTree tree, Symbol sym) { + tree = skipParens(tree); + switch (tree.getTag()) { + case JCTree.IDENT: + ((JCIdent) tree).sym = sym; break; + case JCTree.SELECT: + ((JCFieldAccess) tree).sym = sym; break; + default: + } + } + + /** If this tree is a declaration or a block, return its flags field, + * otherwise return 0. + */ + public static long flags(JCTree tree) { + switch (tree.getTag()) { + case JCTree.VARDEF: + return ((JCVariableDecl) tree).mods.flags; + case JCTree.METHODDEF: + return ((JCMethodDecl) tree).mods.flags; + case JCTree.CLASSDEF: + return ((JCClassDecl) tree).mods.flags; + case JCTree.BLOCK: + return ((JCBlock) tree).flags; + default: + return 0; + } + } + + /** Return first (smallest) flag in `flags': + * pre: flags != 0 + */ + public static long firstFlag(long flags) { + int flag = 1; + while ((flag & StandardFlags) != 0 && (flag & flags) == 0) + flag = flag << 1; + return flag; + } + + /** Return flags as a string, separated by " ". + */ + public static String flagNames(long flags) { + return Flags.toString(flags & StandardFlags).trim(); + } + + /** Operator precedences values. + */ + public static final int + notExpression = -1, // not an expression + noPrec = 0, // no enclosing expression + assignPrec = 1, + assignopPrec = 2, + condPrec = 3, + orPrec = 4, + andPrec = 5, + bitorPrec = 6, + bitxorPrec = 7, + bitandPrec = 8, + eqPrec = 9, + ordPrec = 10, + shiftPrec = 11, + addPrec = 12, + mulPrec = 13, + prefixPrec = 14, + postfixPrec = 15, + precCount = 16; + + + /** Map operators to their precedence levels. + */ + public static int opPrec(int op) { + switch(op) { + case JCTree.POS: + case JCTree.NEG: + case JCTree.NOT: + case JCTree.COMPL: + case JCTree.PREINC: + case JCTree.PREDEC: return prefixPrec; + case JCTree.POSTINC: + case JCTree.POSTDEC: + case JCTree.NULLCHK: return postfixPrec; + case JCTree.ASSIGN: return assignPrec; + case JCTree.BITOR_ASG: + case JCTree.BITXOR_ASG: + case JCTree.BITAND_ASG: + case JCTree.SL_ASG: + case JCTree.SR_ASG: + case JCTree.USR_ASG: + case JCTree.PLUS_ASG: + case JCTree.MINUS_ASG: + case JCTree.MUL_ASG: + case JCTree.DIV_ASG: + case JCTree.MOD_ASG: return assignopPrec; + case JCTree.OR: return orPrec; + case JCTree.AND: return andPrec; + case JCTree.EQ: + case JCTree.NE: return eqPrec; + case JCTree.LT: + case JCTree.GT: + case JCTree.LE: + case JCTree.GE: return ordPrec; + case JCTree.BITOR: return bitorPrec; + case JCTree.BITXOR: return bitxorPrec; + case JCTree.BITAND: return bitandPrec; + case JCTree.SL: + case JCTree.SR: + case JCTree.USR: return shiftPrec; + case JCTree.PLUS: + case JCTree.MINUS: return addPrec; + case JCTree.MUL: + case JCTree.DIV: + case JCTree.MOD: return mulPrec; + case JCTree.TYPETEST: return ordPrec; + default: throw new AssertionError(); + } + } + + static Tree.Kind tagToKind(int tag) { + switch (tag) { + // Postfix expressions + case JCTree.POSTINC: // _ ++ + return Tree.Kind.POSTFIX_INCREMENT; + case JCTree.POSTDEC: // _ -- + return Tree.Kind.POSTFIX_DECREMENT; + + // Unary operators + case JCTree.PREINC: // ++ _ + return Tree.Kind.PREFIX_INCREMENT; + case JCTree.PREDEC: // -- _ + return Tree.Kind.PREFIX_DECREMENT; + case JCTree.POS: // + + return Tree.Kind.UNARY_PLUS; + case JCTree.NEG: // - + return Tree.Kind.UNARY_MINUS; + case JCTree.COMPL: // ~ + return Tree.Kind.BITWISE_COMPLEMENT; + case JCTree.NOT: // ! + return Tree.Kind.LOGICAL_COMPLEMENT; + + // Binary operators + + // Multiplicative operators + case JCTree.MUL: // * + return Tree.Kind.MULTIPLY; + case JCTree.DIV: // / + return Tree.Kind.DIVIDE; + case JCTree.MOD: // % + return Tree.Kind.REMAINDER; + + // Additive operators + case JCTree.PLUS: // + + return Tree.Kind.PLUS; + case JCTree.MINUS: // - + return Tree.Kind.MINUS; + + // Shift operators + case JCTree.SL: // << + return Tree.Kind.LEFT_SHIFT; + case JCTree.SR: // >> + return Tree.Kind.RIGHT_SHIFT; + case JCTree.USR: // >>> + return Tree.Kind.UNSIGNED_RIGHT_SHIFT; + + // Relational operators + case JCTree.LT: // < + return Tree.Kind.LESS_THAN; + case JCTree.GT: // > + return Tree.Kind.GREATER_THAN; + case JCTree.LE: // <= + return Tree.Kind.LESS_THAN_EQUAL; + case JCTree.GE: // >= + return Tree.Kind.GREATER_THAN_EQUAL; + + // Equality operators + case JCTree.EQ: // == + return Tree.Kind.EQUAL_TO; + case JCTree.NE: // != + return Tree.Kind.NOT_EQUAL_TO; + + // Bitwise and logical operators + case JCTree.BITAND: // & + return Tree.Kind.AND; + case JCTree.BITXOR: // ^ + return Tree.Kind.XOR; + case JCTree.BITOR: // | + return Tree.Kind.OR; + + // Conditional operators + case JCTree.AND: // && + return Tree.Kind.CONDITIONAL_AND; + case JCTree.OR: // || + return Tree.Kind.CONDITIONAL_OR; + + // Assignment operators + case JCTree.MUL_ASG: // *= + return Tree.Kind.MULTIPLY_ASSIGNMENT; + case JCTree.DIV_ASG: // /= + return Tree.Kind.DIVIDE_ASSIGNMENT; + case JCTree.MOD_ASG: // %= + return Tree.Kind.REMAINDER_ASSIGNMENT; + case JCTree.PLUS_ASG: // += + return Tree.Kind.PLUS_ASSIGNMENT; + case JCTree.MINUS_ASG: // -= + return Tree.Kind.MINUS_ASSIGNMENT; + case JCTree.SL_ASG: // <<= + return Tree.Kind.LEFT_SHIFT_ASSIGNMENT; + case JCTree.SR_ASG: // >>= + return Tree.Kind.RIGHT_SHIFT_ASSIGNMENT; + case JCTree.USR_ASG: // >>>= + return Tree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT; + case JCTree.BITAND_ASG: // &= + return Tree.Kind.AND_ASSIGNMENT; + case JCTree.BITXOR_ASG: // ^= + return Tree.Kind.XOR_ASSIGNMENT; + case JCTree.BITOR_ASG: // |= + return Tree.Kind.OR_ASSIGNMENT; + + // Null check (implementation detail), for example, __.getClass() + case JCTree.NULLCHK: + return Tree.Kind.OTHER; + + default: + return null; + } + } + + /** + * Returns the underlying type of the tree if it is annotated type, + * or the tree itself otherwise + */ + public static JCExpression typeIn(JCExpression tree) { + switch (tree.getTag()) { + case JCTree.IDENT: /* simple names */ + case JCTree.TYPEIDENT: /* primitive name */ + case JCTree.SELECT: /* qualified name */ + case JCTree.TYPEARRAY: /* array types */ + case JCTree.WILDCARD: /* wild cards */ + case JCTree.TYPEPARAMETER: /* type parameters */ + case JCTree.TYPEAPPLY: /* parameterized types */ + return tree; + default: + throw new AssertionError("Unexpected type tree: " + tree); + } + } + + public static JCTree innermostType(JCTree type) { + switch (type.getTag()) { + case JCTree.TYPEARRAY: + return innermostType(((JCArrayTypeTree)type).elemtype); + case JCTree.WILDCARD: + return innermostType(((JCWildcard)type).inner); + default: + return type; + } + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/tree/TreeMaker.java b/douyu-javac/src/main/java/com/sun/tools/javac/tree/TreeMaker.java new file mode 100644 index 0000000..e3f18a0 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/tree/TreeMaker.java @@ -0,0 +1,902 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.tree; + +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.code.Type.*; +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; + +import com.sun.tools.javac.tree.JCTree.*; + +import static com.sun.tools.javac.code.Flags.*; +import static com.sun.tools.javac.code.Kinds.*; +import static com.sun.tools.javac.code.TypeTags.*; + +/** Factory class for trees. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class TreeMaker implements JCTree.Factory { + + /** The context key for the tree factory. */ + protected static final Context.Key treeMakerKey = + new Context.Key(); + + /** Get the TreeMaker instance. */ + public static TreeMaker instance(Context context) { + TreeMaker instance = context.get(treeMakerKey); + if (instance == null) + instance = new TreeMaker(context); + return instance; + } + + /** The position at which subsequent trees will be created. + */ + public int pos = Position.NOPOS; + + /** The toplevel tree to which created trees belong. + */ + public JCCompilationUnit toplevel; + + /** The current name table. */ + Names names; + + Types types; + + /** The current symbol table. */ + Symtab syms; + + /** Create a tree maker with null toplevel and NOPOS as initial position. + */ + protected TreeMaker(Context context) { + context.put(treeMakerKey, this); + this.pos = Position.NOPOS; + this.toplevel = null; + this.names = Names.instance(context); + this.syms = Symtab.instance(context); + this.types = Types.instance(context); + } + + /** Create a tree maker with a given toplevel and FIRSTPOS as initial position. + */ + TreeMaker(JCCompilationUnit toplevel, Names names, Types types, Symtab syms) { + this.pos = Position.FIRSTPOS; + this.toplevel = toplevel; + this.names = names; + this.types = types; + this.syms = syms; + } + + /** Create a new tree maker for a given toplevel. + */ + public TreeMaker forToplevel(JCCompilationUnit toplevel) { + return new TreeMaker(toplevel, names, types, syms); + } + + /** Reassign current position. + */ + public TreeMaker at(int pos) { + this.pos = pos; + return this; + } + + /** Reassign current position. + */ + public TreeMaker at(DiagnosticPosition pos) { + this.pos = (pos == null ? Position.NOPOS : pos.getStartPosition()); + return this; + } + + /** + * Create given tree node at current position. + * @param defs a list of ClassDef, Import, and Skip + */ + public JCCompilationUnit TopLevel(List packageAnnotations, + JCExpression pid, + List defs) { + Assert.checkNonNull(packageAnnotations); + for (JCTree node : defs) + Assert.check(node instanceof JCClassDecl + || node instanceof JCImport + || node instanceof JCSkip + || node instanceof JCErroneous + || (node instanceof JCExpressionStatement + && ((JCExpressionStatement)node).expr instanceof JCErroneous), + node.getClass().getSimpleName()); + JCCompilationUnit tree = new JCCompilationUnit(packageAnnotations, pid, defs, + null, null, null, null); + tree.pos = pos; + return tree; + } + + public JCImport Import(JCTree qualid, boolean importStatic) { + JCImport tree = new JCImport(qualid, importStatic); + tree.pos = pos; + return tree; + } + + public JCClassDecl ClassDef(JCModifiers mods, + Name name, + List typarams, + JCExpression extending, + List implementing, + List defs) + { + JCClassDecl tree = new JCClassDecl(mods, + name, + typarams, + extending, + implementing, + defs, + null); + tree.pos = pos; + return tree; + } + + public JCMethodDecl MethodDef(JCModifiers mods, + Name name, + JCExpression restype, + List typarams, + List params, + List thrown, + JCBlock body, + JCExpression defaultValue) { + JCMethodDecl tree = new JCMethodDecl(mods, + name, + restype, + typarams, + params, + thrown, + body, + defaultValue, + null); + tree.pos = pos; + return tree; + } + + public JCVariableDecl VarDef(JCModifiers mods, Name name, JCExpression vartype, JCExpression init) { + JCVariableDecl tree = new JCVariableDecl(mods, name, vartype, init, null); + tree.pos = pos; + return tree; + } + + public JCSkip Skip() { + JCSkip tree = new JCSkip(); + tree.pos = pos; + return tree; + } + + public JCBlock Block(long flags, List stats) { + JCBlock tree = new JCBlock(flags, stats); + tree.pos = pos; + return tree; + } + + public JCDoWhileLoop DoLoop(JCStatement body, JCExpression cond) { + JCDoWhileLoop tree = new JCDoWhileLoop(body, cond); + tree.pos = pos; + return tree; + } + + public JCWhileLoop WhileLoop(JCExpression cond, JCStatement body) { + JCWhileLoop tree = new JCWhileLoop(cond, body); + tree.pos = pos; + return tree; + } + + public JCForLoop ForLoop(List init, + JCExpression cond, + List step, + JCStatement body) + { + JCForLoop tree = new JCForLoop(init, cond, step, body); + tree.pos = pos; + return tree; + } + + public JCEnhancedForLoop ForeachLoop(JCVariableDecl var, JCExpression expr, JCStatement body) { + JCEnhancedForLoop tree = new JCEnhancedForLoop(var, expr, body); + tree.pos = pos; + return tree; + } + + public JCLabeledStatement Labelled(Name label, JCStatement body) { + JCLabeledStatement tree = new JCLabeledStatement(label, body); + tree.pos = pos; + return tree; + } + + public JCSwitch Switch(JCExpression selector, List cases) { + JCSwitch tree = new JCSwitch(selector, cases); + tree.pos = pos; + return tree; + } + + public JCCase Case(JCExpression pat, List stats) { + JCCase tree = new JCCase(pat, stats); + tree.pos = pos; + return tree; + } + + public JCSynchronized Synchronized(JCExpression lock, JCBlock body) { + JCSynchronized tree = new JCSynchronized(lock, body); + tree.pos = pos; + return tree; + } + + public JCTry Try(JCBlock body, List catchers, JCBlock finalizer) { + return Try(List.nil(), body, catchers, finalizer); + } + + public JCTry Try(List resources, + JCBlock body, + List catchers, + JCBlock finalizer) { + JCTry tree = new JCTry(resources, body, catchers, finalizer); + tree.pos = pos; + return tree; + } + + public JCCatch Catch(JCVariableDecl param, JCBlock body) { + JCCatch tree = new JCCatch(param, body); + tree.pos = pos; + return tree; + } + + public JCConditional Conditional(JCExpression cond, + JCExpression thenpart, + JCExpression elsepart) + { + JCConditional tree = new JCConditional(cond, thenpart, elsepart); + tree.pos = pos; + return tree; + } + + public JCIf If(JCExpression cond, JCStatement thenpart, JCStatement elsepart) { + JCIf tree = new JCIf(cond, thenpart, elsepart); + tree.pos = pos; + return tree; + } + + public JCExpressionStatement Exec(JCExpression expr) { + JCExpressionStatement tree = new JCExpressionStatement(expr); + tree.pos = pos; + return tree; + } + + public JCBreak Break(Name label) { + JCBreak tree = new JCBreak(label, null); + tree.pos = pos; + return tree; + } + + public JCContinue Continue(Name label) { + JCContinue tree = new JCContinue(label, null); + tree.pos = pos; + return tree; + } + + public JCReturn Return(JCExpression expr) { + JCReturn tree = new JCReturn(expr); + tree.pos = pos; + return tree; + } + + public JCThrow Throw(JCTree expr) { + JCThrow tree = new JCThrow(expr); + tree.pos = pos; + return tree; + } + + public JCAssert Assert(JCExpression cond, JCExpression detail) { + JCAssert tree = new JCAssert(cond, detail); + tree.pos = pos; + return tree; + } + + public JCMethodInvocation Apply(List typeargs, + JCExpression fn, + List args) + { + JCMethodInvocation tree = new JCMethodInvocation(typeargs, fn, args); + tree.pos = pos; + return tree; + } + + public JCNewClass NewClass(JCExpression encl, + List typeargs, + JCExpression clazz, + List args, + JCClassDecl def) + { + JCNewClass tree = new JCNewClass(encl, typeargs, clazz, args, def); + tree.pos = pos; + return tree; + } + + public JCNewArray NewArray(JCExpression elemtype, + List dims, + List elems) + { + JCNewArray tree = new JCNewArray(elemtype, dims, elems); + tree.pos = pos; + return tree; + } + + public JCParens Parens(JCExpression expr) { + JCParens tree = new JCParens(expr); + tree.pos = pos; + return tree; + } + + public JCAssign Assign(JCExpression lhs, JCExpression rhs) { + JCAssign tree = new JCAssign(lhs, rhs); + tree.pos = pos; + return tree; + } + + public JCAssignOp Assignop(int opcode, JCTree lhs, JCTree rhs) { + JCAssignOp tree = new JCAssignOp(opcode, lhs, rhs, null); + tree.pos = pos; + return tree; + } + + public JCUnary Unary(int opcode, JCExpression arg) { + JCUnary tree = new JCUnary(opcode, arg); + tree.pos = pos; + return tree; + } + + public JCBinary Binary(int opcode, JCExpression lhs, JCExpression rhs) { + JCBinary tree = new JCBinary(opcode, lhs, rhs, null); + tree.pos = pos; + return tree; + } + + public JCTypeCast TypeCast(JCTree clazz, JCExpression expr) { + JCTypeCast tree = new JCTypeCast(clazz, expr); + tree.pos = pos; + return tree; + } + + public JCInstanceOf TypeTest(JCExpression expr, JCTree clazz) { + JCInstanceOf tree = new JCInstanceOf(expr, clazz); + tree.pos = pos; + return tree; + } + + public JCArrayAccess Indexed(JCExpression indexed, JCExpression index) { + JCArrayAccess tree = new JCArrayAccess(indexed, index); + tree.pos = pos; + return tree; + } + + public JCFieldAccess Select(JCExpression selected, Name selector) { + JCFieldAccess tree = new JCFieldAccess(selected, selector, null); + tree.pos = pos; + return tree; + } + + public JCIdent Ident(Name name) { + JCIdent tree = new JCIdent(name, null); + tree.pos = pos; + return tree; + } + + public JCLiteral Literal(int tag, Object value) { + JCLiteral tree = new JCLiteral(tag, value); + tree.pos = pos; + return tree; + } + + public JCPrimitiveTypeTree TypeIdent(int typetag) { + JCPrimitiveTypeTree tree = new JCPrimitiveTypeTree(typetag); + tree.pos = pos; + return tree; + } + + public JCArrayTypeTree TypeArray(JCExpression elemtype) { + JCArrayTypeTree tree = new JCArrayTypeTree(elemtype); + tree.pos = pos; + return tree; + } + + public JCTypeApply TypeApply(JCExpression clazz, List arguments) { + JCTypeApply tree = new JCTypeApply(clazz, arguments); + tree.pos = pos; + return tree; + } + + public JCTypeUnion TypeUnion(List components) { + JCTypeUnion tree = new JCTypeUnion(components); + tree.pos = pos; + return tree; + } + + public JCTypeParameter TypeParameter(Name name, List bounds) { + JCTypeParameter tree = new JCTypeParameter(name, bounds); + tree.pos = pos; + return tree; + } + + public JCWildcard Wildcard(TypeBoundKind kind, JCTree type) { + JCWildcard tree = new JCWildcard(kind, type); + tree.pos = pos; + return tree; + } + + public TypeBoundKind TypeBoundKind(BoundKind kind) { + TypeBoundKind tree = new TypeBoundKind(kind); + tree.pos = pos; + return tree; + } + + public JCAnnotation Annotation(JCTree annotationType, List args) { + JCAnnotation tree = new JCAnnotation(annotationType, args); + tree.pos = pos; + return tree; + } + + public JCModifiers Modifiers(long flags, List annotations) { + JCModifiers tree = new JCModifiers(flags, annotations); + boolean noFlags = (flags & (Flags.ModifierFlags | Flags.ANNOTATION)) == 0; + tree.pos = (noFlags && annotations.isEmpty()) ? Position.NOPOS : pos; + return tree; + } + + public JCModifiers Modifiers(long flags) { + return Modifiers(flags, List.nil()); + } + + public JCErroneous Erroneous() { + return Erroneous(List.nil()); + } + + public JCErroneous Erroneous(List errs) { + JCErroneous tree = new JCErroneous(errs); + tree.pos = pos; + return tree; + } + + public LetExpr LetExpr(List defs, JCTree expr) { + LetExpr tree = new LetExpr(defs, expr); + tree.pos = pos; + return tree; + } + +/* *************************************************************************** + * Derived building blocks. + ****************************************************************************/ + + public JCClassDecl AnonymousClassDef(JCModifiers mods, + List defs) + { + return ClassDef(mods, + names.empty, + List.nil(), + null, + List.nil(), + defs); + } + + public LetExpr LetExpr(JCVariableDecl def, JCTree expr) { + LetExpr tree = new LetExpr(List.of(def), expr); + tree.pos = pos; + return tree; + } + + /** Create an identifier from a symbol. + */ + public JCIdent Ident(Symbol sym) { + return (JCIdent)new JCIdent((sym.name != names.empty) + ? sym.name + : sym.flatName(), sym) + .setPos(pos) + .setType(sym.type); + } + + /** Create a selection node from a qualifier tree and a symbol. + * @param base The qualifier tree. + */ + public JCExpression Select(JCExpression base, Symbol sym) { + return new JCFieldAccess(base, sym.name, sym).setPos(pos).setType(sym.type); + } + + /** Create a qualified identifier from a symbol, adding enough qualifications + * to make the reference unique. + */ + public JCExpression QualIdent(Symbol sym) { + return isUnqualifiable(sym) + ? Ident(sym) + : Select(QualIdent(sym.owner), sym); + } + + /** Create an identifier that refers to the variable declared in given variable + * declaration. + */ + public JCExpression Ident(JCVariableDecl param) { + return Ident(param.sym); + } + + /** Create a list of identifiers referring to the variables declared + * in given list of variable declarations. + */ + public List Idents(List params) { + ListBuffer ids = new ListBuffer(); + for (List l = params; l.nonEmpty(); l = l.tail) + ids.append(Ident(l.head)); + return ids.toList(); + } + + /** Create a tree representing `this', given its type. + */ + public JCExpression This(Type t) { + return Ident(new VarSymbol(FINAL, names._this, t, t.tsym)); + } + + /** Create a tree representing a class literal. + */ + public JCExpression ClassLiteral(ClassSymbol clazz) { + return ClassLiteral(clazz.type); + } + + /** Create a tree representing a class literal. + */ + public JCExpression ClassLiteral(Type t) { + VarSymbol lit = new VarSymbol(STATIC | PUBLIC | FINAL, + names._class, + t, + t.tsym); + return Select(Type(t), lit); + } + + /** Create a tree representing `super', given its type and owner. + */ + public JCIdent Super(Type t, TypeSymbol owner) { + return Ident(new VarSymbol(FINAL, names._super, t, owner)); + } + + /** + * Create a method invocation from a method tree and a list of + * argument trees. + */ + public JCMethodInvocation App(JCExpression meth, List args) { + return Apply(null, meth, args).setType(meth.type.getReturnType()); + } + + /** + * Create a no-arg method invocation from a method tree + */ + public JCMethodInvocation App(JCExpression meth) { + return Apply(null, meth, List.nil()).setType(meth.type.getReturnType()); + } + + /** Create a method invocation from a method tree and a list of argument trees. + */ + public JCExpression Create(Symbol ctor, List args) { + Type t = ctor.owner.erasure(types); + JCNewClass newclass = NewClass(null, null, Type(t), args, null); + newclass.constructor = ctor; + newclass.setType(t); + return newclass; + } + + /** Create a tree representing given type. + */ + public JCExpression Type(Type t) { + if (t == null) return null; + JCExpression tp; + switch (t.tag) { + case BYTE: case CHAR: case SHORT: case INT: case LONG: case FLOAT: + case DOUBLE: case BOOLEAN: case VOID: + tp = TypeIdent(t.tag); + break; + case TYPEVAR: + tp = Ident(t.tsym); + break; + case WILDCARD: { + WildcardType a = ((WildcardType) t); + tp = Wildcard(TypeBoundKind(a.kind), Type(a.type)); + break; + } + case CLASS: + Type outer = t.getEnclosingType(); + JCExpression clazz = outer.tag == CLASS && t.tsym.owner.kind == TYP + ? Select(Type(outer), t.tsym) + : QualIdent(t.tsym); + tp = t.getTypeArguments().isEmpty() + ? clazz + : TypeApply(clazz, Types(t.getTypeArguments())); + break; + case ARRAY: + tp = TypeArray(Type(types.elemtype(t))); + break; + case ERROR: + tp = TypeIdent(ERROR); + break; + default: + throw new AssertionError("unexpected type: " + t); + } + return tp.setType(t); + } + + /** Create a list of trees representing given list of types. + */ + public List Types(List ts) { + ListBuffer lb = new ListBuffer(); + for (List l = ts; l.nonEmpty(); l = l.tail) + lb.append(Type(l.head)); + return lb.toList(); + } + + /** Create a variable definition from a variable symbol and an initializer + * expression. + */ + public JCVariableDecl VarDef(VarSymbol v, JCExpression init) { + return (JCVariableDecl) + new JCVariableDecl( + Modifiers(v.flags(), Annotations(v.getAnnotationMirrors())), + v.name, + Type(v.type), + init, + v).setPos(pos).setType(v.type); + } + + /** Create annotation trees from annotations. + */ + public List Annotations(List attributes) { + if (attributes == null) return List.nil(); + ListBuffer result = new ListBuffer(); + for (List i = attributes; i.nonEmpty(); i=i.tail) { + Attribute a = i.head; + result.append(Annotation(a)); + } + return result.toList(); + } + + public JCLiteral Literal(Object value) { + JCLiteral result = null; + if (value instanceof String) { + result = Literal(CLASS, value). + setType(syms.stringType.constType(value)); + } else if (value instanceof Integer) { + result = Literal(INT, value). + setType(syms.intType.constType(value)); + } else if (value instanceof Long) { + result = Literal(LONG, value). + setType(syms.longType.constType(value)); + } else if (value instanceof Byte) { + result = Literal(BYTE, value). + setType(syms.byteType.constType(value)); + } else if (value instanceof Character) { + int v = (int) (((Character) value).toString().charAt(0)); + result = Literal(CHAR, value). + setType(syms.charType.constType(v)); + } else if (value instanceof Double) { + result = Literal(DOUBLE, value). + setType(syms.doubleType.constType(value)); + } else if (value instanceof Float) { + result = Literal(FLOAT, value). + setType(syms.floatType.constType(value)); + } else if (value instanceof Short) { + result = Literal(SHORT, value). + setType(syms.shortType.constType(value)); + } else if (value instanceof Boolean) { + int v = ((Boolean) value) ? 1 : 0; + result = Literal(BOOLEAN, v). + setType(syms.booleanType.constType(v)); + } else { + throw new AssertionError(value); + } + return result; + } + + class AnnotationBuilder implements Attribute.Visitor { + JCExpression result = null; + public void visitConstant(Attribute.Constant v) { + result = Literal(v.value); + } + public void visitClass(Attribute.Class clazz) { + result = ClassLiteral(clazz.type).setType(syms.classType); + } + public void visitEnum(Attribute.Enum e) { + result = QualIdent(e.value); + } + public void visitError(Attribute.Error e) { + result = Erroneous(); + } + public void visitCompound(Attribute.Compound compound) { + result = visitCompoundInternal(compound); + } + public JCAnnotation visitCompoundInternal(Attribute.Compound compound) { + ListBuffer args = new ListBuffer(); + for (List> values = compound.values; values.nonEmpty(); values=values.tail) { + Pair pair = values.head; + JCExpression valueTree = translate(pair.snd); + args.append(Assign(Ident(pair.fst), valueTree).setType(valueTree.type)); + } + return Annotation(Type(compound.type), args.toList()); + } + public void visitArray(Attribute.Array array) { + ListBuffer elems = new ListBuffer(); + for (int i = 0; i < array.values.length; i++) + elems.append(translate(array.values[i])); + result = NewArray(null, List.nil(), elems.toList()).setType(array.type); + } + JCExpression translate(Attribute a) { + a.accept(this); + return result; + } + JCAnnotation translate(Attribute.Compound a) { + return visitCompoundInternal(a); + } + } + AnnotationBuilder annotationBuilder = new AnnotationBuilder(); + + /** Create an annotation tree from an attribute. + */ + public JCAnnotation Annotation(Attribute a) { + return annotationBuilder.translate((Attribute.Compound)a); + } + + /** Create a method definition from a method symbol and a method body. + */ + public JCMethodDecl MethodDef(MethodSymbol m, JCBlock body) { + return MethodDef(m, m.type, body); + } + + /** Create a method definition from a method symbol, method type + * and a method body. + */ + public JCMethodDecl MethodDef(MethodSymbol m, Type mtype, JCBlock body) { + return (JCMethodDecl) + new JCMethodDecl( + Modifiers(m.flags(), Annotations(m.getAnnotationMirrors())), + m.name, + Type(mtype.getReturnType()), + TypeParams(mtype.getTypeArguments()), + Params(mtype.getParameterTypes(), m), + Types(mtype.getThrownTypes()), + body, + null, + m).setPos(pos).setType(mtype); + } + + /** Create a type parameter tree from its name and type. + */ + public JCTypeParameter TypeParam(Name name, TypeVar tvar) { + return (JCTypeParameter) + TypeParameter(name, Types(types.getBounds(tvar))).setPos(pos).setType(tvar); + } + + /** Create a list of type parameter trees from a list of type variables. + */ + public List TypeParams(List typarams) { + ListBuffer tparams = new ListBuffer(); + int i = 0; + for (List l = typarams; l.nonEmpty(); l = l.tail) + tparams.append(TypeParam(l.head.tsym.name, (TypeVar)l.head)); + return tparams.toList(); + } + + /** Create a value parameter tree from its name, type, and owner. + */ + public JCVariableDecl Param(Name name, Type argtype, Symbol owner) { + return VarDef(new VarSymbol(0, name, argtype, owner), null); + } + + /** Create a a list of value parameter trees x0, ..., xn from a list of + * their types and an their owner. + */ + public List Params(List argtypes, Symbol owner) { + ListBuffer params = new ListBuffer(); + MethodSymbol mth = (owner.kind == MTH) ? ((MethodSymbol)owner) : null; + if (mth != null && mth.params != null && argtypes.length() == mth.params.length()) { + for (VarSymbol param : ((MethodSymbol)owner).params) + params.append(VarDef(param, null)); + } else { + int i = 0; + for (List l = argtypes; l.nonEmpty(); l = l.tail) + params.append(Param(paramName(i++), l.head, owner)); + } + return params.toList(); + } + + /** Wrap a method invocation in an expression statement or return statement, + * depending on whether the method invocation expression's type is void. + */ + public JCStatement Call(JCExpression apply) { + return apply.type.tag == VOID ? Exec(apply) : Return(apply); + } + + /** Construct an assignment from a variable symbol and a right hand side. + */ + public JCStatement Assignment(Symbol v, JCExpression rhs) { + return Exec(Assign(Ident(v), rhs).setType(v.type)); + } + + /** Construct an index expression from a variable and an expression. + */ + public JCArrayAccess Indexed(Symbol v, JCExpression index) { + JCArrayAccess tree = new JCArrayAccess(QualIdent(v), index); + tree.type = ((ArrayType)v.type).elemtype; + return tree; + } + + /** Make an attributed type cast expression. + */ + public JCTypeCast TypeCast(Type type, JCExpression expr) { + return (JCTypeCast)TypeCast(Type(type), expr).setType(type); + } + +/* *************************************************************************** + * Helper methods. + ****************************************************************************/ + + /** Can given symbol be referred to in unqualified form? + */ + boolean isUnqualifiable(Symbol sym) { + if (sym.name == names.empty || + sym.owner == null || + sym.owner.kind == MTH || sym.owner.kind == VAR) { + return true; + } else if (sym.kind == TYP && toplevel != null) { + Scope.Entry e; + e = toplevel.namedImportScope.lookup(sym.name); + if (e.scope != null) { + return + e.sym == sym && + e.next().scope == null; + } + e = toplevel.packge.members().lookup(sym.name); + if (e.scope != null) { + return + e.sym == sym && + e.next().scope == null; + } + e = toplevel.starImportScope.lookup(sym.name); + if (e.scope != null) { + return + e.sym == sym && + e.next().scope == null; + } + } + return false; + } + + /** The name of synthetic parameter number `i'. + */ + public Name paramName(int i) { return names.fromString("x" + i); } + + /** The name of synthetic type parameter number `i'. + */ + public Name typaramName(int i) { return names.fromString("A" + i); } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/tree/TreeScanner.java b/douyu-javac/src/main/java/com/sun/tools/javac/tree/TreeScanner.java new file mode 100644 index 0000000..1961966 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/tree/TreeScanner.java @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.tree; + +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.tree.JCTree.*; + +/** A subclass of Tree.Visitor, this class defines + * a general tree scanner pattern. Translation proceeds recursively in + * left-to-right order down a tree. There is one visitor method in this class + * for every possible kind of tree node. To obtain a specific + * scanner, it suffices to override those visitor methods which + * do some interesting work. The scanner class itself takes care of all + * navigational aspects. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class TreeScanner extends Visitor { + + /** Visitor method: Scan a single node. + */ + public void scan(JCTree tree) { + if(tree!=null) tree.accept(this); + } + + /** Visitor method: scan a list of nodes. + */ + public void scan(List trees) { + if (trees != null) + for (List l = trees; l.nonEmpty(); l = l.tail) + scan(l.head); + } + + +/* *************************************************************************** + * Visitor methods + ****************************************************************************/ + + public void visitTopLevel(JCCompilationUnit tree) { + scan(tree.packageAnnotations); + scan(tree.pid); + scan(tree.defs); + } + + public void visitImport(JCImport tree) { + scan(tree.qualid); + } + + public void visitClassDef(JCClassDecl tree) { + scan(tree.mods); + scan(tree.typarams); + scan(tree.extending); + scan(tree.implementing); + scan(tree.defs); + } + + public void visitMethodDef(JCMethodDecl tree) { + scan(tree.mods); + scan(tree.restype); + scan(tree.typarams); + scan(tree.params); + scan(tree.thrown); + scan(tree.defaultValue); + scan(tree.body); + } + + public void visitVarDef(JCVariableDecl tree) { + scan(tree.mods); + scan(tree.vartype); + scan(tree.init); + } + + public void visitSkip(JCSkip tree) { + } + + public void visitBlock(JCBlock tree) { + scan(tree.stats); + } + + public void visitDoLoop(JCDoWhileLoop tree) { + scan(tree.body); + scan(tree.cond); + } + + public void visitWhileLoop(JCWhileLoop tree) { + scan(tree.cond); + scan(tree.body); + } + + public void visitForLoop(JCForLoop tree) { + scan(tree.init); + scan(tree.cond); + scan(tree.step); + scan(tree.body); + } + + public void visitForeachLoop(JCEnhancedForLoop tree) { + scan(tree.var); + scan(tree.expr); + scan(tree.body); + } + + public void visitLabelled(JCLabeledStatement tree) { + scan(tree.body); + } + + public void visitSwitch(JCSwitch tree) { + scan(tree.selector); + scan(tree.cases); + } + + public void visitCase(JCCase tree) { + scan(tree.pat); + scan(tree.stats); + } + + public void visitSynchronized(JCSynchronized tree) { + scan(tree.lock); + scan(tree.body); + } + + public void visitTry(JCTry tree) { + scan(tree.resources); + scan(tree.body); + scan(tree.catchers); + scan(tree.finalizer); + } + + public void visitCatch(JCCatch tree) { + scan(tree.param); + scan(tree.body); + } + + public void visitConditional(JCConditional tree) { + scan(tree.cond); + scan(tree.truepart); + scan(tree.falsepart); + } + + public void visitIf(JCIf tree) { + scan(tree.cond); + scan(tree.thenpart); + scan(tree.elsepart); + } + + public void visitExec(JCExpressionStatement tree) { + scan(tree.expr); + } + + public void visitBreak(JCBreak tree) { + } + + public void visitContinue(JCContinue tree) { + } + + public void visitReturn(JCReturn tree) { + scan(tree.expr); + } + + public void visitThrow(JCThrow tree) { + scan(tree.expr); + } + + public void visitAssert(JCAssert tree) { + scan(tree.cond); + scan(tree.detail); + } + + public void visitApply(JCMethodInvocation tree) { + scan(tree.typeargs); + scan(tree.meth); + scan(tree.args); + } + + public void visitNewClass(JCNewClass tree) { + scan(tree.encl); + scan(tree.clazz); + scan(tree.typeargs); + scan(tree.args); + scan(tree.def); + } + + public void visitNewArray(JCNewArray tree) { + scan(tree.elemtype); + scan(tree.dims); + scan(tree.elems); + } + + public void visitParens(JCParens tree) { + scan(tree.expr); + } + + public void visitAssign(JCAssign tree) { + scan(tree.lhs); + scan(tree.rhs); + } + + public void visitAssignop(JCAssignOp tree) { + scan(tree.lhs); + scan(tree.rhs); + } + + public void visitUnary(JCUnary tree) { + scan(tree.arg); + } + + public void visitBinary(JCBinary tree) { + scan(tree.lhs); + scan(tree.rhs); + } + + public void visitTypeCast(JCTypeCast tree) { + scan(tree.clazz); + scan(tree.expr); + } + + public void visitTypeTest(JCInstanceOf tree) { + scan(tree.expr); + scan(tree.clazz); + } + + public void visitIndexed(JCArrayAccess tree) { + scan(tree.indexed); + scan(tree.index); + } + + public void visitSelect(JCFieldAccess tree) { + scan(tree.selected); + } + + public void visitIdent(JCIdent tree) { + } + + public void visitLiteral(JCLiteral tree) { + } + + public void visitTypeIdent(JCPrimitiveTypeTree tree) { + } + + public void visitTypeArray(JCArrayTypeTree tree) { + scan(tree.elemtype); + } + + public void visitTypeApply(JCTypeApply tree) { + scan(tree.clazz); + scan(tree.arguments); + } + + public void visitTypeUnion(JCTypeUnion tree) { + scan(tree.alternatives); + } + + public void visitTypeParameter(JCTypeParameter tree) { + scan(tree.bounds); + } + + @Override + public void visitWildcard(JCWildcard tree) { + scan(tree.kind); + if (tree.inner != null) + scan(tree.inner); + } + + @Override + public void visitTypeBoundKind(TypeBoundKind that) { + } + + public void visitModifiers(JCModifiers tree) { + scan(tree.annotations); + } + + public void visitAnnotation(JCAnnotation tree) { + scan(tree.annotationType); + scan(tree.args); + } + + public void visitErroneous(JCErroneous tree) { + } + + public void visitLetExpr(LetExpr tree) { + scan(tree.defs); + scan(tree.expr); + } + + public void visitTree(JCTree tree) { + Assert.error(); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/tree/TreeTranslator.java b/douyu-javac/src/main/java/com/sun/tools/javac/tree/TreeTranslator.java new file mode 100644 index 0000000..f024a28 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/tree/TreeTranslator.java @@ -0,0 +1,412 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.tree; + +import com.sun.tools.javac.util.*; +import com.sun.tools.javac.tree.JCTree.*; + +/** A subclass of Tree.Visitor, this class defines + * a general tree translator pattern. Translation proceeds recursively in + * left-to-right order down a tree, constructing translated nodes by + * overwriting existing ones. There is one visitor method in this class + * for every possible kind of tree node. To obtain a specific + * translator, it suffices to override those visitor methods which + * do some interesting work. The translator class itself takes care of all + * navigational aspects. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class TreeTranslator extends JCTree.Visitor { + + /** Visitor result field: a tree + */ + protected JCTree result; + + /** Visitor method: Translate a single node. + */ + @SuppressWarnings("unchecked") + public T translate(T tree) { + if (tree == null) { + return null; + } else { + tree.accept(this); + JCTree result = this.result; + this.result = null; + return (T)result; // XXX cast + } + } + + /** Visitor method: translate a list of nodes. + */ + public List translate(List trees) { + if (trees == null) return null; + for (List l = trees; l.nonEmpty(); l = l.tail) + l.head = translate(l.head); + return trees; + } + + /** Visitor method: translate a list of variable definitions. + */ + public List translateVarDefs(List trees) { + for (List l = trees; l.nonEmpty(); l = l.tail) + l.head = translate(l.head); + return trees; + } + + /** Visitor method: translate a list of type parameters. + */ + public List translateTypeParams(List trees) { + for (List l = trees; l.nonEmpty(); l = l.tail) + l.head = translate(l.head); + return trees; + } + + /** Visitor method: translate a list of case parts of switch statements. + */ + public List translateCases(List trees) { + for (List l = trees; l.nonEmpty(); l = l.tail) + l.head = translate(l.head); + return trees; + } + + /** Visitor method: translate a list of catch clauses in try statements. + */ + public List translateCatchers(List trees) { + for (List l = trees; l.nonEmpty(); l = l.tail) + l.head = translate(l.head); + return trees; + } + + /** Visitor method: translate a list of catch clauses in try statements. + */ + public List translateAnnotations(List trees) { + for (List l = trees; l.nonEmpty(); l = l.tail) + l.head = translate(l.head); + return trees; + } + +/* *************************************************************************** + * Visitor methods + ****************************************************************************/ + + public void visitTopLevel(JCCompilationUnit tree) { + tree.pid = translate(tree.pid); + tree.defs = translate(tree.defs); + result = tree; + } + + public void visitImport(JCImport tree) { + tree.qualid = translate(tree.qualid); + result = tree; + } + + public void visitClassDef(JCClassDecl tree) { + tree.mods = translate(tree.mods); + tree.typarams = translateTypeParams(tree.typarams); + tree.extending = translate(tree.extending); + tree.implementing = translate(tree.implementing); + tree.defs = translate(tree.defs); + result = tree; + } + + public void visitMethodDef(JCMethodDecl tree) { + tree.mods = translate(tree.mods); + tree.restype = translate(tree.restype); + tree.typarams = translateTypeParams(tree.typarams); + tree.params = translateVarDefs(tree.params); + tree.thrown = translate(tree.thrown); + tree.body = translate(tree.body); + result = tree; + } + + public void visitVarDef(JCVariableDecl tree) { + tree.mods = translate(tree.mods); + tree.vartype = translate(tree.vartype); + tree.init = translate(tree.init); + result = tree; + } + + public void visitSkip(JCSkip tree) { + result = tree; + } + + public void visitBlock(JCBlock tree) { + tree.stats = translate(tree.stats); + result = tree; + } + + public void visitDoLoop(JCDoWhileLoop tree) { + tree.body = translate(tree.body); + tree.cond = translate(tree.cond); + result = tree; + } + + public void visitWhileLoop(JCWhileLoop tree) { + tree.cond = translate(tree.cond); + tree.body = translate(tree.body); + result = tree; + } + + public void visitForLoop(JCForLoop tree) { + tree.init = translate(tree.init); + tree.cond = translate(tree.cond); + tree.step = translate(tree.step); + tree.body = translate(tree.body); + result = tree; + } + + public void visitForeachLoop(JCEnhancedForLoop tree) { + tree.var = translate(tree.var); + tree.expr = translate(tree.expr); + tree.body = translate(tree.body); + result = tree; + } + + public void visitLabelled(JCLabeledStatement tree) { + tree.body = translate(tree.body); + result = tree; + } + + public void visitSwitch(JCSwitch tree) { + tree.selector = translate(tree.selector); + tree.cases = translateCases(tree.cases); + result = tree; + } + + public void visitCase(JCCase tree) { + tree.pat = translate(tree.pat); + tree.stats = translate(tree.stats); + result = tree; + } + + public void visitSynchronized(JCSynchronized tree) { + tree.lock = translate(tree.lock); + tree.body = translate(tree.body); + result = tree; + } + + public void visitTry(JCTry tree) { + tree.resources = translate(tree.resources); + tree.body = translate(tree.body); + tree.catchers = translateCatchers(tree.catchers); + tree.finalizer = translate(tree.finalizer); + result = tree; + } + + public void visitCatch(JCCatch tree) { + tree.param = translate(tree.param); + tree.body = translate(tree.body); + result = tree; + } + + public void visitConditional(JCConditional tree) { + tree.cond = translate(tree.cond); + tree.truepart = translate(tree.truepart); + tree.falsepart = translate(tree.falsepart); + result = tree; + } + + public void visitIf(JCIf tree) { + tree.cond = translate(tree.cond); + tree.thenpart = translate(tree.thenpart); + tree.elsepart = translate(tree.elsepart); + result = tree; + } + + public void visitExec(JCExpressionStatement tree) { + tree.expr = translate(tree.expr); + result = tree; + } + + public void visitBreak(JCBreak tree) { + result = tree; + } + + public void visitContinue(JCContinue tree) { + result = tree; + } + + public void visitReturn(JCReturn tree) { + tree.expr = translate(tree.expr); + result = tree; + } + + public void visitThrow(JCThrow tree) { + tree.expr = translate(tree.expr); + result = tree; + } + + public void visitAssert(JCAssert tree) { + tree.cond = translate(tree.cond); + tree.detail = translate(tree.detail); + result = tree; + } + + public void visitApply(JCMethodInvocation tree) { + tree.meth = translate(tree.meth); + tree.args = translate(tree.args); + result = tree; + } + + public void visitNewClass(JCNewClass tree) { + tree.encl = translate(tree.encl); + tree.clazz = translate(tree.clazz); + tree.args = translate(tree.args); + tree.def = translate(tree.def); + result = tree; + } + + public void visitNewArray(JCNewArray tree) { + tree.elemtype = translate(tree.elemtype); + tree.dims = translate(tree.dims); + tree.elems = translate(tree.elems); + result = tree; + } + + public void visitParens(JCParens tree) { + tree.expr = translate(tree.expr); + result = tree; + } + + public void visitAssign(JCAssign tree) { + tree.lhs = translate(tree.lhs); + tree.rhs = translate(tree.rhs); + result = tree; + } + + public void visitAssignop(JCAssignOp tree) { + tree.lhs = translate(tree.lhs); + tree.rhs = translate(tree.rhs); + result = tree; + } + + public void visitUnary(JCUnary tree) { + tree.arg = translate(tree.arg); + result = tree; + } + + public void visitBinary(JCBinary tree) { + tree.lhs = translate(tree.lhs); + tree.rhs = translate(tree.rhs); + result = tree; + } + + public void visitTypeCast(JCTypeCast tree) { + tree.clazz = translate(tree.clazz); + tree.expr = translate(tree.expr); + result = tree; + } + + public void visitTypeTest(JCInstanceOf tree) { + tree.expr = translate(tree.expr); + tree.clazz = translate(tree.clazz); + result = tree; + } + + public void visitIndexed(JCArrayAccess tree) { + tree.indexed = translate(tree.indexed); + tree.index = translate(tree.index); + result = tree; + } + + public void visitSelect(JCFieldAccess tree) { + tree.selected = translate(tree.selected); + result = tree; + } + + public void visitIdent(JCIdent tree) { + result = tree; + } + + public void visitLiteral(JCLiteral tree) { + result = tree; + } + + public void visitTypeIdent(JCPrimitiveTypeTree tree) { + result = tree; + } + + public void visitTypeArray(JCArrayTypeTree tree) { + tree.elemtype = translate(tree.elemtype); + result = tree; + } + + public void visitTypeApply(JCTypeApply tree) { + tree.clazz = translate(tree.clazz); + tree.arguments = translate(tree.arguments); + result = tree; + } + + public void visitTypeUnion(JCTypeUnion tree) { + tree.alternatives = translate(tree.alternatives); + result = tree; + } + + public void visitTypeParameter(JCTypeParameter tree) { + tree.bounds = translate(tree.bounds); + result = tree; + } + + @Override + public void visitWildcard(JCWildcard tree) { + tree.kind = translate(tree.kind); + tree.inner = translate(tree.inner); + result = tree; + } + + @Override + public void visitTypeBoundKind(TypeBoundKind tree) { + result = tree; + } + + public void visitErroneous(JCErroneous tree) { + result = tree; + } + + public void visitLetExpr(LetExpr tree) { + tree.defs = translateVarDefs(tree.defs); + tree.expr = translate(tree.expr); + result = tree; + } + + public void visitModifiers(JCModifiers tree) { + tree.annotations = translateAnnotations(tree.annotations); + result = tree; + } + + public void visitAnnotation(JCAnnotation tree) { + tree.annotationType = translate(tree.annotationType); + tree.args = translate(tree.args); + result = tree; + } + + public void visitTree(JCTree tree) { + throw new AssertionError(tree); + } +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/util/Abort.java b/douyu-javac/src/main/java/com/sun/tools/javac/util/Abort.java new file mode 100644 index 0000000..996f177 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/util/Abort.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 1999, 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.util; + +/** Throwing an instance of + * this class causes (silent) termination of the main compiler method. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class Abort extends Error { + private static final long serialVersionUID = 0; + + public Abort(Throwable cause) { + super(cause); + } + + public Abort() { + super(); + } + +} diff --git a/douyu-javac/src/main/java/com/sun/tools/javac/util/AbstractDiagnosticFormatter.java b/douyu-javac/src/main/java/com/sun/tools/javac/util/AbstractDiagnosticFormatter.java new file mode 100644 index 0000000..4ff2376 --- /dev/null +++ b/douyu-javac/src/main/java/com/sun/tools/javac/util/AbstractDiagnosticFormatter.java @@ -0,0 +1,509 @@ +/* + * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.sun.tools.javac.util; + +import java.util.Arrays; +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import javax.tools.JavaFileObject; + +import com.sun.tools.javac.api.DiagnosticFormatter; +import com.sun.tools.javac.api.DiagnosticFormatter.Configuration.DiagnosticPart; +import com.sun.tools.javac.api.DiagnosticFormatter.Configuration.MultilineLimit; +import com.sun.tools.javac.api.DiagnosticFormatter.PositionKind; +import com.sun.tools.javac.api.Formattable; +import com.sun.tools.javac.code.Lint.LintCategory; +import com.sun.tools.javac.code.Printer; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.Type.CapturedType; + +import com.sun.tools.javac.file.BaseFileObject; +import static com.sun.tools.javac.util.JCDiagnostic.DiagnosticType.*; + +/** + * This abstract class provides a basic implementation of the functionalities that should be provided + * by any formatter used by javac. Among the main features provided by AbstractDiagnosticFormatter are: + * + *