From 1268e9f2045dc8a53a60283b78ae2ade09e384b5 Mon Sep 17 00:00:00 2001 From: lusiyi Date: Thu, 10 Nov 2022 16:25:24 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 10 + pom.xml | 257 + src/main/go-mod/excel.go | 48 + src/main/go-mod/func.go | 24 + src/main/go-mod/main.go | 4 + src/main/go-mod/struct.go | 7 + .../bridge/controller/BaseController.java | 25 + .../bridge/controller/MainController.java | 49 + .../bridge/handler/WebsshHandler.java | 38 + .../com/educoder/bridge/model/SSHInfo.java | 41 + .../com/educoder/bridge/model/SSHSession.java | 45 + .../educoder/bridge/service/JchService.java | 261 + .../educoder/bridge/tmp/ASTStringUtilExt.java | 169 + .../bridge/tmp/AbstractLangProcessor.java | 248 + .../com/educoder/bridge/tmp/AliasEntity.java | 203 + .../bridge/tmp/AnnotationProcessor.java | 121 + .../educoder/bridge/tmp/BindingResolver.java | 297 + .../com/educoder/bridge/tmp/BuiltInType.java | 110 + .../educoder/bridge/tmp/CandidateTypes.java | 311 ++ .../educoder/bridge/tmp/CdtCppFileParser.java | 86 + .../educoder/bridge/tmp/ContainerEntity.java | 467 ++ .../educoder/bridge/tmp/CppBuiltInType.java | 66 + .../bridge/tmp/CppImportLookupStrategy.java | 133 + .../com/educoder/bridge/tmp/CppVisitor.java | 338 ++ .../educoder/bridge/tmp/DecoratedEntity.java | 148 + .../bridge/tmp/DependencyGenerator.java | 160 + .../educoder/bridge/tmp/DependencyMatrix.java | 102 + .../educoder/bridge/tmp/DependsCommand.java | 161 + ...TestBase_No_ResponseDuirngTypeResolve.java | 222 + .../java/com/educoder/bridge/tmp/Entity.java | 275 + .../bridge/tmp/EntityExtractTest.java | 101 + .../tmp/ExcelXlsFormatDependencyDumper.java | 120 + .../tmp/ExcelXlsxFormatDependencyDumper.java | 119 + .../com/educoder/bridge/tmp/Expression.java | 413 ++ .../educoder/bridge/tmp/ExpressionUsage.java | 298 + .../com/educoder/bridge/tmp/ExtractText.java | 129 + .../com/educoder/bridge/tmp/FileEntity.java | 169 + .../tmp/FormalParameterListContextHelper.java | 99 + .../educoder/bridge/tmp/FunctionEntity.java | 149 + .../com/educoder/bridge/tmp/GenericName.java | 117 + .../bridge/tmp/GoImportLookupStrategy.java | 103 + .../com/educoder/bridge/tmp/GoListener.java | 136 + .../com/educoder/bridge/tmp/GoParserBase.java | 132 + .../educoder/bridge/tmp/HandlerContext.java | 333 ++ .../bridge/tmp/InMemoryEntityRepo.java | 135 + .../bridge/tmp/IncludeRelationTest.java | 109 + .../com/educoder/bridge/tmp/JDataBuilder.java | 88 + .../com/educoder/bridge/tmp/JRubyVisitor.java | 281 + .../educoder/bridge/tmp/JavaBuiltInType.java | 84 + .../bridge/tmp/JavaImportLookupStrategy.java | 105 + .../com/educoder/bridge/tmp/JavaListener.java | 337 ++ .../bridge/tmp/JavaVarResolveTest.java | 100 + .../com/educoder/bridge/tmp/JchService.java | 261 + .../java/com/educoder/bridge/tmp/Main.java | 197 + .../bridge/tmp/MatrixLevelReducer.java | 131 + .../com/educoder/bridge/tmp/ParserTest.java | 109 + .../com/educoder/bridge/tmp/PomListener.java | 165 + .../bridge/tmp/PreprocessorHandler.java | 126 + .../bridge/tmp/PythonBuiltInType.java | 96 + .../bridge/tmp/PythonCodeListener.java | 371 ++ .../educoder/bridge/tmp/PythonImportTest.java | 284 + .../educoder/bridge/tmp/PythonLexerBase.java | 186 + .../tmp/PythonParameterTypeDedudceTest.java | 118 + .../educoder/bridge/tmp/RelationCounter.java | 246 + .../bridge/tmp/RubyHandlerContext.java | 105 + .../educoder/bridge/tmp/RubyParserHelper.java | 165 + .../com/educoder/bridge/tmp/RubyVarTest.java | 172 + .../com/educoder/bridge/tmp/TypeEntity.java | 204 + .../bridge/tmp/UnsolvedSymbolDumper.java | 96 + .../com/educoder/bridge/tmp/VarEntity.java | 106 + .../com/educoder/bridge/utils/Base64Util.java | 52 + src/main/resources/applicationContext.xml | 42 + src/main/resources/logback.xml | 47 + .../webapp/WEB-INF/mvc-dispatcher-servlet.xml | 34 + src/main/webapp/WEB-INF/pages/index.ftl | 61 + src/main/webapp/WEB-INF/web.xml | 63 + src/main/webapp/index.html | 2 + src/main/webapp/static/css/main.css | 53 + src/main/webapp/static/css/pure-min.css | 11 + src/main/webapp/static/css/reset.css | 44 + src/main/webapp/static/css/style.css | 187 + src/main/webapp/static/css/supersized.css | 34 + src/main/webapp/static/css/tooltip.css | 25 + src/main/webapp/static/css/xterm.css | 2273 ++++++++ .../webapp/static/image/backgrounds/1.jpg | Bin 0 -> 72128 bytes .../webapp/static/image/backgrounds/2.jpg | Bin 0 -> 68833 bytes .../webapp/static/image/backgrounds/3.jpg | Bin 0 -> 52931 bytes src/main/webapp/static/image/facebook.png | Bin 0 -> 623 bytes src/main/webapp/static/image/favicon.ico | Bin 0 -> 16958 bytes src/main/webapp/static/image/progress.gif | Bin 0 -> 2608 bytes src/main/webapp/static/image/twitter.png | Bin 0 -> 933 bytes src/main/webapp/static/js/base64.js | 103 + src/main/webapp/static/js/formvalid.js | 234 + src/main/webapp/static/js/jquerymin.js | 5 + src/main/webapp/static/js/main.js | 124 + src/main/webapp/static/js/supersized-init.js | 30 + .../webapp/static/js/supersized.3.2.7.min.js | 13 + src/main/webapp/static/js/ws.js | 67 + src/main/webapp/static/js/xterm.js | 4959 +++++++++++++++++ 99 files changed, 19684 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/go-mod/excel.go create mode 100644 src/main/go-mod/func.go create mode 100644 src/main/go-mod/main.go create mode 100644 src/main/go-mod/struct.go create mode 100644 src/main/java/com/educoder/bridge/controller/BaseController.java create mode 100644 src/main/java/com/educoder/bridge/controller/MainController.java create mode 100644 src/main/java/com/educoder/bridge/handler/WebsshHandler.java create mode 100644 src/main/java/com/educoder/bridge/model/SSHInfo.java create mode 100644 src/main/java/com/educoder/bridge/model/SSHSession.java create mode 100644 src/main/java/com/educoder/bridge/service/JchService.java create mode 100644 src/main/java/com/educoder/bridge/tmp/ASTStringUtilExt.java create mode 100644 src/main/java/com/educoder/bridge/tmp/AbstractLangProcessor.java create mode 100644 src/main/java/com/educoder/bridge/tmp/AliasEntity.java create mode 100644 src/main/java/com/educoder/bridge/tmp/AnnotationProcessor.java create mode 100644 src/main/java/com/educoder/bridge/tmp/BindingResolver.java create mode 100644 src/main/java/com/educoder/bridge/tmp/BuiltInType.java create mode 100644 src/main/java/com/educoder/bridge/tmp/CandidateTypes.java create mode 100644 src/main/java/com/educoder/bridge/tmp/CdtCppFileParser.java create mode 100644 src/main/java/com/educoder/bridge/tmp/ContainerEntity.java create mode 100644 src/main/java/com/educoder/bridge/tmp/CppBuiltInType.java create mode 100644 src/main/java/com/educoder/bridge/tmp/CppImportLookupStrategy.java create mode 100644 src/main/java/com/educoder/bridge/tmp/CppVisitor.java create mode 100644 src/main/java/com/educoder/bridge/tmp/DecoratedEntity.java create mode 100644 src/main/java/com/educoder/bridge/tmp/DependencyGenerator.java create mode 100644 src/main/java/com/educoder/bridge/tmp/DependencyMatrix.java create mode 100644 src/main/java/com/educoder/bridge/tmp/DependsCommand.java create mode 100644 src/main/java/com/educoder/bridge/tmp/EclipseTestBase_No_ResponseDuirngTypeResolve.java create mode 100644 src/main/java/com/educoder/bridge/tmp/Entity.java create mode 100644 src/main/java/com/educoder/bridge/tmp/EntityExtractTest.java create mode 100644 src/main/java/com/educoder/bridge/tmp/ExcelXlsFormatDependencyDumper.java create mode 100644 src/main/java/com/educoder/bridge/tmp/ExcelXlsxFormatDependencyDumper.java create mode 100644 src/main/java/com/educoder/bridge/tmp/Expression.java create mode 100644 src/main/java/com/educoder/bridge/tmp/ExpressionUsage.java create mode 100644 src/main/java/com/educoder/bridge/tmp/ExtractText.java create mode 100644 src/main/java/com/educoder/bridge/tmp/FileEntity.java create mode 100644 src/main/java/com/educoder/bridge/tmp/FormalParameterListContextHelper.java create mode 100644 src/main/java/com/educoder/bridge/tmp/FunctionEntity.java create mode 100644 src/main/java/com/educoder/bridge/tmp/GenericName.java create mode 100644 src/main/java/com/educoder/bridge/tmp/GoImportLookupStrategy.java create mode 100644 src/main/java/com/educoder/bridge/tmp/GoListener.java create mode 100644 src/main/java/com/educoder/bridge/tmp/GoParserBase.java create mode 100644 src/main/java/com/educoder/bridge/tmp/HandlerContext.java create mode 100644 src/main/java/com/educoder/bridge/tmp/InMemoryEntityRepo.java create mode 100644 src/main/java/com/educoder/bridge/tmp/IncludeRelationTest.java create mode 100644 src/main/java/com/educoder/bridge/tmp/JDataBuilder.java create mode 100644 src/main/java/com/educoder/bridge/tmp/JRubyVisitor.java create mode 100644 src/main/java/com/educoder/bridge/tmp/JavaBuiltInType.java create mode 100644 src/main/java/com/educoder/bridge/tmp/JavaImportLookupStrategy.java create mode 100644 src/main/java/com/educoder/bridge/tmp/JavaListener.java create mode 100644 src/main/java/com/educoder/bridge/tmp/JavaVarResolveTest.java create mode 100644 src/main/java/com/educoder/bridge/tmp/JchService.java create mode 100644 src/main/java/com/educoder/bridge/tmp/Main.java create mode 100644 src/main/java/com/educoder/bridge/tmp/MatrixLevelReducer.java create mode 100644 src/main/java/com/educoder/bridge/tmp/ParserTest.java create mode 100644 src/main/java/com/educoder/bridge/tmp/PomListener.java create mode 100644 src/main/java/com/educoder/bridge/tmp/PreprocessorHandler.java create mode 100644 src/main/java/com/educoder/bridge/tmp/PythonBuiltInType.java create mode 100644 src/main/java/com/educoder/bridge/tmp/PythonCodeListener.java create mode 100644 src/main/java/com/educoder/bridge/tmp/PythonImportTest.java create mode 100644 src/main/java/com/educoder/bridge/tmp/PythonLexerBase.java create mode 100644 src/main/java/com/educoder/bridge/tmp/PythonParameterTypeDedudceTest.java create mode 100644 src/main/java/com/educoder/bridge/tmp/RelationCounter.java create mode 100644 src/main/java/com/educoder/bridge/tmp/RubyHandlerContext.java create mode 100644 src/main/java/com/educoder/bridge/tmp/RubyParserHelper.java create mode 100644 src/main/java/com/educoder/bridge/tmp/RubyVarTest.java create mode 100644 src/main/java/com/educoder/bridge/tmp/TypeEntity.java create mode 100644 src/main/java/com/educoder/bridge/tmp/UnsolvedSymbolDumper.java create mode 100644 src/main/java/com/educoder/bridge/tmp/VarEntity.java create mode 100644 src/main/java/com/educoder/bridge/utils/Base64Util.java create mode 100644 src/main/resources/applicationContext.xml create mode 100644 src/main/resources/logback.xml create mode 100644 src/main/webapp/WEB-INF/mvc-dispatcher-servlet.xml create mode 100644 src/main/webapp/WEB-INF/pages/index.ftl create mode 100644 src/main/webapp/WEB-INF/web.xml create mode 100644 src/main/webapp/index.html create mode 100644 src/main/webapp/static/css/main.css create mode 100644 src/main/webapp/static/css/pure-min.css create mode 100644 src/main/webapp/static/css/reset.css create mode 100644 src/main/webapp/static/css/style.css create mode 100644 src/main/webapp/static/css/supersized.css create mode 100644 src/main/webapp/static/css/tooltip.css create mode 100644 src/main/webapp/static/css/xterm.css create mode 100644 src/main/webapp/static/image/backgrounds/1.jpg create mode 100644 src/main/webapp/static/image/backgrounds/2.jpg create mode 100644 src/main/webapp/static/image/backgrounds/3.jpg create mode 100644 src/main/webapp/static/image/facebook.png create mode 100644 src/main/webapp/static/image/favicon.ico create mode 100644 src/main/webapp/static/image/progress.gif create mode 100644 src/main/webapp/static/image/twitter.png create mode 100644 src/main/webapp/static/js/base64.js create mode 100644 src/main/webapp/static/js/formvalid.js create mode 100644 src/main/webapp/static/js/jquerymin.js create mode 100644 src/main/webapp/static/js/main.js create mode 100644 src/main/webapp/static/js/supersized-init.js create mode 100644 src/main/webapp/static/js/supersized.3.2.7.min.js create mode 100644 src/main/webapp/static/js/ws.js create mode 100644 src/main/webapp/static/js/xterm.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ae05cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +.idea/ +*.iml +target/ +2017* +.project +.classpath +.settings/ +*.log +bin/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..3d2baa8 --- /dev/null +++ b/pom.xml @@ -0,0 +1,257 @@ + + 4.0.0 + com.educoder + webssh + war + 1.0-SNAPSHOT + webssh Maven Webapp + http://maven.apache.org + + + 2.6.1 + 4.3.6.RELEASE + 2.3.25-incubating + 0.1.54 + 7.0 + 2.6 + 2.4 + 1.7.21 + 1.2.20 + 2.8.6 + 1.10 + + 1.8 + 1.8 + UTF-8 + + + + + + org.freemarker + freemarker + ${freemarker.version} + + + + com.jcraft + jsch + ${jsch.version} + + + + io.springfox + springfox-swagger2 + ${swagger2.version} + + + io.springfox + springfox-swagger-ui + ${swagger2.version} + + + + org.springframework + spring-webmvc + ${spring.version} + + + + org.springframework + spring-websocket + ${spring.version} + + + + org.springframework + spring-context-support + ${spring.version} + + + + org.springframework + spring-test + ${spring.version} + + + + javax + javaee-api + ${javax.version} + provided + + + + commons-lang + commons-lang + ${commons-lang.version} + + + + commons-io + commons-io + ${commons-io.version} + + + + junit + junit + 4.12 + test + + + + org.springframework + spring-aop + 4.3.6.RELEASE + + + + org.aspectj + aspectjrt + 1.8.10 + + + + org.springframework + spring-aspects + ${spring.version} + + + + org.springframework + spring-test + ${spring.version} + + + + ch.qos.logback + logback-classic + 1.2.3 + + + org.logback-extensions + logback-ext-spring + 0.1.4 + + + + com.alibaba + fastjson + ${fastjson.version} + + + + commons-codec + commons-codec + ${codec.version} + + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.18.1 + + true + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.0.0 + + + package + + shade + + + ${project.build.directory}/dependency-reduced-pom.xml + + + META-INF/spring.handlers + + + META-INF/spring.schemas + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + org.apache.tomcat.maven + tomcat7-maven-plugin + 2.2 + +
0.0.0.0
+ 9001 + / + UTF-8 + webssh + tomcat7 +
+
+ + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + 1.8 + 1.8 + + +
+ + webssh +
+
diff --git a/src/main/go-mod/excel.go b/src/main/go-mod/excel.go new file mode 100644 index 0000000..1bfb4a0 --- /dev/null +++ b/src/main/go-mod/excel.go @@ -0,0 +1,48 @@ +package excel + +import ( + "fmt" + "reflect" + + "github.com/360EntSecGroup-Skylar/excelize" +) + +// WriteXlsx 生成表格到本地服务器 +func WriteXlsx(sheet string, records interface{}) *excelize.File { + f := excelize.NewFile() // new file + index := f.NewSheet(sheet) // new sheet + f.SetActiveSheet(index) // set active (default) sheet + firstCharacter := 65 // start from 'A' line + t := reflect.TypeOf(records) + + if t.Kind() != reflect.Slice { + return f + } + + s := reflect.ValueOf(records) + for i := 0; i < s.Len(); i++ { + elem := s.Index(i).Interface() + elemType := reflect.TypeOf(elem) + elemValue := reflect.ValueOf(elem) + for j := 0; j < elemType.NumField(); j++ { + field := elemType.Field(j) + // 结构体中xlsx 类似json + tag := field.Tag.Get("xlsx") + name := tag + column := string(firstCharacter + j) + if tag == "" { + continue + } + // 设置表头 + if i == 0 { + f.SetCellValue(sheet, fmt.Sprintf("%s%d", column, i+1), name) + } + // 设置列宽 + f.SetColWidth(sheet, "A", fmt.Sprintf("%s", column), 20) + + // 设置内容 + f.SetCellValue(sheet, fmt.Sprintf("%s%d", column, i+2), elemValue.Field(j).Interface()) + } + } + return f +} diff --git a/src/main/go-mod/func.go b/src/main/go-mod/func.go new file mode 100644 index 0000000..d88120a --- /dev/null +++ b/src/main/go-mod/func.go @@ -0,0 +1,24 @@ +package main + +import "fmt" + +type Publisher struct { + name string + address string +} +type Book struct { + title, name string + publisher Publisher +} + +func main() { + var book = Book{"title","name",Publisher{"pub","beijing"}} + /* 这是我的第一个简单的程序 */ + fmt.Println("Hello, World!") + fmt.Println(book) + +} + +func test (book Book) (b1 Book, b2 Book){ + return book,book +} \ No newline at end of file diff --git a/src/main/go-mod/main.go b/src/main/go-mod/main.go new file mode 100644 index 0000000..b9754f0 --- /dev/null +++ b/src/main/go-mod/main.go @@ -0,0 +1,4 @@ +func main(){ + printf('hello wold') + io.out('ok') + } \ No newline at end of file diff --git a/src/main/go-mod/struct.go b/src/main/go-mod/struct.go new file mode 100644 index 0000000..1de4533 --- /dev/null +++ b/src/main/go-mod/struct.go @@ -0,0 +1,7 @@ +package test +type Books struct { + title string + author string + subject string + book_id int +} diff --git a/src/main/java/com/educoder/bridge/controller/BaseController.java b/src/main/java/com/educoder/bridge/controller/BaseController.java new file mode 100644 index 0000000..5fafd7d --- /dev/null +++ b/src/main/java/com/educoder/bridge/controller/BaseController.java @@ -0,0 +1,25 @@ +package com.educoder.bridge.controller; + +import org.springframework.web.bind.annotation.ModelAttribute; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + + +/** + * @author lqk + * @version 0.1 + */ +public class BaseController { + protected HttpServletRequest request; + protected HttpServletResponse response; + protected HttpSession session; + + @ModelAttribute + public void setReqAndRes(HttpServletRequest request, HttpServletResponse response) { + this.request = request; + this.response = response; + this.session = request.getSession(); + } +} diff --git a/src/main/java/com/educoder/bridge/controller/MainController.java b/src/main/java/com/educoder/bridge/controller/MainController.java new file mode 100644 index 0000000..f232380 --- /dev/null +++ b/src/main/java/com/educoder/bridge/controller/MainController.java @@ -0,0 +1,49 @@ +package com.educoder.bridge.controller; + +import io.swagger.annotations.Api; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.ModelAndView; + +/** + * @author guange + * + * @date 2017/08/02 + */ + +@Api(value = "提供webssh连接", hidden = true) +@RestController +public class MainController extends BaseController { + private final static Logger logger = LoggerFactory.getLogger(MainController.class); +// +// @RequestMapping(value={"/"}, method= RequestMethod.GET) +// public ModelAndView index(@RequestParam("host")String host, +// @RequestParam("port")int port, +// @RequestParam("username")String username, +// @RequestParam("password")String password, +// @RequestParam("rows")int rows) { +// logger.debug("/ssh: 接收到连接请求, host: {}, port: {}", host, port); +// ModelAndView mv = new ModelAndView(); +// mv.setViewName("index"); +// mv.addObject("host", host); +// mv.addObject("port", port); +// mv.addObject("username", username); +// mv.addObject("password", password); +// mv.addObject("rows", rows); +// mv.addObject("digest", System.currentTimeMillis()); +// return mv; +// } + + @RequestMapping(value={"/"}, method= RequestMethod.GET) + public ModelAndView index() { + ModelAndView mv = new ModelAndView(); + mv.setViewName("index"); + mv.addObject("digest", System.currentTimeMillis()); + return mv; + } + +} diff --git a/src/main/java/com/educoder/bridge/handler/WebsshHandler.java b/src/main/java/com/educoder/bridge/handler/WebsshHandler.java new file mode 100644 index 0000000..34665bc --- /dev/null +++ b/src/main/java/com/educoder/bridge/handler/WebsshHandler.java @@ -0,0 +1,38 @@ +package com.educoder.bridge.handler; + +import com.educoder.bridge.service.JchService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +public class WebsshHandler extends TextWebSocketHandler { + + @Autowired + JchService jchService; + + @Override + public void afterConnectionEstablished(WebSocketSession wsSession) throws Exception { + super.afterConnectionEstablished(wsSession); + jchService.add(wsSession); + } + + /** + * 重写handleTextMessage方法,用于处理从websocket接收到的信息 + */ + @Override + protected void handleTextMessage(WebSocketSession wsSession, TextMessage message) throws Exception { + jchService.recv(message.getPayload(), wsSession); + super.handleTextMessage(wsSession, message); + } + + + @Override + public void afterConnectionClosed(WebSocketSession wsSession, CloseStatus status) throws Exception { + super.afterConnectionClosed(wsSession, status); + jchService.closeByWebSocket(wsSession); + } +} diff --git a/src/main/java/com/educoder/bridge/model/SSHInfo.java b/src/main/java/com/educoder/bridge/model/SSHInfo.java new file mode 100644 index 0000000..4881f63 --- /dev/null +++ b/src/main/java/com/educoder/bridge/model/SSHInfo.java @@ -0,0 +1,41 @@ +package com.educoder.bridge.model; + +public class SSHInfo { + private String host; + private String port; + private String username; + private String password; + + public void setHost(String host) { + this.host = host; + } + + public void setPort(String port) { + this.port = port; + } + + public void setUsername(String username) { + this.username = username; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getHost() { + return host; + } + + public int getPort() { + return Integer.parseInt(port); + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + +} diff --git a/src/main/java/com/educoder/bridge/model/SSHSession.java b/src/main/java/com/educoder/bridge/model/SSHSession.java new file mode 100644 index 0000000..e9db868 --- /dev/null +++ b/src/main/java/com/educoder/bridge/model/SSHSession.java @@ -0,0 +1,45 @@ +package com.educoder.bridge.model; + +import com.jcraft.jsch.ChannelShell; +import org.springframework.web.socket.WebSocketSession; + +import java.io.OutputStream; + +public class SSHSession { + private WebSocketSession webSocketSession; + private OutputStream outputStream; + private ChannelShell channel; + private SSHInfo SSHInfo; + + public SSHInfo getSSHInfo() { + return SSHInfo; + } + + public void setSSHInfo(SSHInfo SSHInfo) { + this.SSHInfo = SSHInfo; + } + + public ChannelShell getChannel() { + return channel; + } + + public void setChannel(ChannelShell channel) { + this.channel = channel; + } + + public WebSocketSession getWebSocketSession() { + return webSocketSession; + } + + public void setWebSocketSession(WebSocketSession webSocketSession) { + this.webSocketSession = webSocketSession; + } + + public OutputStream getOutputStream() { + return outputStream; + } + + public void setOutputStream(OutputStream outputStream) { + this.outputStream = outputStream; + } +} diff --git a/src/main/java/com/educoder/bridge/service/JchService.java b/src/main/java/com/educoder/bridge/service/JchService.java new file mode 100644 index 0000000..8657d1d --- /dev/null +++ b/src/main/java/com/educoder/bridge/service/JchService.java @@ -0,0 +1,261 @@ +package com.educoder.bridge.service; + +import com.alibaba.fastjson.JSONObject; +import com.educoder.bridge.model.SSHInfo; +import com.educoder.bridge.model.SSHSession; +import com.educoder.bridge.utils.Base64Util; +import com.jcraft.jsch.ChannelShell; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.UserInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Service +public class JchService { + + private static List sshSessionQueue = new CopyOnWriteArrayList<>(); + private ExecutorService executorService = Executors.newCachedThreadPool(); + private Logger logger = LoggerFactory.getLogger(getClass()); + + com.jcraft.jsch.Logger jschLogger = new com.jcraft.jsch.Logger() { + + @Override + public boolean isEnabled(int arg0) { + return true; + } + + @Override + public void log(int arg0, String arg1) { + if (logger.isTraceEnabled()) { + logger.trace("JSch Log [Level " + arg0 + "]: " + arg1); + } + } + }; + + + /** + * 在webSocket连接时,初始化一个ssh连接 + * + * @param webSocketSession webSocket连接 + */ + public void add(WebSocketSession webSocketSession) { + + SSHSession sshSession = new SSHSession(); + sshSession.setWebSocketSession(webSocketSession); + + sshSessionQueue.add(sshSession); + } + + /** + * 处理客户端发过来的数据 + * @param buffer 数据 + * @param webSocketSession webSocket连接 + */ + public void recv(String buffer, WebSocketSession webSocketSession) { + + SSHSession sshSession = null; + try { + logger.debug("webSocketSessionID: {}, 信息: {}", webSocketSession.getId(), buffer); + JSONObject info = JSONObject.parseObject(buffer); + String tp = info.getString("tp"); + sshSession = findByWebSocketSession(webSocketSession); + + //初始化连接 + if ("init".equals(tp)) { +// {"tp":"init","data":{"host":"127.0.0.1","port":"41080","username":"root","password":"123123"}} + SSHInfo sshInfo = info.getObject("data", SSHInfo.class); + sshSession.setSSHInfo(sshInfo); + + if (sshSession != null) { + SSHSession finalSSHSession = sshSession; + + // 新开一个线程建立连接,连接开启之后以一直监听来自客户端的输入 + executorService.execute(() -> { + connectTossh(finalSSHSession); + }); + } + } else if ("client".equals(tp)) { + String data = info.getString("data"); + + // 将网页输入的数据传送给后端服务器 + if (sshSession != null) { + transTossh(sshSession.getOutputStream(), data); + } + } + } catch (Exception e) { + logger.error("转发命令到ssh出错: {}", e); + + close(sshSession); + } + + } + + /** + * 将数据传送给服务端作为SSH的输入 + * + * @param outputStream + * @param data + * @throws IOException + */ + private void transTossh(OutputStream outputStream, String data) throws IOException { + if (outputStream != null) { + outputStream.write(data.getBytes()); + outputStream.flush(); + } + } + + /** + * 连接ssh + * + * @param sshSession ssh连接需要的信息 + */ + private void connectTossh(SSHSession sshSession){ + Session jschSession = null; + SSHInfo SSHInfo = sshSession.getSSHInfo(); + try { + JSch jsch = new JSch(); + JSch.setLogger(jschLogger); + + //启动线程 + java.util.Properties config = new java.util.Properties(); + config.put("StrictHostKeyChecking", "no"); + jschSession = jsch.getSession(SSHInfo.getUsername(), SSHInfo.getHost(), SSHInfo.getPort()); + + jschSession.setConfig(config); + jschSession.setPassword(SSHInfo.getPassword()); + jschSession.setUserInfo(new UserInfo() { + @Override + public String getPassphrase() { + return null; + } + + @Override + public String getPassword() { + return null; + } + + @Override + public boolean promptPassword(String s) { + return false; + } + + @Override + public boolean promptPassphrase(String s) { + return false; + } + + @Override + public boolean promptYesNo(String s) { + return true; + } // Accept all server keys + + @Override + public void showMessage(String s) { + } + }); + + jschSession.connect(); + ChannelShell channel = (ChannelShell) jschSession.openChannel("shell"); + channel.setPtyType("xterm"); + + channel.connect(); + + sshSession.setChannel(channel); + InputStream inputStream = channel.getInputStream(); + sshSession.setOutputStream(channel.getOutputStream()); + + sshSession.setSSHInfo(SSHInfo); + logger.debug("主机: {} 连接成功!", SSHInfo.getHost()); + + // 循环读取,jsch的输入为服务器执行命令之后的返回数据 + byte[] buf = new byte[1024]; + while (true) { + int length = inputStream.read(buf); + if (length < 0) { + close(sshSession); + throw new Exception("读取出错,数据长度:" + length); + } + sendMsg(sshSession.getWebSocketSession(), Arrays.copyOfRange(buf, 0, length)); + } + + } catch (Exception e) { + logger.error("ssh连接出错, e: {}", e); + } finally { + logger.info("连接关闭, {}", SSHInfo.getHost()); + if (jschSession != null) { + jschSession.disconnect(); + } + + close(sshSession); + } + } + + + /** + * 发送数据回websocket + * + * @param webSocketSession webSocket连接 + * @param buffer 数据 + * @throws IOException + */ + public void sendMsg(WebSocketSession webSocketSession, byte[] buffer) throws IOException { + logger.debug("服务端返回的数据: {}", new String(buffer, "UTF-8")); + + webSocketSession.sendMessage(new TextMessage(Base64Util.encodeBytes(buffer))); + } + + /** + * 通过webSocket连接在队列中找到对应的SSH连接 + * + * @param webSocketSession webSocket连接 + */ + public SSHSession findByWebSocketSession(WebSocketSession webSocketSession) { + Optional optional = sshSessionQueue.stream().filter(webscoketObj -> webscoketObj.getWebSocketSession() == webSocketSession).findFirst(); + if (optional.isPresent()) { + return optional.get(); + } + return null; + } + + /** + * 关闭ssh和websocket连接 + * + * @param sshSession ssh连接 + */ + private void close(SSHSession sshSession) { + if (sshSession != null) { + sshSession.getChannel().disconnect(); + try { + sshSession.getWebSocketSession().close(); + sshSession.getOutputStream().close(); + } catch (IOException e) { + logger.error("连接关闭失败!e: {}", e); + } + + sshSessionQueue.remove(sshSession); + } + } + + /** + * 通过webSocketSession关闭ssh与webSocket连接 + * + * @param webSocketSession + */ + public void closeByWebSocket(WebSocketSession webSocketSession) { + close(findByWebSocketSession(webSocketSession)); + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/ASTStringUtilExt.java b/src/main/java/com/educoder/bridge/tmp/ASTStringUtilExt.java new file mode 100644 index 0000000..c148844 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/ASTStringUtilExt.java @@ -0,0 +1,169 @@ +package depends.extractor.cpp.cdt; + +import depends.entity.GenericName; +import depends.entity.TypeEntity; +import org.eclipse.cdt.core.dom.ast.*; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNameSpecifier; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTUsingDeclaration; +import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPASTTemplateId; +import org.eclipse.cdt.internal.core.model.ASTStringUtil; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** + * This extends the CDT ASTStringUtil class. + * A tricky point here is that we have to use some of the reflection mechanism to invoke + * some private functions in ASTStringUtils class + * It is not good, but it seems the most easiest one to reuse existing functions + */ +public class ASTStringUtilExt extends ASTStringUtil { + public static String getName(IASTDeclSpecifier decl) { + StringBuilder buffer = new StringBuilder(); + String name = appendBareDeclSpecifierString(buffer, decl).toString().replace("::", ".").replace("...", ""); + return name; + } + + public static String getName(IASTLiteralExpression expr) { + return expr.getRawSignature().replace("::", ".").replace("...", ""); + } + + public static String getTypeIdString(IASTTypeId typeId) { + StringBuilder sb = new StringBuilder(); + return appendBareTypeIdString(sb, typeId).toString().replace("::", "."); + } + + + /** + * retrieve template parameters from declSpecifier + */ + public static List getTemplateParameters(IASTDeclSpecifier declSpecifier) { + List parameters = new ArrayList<>(); + declSpecifier.accept(new TemplateParameterASTVisitor(parameters)); + return parameters; + } + + + + private static StringBuilder appendBareDeclSpecifierString(StringBuilder buffer, IASTDeclSpecifier declSpecifier) { + if (declSpecifier instanceof IASTCompositeTypeSpecifier) { + final IASTCompositeTypeSpecifier compositeTypeSpec = (IASTCompositeTypeSpecifier) declSpecifier; + appendBareNameString(buffer, compositeTypeSpec.getName()); + } else if (declSpecifier instanceof IASTElaboratedTypeSpecifier) { + final IASTElaboratedTypeSpecifier elaboratedTypeSpec = (IASTElaboratedTypeSpecifier) declSpecifier; + appendBareNameString(buffer, elaboratedTypeSpec.getName()); + } else if (declSpecifier instanceof IASTEnumerationSpecifier) { + final IASTEnumerationSpecifier enumerationSpec = (IASTEnumerationSpecifier) declSpecifier; + appendBareNameString(buffer, enumerationSpec.getName()); + } else if (declSpecifier instanceof IASTSimpleDeclSpecifier) { + buffer.append(TypeEntity.buildInType.getRawName()); + } else if (declSpecifier instanceof IASTNamedTypeSpecifier) { + final IASTNamedTypeSpecifier namedTypeSpec = (IASTNamedTypeSpecifier) declSpecifier; + appendBareNameString(buffer, namedTypeSpec.getName()); + } + return buffer; + } + + private static StringBuilder appendBareNameString(StringBuilder buffer, IASTName name) { + if (name instanceof ICPPASTQualifiedName) { + final ICPPASTQualifiedName qualifiedName = (ICPPASTQualifiedName) name; + final ICPPASTNameSpecifier[] segments = qualifiedName.getAllSegments(); + for (int i = 0; i < segments.length; i++) { + if (i > 0) { + buffer.append("."); + } + appendQualifiedNameStringWithReflection(buffer, segments[i]); + } + } else if (name instanceof CPPASTTemplateId) { + appendQualifiedNameStringWithReflection(buffer,(CPPASTTemplateId)name); + } else if (name != null) { + buffer.append(name.getSimpleID()); + } + return buffer; + } + + private static void appendQualifiedNameStringWithReflection(StringBuilder buffer, IASTName name) { + try { + Method m = ASTStringUtil.class.getDeclaredMethod("appendQualifiedNameString", StringBuilder.class, + IASTName.class); + m.setAccessible(true); // if security settings allow this + m.invoke(null, buffer, name); // use null if the method is static + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException + | InvocationTargetException e) { + System.err.println("Error: cannot invoke ASTStringUtils method of "); + } + } + + private static void appendQualifiedNameStringWithReflection(StringBuilder buffer, + CPPASTTemplateId templateId) { + appendQualifiedNameStringWithReflection(buffer,templateId.getTemplateName()); + } + + private static void appendQualifiedNameStringWithReflection(StringBuilder buffer, + ICPPASTNameSpecifier nameSpecifier) { + if (nameSpecifier instanceof CPPASTTemplateId) { + appendQualifiedNameStringWithReflection(buffer,(CPPASTTemplateId)nameSpecifier); + return; + } + try { + Method m = ASTStringUtil.class.getDeclaredMethod("appendQualifiedNameString", StringBuilder.class, + ICPPASTNameSpecifier.class); + m.setAccessible(true); // if security settings allow this + m.invoke(null, buffer, nameSpecifier); // use null if the method is static + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException + | InvocationTargetException e) { + System.err.println("Error: cannot invoke ASTStringUtils method of "); + } + } + + private static StringBuilder appendBareTypeIdString(StringBuilder buffer, IASTTypeId typeId) { + return appendBareDeclSpecifierString(buffer, typeId.getDeclSpecifier()); + } + + + public static String getName(IASTDeclarator declarator) { + return declarator.getName().toString().replace("::", "."); + } + + + public static String getName(ICPPASTUsingDeclaration declaration) { + return declaration.getName().toString().replace("::", "."); + } + + + public static String getName(IASTName name) { + return name.getRawSignature().toString().replace("::", "."); + } + + + private static StringBuilder appendBareNameString(StringBuilder buffer, ICPPASTNameSpecifier name) { + if (name instanceof ICPPASTQualifiedName) { + final ICPPASTQualifiedName qualifiedName = (ICPPASTQualifiedName) name; + final ICPPASTNameSpecifier[] segments = qualifiedName.getAllSegments(); + for (int i = 0; i < segments.length; i++) { + if (i > 0) { + buffer.append("."); + } + appendQualifiedNameStringWithReflection(buffer, segments[i]); + } + } else if (name instanceof CPPASTTemplateId) { + appendQualifiedNameStringWithReflection(buffer,(CPPASTTemplateId)name); + } else if (name != null) { + buffer.append(name.getRawSignature()); + } + return buffer; + } + + public static String getName(ICPPASTNameSpecifier nameSpecifier) { + StringBuilder buffer = new StringBuilder(); + String name = appendBareNameString(buffer, nameSpecifier).toString().replace("::", ".").replace("...", ""); + return name; + } + + + + +} \ No newline at end of file diff --git a/src/main/java/com/educoder/bridge/tmp/AbstractLangProcessor.java b/src/main/java/com/educoder/bridge/tmp/AbstractLangProcessor.java new file mode 100644 index 0000000..da5728c --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/AbstractLangProcessor.java @@ -0,0 +1,248 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.extractor; + +import depends.entity.Entity; +import depends.entity.FileEntity; +import depends.entity.repo.BuiltInType; +import depends.entity.repo.EntityRepo; +import depends.entity.repo.InMemoryEntityRepo; +import depends.relations.ImportLookupStrategy; +import depends.relations.IBindingResolver; +import multilang.depends.util.file.FileTraversal; +import multilang.depends.util.file.FileUtil; +import org.codehaus.plexus.util.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +abstract public class AbstractLangProcessor { + + /** + * The name of the lang + * + * @return + */ + public abstract String supportedLanguage(); + + /** + * The file suffixes in the lang + * + * @return + */ + public abstract String[] fileSuffixes(); + + /** + * Strategy of how to lookup types and entities in the lang. + * + * @return + */ + public abstract ImportLookupStrategy getImportLookupStrategy(); + + /** + * The builtInType of the lang. + * + * @return + */ + public abstract BuiltInType getBuiltInType(); + + /** + * The language specific file parser + * + * @param fileFullPath + * @return + */ + public abstract FileParser createFileParser(); + + public IBindingResolver bindingResolver; + protected EntityRepo entityRepo; + protected String inputSrcPath; + public String[] includeDirs; + private Set potentialExternalDependencies; + private List includePaths; + private static Logger logger = LoggerFactory.getLogger(AbstractLangProcessor.class); + + public AbstractLangProcessor() { + entityRepo = new InMemoryEntityRepo(); + } + + /** + * The process steps of build dependencies. Step 1: parse all files, add + * entities and expression into repositories Step 2: resolve bindings of files + * (if not resolved yet) Step 3: identify dependencies + * + * @param inputDir + * @param includeDir + * @param bindingResolver + * @return + */ + public EntityRepo buildDependencies(String inputDir, String[] includeDir, IBindingResolver bindingResolver) { + this.inputSrcPath = inputDir; + this.includeDirs = includeDir; + this.bindingResolver = bindingResolver; + logger.info("Start parsing files..."); + parseAllFiles(); + markAllEntitiesScope(); + if (logger.isInfoEnabled()) { + logger.info("Resolve types and bindings of variables, methods and expressions.... " + this.inputSrcPath); + logger.info("Heap Information: " + ManagementFactory.getMemoryMXBean().getHeapMemoryUsage()); + } + resolveBindings(); + if (logger.isInfoEnabled()) { + System.gc(); + logger.info("Heap Information: " + ManagementFactory.getMemoryMXBean().getHeapMemoryUsage()); + } + return entityRepo; + } + + private void markAllEntitiesScope() { + entityRepo.getFileEntities().stream().forEach(entity -> { + Entity file = entity.getAncestorOfType(FileEntity.class); + try { + if (!file.getQualifiedName().startsWith(this.inputSrcPath)) { + entity.setInScope(false); + } + } catch (Exception e) { + + } + }); + } + + /** + * @return unsolved bindings + */ + public void resolveBindings() { + System.out.println("Resolve types and bindings of variables, methods and expressions...."); + this.potentialExternalDependencies = bindingResolver.resolveAllBindings(this.isEagerExpressionResolve()); + if (getExternalDependencies().size() > 0) { + System.out.println("There are " + getExternalDependencies().size() + " items are potential external dependencies."); + } + System.out.println("types and bindings resolved successfully..."); + } + + + + private final void parseAllFiles() { + System.out.println("Start parsing files..."); + Set phase2Files = new HashSet<>(); + FileTraversal fileTransversal = new FileTraversal(new FileTraversal.IFileVisitor() { + @Override + public void visit(File file) { + String fileFullPath = file.getAbsolutePath(); + if (!fileFullPath.startsWith(inputSrcPath)) { + return; + } + parseFile(fileFullPath, phase2Files); + } + + }); + fileTransversal.extensionFilter(this.fileSuffixes()); + fileTransversal.travers(this.inputSrcPath); + for (String f : phase2Files) { + parseFile(f, phase2Files); + } + System.out.println("all files procceed successfully..."); + + } + + protected void parseFile(String fileFullPath, Set phase2Files) { + FileParser fileParser = createFileParser(); + try { + if (fileParser.isPhase2Files(fileFullPath)){ + phase2Files.add(fileFullPath); + }else { + fileParser.parse(fileFullPath); + } + } catch (IOException e) { + e.printStackTrace(); + } catch (Exception e) { + System.err.println("error occoured during parse file " + fileFullPath); + e.printStackTrace(); + } + } + + public List includePaths() { + if (this.includePaths ==null) { + this.includePaths = buildIncludePath(); + } + return includePaths; + } + + private List buildIncludePath() { + includePaths = new ArrayList(); + for (String path : includeDirs) { + if (FileUtils.fileExists(path)) { + path = FileUtil.uniqFilePath(path); + if (!includePaths.contains(path)) + includePaths.add(path); + } + path = this.inputSrcPath + File.separator + path; + if (FileUtils.fileExists(path)) { + path = FileUtil.uniqFilePath(path); + if (!includePaths.contains(path)) + includePaths.add(path); + } + } + return includePaths; + } + + + public EntityRepo getEntityRepo() { + return this.entityRepo; + } + + + public abstract List supportedRelations(); + + public Set getExternalDependencies() { + return potentialExternalDependencies; + } + + public String getRelationMapping(String relation) { + return relation; + } + + /** + * Whether to resolve expression immediately during parse + * @return + */ + public boolean isEagerExpressionResolve(){ + return false; + } + + /** + * Call as Impl: + * implicit call (for example polymorphic in cpp) + * @return + */ + public boolean supportCallAsImpl(){return false;}; +} diff --git a/src/main/java/com/educoder/bridge/tmp/AliasEntity.java b/src/main/java/com/educoder/bridge/tmp/AliasEntity.java new file mode 100644 index 0000000..219fba5 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/AliasEntity.java @@ -0,0 +1,203 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.entity; + +import depends.relations.IBindingResolver; + +import java.util.*; + +public class AliasEntity extends Entity { + private Entity referToEntity = new EmptyTypeEntity(); + private GenericName originName; + private List referPath = new ArrayList<>(); + private boolean deepResolve = false; + public AliasEntity() { + + } + public AliasEntity(GenericName simpleName, Entity parent, Integer id, GenericName originTypeName) { + super(simpleName, parent, id); + this.originName = originTypeName; + } + + public void inferLocalLevelEntities(IBindingResolver bindingResolver) { + if (!(referToEntity instanceof EmptyTypeEntity)) return; + Entity entity = bindingResolver.resolveName(this, originName, true); + while(entity instanceof AliasEntity) { + AliasEntity aliasEntity = (AliasEntity)entity; + if (this.referPath.contains(aliasEntity)) { + entity = null; + break; + } + this.referPath.add(aliasEntity); + entity = bindingResolver.resolveName(aliasEntity, aliasEntity.originName,true); + if (entity==null) break; + if (entity.equals(this)) { + entity = null; + break; + } + } + if (entity != null) + referToEntity = entity; + } + + public Collection getResolvedTypeParameters() { + if (!(referToEntity instanceof DecoratedEntity)) + return new ArrayList<>(); + DecoratedEntity origin = (DecoratedEntity) referToEntity; + return origin.getResolvedTypeParameters(); + } + + public Collection getResolvedAnnotations() { + if (!(referToEntity instanceof DecoratedEntity)) + return new ArrayList<>(); + DecoratedEntity origin = (DecoratedEntity) referToEntity; + return origin.getResolvedAnnotations(); + } + + public ArrayList getVars() { + if (!(referToEntity instanceof ContainerEntity)) + return new ArrayList<>(); + ContainerEntity origin = (ContainerEntity) referToEntity; + return origin.getVars(); + } + + public ArrayList getFunctions() { + if (!(referToEntity instanceof ContainerEntity)) + return new ArrayList<>(); + ContainerEntity origin = (ContainerEntity) referToEntity; + return origin.getFunctions(); + } + + protected FunctionEntity lookupFunctionLocally(GenericName functionName) { + if (!(referToEntity instanceof ContainerEntity)) + return null; + ContainerEntity origin = (ContainerEntity) referToEntity; + return origin.lookupFunctionLocally(functionName); + } + + public List lookupFunctionInVisibleScope(GenericName functionName) { + if (!(referToEntity instanceof ContainerEntity)) + return null; + ContainerEntity origin = (ContainerEntity) referToEntity; + return origin.lookupFunctionInVisibleScope(functionName); + } + + public Entity lookupVarsInVisibleScope(GenericName varName) { + if (!(referToEntity instanceof ContainerEntity)) + return null; + ContainerEntity origin = (ContainerEntity) referToEntity; + return origin.lookupVarInVisibleScope(varName); + } + + public Collection getResolvedMixins() { + if (!(referToEntity instanceof ContainerEntity)) + return new ArrayList<>(); + ContainerEntity origin = (ContainerEntity) referToEntity; + return origin.getResolvedMixins(); + } + + public Collection getInheritedTypes() { + if (referToEntity instanceof TypeEntity) + return ((TypeEntity) referToEntity).getInheritedTypes(); + return new ArrayList<>(); + } + + public Collection getImplementedTypes() { + if (referToEntity instanceof TypeEntity) + return ((TypeEntity) referToEntity).getImplementedTypes(); + return new ArrayList<>(); + } + + public TypeEntity getInheritedType() { + if (referToEntity instanceof TypeEntity) + return ((TypeEntity) referToEntity).getInheritedType(); + return null; + } + + public Collection getReturnTypes() { + if (!(referToEntity instanceof FunctionEntity)) + return new ArrayList<>(); + FunctionEntity origin = (FunctionEntity) referToEntity; + return origin.getReturnTypes(); + } + + public TypeEntity getType() { + return referToEntity.getType(); + } + + public Collection getParameters() { + if (!(referToEntity instanceof FunctionEntity)) + return new ArrayList<>(); + FunctionEntity origin = (FunctionEntity) referToEntity; + return origin.getParameters(); + } + + public Collection getThrowTypes() { + if (!(referToEntity instanceof FunctionEntity)) + return new ArrayList<>(); + FunctionEntity origin = (FunctionEntity) referToEntity; + return origin.getThrowTypes(); + } + + public Entity getOriginType() { + return referToEntity; + } + public Entity getReferToEntity() { + return referToEntity; + } + public void setReferToEntity(Entity referToEntity) { + this.referToEntity = referToEntity; + } + public Entity deepResolve() { + if (!deepResolve) return this; + Set searched = new HashSet<>(); + int i=0; + Entity current = this; + while(i<100) { //maximum 100 levels + if (searched.contains(current)) return current; //avoid a loop + if (!(current instanceof AliasEntity)) return current; + + searched.add(current); + Entity originalFile = current.getAncestorOfType(FileEntity.class); + current = ((AliasEntity)current).getReferToEntity(); + + if (current ==null) return this; + //if already out of current file, return current + if (!current.getAncestorOfType(FileEntity.class).equals(originalFile)) { + return current; + } + i++; + } + return current; + } + public boolean isDeepResolve() { + return deepResolve; + } + public void setDeepResolve(boolean deepResolve) { + this.deepResolve = deepResolve; + } + + +} diff --git a/src/main/java/com/educoder/bridge/tmp/AnnotationProcessor.java b/src/main/java/com/educoder/bridge/tmp/AnnotationProcessor.java new file mode 100644 index 0000000..cf40e49 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/AnnotationProcessor.java @@ -0,0 +1,121 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.extractor.java.context; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.stream.Collectors; + +import org.antlr.v4.runtime.RuleContext; +import org.codehaus.plexus.util.StringUtils; + +import depends.entity.ContainerEntity; +import depends.entity.GenericName; +import depends.extractor.java.JavaParser.AnnotationContext; + +public class AnnotationProcessor { + + public AnnotationProcessor() { + } + + public void processAnnotationModifier(RuleContext ctx, @SuppressWarnings("rawtypes") Class rootClass, + String toAnnotationPath,ContainerEntity container) { + List list = new ArrayList<>() ; + list.add(container); + processAnnotationModifier(ctx, rootClass, + toAnnotationPath, list); + } + + public void processAnnotationModifier(RuleContext ctx, @SuppressWarnings("rawtypes") Class rootClass, + String toAnnotationPath, List containers) { + + while (true) { + if (ctx == null) + break; + if (ctx.getClass().equals(rootClass)) + break; + ctx = ctx.parent; + } + if (ctx == null) + return; + + + try { + Object r = ctx; + String[] paths = toAnnotationPath.split("\\."); + for (String path : paths) { + r = invokeMethod(r, path); + if (r == null) + return; + } + Collection contexts = new HashSet<>(); + mergeElements(contexts, r); + for (Object item : contexts) { + AnnotationContext annotation = (AnnotationContext) item; + String name = QualitiedNameContextHelper.getName(annotation.qualifiedName()); + containers.stream().forEach(container->((ContainerEntity)container).addAnnotation(GenericName.build(name))); + } + } catch (Exception e) { + return; + } + } + + + private void mergeElements(Collection collection, Object r) { + if (r instanceof Collection) { + for (Object item : (Collection) r) { + mergeElements(collection, item); + } + } else { + if (r instanceof AnnotationContext) + collection.add((AnnotationContext) r); + } + } + + private Object invokeMethod(Object r, String path) { + if (StringUtils.isEmpty(path)) + return null; + if (r instanceof Collection) { + Collection list = (Collection) r; + return list.stream().map(item -> invokeMethod(item, path)).filter(item -> item != null) + .collect(Collectors.toSet()); + } + try { + Method m = r.getClass().getMethod(path); + return m.invoke(r); + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException + | InvocationTargetException e) { + return null; + } + } + + + + +} diff --git a/src/main/java/com/educoder/bridge/tmp/BindingResolver.java b/src/main/java/com/educoder/bridge/tmp/BindingResolver.java new file mode 100644 index 0000000..66134db --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/BindingResolver.java @@ -0,0 +1,297 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.relations; +import depends.entity.*; +import depends.entity.repo.BuiltInType; +import depends.entity.repo.EntityRepo; +import depends.extractor.AbstractLangProcessor; +import depends.extractor.UnsolvedBindings; +import depends.extractor.empty.EmptyBuiltInType; +import depends.importtypes.Import; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.management.ManagementFactory; +import java.util.*; + +public class BindingResolver implements IBindingResolver{ + + private BuiltInType buildInTypeManager = new EmptyBuiltInType(); + private ImportLookupStrategy importLookupStrategy; + private Set unsolvedSymbols = new HashSet<>(); + private EntityRepo repo; + + private boolean eagerExpressionResolve = false; + private boolean isCollectUnsolvedBindings = false; + private boolean isDuckTypingDeduce = true; + private static Logger logger = LoggerFactory.getLogger(IBindingResolver.class); + + public BindingResolver(AbstractLangProcessor langProcessor, + boolean isCollectUnsolvedBindings, boolean isDuckTypingDeduce) { + this.repo = langProcessor.getEntityRepo(); + this.importLookupStrategy = langProcessor.getImportLookupStrategy(); + this.buildInTypeManager = langProcessor.getBuiltInType(); + this.isCollectUnsolvedBindings = isCollectUnsolvedBindings; + this.isDuckTypingDeduce = isDuckTypingDeduce; + unsolvedSymbols= new HashSet<>(); + importLookupStrategy.setBindingResolver(this); + } + + + @Override + public Set resolveAllBindings(boolean isEagerExpressionResolve) { + System.out.println("Resolve type bindings...."); + if (logger.isInfoEnabled()) { + logger.info("Resolve type bindings..."); + } + resolveTypes(isEagerExpressionResolve); + System.out.println("Dependency analaysing...."); + if (logger.isInfoEnabled()) { + logger.info("Dependency analaysing..."); + } + logger.info("Heap Information: " + ManagementFactory.getMemoryMXBean().getHeapMemoryUsage()); + return unsolvedSymbols; + } + + + private void resolveTypes(boolean eagerExpressionResolve) { + this.eagerExpressionResolve = eagerExpressionResolve; + Iterator iterator = repo.sortedFileIterator(); + while(iterator.hasNext()) { + Entity entity= iterator.next(); + entity.inferEntities(this); + } + } + + + @Override + public Collection getImportedRelationEntities(List importedNames) { + return importLookupStrategy.getImportedRelationEntities(importedNames); + } + + @Override + public Collection getImportedTypes(List importedNames, FileEntity fileEntity) { + HashSet unsolved = new HashSet(); + Collection result = importLookupStrategy.getImportedTypes(importedNames,unsolved); + for (UnsolvedBindings item:unsolved) { + item.setFromEntity(fileEntity); + addUnsolvedBinding(item); + } + return result; + } + + private void addUnsolvedBinding(UnsolvedBindings item) { + if (!isCollectUnsolvedBindings) return; + this.unsolvedSymbols.add(item); + } + @Override + public Collection getImportedFiles(List importedNames) { + return importLookupStrategy.getImportedFiles(importedNames); + } + + + @Override + public TypeEntity inferTypeFromName(Entity fromEntity, GenericName rawName) { + Entity data = resolveName(fromEntity, rawName, true); + if (data == null) + return null; + return data.getType(); + } + + + @Override + public Entity resolveName(Entity fromEntity, GenericName rawName, boolean searchImport) { + if (rawName==null) return null; + Entity entity = resolveNameInternal(fromEntity,rawName,searchImport); + if (entity==null ) { + if (!this.buildInTypeManager.isBuiltInType(rawName.getName())) { + addUnsolvedBinding(new UnsolvedBindings(rawName.getName(), fromEntity)); + } + } + return entity; + } + + private Entity resolveNameInternal(Entity fromEntity, GenericName rawName, boolean searchImport) { + if (rawName==null || rawName.getName()==null) + return null; + if (buildInTypeManager.isBuiltInType(rawName.getName())) { + return TypeEntity.buildInType; + } + // qualified name will first try global name directly + if (rawName.startsWith(".")) { + rawName = rawName.substring(1); + if (repo.getEntity(rawName) != null) + return repo.getEntity(rawName); + } + Entity entity = null; + int indexCount = 0; + String name = rawName.getName(); + if (fromEntity==null) return null; + do { + entity = lookupEntity(fromEntity, name, searchImport); + if (entity!=null ) { + break; + } + if (importLookupStrategy.supportGlobalNameLookup()) { + if (repo.getEntity(name) != null) { + entity = repo.getEntity(name); + break; + } + } + + indexCount++; + if (name.contains(".")) + name = name.substring(0,name.lastIndexOf('.')); + else + break; + }while (true); + if (entity == null) { + return null; + } + String[] names = rawName.getName().split("\\."); + if (names.length == 0) + return null; + if (names.length == 1) { + return entity; + } + // then find the subsequent symbols + return findEntitySince(entity, names, names.length-indexCount); + } + + private Entity lookupEntity(Entity fromEntity, String name, boolean searchImport) { + if (name.equals("this") || name.equals("class") ) { + TypeEntity entityType = (TypeEntity) (fromEntity.getAncestorOfType(TypeEntity.class)); + return entityType; + } else if (name.equals("super")) { + TypeEntity parent = (TypeEntity) (fromEntity.getAncestorOfType(TypeEntity.class)); + if (parent != null) { + TypeEntity parentType = parent.getInheritedType(); + if (parentType!=null) + return parentType; + } + } + Entity inferData = findEntityUnderSamePackage(fromEntity, name); + if (inferData != null) { + return inferData; + } + if (searchImport) + inferData = lookupTypeInImported((FileEntity)(fromEntity.getAncestorOfType(FileEntity.class)), name); + return inferData; + } + /** + * To lookup entity in case of a.b.c from a; + * @param precendenceEntity + * @param names + * @param nameIndex + * @return + */ + private Entity findEntitySince(Entity precendenceEntity, String[] names, int nameIndex) { + if (nameIndex >= names.length) { + return precendenceEntity; + } + if (nameIndex == -1) { + System.err.println("No expected symbols: names"+Arrays.toString(names) +", index=" + nameIndex); + return null; + } + //If it is not an entity with types (not a type, var, function), fall back to itself + if (precendenceEntity.getType()==null) + return precendenceEntity; + + for (Entity child : precendenceEntity.getType().getChildren()) { + if (child.getRawName().getName().equals(names[nameIndex])) { + return findEntitySince(child, names, nameIndex + 1); + } + } + return null; + } + + @Override + public Entity lookupTypeInImported(FileEntity fileEntity, String name) { + if (fileEntity == null) + return null; + Entity type = importLookupStrategy.lookupImportedType(name, fileEntity); + if (type != null) + return type; + return null; + } + + + /** + * In Java/C++ etc, the same package names should take priority of resolving. + * the entity lookup is implemented recursively. + * @param fromEntity + * @param name + * @return + */ + private Entity findEntityUnderSamePackage(Entity fromEntity, String name) { + while (true) { + Entity entity = fromEntity.getByName(name, new HashSet<>()); + if (entity!=null) return entity; + fromEntity = fromEntity.getParent(); + if (fromEntity == null) + break; + } + return null; + } + + + @Override + public List calculateCandidateTypes(VarEntity fromEntity, List functionCalls) { + if (buildInTypeManager.isBuildInTypeMethods(functionCalls)) { + return new ArrayList<>(); + } + if (!isDuckTypingDeduce) + return new ArrayList<>(); + return searchTypesInRepo(fromEntity, functionCalls); + } + + private List searchTypesInRepo(VarEntity fromEntity, List functionCalls) { + List types = new ArrayList<>(); + Iterator iterator = repo.sortedFileIterator(); + while(iterator.hasNext()) { + Entity f = iterator.next(); + if (f instanceof FileEntity) { + for (TypeEntity type:((FileEntity)f).getDeclaredTypes()) { + FunctionMatcher functionMatcher = new FunctionMatcher(type.getFunctions()); + if (functionMatcher.containsAll(functionCalls)) { + types.add(type); + } + } + } + } + return types; + } + + @Override + public boolean isEagerExpressionResolve() { + return eagerExpressionResolve; + } + + @Override + public EntityRepo getRepo() { + return repo; + } + +} diff --git a/src/main/java/com/educoder/bridge/tmp/BuiltInType.java b/src/main/java/com/educoder/bridge/tmp/BuiltInType.java new file mode 100644 index 0000000..1771cc9 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/BuiltInType.java @@ -0,0 +1,110 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.entity.repo; + +import depends.entity.FunctionCall; +import depends.entity.TypeEntity; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public abstract class BuiltInType { + + public BuiltInType(){ + createBuiltInTypes(); + } + /** + * Init the build in types data + */ + private void createBuiltInTypes() { + for(String prefix: getBuiltInTypePrefix()) { + builtInPrefix.add(prefix); + } + for (String type: getBuiltInTypeName()) { + builtInType.add(type); + } + for (String method:getBuiltInMethods()) { + builtInMethod.add(method); + } + } + + protected String[] getBuiltInMethods(){return new String[]{};} + protected String[] getBuiltInTypeName(){return new String[]{};} + protected String[] getBuiltInTypePrefix() {return new String[]{};} + + private Set builtInType = new HashSet<>(); + private Set builtInPrefix = new HashSet<>(); + private Set builtInMethod = new HashSet<>(); + + /** + * To determine whether a type name is built-in + * @param typeName + * @return + */ + public boolean isBuiltInType(String typeName) { + return TypeEntity.buildInType.getRawName().uniqName().equals(typeName) || + builtInType.contains(typeName)|| + isBuiltInTypePrefix(typeName); + } + + /** + * To determine a typeName is a built-in type based on prefix. + * For example, in Java language, name start with java.*, javax.*, com.sun.* + * is build-in types + * @param typeName + * @return + */ + private boolean isBuiltInTypePrefix(String typeName) { + for (String prefix:builtInPrefix) { + if (typeName.startsWith(prefix)) return true; + } + return false; + } + + /** + * In some language, there are common methods, like in ruby, + * object_id is a method for all type + * @param name + * @return + */ + public boolean isBuildInMethod(String name) { + return builtInMethod.contains(name); + } + + /** + * Used by duck typing deduce feature: + * - if all calls of a type are build in method, + * then no duck typing is deduced + * Refer to Python built-in type for example + * + * @param functionCalls + * @return + */ + public boolean isBuildInTypeMethods(List functionCalls) { + return false; + } + +} diff --git a/src/main/java/com/educoder/bridge/tmp/CandidateTypes.java b/src/main/java/com/educoder/bridge/tmp/CandidateTypes.java new file mode 100644 index 0000000..04d669b --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/CandidateTypes.java @@ -0,0 +1,311 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.entity; + +import depends.relations.IBindingResolver; +import depends.relations.Relation; + +import java.util.*; + +public class CandidateTypes extends TypeEntity { + private List candidateTypes; + + public CandidateTypes() { + candidateTypes = new ArrayList<>(); + } + public CandidateTypes(List candidateTypes, Integer id) { + super(GenericName.build("candidateTypes"), null, id); + this.candidateTypes = candidateTypes; + } + + public List getCandidateTypes() { + return candidateTypes; + } + + @Override + public Collection getInheritedTypes() { + List result = new ArrayList<>(); + for (TypeEntity type:candidateTypes) { + result.addAll(type.getInheritedTypes()); + } + return result; + } + + @Override + public Collection getImplementedTypes() { + List result = new ArrayList<>(); + for (TypeEntity type:candidateTypes) { + result.addAll(type.getImplementedTypes()); + } + return result; + } + + @Override + public ArrayList getFunctions() { + ArrayList result = new ArrayList<>(); + for (TypeEntity type:candidateTypes) { + result.addAll(type.getFunctions()); + } + return result; + } + + @Override + public TypeEntity getInheritedType() { + return inheritedType; + } + @Override + public List lookupFunctionInVisibleScope(GenericName functionName) { + List functions = new ArrayList<>(); + for (TypeEntity type:candidateTypes) { + List f = type.lookupFunctionInVisibleScope(functionName); + if (f!=null) { + functions.addAll(f); + } + } + if (functions.size()==0) + return null; + return functions; + } + + @Override + public Entity lookupVarInVisibleScope(GenericName varName) { + for (TypeEntity type:candidateTypes) { + Entity v = type.lookupVarInVisibleScope(varName); + if (v!=null) return v; + } + return null; + } + + + @Override + public VarEntity lookupVarLocally(String varName) { + for (TypeEntity type:candidateTypes) { + VarEntity v = type.lookupVarLocally(varName); + if (v!=null) return v; + } + return null; + } + + @Override + public TypeEntity getType() { + if (candidateTypes.size()>0) return candidateTypes.get(0); + return null; + } + + @Override + public void inferLocalLevelEntities(IBindingResolver bindingResolver) { + System.err.println("error: inferLocalLevelEntities should not been invoked"); + super.inferLocalLevelEntities(bindingResolver); + } + + @Override + public void addImplements(GenericName typeName) { + System.err.println("error: addImplements should not been invoked"); + super.addImplements(typeName); + } + + @Override + public void addExtends(GenericName typeName) { + System.err.println("error: addExtends should not been invoked"); + super.addExtends(typeName); + } + + @Override + public void addVar(VarEntity var) { + System.err.println("error: addVar should not been invoked"); + super.addVar(var); + } + + @Override + public ArrayList getVars() { + System.err.println("error: getVars should not been invoked"); + return super.getVars(); + } + + @Override + public void addFunction(FunctionEntity functionEntity) { + System.err.println("error: addFunction should not been invoked"); + super.addFunction(functionEntity); + } + + @Override + public HashMap expressions() { + System.err.println("error: expressions should not been invoked"); + return super.expressions(); + } + + @Override + public void addExpression(Object key, Expression expression) { + System.err.println("error: addExpression should not been invoked"); + super.addExpression(key, expression); + } + + public void resolveExpressions(IBindingResolver bindingResolver) { + System.err.println("error: resolveExpressions should not been invoked"); + super.resolveExpressions(bindingResolver); + } + + + @Override + public void addMixin(GenericName moduleName) { + System.err.println("error: addMixin should not been invoked"); + super.addMixin(moduleName); + } + + @Override + public Collection getResolvedMixins() { + System.err.println("error: getResolvedMixins should not been invoked"); + return super.getResolvedMixins(); + } + + @Override + public void addAnnotation(GenericName name) { + System.err.println("error: addAnnotation should not been invoked"); + super.addAnnotation(name); + } + + + @Override + public Collection getResolvedTypeParameters() { + System.err.println("error: getResolvedTypeParameters should not been invoked"); + return super.getResolvedTypeParameters(); + } + + @Override + public Collection getResolvedAnnotations() { + System.err.println("error: getResolvedAnnotations should not been invoked"); + return super.getResolvedAnnotations(); + } + + @Override + public boolean isGenericTypeParameter(GenericName rawType) { + System.err.println("error: isGenericTypeParameter should not been invoked"); + return super.isGenericTypeParameter(rawType); + } + + @Override + protected Collection identiferToEntities(IBindingResolver bindingResolver, Collection identifiers) { + System.err.println("error: identiferToTypes should not been invoked"); + return super.identiferToEntities(bindingResolver, identifiers); + } + + @Override + public GenericName getRawName() { + return super.getRawName(); + } + + @Override + public Integer getId() { + return super.getId(); + } + + @Override + public void addRelation(Relation relation) { + System.err.println("error: addRelation should not been invoked"); + super.addRelation(relation); + } + + @Override + public ArrayList getRelations() { + System.err.println("error: getRelations should not been invoked"); + return super.getRelations(); + } + + @Override + public void addChild(Entity child) { + System.err.println("error: addChild should not been invoked"); + super.addChild(child); + } + + @Override + public Entity getParent() { + return null; + } + + @Override + public void setParent(Entity parent) { + System.err.println("error: setParent should not been invoked"); + super.setParent(parent); + } + + @Override + public Collection getChildren() { + List children = new ArrayList<>(); + for (Entity entity:this.candidateTypes) { + children.addAll(entity.getChildren()); + } + return children; + } + + @Override + public void setQualifiedName(String qualifiedName) { + System.err.println("error: setQualifiedName should not been invoked"); + super.setQualifiedName(qualifiedName); + } + + @Override + public void setRawName(GenericName rawName) { + System.err.println("error: setRawName should not been invoked"); + super.setRawName(rawName); + } + + @Override + public String getQualifiedName(boolean overrideFileWithPackage) { + System.err.println("error: getQualifiedName should not been invoked"); + return super.getQualifiedName(overrideFileWithPackage); + } + + + @Override + public Entity getAncestorOfType(@SuppressWarnings("rawtypes") Class classType) { + return null; + } + + @Override + public void inferEntities(IBindingResolver bindingResolver) { + System.err.println("error: inferEntities should not been invoked"); + super.inferEntities(bindingResolver); + } + + @Override + public String getDisplayName() { + System.err.println("error: getDisplayName should not been invoked"); + return super.getDisplayName(); + } + @Override + public Entity getByName(String name, HashSet searched) { + Entity entity = super.getByName(name, searched); + if (entity!=null) return entity; + for (TypeEntity type:getCandidateTypes()) { + if (searched.contains(type)) continue; + Entity e = type.getByName(name, searched); + if (e !=null) return e; + } + return null; + } + + + +} diff --git a/src/main/java/com/educoder/bridge/tmp/CdtCppFileParser.java b/src/main/java/com/educoder/bridge/tmp/CdtCppFileParser.java new file mode 100644 index 0000000..42360f6 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/CdtCppFileParser.java @@ -0,0 +1,86 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.extractor.cpp.cdt; + +import depends.entity.repo.EntityRepo; +import depends.extractor.cpp.CppFileParser; +import depends.extractor.cpp.MacroRepo; +import depends.relations.IBindingResolver; +import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class CdtCppFileParser extends CppFileParser { + private PreprocessorHandler preprocessorHandler ; + private IBindingResolver bindingResolver; + private MacroRepo macroRepo; + + public CdtCppFileParser(EntityRepo entityRepo, PreprocessorHandler preprocessorHandler, IBindingResolver bindingResolver, MacroRepo macroRepo) { + super(entityRepo); + this.preprocessorHandler = preprocessorHandler; + this.bindingResolver = bindingResolver; + this.macroRepo= macroRepo; + } + @Override + protected void parseFile(String fileFullPath) throws IOException { + Map macroMap = new HashMap<>(macroRepo.getDefaultMap()); + parse(fileFullPath,macroMap); + } + + /** + * + * @param isInScope whether the parse is invoked by project file or an 'indirect' included file + * @return + */ + public void parse(String fileFullPath,Map macroMap) throws IOException { + CppVisitor bridge = new CppVisitor(fileFullPath, entityRepo, preprocessorHandler, bindingResolver); + IASTTranslationUnit tu = (new CDTParser(preprocessorHandler.getIncludePaths())).parse(fileFullPath,macroMap); + boolean containsIncludes = false; + for (String incl:preprocessorHandler.getDirectIncludedFiles(tu.getAllPreprocessorStatements(),fileFullPath)) { + CdtCppFileParser importedParser = new CdtCppFileParser(entityRepo, preprocessorHandler, bindingResolver,macroRepo); + importedParser.parse(incl); + Map macros = macroRepo.get(incl); + if (macros!=null) + macroMap.putAll(macros); + containsIncludes = true; + } + if (containsIncludes) { + tu = (new CDTParser(preprocessorHandler.getIncludePaths())).parse(fileFullPath,macroMap); + } + macroRepo.putMacros(fileFullPath,macroMap,tu.getMacroDefinitions()); + tu.accept(bridge); + return; + } + + @Override + protected boolean isPhase2Files(String fileFullPath) { + if (fileFullPath.endsWith(".h") || fileFullPath.endsWith(".hh") || fileFullPath.endsWith(".hpp") + || fileFullPath.endsWith(".hxx")) + return true; + return false; + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/ContainerEntity.java b/src/main/java/com/educoder/bridge/tmp/ContainerEntity.java new file mode 100644 index 0000000..b299763 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/ContainerEntity.java @@ -0,0 +1,467 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.entity; + +import depends.entity.repo.EntityRepo; +import depends.relations.IBindingResolver; +import depends.relations.Relation; +import multilang.depends.util.file.TemporaryFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.lang.ref.WeakReference; +import java.util.*; + +/** + * ContainerEntity for example file, class, method, etc. they could contain + * vars, functions, ecpressions, type parameters, etc. + */ +public abstract class ContainerEntity extends DecoratedEntity { + private static final Logger logger = LoggerFactory.getLogger(ContainerEntity.class); + + private ArrayList vars; + private ArrayList functions; + WeakReference> expressionWeakReference; + private ArrayList expressionList; + private int expressionCount = 0; + private Collection mixins; + private Collection resolvedMixins; + + private ArrayList vars() { + if (vars==null) + vars = new ArrayList<>(); + return this.vars; + } + + private Collection mixins() { + if (mixins==null) + mixins = new ArrayList<>(); + return this.mixins; + } + + private ArrayList functions() { + if (functions==null) + functions = new ArrayList<>(); + return this.functions; + } + + public ContainerEntity() { + } + + public ContainerEntity(GenericName rawName, Entity parent, Integer id) { + super(rawName, parent, id); + } + + public void addVar(VarEntity var) { + if (logger.isDebugEnabled()) { + logger.debug("var found: " + var.getRawName() + ":" + var.getRawType()); + } + this.vars().add(var); + } + + public ArrayList getVars() { + if (vars==null) + return new ArrayList<>(); + return this.vars(); + } + + public void addFunction(FunctionEntity functionEntity) { + this.functions().add(functionEntity); + } + + public ArrayList getFunctions() { + if (functions==null) + return new ArrayList<>(); + return this.functions; + } + + public HashMap expressions() { + if (expressionWeakReference==null) + expressionWeakReference= new WeakReference>(new HashMap<>()); + HashMap r = expressionWeakReference.get(); + if (r==null) return new HashMap<>(); + return r; + } + + public void addExpression(Object key, Expression expression) { + expressions().put(key, expression); + expressionList().add(expression); + expressionCount = expressionList.size(); + } + + public boolean containsExpression(Object key) { + return expressions().containsKey(key); + } + /** + * For all data in the class, infer their types. Should be override in + * sub-classes + */ + public void inferLocalLevelEntities(IBindingResolver bindingResolver) { + super.inferLocalLevelEntities(bindingResolver); + for (VarEntity var : this.vars()) { + if (var.getParent()!=this) { + var.inferLocalLevelEntities(bindingResolver); + } + } + for (FunctionEntity func : this.getFunctions()) { + if (func.getParent()!=this) { + func.inferLocalLevelEntities(bindingResolver); + } + } + if (bindingResolver.isEagerExpressionResolve()) { + reloadExpression(bindingResolver.getRepo()); + resolveExpressions(bindingResolver); + cacheExpressions(); + } + resolvedMixins = identiferToContainerEntity(bindingResolver, getMixins()); + } + + private Collection getMixins() { + if (mixins==null) + return new ArrayList<>(); + return mixins; + } + + private Collection identiferToContainerEntity(IBindingResolver bindingResolver, Collection identifiers) { + if (identifiers.size()==0) return null; + ArrayList r = new ArrayList<>(); + for (GenericName identifier : identifiers) { + Entity entity = bindingResolver.resolveName(this, identifier, true); + if (entity == null) { + continue; + } + if (entity instanceof ContainerEntity) + r.add((ContainerEntity) entity); + } + return r; + } + + /** + * Resolve all expression's type + * + * @param bindingResolver + */ + public void resolveExpressions(IBindingResolver bindingResolver) { + if (this instanceof FunctionEntity) { + ((FunctionEntity)this).linkReturnToLastExpression(); + } + + if (expressionList==null) return; + if(expressionList.size()>10000) return; + + + for (Expression expression : expressionList) { + // 1. if expression's type existed, break; + if (expression.getType() != null) + continue; + if (expression.isDot()) { // wait for previous + continue; + } + if (expression.getRawType() == null && expression.getIdentifier() == null) + continue; + + // 2. if expression's rawType existed, directly infer type by rawType + // if expression's rawType does not existed, infer type based on identifiers + if (expression.getRawType() != null) { + expression.setType(bindingResolver.inferTypeFromName(this, expression.getRawType()), null, bindingResolver); + if (expression.getType() != null) { + continue; + } + } + if (expression.getIdentifier() != null) { + Entity entity = bindingResolver.resolveName(this, expression.getIdentifier(), true); + String composedName = expression.getIdentifier().toString(); + Expression theExpr = expression; + if (entity==null) { + while(theExpr.getParent()!=null && theExpr.getParent().isDot()) { + theExpr = theExpr.getParent(); + if (theExpr.getIdentifier()==null) break; + composedName = composedName + "." + theExpr.getIdentifier().toString(); + entity = bindingResolver.resolveName(this, GenericName.build(composedName), true); + if (entity!=null) + break; + } + } + if (entity != null) { + expression.setType(entity.getType(), entity, bindingResolver); + continue; + } + if (expression.isCall()) { + List funcs = this.lookupFunctionInVisibleScope(expression.getIdentifier()); + if (funcs != null) { + for (Entity func:funcs) { + expression.setType(func.getType(), func, bindingResolver); + } + } + } else { + + Entity varEntity = this.lookupVarInVisibleScope(expression.getIdentifier()); + if (varEntity != null) { + expression.setType(varEntity.getType(), varEntity, bindingResolver); + } + } + } + } + } + + public void cacheChildExpressions() { + cacheExpressions(); + for (Entity child:getChildren()) { + if (child instanceof ContainerEntity) { + ((ContainerEntity)child).cacheChildExpressions(); + } + } + } + + + public void cacheExpressions() { + if (expressionWeakReference==null) return; + if (expressionList==null) return; + this.expressions().clear(); + this.expressionWeakReference.clear(); + cacheExpressionListToFile(); + this.expressionList.clear(); + this.expressionList=null; + this.expressionList = new ArrayList<>(); + } + + public void clearExpressions() { + if (expressionWeakReference==null) return; + if (expressionList==null) return; + this.expressions().clear(); + this.expressionWeakReference.clear(); + this.expressionList.clear(); + this.expressionList=null; + this.expressionList = new ArrayList<>(); + this.expressionUseList = null; + } + + private void cacheExpressionListToFile() { + if (expressionCount ==0) return; + try { + FileOutputStream fileOut = new FileOutputStream(TemporaryFile.getInstance().exprPath(this.id)); + ObjectOutputStream out = new ObjectOutputStream(fileOut); + out.writeObject(this.expressionList); + out.close(); + fileOut.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @SuppressWarnings("unchecked") + public void reloadExpression(EntityRepo repo) { + if (expressionCount ==0) return; + try + { + FileInputStream fileIn = new FileInputStream(TemporaryFile.getInstance().exprPath(this.id)); + ObjectInputStream in = new ObjectInputStream(fileIn); + expressionList = (ArrayList) in.readObject(); + if (expressionList==null) expressionList = new ArrayList<>(); + for (Expression expr:expressionList) { + expr.reload(repo,expressionList); + } + in.close(); + fileIn.close(); + }catch(IOException | ClassNotFoundException i) + { + return; + } + } + + + public List expressionList() { + if (expressionList==null) + expressionList = new ArrayList<>(); + return expressionList; + } + + public boolean containsExpression() { + return expressions().size() > 0; + } + + /** + * The entry point of lookup functions. It will treat multi-declare entities and + * normal entity differently. - for multiDeclare entity, it means to lookup all + * entities - for normal entity, it means to lookup entities from current scope + * still root + * + * @param functionName + * @return + */ + public List lookupFunctionInVisibleScope(GenericName functionName) { + List functions = new ArrayList<>(); + if (this.getMutliDeclare() != null) { + for (Entity fromEntity : this.getMutliDeclare().getEntities()) { + Entity f = lookupFunctionBottomUpTillTopContainer(functionName, fromEntity); + if (f != null) { + functions.add(f); + return functions; + } + } + } else { + ContainerEntity fromEntity = this; + Entity f = lookupFunctionBottomUpTillTopContainer(functionName, fromEntity); + if (f != null) { + functions.add(f); + return functions; + } + } + return null; + } + + /** + * lookup function bottom up till the most outside container + * + * @param functionName + * @param fromEntity + * @return + */ + private Entity lookupFunctionBottomUpTillTopContainer(GenericName functionName, Entity fromEntity) { + while (fromEntity != null) { + if (fromEntity instanceof ContainerEntity) { + FunctionEntity func = ((ContainerEntity) fromEntity).lookupFunctionLocally(functionName); + if (func != null) + return func; + } + for (Entity child:this.getChildren()) { + if (child instanceof AliasEntity) { + if (child.getRawName().equals(functionName)) + return child; + } + } + fromEntity = (ContainerEntity) this.getAncestorOfType(ContainerEntity.class); + } + return null; + } + + /** + * lookup function in local entity. It could be override such as the type entity + * (it should also lookup the inherit/implemented types + * + * @param functionName + * @return + */ + public FunctionEntity lookupFunctionLocally(GenericName functionName) { + for (FunctionEntity func : getFunctions()) { + if (func.getRawName().equals(functionName)) + return func; + } + return null; + } + + /** + * The entry point of lookup var. It will treat multi-declare entities and + * normal entity differently. - for multiDeclare entity, it means to lookup all + * entities - for normal entity, it means to lookup entities from current scope + * still root + * + * @param varName + * @return + */ + public Entity lookupVarInVisibleScope(GenericName varName) { + ContainerEntity fromEntity = this; + return lookupVarBottomUpTillTopContainer(varName, fromEntity); + } + + /** + * To found the var. + * + * @param fromEntity + * @param varName + * @return + */ + private Entity lookupVarBottomUpTillTopContainer(GenericName varName, ContainerEntity fromEntity) { + while (fromEntity != null) { + if (fromEntity instanceof ContainerEntity) { + VarEntity var = ((ContainerEntity) fromEntity).lookupVarLocally(varName); + if (var != null) + return var; + } + for (Entity child:this.getChildren()) { + if (child instanceof AliasEntity) { + if (child.getRawName().equals(varName)) + return child; + } + } + fromEntity = (ContainerEntity) this.getAncestorOfType(ContainerEntity.class); + } + return null; + } + + public VarEntity lookupVarLocally(GenericName varName) { + for (VarEntity var : getVars()) { + if (var.getRawName().equals(varName)) + return var; + } + return null; + } + + public VarEntity lookupVarLocally(String varName) { + return this.lookupVarLocally(GenericName.build(varName)); + } + + public void addMixin(GenericName moduleName) { + mixins().add(moduleName); + } + + public Collection getResolvedMixins() { + if (resolvedMixins==null) return new ArrayList<>(); + return resolvedMixins; + } + + HashMap> expressionUseList = null; + public void addRelation(Expression expression, Relation relation) { + String key = relation.getEntity().qualifiedName+relation.getType(); + if (this.expressionUseList==null) + expressionUseList = new HashMap<>(); + if (expressionUseList.containsKey(key)){ + Set expressions = expressionUseList.get(key); + for (Expression expr:expressions){ + if (linkedExpr(expr,expression)) return; + } + }else{ + expressionUseList.put(key,new HashSet<>()); + } + + expressionUseList.get(key).add(expression); + super.addRelation(relation); + } + + private boolean linkedExpr(Expression a, Expression b) { + Expression parent = a.getParent(); + while(parent!=null){ + if (parent==b) return true; + parent = parent.getParent(); + } + parent = b.getParent(); + while(parent!=null){ + if (parent==a) return true; + parent = parent.getParent(); + } + return false; + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/CppBuiltInType.java b/src/main/java/com/educoder/bridge/tmp/CppBuiltInType.java new file mode 100644 index 0000000..e94528f --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/CppBuiltInType.java @@ -0,0 +1,66 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.extractor.cpp; + +import depends.entity.repo.BuiltInType; + +public class CppBuiltInType extends BuiltInType { + + @Override + protected String[] getBuiltInTypeName() { + return new String[] { "alignas", "alignof", "asm", "auto", "bool", "break", "case", "catch", "char", + "char16_t", "char32_t", "class", "const", "constexpr", "const_cast", "continue", "decltype", + "default", "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", + "false", "final", "float", "for", "friend", "goto", "if", "inline", "int", "long", "mutable", + "namespace", "new", "noexcept", "nullptr", "operator", "override", "private", "protected", "public", + "register", "reinterpret_cast", "return", "short", "signed", "sizeof", "static", "static_assert", + "static_cast", "struct", "switch", "template", "this", "thread_local", "throw", "true", "try", + "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", + "wchar_t", "while", "", + "__cplusplus","_cpp_aggregate_bases","__cpp_aggregate_nsdmi","__cpp_alias_templates","__cpp_aligned_new", + "__cpp_attributes","__cpp_binary_literals","__cpp_capture_star_this","__cpp_constexpr","__cpp_decltype", + "__cpp_decltype_auto","__cpp_deduction_guides","__cpp_delegating_constructors", + "__cpp_enumerator_attributes","__cpp_explicit_bool","__cpp_fold_expressions","__cpp_generic_lambdas", + "__cpp_guaranteed_copy_elision","__cpp_hex_float","__cpp_if_constexpr","__cpp_inheriting_constructors", + "__cpp_init_captures","__cpp_initializer_lists","__cpp_inline_variables","__cpp_lambdas", + "__cpp_namespace_attributes","__cpp_noexcept_function_type","__cpp_nontype_template_args", + "__cpp_nontype_template_parameter_auto","__cpp_nontype_template_parameter_class","__cpp_nsdmi" + + "","__cpp_range_based_for","__cpp_raw_strings","__cpp_ref_qualifiers","__cpp_return_type_deduction" + ,"__cpp_rvalue_references","__cpp_sized_deallocation","__cpp_static_assert","__cpp_structured_bindings", + "__cpp_template_template_args","__cpp_threadsafe_static_init","__cpp_unicode_characters","__cpp_unicode_literals", + "__cpp_user_defined_literals","__cpp_variable_templates","__cpp_variadic_templates","__cpp_variadic_using", + "__DATE__","__FILE__","__LINE__","__STDC__","__STDC_ANALYZABLE__","__STDC_HOSTED__","__STDC_IEC_559__", + "__STDC_IEC_559_COMPLEX__","__STDC_ISO_10646__","__STDC_LIB_EXT1__","__STDC_MB_MIGHT_NEQ_WC__", + "__STDC_NO_ATOMICS__","__STDC_NO_COMPLEX__","__STDC_NO_THREADS__","__STDC_NO_VLA__", + "__STDCPP_DEFAULT_NEW_ALIGNMENT__","__STDCPP_STRICT_POINTER_SAFETY__","__STDCPP_THREADS__", + "__STDC_UTF_16__","__STDC_UTF_32__","__STDC_VERSION__","__TIME__" + }; + } + + @Override + protected String[] getBuiltInTypePrefix() { + return new String[] {"__"}; + } +} \ No newline at end of file diff --git a/src/main/java/com/educoder/bridge/tmp/CppImportLookupStrategy.java b/src/main/java/com/educoder/bridge/tmp/CppImportLookupStrategy.java new file mode 100644 index 0000000..b723455 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/CppImportLookupStrategy.java @@ -0,0 +1,133 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.extractor.cpp; + +import depends.entity.Entity; +import depends.entity.FileEntity; +import depends.entity.GenericName; +import depends.entity.repo.EntityRepo; +import depends.extractor.UnsolvedBindings; +import depends.importtypes.FileImport; +import depends.importtypes.Import; +import depends.relations.ImportLookupStrategy; + +import java.util.*; + +public class CppImportLookupStrategy extends ImportLookupStrategy { + + + public CppImportLookupStrategy(EntityRepo repo){ + super(repo); + } + @Override + public Entity lookupImportedType(String name, FileEntity fileEntity) { + String importedString = fileEntity.importedSuffixMatch(name); + if (importedString!=null) { + Entity r = repo.getEntity(importedString); + if (r!=null) return r; + } + + HashSet fileSet = getIncludedFiles(fileEntity); + + for (Integer file:fileSet) { + Entity importedItem = repo.getEntity(file); + if (importedItem instanceof FileEntity) { + FileEntity importedFile = (FileEntity) repo.getEntity(file); + if (importedFile==null) continue; + Entity entity = bindingResolver.resolveName(importedFile,GenericName.build(name), false); + if (entity!=null) return entity; + Collection namespaces = fileEntity.getImportedTypes(); + for (Entity ns:namespaces) { + String nameWithPrefix = ns.getQualifiedName() + "." + name; + entity = bindingResolver.resolveName(importedFile,GenericName.build(nameWithPrefix), false); + if (entity!=null) return entity; + } + } + } + return null; + } + + private Map > includedFiles = new HashMap<>(); + private HashSet getIncludedFiles(FileEntity fileEntity) { + + if (includedFiles.containsKey(fileEntity.getId())) { + return includedFiles.get(fileEntity.getId()); + } + HashSet fileSet = new HashSet<>(); + foundIncludedFiles(fileSet, fileEntity.getImportedFiles()); + includedFiles.put(fileEntity.getId(), fileSet); + return fileSet; + } + + private void foundIncludedFiles(HashSet fileSet, Collection importedFiles) { + for (Entity file:importedFiles) { + if (file==null ) continue; + if (!(file instanceof FileEntity)) continue; + if (fileSet.contains(file.getId())) continue; + fileSet.add(file.getId()); + foundIncludedFiles(fileSet,((FileEntity)file).getImportedFiles()); + } + } + + + @Override + public List getImportedRelationEntities(List importedList) { + ArrayList result = new ArrayList<>(); + for (Import importedItem:importedList) { + if (importedItem instanceof FileImport) { + Entity imported = repo.getEntity(importedItem.getContent()); + if (imported==null) continue; + result.add(imported); + } + } + return result; + } + + @Override + public List getImportedTypes(List importedList, Set unsolvedBindings) { + ArrayList result = new ArrayList<>(); + for (Import importedItem:importedList) { + if (!(importedItem instanceof FileImport)) { + Entity imported = repo.getEntity(importedItem.getContent()); + if (imported==null) { + unsolvedBindings.add(new UnsolvedBindings(importedItem.getContent(),null)); + continue; + } + result.add(imported); + } + } + return result; + } + + @Override + public List getImportedFiles(List importedList) { + return getImportedRelationEntities(importedList); + } + + @Override + public boolean supportGlobalNameLookup() { + return false; + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/CppVisitor.java b/src/main/java/com/educoder/bridge/tmp/CppVisitor.java new file mode 100644 index 0000000..e291c7f --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/CppVisitor.java @@ -0,0 +1,338 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.extractor.cpp.cdt; + +import depends.entity.*; +import depends.entity.repo.EntityRepo; +import depends.entity.repo.IdGenerator; +import depends.extractor.cpp.CppHandlerContext; +import depends.importtypes.ExactMatchImport; +import depends.importtypes.FileImport; +import depends.importtypes.PackageWildCardImport; +import depends.relations.IBindingResolver; +import org.codehaus.plexus.util.StringUtils; +import org.eclipse.cdt.core.dom.ast.*; +import org.eclipse.cdt.core.dom.ast.IASTEnumerationSpecifier.IASTEnumerator; +import org.eclipse.cdt.core.dom.ast.cpp.*; +import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier.ICPPASTBaseSpecifier; +import org.eclipse.cdt.internal.core.dom.parser.cpp.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +public class CppVisitor extends ASTVisitor { + private static final Logger logger = LoggerFactory.getLogger(CppVisitor.class); + private CppHandlerContext context; + private IdGenerator idGenerator; + private PreprocessorHandler preprocessorHandler; + IBindingResolver bindingResolver; + private ExpressionUsage expressionUsage; + HashSet file; + public CppVisitor(String fileFullPath, EntityRepo entityRepo, PreprocessorHandler preprocessorHandler, IBindingResolver bindingResolver) { + super(true); + this.shouldVisitAmbiguousNodes = true; + this.shouldVisitImplicitNames = true; + this.includeInactiveNodes = true; + this.context = new CppHandlerContext(entityRepo, bindingResolver); + idGenerator = entityRepo; + this.bindingResolver = bindingResolver; + this.preprocessorHandler = preprocessorHandler; + expressionUsage = new ExpressionUsage(context,entityRepo); + file = new HashSet<>(); + context.startFile(fileFullPath); + file.add(this.context.currentFile().getQualifiedName()); + } + + @Override + public int visit(IASTTranslationUnit tu) { + for (String incl:preprocessorHandler.getDirectIncludedFiles(tu.getAllPreprocessorStatements(),context.currentFile().getQualifiedName())) { + context.foundNewImport(new FileImport(incl)); + } + MacroExtractor macroExtractor = new MacroExtractor(tu.getAllPreprocessorStatements(),context.currentFile().getQualifiedName()); + macroExtractor.extract(context); + + for (IASTNode child:tu.getChildren()) { + if (notLocalFile(child)) continue; + child.accept(this); + } + return ASTVisitor.PROCESS_SKIP; + } + + + @Override + public int visit(IASTProblem problem) { + if (notLocalFile(problem)) return ASTVisitor.PROCESS_SKIP; + System.out.println("warning: parse error " + problem.getOriginalNode().getRawSignature() + problem.getMessageWithLocation()); + return super.visit(problem); + } + + private boolean notLocalFile(IASTNode node) { + if (file.contains(node.getFileLocation().getFileName())) { + return false; + } + return true; + } + + // PACKAGES + @Override + public int visit(ICPPASTNamespaceDefinition namespaceDefinition) { + if (notLocalFile(namespaceDefinition)) return ASTVisitor.PROCESS_SKIP; + String ns = namespaceDefinition.getName().toString().replace("::", "."); + logger.trace("enter ICPPASTNamespaceDefinition " + ns); + Entity pkg = context.foundNamespace(ns,namespaceDefinition.getFileLocation().getStartingLineNumber()); + context.foundNewImport(new PackageWildCardImport(ns)); + return super.visit(namespaceDefinition); + } + + @Override + public int leave(ICPPASTNamespaceDefinition namespaceDefinition) { + if (notLocalFile(namespaceDefinition)) return ASTVisitor.PROCESS_SKIP; + context.exitLastedEntity(); + return super.leave(namespaceDefinition); + } + + // Types + @Override + public int visit(IASTDeclSpecifier declSpec) { + if (notLocalFile(declSpec)) return ASTVisitor.PROCESS_SKIP; + logger.trace("enter IASTDeclSpecifier " + declSpec.getClass().getSimpleName()); + if (declSpec instanceof IASTCompositeTypeSpecifier) { + IASTCompositeTypeSpecifier type = (IASTCompositeTypeSpecifier)declSpec; + String name = ASTStringUtilExt.getName(type); + List param = ASTStringUtilExt.getTemplateParameters(type); + TypeEntity typeEntity = context.foundNewType(name, type.getFileLocation().getStartingLineNumber()); + if (declSpec instanceof ICPPASTCompositeTypeSpecifier) { + ICPPASTBaseSpecifier[] baseSpecififers = ((ICPPASTCompositeTypeSpecifier)declSpec).getBaseSpecifiers(); + for (ICPPASTBaseSpecifier baseSpecififer:baseSpecififers) { + String extendName = ASTStringUtilExt.getName(baseSpecififer.getNameSpecifier()); + context.foundExtends(extendName); + } + } + } + else if (declSpec instanceof IASTEnumerationSpecifier) { + context.foundNewType(ASTStringUtilExt.getName(declSpec), declSpec.getFileLocation().getStartingLineNumber()); + }else { + //we do not care other types + } + return super.visit(declSpec); + } + + @Override + public int leave(IASTDeclSpecifier declSpec) { + if (notLocalFile(declSpec)) return ASTVisitor.PROCESS_SKIP; + if (declSpec instanceof IASTCompositeTypeSpecifier) { + context.exitLastedEntity(); + } + else if (declSpec instanceof IASTEnumerationSpecifier) { + context.exitLastedEntity(); + }else { + //we do not care other types + } + return super.leave(declSpec); + } + + //Function or Methods + @Override + public int visit(IASTDeclarator declarator) { + if (notLocalFile(declarator)) return ASTVisitor.PROCESS_SKIP; + logger.trace("enter IASTDeclarator " + declarator.getClass().getSimpleName()); + if (declarator instanceof IASTFunctionDeclarator){ + GenericName returnType = null; + if ( declarator.getParent() instanceof IASTSimpleDeclaration) { + IASTSimpleDeclaration decl = (IASTSimpleDeclaration)(declarator.getParent()); + returnType = buildGenericNameFromDeclSpecifier(decl.getDeclSpecifier()); + String rawName = ASTStringUtilExt.getName(declarator); + List namedEntity = context.currentFile().lookupFunctionInVisibleScope(GenericName.build(rawName)); + if (namedEntity!=null) { + rawName = namedEntity.get(0).getQualifiedName(); + } + returnType = reMapIfConstructDeconstruct(rawName,returnType); + context.foundMethodDeclaratorProto(rawName, returnType,decl.getFileLocation().getStartingLineNumber()); + } + else if ( declarator.getParent() instanceof IASTFunctionDefinition) { + IASTFunctionDefinition decl = (IASTFunctionDefinition)declarator.getParent(); + returnType = buildGenericNameFromDeclSpecifier(decl.getDeclSpecifier()); + String rawName = ASTStringUtilExt.getName(declarator); + List namedEntity = context.currentFile().lookupFunctionInVisibleScope(GenericName.build(rawName)); + if (namedEntity!=null) { + rawName = namedEntity.get(0).getQualifiedName(); + } + returnType = reMapIfConstructDeconstruct(rawName,returnType); + context.foundMethodDeclaratorImplementation(rawName, returnType,decl.getFileLocation().getStartingLineNumber()); + } + } + return super.visit(declarator); + } + + private GenericName buildGenericNameFromDeclSpecifier(IASTDeclSpecifier decl) { + String name = ASTStringUtilExt.getName(decl); + List templateParams = ASTStringUtilExt.getTemplateParameters(decl); + if (name==null) + return null; + return new GenericName(name,templateParams); + } + + /** + * In case of return type is empty, it maybe a construct/deconstruct function + * @param functionname + * @param returnType + * @return + */ + private GenericName reMapIfConstructDeconstruct(String functionname, GenericName returnType) { + if (returnType!=null && returnType.uniqName().length()>0) + return returnType; + if (functionname.contains("::")) { + return new GenericName(functionname.substring(0, functionname.indexOf("::"))); + }else { + return new GenericName(functionname); + } + } + + @Override + public int leave(IASTDeclarator declarator) { + if (notLocalFile(declarator)) return ASTVisitor.PROCESS_SKIP; + if (declarator instanceof IASTFunctionDeclarator){ + if ( declarator.getParent() instanceof IASTSimpleDeclaration) { + String rawName = ASTStringUtilExt.getName(declarator); + if (rawName.equals(context.lastContainer().getRawName().getName())) { + context.exitLastedEntity(); + }else { + System.err.println("unexpected symbol"); + } + } + } + return super.leave(declarator); + } + + @Override + public int leave(IASTDeclaration declaration) { + if (notLocalFile(declaration)) return ASTVisitor.PROCESS_SKIP; + if ( declaration instanceof IASTFunctionDefinition) { + context.exitLastedEntity(); + } + return super.leave(declaration); + } + + // Variables + @Override + public int visit(IASTDeclaration declaration) { + if (notLocalFile(declaration)) return ASTVisitor.PROCESS_SKIP; + logger.trace("enter IASTDeclaration " + declaration.getClass().getSimpleName()); + + if (declaration instanceof ICPPASTUsingDeclaration) { + String ns = ASTStringUtilExt.getName((ICPPASTUsingDeclaration)declaration); + context.foundNewImport(new PackageWildCardImport(ns)); + } + else if (declaration instanceof ICPPASTUsingDirective) { + String ns = ((ICPPASTUsingDirective)declaration).getQualifiedName().toString().replace("::", "."); + context.foundNewImport(new ExactMatchImport(ns)); + } + else if (declaration instanceof IASTSimpleDeclaration ) { + for (IASTDeclarator declarator:((IASTSimpleDeclaration) declaration).getDeclarators()) { + IASTDeclSpecifier declSpecifier = ((IASTSimpleDeclaration) declaration).getDeclSpecifier(); + //Found new typedef definition + if (declSpecifier.getStorageClass()==IASTDeclSpecifier.sc_typedef) { + context.foundNewAlias(ASTStringUtilExt.getName(declarator),ASTStringUtilExt.getName(declSpecifier)); + }else if (!(declarator instanceof IASTFunctionDeclarator)) { + String varType = ASTStringUtilExt.getName(declSpecifier); + String varName = ASTStringUtilExt.getName(declarator); + if (!StringUtils.isEmpty(varType)) { + context.foundVarDefinition(varName, GenericName.build(varType), ASTStringUtilExt.getTemplateParameters(declSpecifier),declarator.getFileLocation().getStartingLineNumber()); + }else { + expressionUsage.foundCallExpressionOfFunctionStyle(varName,declarator); + } + + } + } + }else if (declaration instanceof IASTFunctionDefinition){ + //handled in declarator + }else if (declaration instanceof CPPASTVisibilityLabel){ + //we ignore the visibility in dependency check + }else if (declaration instanceof CPPASTLinkageSpecification){ + + }else if (declaration instanceof CPPASTTemplateDeclaration){ + + }else if (declaration instanceof CPPASTProblemDeclaration){ + System.err.println("parsing error \n" + declaration.getRawSignature()); + }else if (declaration instanceof ICPPASTAliasDeclaration){ + IASTName name = ((ICPPASTAliasDeclaration)declaration).getAlias(); + String alias = ASTStringUtilExt.getSimpleName(name).replace("::", "."); + ICPPASTTypeId mapped = ((ICPPASTAliasDeclaration)declaration).getMappingTypeId(); + String originalName1 = ASTStringUtilExt.getTypeIdString(mapped); + context.foundNewAlias(alias, originalName1); + }else if (declaration instanceof CPPASTNamespaceAlias){ + IASTName name = ((CPPASTNamespaceAlias)declaration).getAlias(); + String alias = ASTStringUtilExt.getSimpleName(name).replace("::", "."); + IASTName mapped = ((CPPASTNamespaceAlias)declaration).getMappingName(); + String originalName = ASTStringUtilExt.getName(mapped); + context.foundNewAlias(alias, originalName); + } + else if(declaration instanceof CPPASTStaticAssertionDeclaration) + { + + }else if (declaration instanceof CPPASTTemplateSpecialization) { + } + else{ + System.out.println("not handled type: " + declaration.getClass().getName()); + System.out.println(declaration.getRawSignature()); + } + return super.visit(declaration); + } + + @Override + public int visit(IASTEnumerator enumerator) { + if (notLocalFile(enumerator)) return ASTVisitor.PROCESS_SKIP; + logger.trace("enter IASTEnumerator " + enumerator.getClass().getSimpleName()); + VarEntity var = context.foundVarDefinition(enumerator.getName().toString(), context.currentType().getRawName(), new ArrayList<>(),enumerator.getFileLocation().getStartingLineNumber()); + return super.visit(enumerator); + } + + @Override + public int visit(IASTExpression expression) { + if (notLocalFile(expression)) return ASTVisitor.PROCESS_SKIP; + Expression expr = expressionUsage.foundExpression(expression); + expr.setLine(expression.getFileLocation().getStartingLineNumber()); + return super.visit(expression); + } + + @Override + public int visit(IASTParameterDeclaration parameterDeclaration) { + if (notLocalFile(parameterDeclaration)) return ASTVisitor.PROCESS_SKIP; + + logger.trace("enter IASTParameterDeclaration " + parameterDeclaration.getClass().getSimpleName()); + String parameterName = ASTStringUtilExt.getName(parameterDeclaration.getDeclarator()); + String parameterType = ASTStringUtilExt.getName(parameterDeclaration.getDeclSpecifier()); + if (context.currentFunction()!=null) { + VarEntity var = new VarEntity(GenericName.build(parameterName),GenericName.build(parameterType),context.currentFunction(),idGenerator.generateId()); + context.currentFunction().addParameter(var ); + }else { + //System.out.println("** parameterDeclaration = " + parameter); + } + return super.visit(parameterDeclaration); + } +} \ No newline at end of file diff --git a/src/main/java/com/educoder/bridge/tmp/DecoratedEntity.java b/src/main/java/com/educoder/bridge/tmp/DecoratedEntity.java new file mode 100644 index 0000000..a18344d --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/DecoratedEntity.java @@ -0,0 +1,148 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.entity; + +import depends.relations.IBindingResolver; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public abstract class DecoratedEntity extends Entity{ + private Collection annotations; + private Collection resolvedAnnotations; + private Collection resolvedTypeParameters; + public DecoratedEntity() { + } + + public DecoratedEntity(GenericName rawName, Entity parent, Integer id) { + super(rawName, parent, id); + } + + public void addAnnotation(GenericName name) { + if(this.annotations==null) + annotations = new ArrayList<>(); + this.annotations.add(name); + } + + public void addTypeParameter(List parameters) { + this.getRawName().appendArguments(parameters); + } + + + + public void addTypeParameter(GenericName parameter) { + this.getRawName().appendArguments(parameter); + } + + protected void appendTypeParameters(Collection typeParameterEntities) { + if (resolvedTypeParameters==null) + resolvedTypeParameters = new ArrayList<>(); + resolvedTypeParameters.addAll(typeParameterEntities); + } + + /** + * For all data in the class, infer their types. + * Should be override in sub-classes + */ + public void inferLocalLevelEntities(IBindingResolver bindingResolver) { + Collection typeParameterEntities = typeParametersToEntities(bindingResolver); + appendTypeParameters(typeParameterEntities); +// if (this.getAncestorOfType(FileEntity.class).getRawName().contains("/examples/usersession/server.py") +// ) { +// System.out.print("dd"); +// } + resolvedAnnotations = identiferToEntities(bindingResolver, annotations); + } + + + + + private Collection typeParametersToEntities(IBindingResolver bindingResolver) { + ArrayList r = new ArrayList<>(); + for (GenericName typeParameter:this.getRawName().getArguments()) { + toEntityList(bindingResolver, r,typeParameter); + } + return r; + } + + protected void toEntityList(IBindingResolver bindingResolver, ArrayList r, GenericName typeParameter) { + Entity entity = resolveEntity(bindingResolver, typeParameter); + if (entity != null) + r.add(entity); + for (GenericName arg: typeParameter.getArguments()) { + toEntityList(bindingResolver,r,arg); + } + } + + public Collection getResolvedTypeParameters() { + if (resolvedTypeParameters==null) + return new ArrayList<>(); + return resolvedTypeParameters; + } + + + public Collection getResolvedAnnotations() { + if (resolvedAnnotations==null) + return new ArrayList<>(); + return resolvedAnnotations; + } + + public boolean isGenericTypeParameter(GenericName rawType) { + boolean foundInCurrentLevel = rawType.find(rawType); + if (foundInCurrentLevel) return true; + if (this.getParent()==null || !(this.getParent() instanceof ContainerEntity)) + return false; + return ((ContainerEntity)getParent()).isGenericTypeParameter(rawType); + } + /** + * A common utility function used to transfer the identifiers + * to types. + * @param bindingResolver - the inferer object + * @param identifiers - the identifiers will be translated + * @return The translated Types + */ + protected Collection identiferToEntities(IBindingResolver bindingResolver, Collection identifiers) { + if (identifiers==null) return null; + if (identifiers.size()==0) return null; + ArrayList r = new ArrayList<>(); + for (GenericName name : identifiers) { + Entity entity = resolveEntity(bindingResolver, name); + if (entity != null) + r.add(entity); + } + return r; + } + + private Entity resolveEntity(IBindingResolver bindingResolver, GenericName name) { + Entity entity = bindingResolver.resolveName(this, name, true); + if (entity==null) { + if (((ContainerEntity)getParent()).isGenericTypeParameter(name)) { + entity = TypeEntity.genericParameterType; + } + } + return entity; + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/DependencyGenerator.java b/src/main/java/com/educoder/bridge/tmp/DependencyGenerator.java new file mode 100644 index 0000000..6f943f5 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/DependencyGenerator.java @@ -0,0 +1,160 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.generator; + +import depends.entity.CandidateTypes; +import depends.entity.Entity; +import depends.entity.EntityNameBuilder; +import depends.entity.FileEntity; +import depends.entity.repo.EntityRepo; +import depends.matrix.core.DependencyDetail; +import depends.matrix.core.DependencyMatrix; +import depends.matrix.core.LocationInfo; +import depends.matrix.transform.OrderedMatrixGenerator; +import depends.relations.Relation; +import multilang.depends.util.file.path.EmptyFilenameWritter; +import multilang.depends.util.file.path.FilenameWritter; +import multilang.depends.util.file.strip.EmptyLeadingNameStripper; +import multilang.depends.util.file.strip.ILeadingNameStrippper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import static depends.deptypes.DependencyType.DuckTypingLabel; + +public abstract class DependencyGenerator { + + private static Logger logger = LoggerFactory.getLogger(DependencyGenerator.class); + + public DependencyMatrix identifyDependencies(EntityRepo entityRepo, List typeFilter) { + System.out.println("dependencie data generating..."); + DependencyMatrix dependencyMatrix = build(entityRepo, typeFilter); + entityRepo.clear(); + System.out.println("reorder dependency matrix..."); + dependencyMatrix = new OrderedMatrixGenerator(dependencyMatrix).build(); + System.out.println("Dependencies data generating done successfully..."); + logger.info("Dependencies data generating done successfully..."); + return dependencyMatrix; + } + + /** + * Build the dependency matrix (without re-mapping file id) + * @param entityRepo which contains entities and relations + * @return the generated dependency matrix + */ + public DependencyMatrix build(EntityRepo entityRepo,List typeFilter) { + DependencyMatrix dependencyMatrix = new DependencyMatrix(typeFilter); + Iterator iterator = entityRepo.entityIterator(); + System.out.println("Start create dependencies matrix...."); + while(iterator.hasNext()) { + Entity entity = iterator.next(); + if (!entity.inScope()) continue; + if (outputLevelMatch(entity)){ + dependencyMatrix.addNode(nameOf(entity),entity.getId()); + } + int entityFrom = upToOutputLevelEntityId(entityRepo, entity); + if (entityFrom==-1) continue; + for (Relation relation:entity.getRelations()) { + Entity relatedEntity = relation.getEntity(); + if (relatedEntity==null) continue; + List relatedEntities = expandEntity(relatedEntity); + String duckTypingFlag = relatedEntity instanceof CandidateTypes? DuckTypingLabel:""; + relatedEntities.forEach(theEntity->{ + if (theEntity.getId()>=0) { + int entityTo = upToOutputLevelEntityId(entityRepo,theEntity); + if (entityTo!=-1) { + DependencyDetail detail = buildDescription(entity, theEntity, relation.getFromLine()); + detail = rewriteDetail(detail); + dependencyMatrix.addDependency(relation.getType()+duckTypingFlag, entityFrom,entityTo,1,detail); + } + } + }); + } + } + System.out.println("Finish create dependencies matrix...."); + return dependencyMatrix; + } + + private List expandEntity(Entity relatedEntity) { + List entities = new ArrayList<>(); + if (relatedEntity instanceof CandidateTypes) { + entities = Collections.unmodifiableList((List) ((CandidateTypes) relatedEntity).getCandidateTypes()); + }else { + entities.add(relatedEntity); + } + return entities; + } + + private DependencyDetail rewriteDetail(DependencyDetail detail) { + if (detail==null) return null; + String srcFile = filenameWritter.reWrite( + stripper.stripFilename(detail.getSrc().getFile()) + ); + String dstFile = filenameWritter.reWrite( + stripper.stripFilename(detail.getDest().getFile())); + return new DependencyDetail( + new LocationInfo(detail.getSrc().getObject(), + srcFile, detail.getSrc().getLineNumber()) + , + new LocationInfo(detail.getDest().getObject(), + dstFile, detail.getDest().getLineNumber())); + } + + protected abstract int upToOutputLevelEntityId(EntityRepo entityRepo, Entity entity); + + protected abstract String nameOf(Entity entity); + + protected abstract boolean outputLevelMatch(Entity entity); + + protected ILeadingNameStrippper stripper = new EmptyLeadingNameStripper(); + protected FilenameWritter filenameWritter = new EmptyFilenameWritter(); + private boolean generateDetail = false; + + public void setLeadingStripper(ILeadingNameStrippper stripper) { + this.stripper = stripper; + } + protected DependencyDetail buildDescription(Entity fromEntity, Entity toEntity, Integer fromLineNumber) { + if (!generateDetail) return null; + String fromObject = EntityNameBuilder.build(fromEntity); + String toObject = EntityNameBuilder.build(toEntity); + + Entity fromFile = fromEntity.getAncestorOfType(FileEntity.class); + Entity toFile = toEntity.getAncestorOfType(FileEntity.class); + + return new DependencyDetail( + new LocationInfo(stripper.stripFilename(fromObject),stripper.stripFilename(fromFile.getQualifiedName()),fromLineNumber), + new LocationInfo(stripper.stripFilename(toObject),stripper.stripFilename(toFile.getQualifiedName()),toEntity.getLine())); + } + public void setFilenameRewritter(FilenameWritter filenameWritter) { + this.filenameWritter = filenameWritter; + } + public void setGenerateDetail(boolean generateDetail) { + this.generateDetail = generateDetail; + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/DependencyMatrix.java b/src/main/java/com/educoder/bridge/tmp/DependencyMatrix.java new file mode 100644 index 0000000..f74cc84 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/DependencyMatrix.java @@ -0,0 +1,102 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.matrix.core; + +import multilang.depends.util.file.path.FilenameWritter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; + +import static depends.deptypes.DependencyType.DuckTypingLabel; + +public class DependencyMatrix { + private HashMap dependencyPairs = new HashMap<>(); + private ArrayList nodes = new ArrayList<>(); + private HashMap nodeIdToName = new HashMap<>(); + private List typeFilter; + public DependencyMatrix() { + } + public DependencyMatrix(int size) { + dependencyPairs = new HashMap<>(size); + } + public DependencyMatrix(List typeFilter) { + this.typeFilter = typeFilter; + } + public Collection getDependencyPairs() { + return dependencyPairs.values(); + } + + public void addNode(String name, int id) { + this.nodes.add(name); + this.nodeIdToName.put(id, name); + } + + public void addDependency(String depType, Integer from, Integer to, int weight,List details) { + if (typeFilter!=null && (!typeFilter.contains(depType))) + return; + if(from.equals(to) || from == -1 || to == -1) { + return; + } + if (dependencyPairs.get(DependencyPair.key(from,to))==null) { + dependencyPairs.put(DependencyPair.key(from,to),new DependencyPair(from,to)); + } + DependencyPair dependencyPair = dependencyPairs.get(DependencyPair.key(from,to)); + dependencyPair.addDependency(depType,weight,details); + } + + public void addDependency(String depType, Integer from, Integer to, int weight,DependencyDetail detail) { + if (typeFilter!=null && (!typeFilter.contains(depType.replace(DuckTypingLabel,"")))) + return; + if(from.equals(to) || from == -1 || to == -1) { + return; + } + if (dependencyPairs.get(DependencyPair.key(from,to))==null) { + dependencyPairs.put(DependencyPair.key(from,to),new DependencyPair(from,to)); + } + DependencyPair dependencyPair = dependencyPairs.get(DependencyPair.key(from,to)); + dependencyPair.addDependency(depType,weight,detail); + } + + public ArrayList getNodes() { + return nodes; + } + + + public DependencyMatrix reWriteFilenamePattern(FilenameWritter filenameRewritter) { + this.nodeIdToName = new HashMap<>(); + for (int i=0;i { + private static final long serialVersionUID = 1L; + public SupportedLangs() { super( LangProcessorRegistration.getRegistry().getLangs()); } + } + + public static class SupportedTypes extends ArrayList { + private static final long serialVersionUID = 1L; + public SupportedTypes() { super( DependencyType.allDependencies()); } + } + + @Parameters(index = "0", completionCandidates = DependsCommand.SupportedLangs.class, description = "The lanauge of project files: [${COMPLETION-CANDIDATES}]") + private String lang; + @Parameters(index = "1", description = "The directory to be analyzed") + private String src; + @Parameters(index = "2", description = "The output file name") + private String output; + @Option(names = {"-f", "--format"},split=",", description = "the output format: [json(default),xml,excel,detail,dot,plantuml]") + private String[] format=new String[]{"json"}; + @Option(names = {"-d", "--dir"}, description = "The output directory") + private String dir; + @Option(names = {"-m", "--map"}, description = "Output DV8 dependency map file.") + private boolean dv8map = true; + @Option(names = {"-s", "--strip-leading-path"}, description = "Strip the leading path.") + private boolean stripLeadingPath = false; + @Option(names = {"--strip-paths"}, split=",", description = "The path(s) to be stripped. if -s enabled, the path(s) start after . " + + "Otherwise, the path(s) should be valid.") + private String[] strippedPaths = new String[]{}; + @Option(names = {"-g", "--granularity"}, description = "Granularity of dependency.[file(default),method,structure,L#(the level of folder. e.g. L1=1st level folder)]") + private String granularity="file"; + @Option(names = {"-p", "--namepattern"}, description = "The name path pattern.[dot(.), unix(/) or windows(\\)") + private String namePathPattern=""; + @Option(names = {"-i","--includes"},split=",", description = "The files of searching path") + private String[] includes = new String[] {}; + @Option(names = {"--auto-include"},split=",", description = "auto include all paths under the source path (please notice the potential side effect)") + private boolean autoInclude = false; + @Option(names = {"--detail"},split=",", description = "add detail dependency information to output (only applicable for JSON output format)") + private boolean detail = false; + @Option(names = {"--auto-stub"},split=",", description = "create stub files for unsolved symbols (exprimental feature, only for java)") + private boolean autoStub = false; + @Option(names = {"--type-filter"},split=",", completionCandidates = DependsCommand.SupportedTypes.class, description = "only filter the listed dependency types[${COMPLETION-CANDIDATES}]") + private String[] typeFilter=new String[]{}; + @Option(names = {"--external-deps"}, description = "Output external dependencies") + private boolean outputExternalDependencies = false; + @Option(names = {"--duck-typing-deduce"}, description = "Deduce implicit variable types") + private boolean duckTypingDeduce = true; + @Option(names = {"-h","--help"}, usageHelp = true, description = "display this help and exit") + boolean help; + public DependsCommand() { + } + public String getLang() { + return lang; + } + public void setLang(String lang) { + this.lang = lang; + } + public String getSrc() { + return src; + } + public void setSrc(String src) { + this.src = src; + } + public String getOutputName() { + return output; + } + public void setOutput(String output) { + this.output = output; + } + public String[] getFormat() { + return format; + } + public String getOutputDir() { + if (dir==null) { + dir = System.getProperty("user.dir"); + } + return dir; + } + public boolean isDv8map() { + return dv8map; + } + public String[] getIncludes() { + return includes; + } + public boolean isHelp() { + return help; + } + public String getGranularity() { + return granularity; + } + public String getNamePathPattern() { + return namePathPattern; + } + public boolean isStripLeadingPath() { + return stripLeadingPath; + } + + public boolean isAutoInclude () { + return autoInclude; + } + public boolean isDetail () { + return detail; + } + public String[] getStrippedPaths() { + return strippedPaths; + } + public void setStrippedPaths(String[] strippedPaths) { + this.strippedPaths = strippedPaths; + } + public boolean isAutoStub() { + return autoStub; + } + public List getTypeFilter() { + if (typeFilter.length==0) { + return DependencyType.allDependencies(); + } + return java.util.Arrays.asList(typeFilter); + } + public boolean isOutputExternalDependencies() { + return outputExternalDependencies; + } + + public boolean isDuckTypingDeduce() { + return this.duckTypingDeduce; + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/EclipseTestBase_No_ResponseDuirngTypeResolve.java b/src/main/java/com/educoder/bridge/tmp/EclipseTestBase_No_ResponseDuirngTypeResolve.java new file mode 100644 index 0000000..25f2020 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/EclipseTestBase_No_ResponseDuirngTypeResolve.java @@ -0,0 +1,222 @@ +/******************************************************************************* + * Copyright (c) 2007, 2014 BEA Systems, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * BEA Systems, Inc. - initial API and implementation + * Jesper Steen Moller - Bug 412150 [1.8] [compiler] Enable reflected parameter names during annotation processing + *******************************************************************************/ +package org.eclipse.jdt.apt.pluggable.tests; + +import java.io.File; +import java.io.InputStream; +import java.net.URL; +import java.util.Map; + +import javax.lang.model.SourceVersion; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.jdt.apt.core.internal.util.FactoryContainer; +import org.eclipse.jdt.apt.core.internal.util.FactoryContainer.FactoryType; +import org.eclipse.jdt.apt.core.internal.util.FactoryPath; +import org.eclipse.jdt.apt.core.util.AptConfig; +import org.eclipse.jdt.core.IClasspathAttribute; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.tests.builder.BuilderTests; +import org.eclipse.jdt.core.tests.util.Util; +import org.eclipse.jdt.internal.core.ClasspathEntry; + +import junit.framework.Test; + +public class TestBase extends BuilderTests +{ + + protected static final String JAVA_16_COMPLIANCE = "1.6"; + protected static final String JAVA_18_COMPLIANCE = "1.8"; + protected static final String JAVA_9_COMPLIANCE = "9"; + + protected String _projectName; + protected static int _projectSerial = 0; // used to create unique project names, to avoid resource deletion problems + + public TestBase(String name) { + super(name); + } + + public static Test suite() { + throw new IllegalStateException("This is a base test class whose suite() method must not be called.\n" + + "This exception is thrown to avoid running org.eclipse.jdt.core.tests.builder.BuilderTests#suite() twice."); + } + + /** + * Extract lib/annotations.jar from the test bundle and add it to the specified project + */ + private static void addAnnotationJar(IJavaProject jproj, boolean addToModulePath) throws Exception { + final String resName = "lib/annotations.jar"; // name in bundle + final String libName = resName; // name in destination project + InputStream is = null; + URL resURL = Apt6TestsPlugin.thePlugin().getBundle().getEntry(resName); + is = resURL.openStream(); + IPath projPath = jproj.getPath(); + IProject proj = jproj.getProject(); + IFile libFile = proj.getFile(libName); + env.addFolder(projPath, "lib"); + if (libFile.exists()) { + libFile.setContents(is, true, false, null); + } else { + libFile.create(is, true, null); + } + if (addToModulePath) { + IClasspathAttribute[] attributes = { JavaCore.newClasspathAttribute(IClasspathAttribute.MODULE, "true") }; + env.addEntry(projPath, JavaCore.newLibraryEntry(libFile.getFullPath(), null, null, + ClasspathEntry.NO_ACCESS_RULES, attributes, false)); + } else { + env.addLibrary(projPath, libFile.getFullPath(), null, null); + } + } + + /** + * Create a java project with java libraries and test annotations on classpath + * (compiler level is 1.6). Use "src" as source folder and "bin" as output folder. APT + * is not enabled. + * + * @param projectName + * @return a java project that has been added to the current workspace. + * @throws Exception + */ + protected static IJavaProject createJavaProject(final String projectName) throws Exception + { + IPath projectPath = env.addProject(projectName, JAVA_16_COMPLIANCE); + env.addExternalJars(projectPath, Util.getJavaClassLibs()); + // remove old package fragment root so that names don't collide + env.removePackageFragmentRoot(projectPath, ""); //$NON-NLS-1$ + env.addPackageFragmentRoot(projectPath, "src"); //$NON-NLS-1$ + env.setOutputFolder(projectPath, "bin"); //$NON-NLS-1$ + final IJavaProject javaProj = env.getJavaProject(projectPath); + addAnnotationJar(javaProj, false); + return javaProj; + } + + /** + * Create a java project with java libraries and test annotations on classpath + * (compiler level is 1.8). Use "src" as source folder and "bin" as output folder. APT + * is not enabled. + * + * @param projectName + * @return a java project that has been added to the current workspace. + * @throws Exception + */ + protected static IJavaProject createJava8Project(final String projectName) throws Exception { + // Note, make sure this is run only with a JRE 8 and above. + IPath projectPath = env.addProject(projectName, JAVA_18_COMPLIANCE); + env.addExternalJars(projectPath, Util.getJavaClassLibs()); + + // remove old package fragment root so that names don't collide + env.removePackageFragmentRoot(projectPath, ""); //$NON-NLS-1$ + env.addPackageFragmentRoot(projectPath, "src"); //$NON-NLS-1$ + env.setOutputFolder(projectPath, "bin"); //$NON-NLS-1$ + final IJavaProject javaProj = env.getJavaProject(projectPath); + javaProj.getProject().getFolder("prebuilt").create(true, true, null); + javaProj.getProject().getFolder("prebuilt").getFolder("p").create(true, true, null); + env.addClassFolder(projectPath, projectPath.append("prebuilt"), true); + addAnnotationJar(javaProj, false); + return javaProj; + } + + /** + * Create a java project with java libraries and test annotations on modulepath + * (compiler level is 1.9). Use "src" as source folder and "bin" as output folder. APT + * is not enabled. + * + * @param projectName + * @return a java project that has been added to the current workspace. + * @throws Exception + */ + protected static IJavaProject createJava9Project(final String projectName) throws Exception { + // Note, make sure this is run only with a JRE 9 and above. + IPath projectPath = env.addProject(projectName, JAVA_9_COMPLIANCE); + env.addExternalJars(projectPath, Util.getJavaClassLibs()); + // remove old package fragment root so that names don't collide + env.removePackageFragmentRoot(projectPath, ""); //$NON-NLS-1$ + env.addPackageFragmentRoot(projectPath, "src"); //$NON-NLS-1$ + env.setOutputFolder(projectPath, "bin"); //$NON-NLS-1$ + final IJavaProject javaProj = env.getJavaProject(projectPath); + addAnnotationJar(javaProj, true); + return javaProj; + } + + /** + * Ensure that there are no Java 5 processors on the factory path, as they can cause + * units to be multiply compiled, which can mess up tests that expect a certain number + * of compilations to occur. + * @param jproj the project whose factory path will be edited + * @throws CoreException + */ + protected void disableJava5Factories(IJavaProject jproj) throws CoreException { + FactoryPath fp = (FactoryPath) AptConfig.getFactoryPath(jproj); + for (Map.Entry entry : fp.getAllContainers().entrySet()) { + if (entry.getKey().getType() == FactoryType.PLUGIN) { + String id = entry.getKey().getId(); + if (!Apt6TestsPlugin.PLUGIN_ID.equals(id)) { + fp.disablePlugin(id); + } + } + } + AptConfig.setFactoryPath(jproj, fp); + } + + /** + * Verify that an expected file exists within a project. + * @param fileName the filename relative to the project root. + */ + protected void expectingFile(IProject proj, String fileName) throws Exception + { + IPath path = proj.getLocation().append(fileName); + File file = new File(path.toOSString()); + assertTrue("Expected file " + fileName + " was missing from project", file != null && file.exists()); + } + + /** + * Verify that an expected file exists within a project. + * @param fileName the filename relative to the project root. + */ + protected void expectingNoFile(IProject proj, String fileName) throws Exception + { + IPath path = proj.getLocation().append(fileName); + File file = new File(path.toOSString()); + boolean exists = file.exists(); + // work around a timing bug in some versions of JRE 1.6 on Linux: + // Before assuming the test has failed, wait half a second and try again. + // This delay is not encountered when the test is passing normally. + if (exists) { + Thread.sleep(500); + exists = file.exists(); + } + assertTrue("File " + fileName + " was expected to not exist", file == null || !exists); + } + + @Override + protected void setUp() throws Exception + { + super.setUp(); + env.setAutoBuilding(false); + _projectName = String.format("testproj%04d", ++_projectSerial); + } + public boolean canRunJava9() { + try { + SourceVersion.valueOf("RELEASE_9"); + } catch(IllegalArgumentException iae) { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/src/main/java/com/educoder/bridge/tmp/Entity.java b/src/main/java/com/educoder/bridge/tmp/Entity.java new file mode 100644 index 0000000..75174c5 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/Entity.java @@ -0,0 +1,275 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.entity; + +import depends.relations.IBindingResolver; +import depends.relations.Relation; + +import java.util.*; + +/** + * Entity is the root of all entities, including file, package, module, + * class, method/function etc. + * Each entity has unique id, name,qualifiedName, parent, children + * We also use entity to record relations + */ +public abstract class Entity { + + Integer id=-1; + String qualifiedName = null; + GenericName rawName = GenericName.build(""); + Entity parent; + private MultiDeclareEntities mutliDeclare = null; + private Set children; + ArrayList relations; + private Entity actualReferTo = null; + private boolean inScope = true; + protected HashMap visibleNames = new HashMap<>(); + private Location location = new Location(); + public Entity() {}; + public Entity(GenericName rawName, Entity parent, Integer id) { + this.qualifiedName = null; + this.rawName = rawName; + this.parent = parent; + this.id = id; + if (parent!=null) + parent.addChild(this); + deduceQualifiedName(); + visibleNames.put(rawName.getName(), this); + visibleNames.put(qualifiedName, this); + } + + private Set children() { + if (children==null) + children = new HashSet<>(); + return children; + } + /** + * Rule 1: if it start with '.' , then the name is equal to raw name + * Rule 2: if parent not exists, the name is equal to raw name + * Rule 3: if parent exists but no qualified name exists or empty, the name is equal to raw name + * Rule 4: otherwise, qualified name = parent_qualfied_name + "."+rawName + * Rule 5: make sure the qualified name do not start with '.' + */ + private void deduceQualifiedName() { + rawName = rawName.replace("::","." ); + if (this.rawName.startsWith(".")) { + this.qualifiedName = this.rawName.uniqName().substring(1); + return; //already qualified + } + if (parent==null) { + this.qualifiedName = this.rawName.uniqName(); + return; + } + if (parent.getQualifiedName(true)==null) { + this.qualifiedName = this.rawName.uniqName(); + return; + } + if (parent.getQualifiedName(true).isEmpty()) { + this.qualifiedName = rawName.uniqName(); + return; + } + this.qualifiedName= parent.getQualifiedName(true)+"." + rawName.uniqName(); + } + + + public GenericName getRawName() { + return rawName; + } + + public Integer getId() { + return id; + } + + public void addRelation(Relation relation) { + if (relations==null) + relations = new ArrayList<>(); + if (relation.getEntity()==null) return; + relations.add(relation); + } + + public ArrayList getRelations() { + if (relations==null) + return new ArrayList<>(); + return relations; + } + + public void addChild(Entity child) { + children().add(child); + visibleNames.put(child.getRawName().getName(), child); + visibleNames.put(child.getQualifiedName(), child); + } + + public Entity getParent() { + return parent; + } + + public void setParent(Entity parent) { + this.parent = parent; + } + + public Collection getChildren() { + if (children==null) + return new HashSet<>(); + return children; + } + + public void setQualifiedName(String qualifiedName) { + this.qualifiedName = qualifiedName; + } + + public void setRawName(GenericName rawName) { + this.rawName = rawName; + deduceQualifiedName(); + } + + public final String getQualifiedName() { + return qualifiedName; + } + + public String getQualifiedName(boolean overrideFileWithPackage) { + return qualifiedName; + } + + @Override + public String toString() { + return "Entity [id=" + id + ", qualifiedName=" + qualifiedName + ", rawName=" + rawName + "]"; + } + + /** + * Get ancestor of type. + * @param classType + * @return null (if not exist) or the type + */ + public Entity getAncestorOfType(@SuppressWarnings("rawtypes") Class classType) { + Entity fromEntity = this; + while(fromEntity!=null) { + if (fromEntity.getClass().equals(classType)) + return fromEntity; + if (fromEntity.getParent()==null) return null; + fromEntity = fromEntity.getParent(); + } + return null; + } + + /** + * Invoke inferer to resolve the entity type etc. + * */ + public void inferEntities(IBindingResolver bindingResolver) { + inferLocalLevelEntities(bindingResolver); + for (Entity child:this.getChildren()) { + child.inferEntities(bindingResolver); + } + } + public abstract void inferLocalLevelEntities(IBindingResolver bindingResolver); + + public TypeEntity getType() { + return null; + } + + public String getDisplayName() { + return getRawName().uniqName(); + } + + public MultiDeclareEntities getMutliDeclare() { + return mutliDeclare; + } + + public void setMutliDeclare(MultiDeclareEntities mutliDeclare) { + this.mutliDeclare = mutliDeclare; + } + + public Entity getActualReferTo() { + if (this.actualReferTo ==null) + return this; + return actualReferTo; + } + + public void setActualReferTo(Entity actualReferTo) { + this.actualReferTo = actualReferTo; + } + + public static void setParent(Entity child, Entity parent) { + if (parent == null) + return; + if (child == null) + return; + if (parent.equals(child.getParent())) + return; + child.setParent(parent); + parent.addChild(child); + } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Entity other = (Entity) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + + public void setInScope(boolean value) { + this.inScope = value; + children().forEach(child->child.setInScope(value)); + } + + public boolean inScope() { + return inScope; + } + public Entity getByName(String name, HashSet searched) { + if (searched.contains(this)) return null; + searched.add(this); + return visibleNames.get(name); + } + + public Integer getLine() { + return location.getLine(); + } + + public void setLine(int lineNumber) { + this.location.setLine(lineNumber); + } + + public Location getLocation() { + return this.location; + } + +} diff --git a/src/main/java/com/educoder/bridge/tmp/EntityExtractTest.java b/src/main/java/com/educoder/bridge/tmp/EntityExtractTest.java new file mode 100644 index 0000000..9c5e1a1 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/EntityExtractTest.java @@ -0,0 +1,101 @@ +package depends.extractor.pom; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class EntityExtractTest extends MavenParserTest{ + @Before + public void setUp() { + super.init(); + } + + @Test + public void use_package_contains() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/maven-code-examples/simple/log4j.pom", + }; + + for (String src:srcs) { + PomFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + assertEquals(0,entityRepo.getEntity("org.log4j-test.log4j_1.2.12_").getRelations().size()); + } + + + @Test + public void should_use_parent_groupId() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/maven-code-examples/use_parent_groupId_and_version.pom", + }; + + for (String src:srcs) { + PomFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + assertNotNull(entityRepo.getEntity("org.apache.maven.surefire.surefire-junit4_2.12.4_")); + } + + @Test + public void should_parse_properties_in_same_pom() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/maven-code-examples/properties-test1.pom", + }; + + for (String src:srcs) { + PomFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + PomArtifactEntity entity = (PomArtifactEntity)(entityRepo.getEntity("properties-test.test_1_")); + /* + 1.00 + 3.1.4 + Apache ActiveMQ + activemq-${project.version} */ + assertEquals("1.00",entity.getProperty("project.version")); + assertEquals("activemq-1.00",entity.getProperty("siteId")); + } + + + @Test + public void should_parse_multiple_properties_in_same_pom() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/maven-code-examples/properties-test1.pom", + }; + + for (String src:srcs) { + PomFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + PomArtifactEntity entity = (PomArtifactEntity)(entityRepo.getEntity("properties-test.test_1_")); + /* + 1.00 + 3.1.4 + Apache ActiveMQ + activemq-${project.version}--${activeio-version} */ + assertEquals("activemq-1.00-3.1.4",entity.getProperty("anotherId")); + } + + @Test + public void should_parse_multiple_properties_in_parent_pom() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/maven-code-examples/properties-test-child.pom" + }; + + for (String src:srcs) { + PomFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + PomArtifactEntity entity = (PomArtifactEntity)(entityRepo.getEntity("properties-test.test_1_")); + assertEquals("13",entity.getProperty("project.version")); + } +} \ No newline at end of file diff --git a/src/main/java/com/educoder/bridge/tmp/ExcelXlsFormatDependencyDumper.java b/src/main/java/com/educoder/bridge/tmp/ExcelXlsFormatDependencyDumper.java new file mode 100644 index 0000000..6831f18 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/ExcelXlsFormatDependencyDumper.java @@ -0,0 +1,120 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.format.excel; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; + +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; + +import depends.format.AbstractFormatDependencyDumper; +import depends.matrix.core.DependencyMatrix; +import depends.matrix.core.DependencyPair; +import depends.matrix.core.DependencyValue; + +public class ExcelXlsFormatDependencyDumper extends AbstractFormatDependencyDumper { + private HSSFWorkbook workbook; + private HSSFSheet sheet; + @Override + public String getFormatName() { + return "xls"; + } + public ExcelXlsFormatDependencyDumper(DependencyMatrix dependencyMatrix, String projectName, String outputDir) { + super(dependencyMatrix, projectName,outputDir); + } + + @Override + public boolean output() { + String filename = composeFilename() + ".xls"; + if (matrix.getNodes().size() > 255) { + System.out.println("We can only export small matrix(<256 items) to excel" + "due to MS Office limitation"); + return false; + } + startFile(); + Collection dependencyPairs = matrix.getDependencyPairs(); + HSSFRow[] row = new HSSFRow[matrix.getNodes().size()]; + + // create header row + HSSFRow header = sheet.createRow(0); + for (int i = 0; i < matrix.getNodes().size(); i++) { + HSSFCell cell = header.createCell(i + 2); + cell.setCellValue(i); + } + ; + + // create header col + for (int i = 0; i < matrix.getNodes().size(); i++) { + row[i] = sheet.createRow(i + 1); + String node = matrix.getNodes().get(i); + HSSFCell cell = row[i].createCell(0); + cell.setCellValue(i); + cell = row[i].createCell(1); + cell.setCellValue(node); + } + ; + + // create header col + for (int i = 0; i < matrix.getNodes().size(); i++) { + HSSFCell cell = row[i].createCell(i + 2); + cell.setCellValue("(" + i + ")"); + } + ; + + for (DependencyPair dependencyPair : dependencyPairs) { + HSSFCell cell = row[dependencyPair.getFrom()].createCell(dependencyPair.getTo() + 2); + cell.setCellValue(buildDependencyValues(dependencyPair.getDependencies())); + } + closeFile(filename); + return true; + } + + private String buildDependencyValues(Collection dependencies) { + StringBuilder sb = new StringBuilder(); + for (DependencyValue dependency : dependencies) { + String comma = sb.length() > 0 ? "," : ""; + sb.append(comma).append(dependency.getType()).append("(").append(dependency.getWeight()).append(")"); + } + return sb.toString(); + } + + private void closeFile(String filename) { + try { + workbook.write(new File(filename)); + workbook.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void startFile() { + workbook = new HSSFWorkbook(); + sheet = workbook.createSheet("DSM"); + } + +} diff --git a/src/main/java/com/educoder/bridge/tmp/ExcelXlsxFormatDependencyDumper.java b/src/main/java/com/educoder/bridge/tmp/ExcelXlsxFormatDependencyDumper.java new file mode 100644 index 0000000..c27b68a --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/ExcelXlsxFormatDependencyDumper.java @@ -0,0 +1,119 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.format.excel; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Collection; + +import org.apache.poi.xssf.usermodel.XSSFCell; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import depends.format.AbstractFormatDependencyDumper; +import depends.matrix.core.DependencyMatrix; +import depends.matrix.core.DependencyPair; +import depends.matrix.core.DependencyValue; + +public class ExcelXlsxFormatDependencyDumper extends AbstractFormatDependencyDumper { + private XSSFWorkbook workbook; + private XSSFSheet sheet; + @Override + public String getFormatName() { + return "xlsx"; + } + public ExcelXlsxFormatDependencyDumper(DependencyMatrix dependencyMatrix, String projectName, String outputDir) { + super(dependencyMatrix, projectName,outputDir); + } + + @Override + public boolean output() { + String filename = composeFilename() + ".xlsx"; + + startFile(); + Collection dependencyPairs = matrix.getDependencyPairs(); + XSSFRow[] row = new XSSFRow[matrix.getNodes().size()]; + + // create header row + XSSFRow header = sheet.createRow(0); + for (int i = 0; i < matrix.getNodes().size(); i++) { + XSSFCell cell = header.createCell(i + 2); + cell.setCellValue(i); + } + ; + + // create header col + for (int i = 0; i < matrix.getNodes().size(); i++) { + row[i] = sheet.createRow(i + 1); + String node = matrix.getNodes().get(i); + XSSFCell cell = row[i].createCell(0); + cell.setCellValue(i); + cell = row[i].createCell(1); + cell.setCellValue(node); + } + ; + + // create header col + for (int i = 0; i < matrix.getNodes().size(); i++) { + XSSFCell cell = row[i].createCell(i + 2); + cell.setCellValue("(" + i + ")"); + } + ; + + for (DependencyPair dependencyPair : dependencyPairs) { + XSSFCell cell = row[dependencyPair.getFrom()].createCell(dependencyPair.getTo() + 2); + cell.setCellValue(buildDependencyValues(dependencyPair.getDependencies())); + } + closeFile(filename); + return true; + } + + private String buildDependencyValues(Collection dependencies) { + StringBuilder sb = new StringBuilder(); + for (DependencyValue dependency : dependencies) { + String comma = sb.length() > 0 ? "," : ""; + sb.append(comma).append(dependency.getType()).append("(").append(dependency.getWeight()).append(")"); + } + return sb.toString(); + } + + private void closeFile(String filename) { + try { + FileOutputStream out = new FileOutputStream(filename); + workbook.write(out); + workbook.close(); + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void startFile() { + workbook = new XSSFWorkbook(); + sheet = workbook.createSheet("DSM"); + } + +} diff --git a/src/main/java/com/educoder/bridge/tmp/Expression.java b/src/main/java/com/educoder/bridge/tmp/Expression.java new file mode 100644 index 0000000..5639248 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/Expression.java @@ -0,0 +1,413 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.entity; + +import depends.entity.repo.EntityRepo; +import depends.relations.IBindingResolver; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * Expression + */ +public class Expression implements Serializable{ + private static final long serialVersionUID = 1L; + + public Integer id; + private String text; // for debug purpose + private GenericName rawType; // the raw type name + private GenericName identifier; // the varName, or method name, etc. + private boolean isSet = false; // is a set relation from right to leftHand + private boolean isDot = false; // is a dot expression, will decuce variable tfype left to right + private boolean isCall = false; + private boolean isLogic = false; + private boolean isCreate = false; + private boolean isCast = false; + private boolean isThrow = false; + private boolean isStatement = false; //statement is only used for return type calcuation in some langs such as ruby + //they will not be treat as real expressions in case of relation calculation + private boolean deriveTypeFromChild = true; + + private Integer deduceTypeBasedId; //by default, parent expression type determined by most left child + + private Integer parentId = -1; + private transient Expression parent; + + private transient List deducedTypeVars = new ArrayList<>(); + private List deducedTypeVarsId = new ArrayList<>(); + + private transient List deducedTypeFunctions= new ArrayList<>(); + private List deducedTypeFunctionsId = new ArrayList<>(); + + private Integer referredEntityId; + private transient Entity referredEntity; + + private transient TypeEntity type; // the type we care - for relation calculation. + private Location location = new Location(); + //for leaf, it equals to referredEntity.getType. otherwise, depends on child's type strategy + + /* + * */ + + public Expression() { + deducedTypeVars = new ArrayList<>(); + deducedTypeFunctions = new ArrayList<>(); + } + + public Expression(Integer id) { + this.id = id; + deducedTypeVars = new ArrayList<>(); + deducedTypeFunctions = new ArrayList<>(); + } + + public void reload(EntityRepo repo, ArrayList expressionList) { + this.deducedTypeFunctions = new ArrayList<>(); + this.deducedTypeVars = new ArrayList<>(); + + //recover parent relation + if (parentId!=-1) { + for (Expression expr:expressionList) { + if (expr.id==parentId) { + parent = expr; + break; + } + } + } + + //recover deducedTypeFunctionsId + if (deducedTypeFunctionsId!=null) { + for (Integer funcId:this.deducedTypeFunctionsId) { + this.deducedTypeFunctions.add((FunctionEntity) repo.getEntity(funcId)); + } + } + + //recover deducedTypeVars + if (deducedTypeVarsId!=null) { + for (Integer varId:this.deducedTypeVarsId) { + this.deducedTypeVars.add((VarEntity) repo.getEntity(varId)); + } + } + + //referer referredEntity -- TODO:maybe not require + if (this.referredEntityId!=null && this.referredEntity==null) { + this.referredEntity = repo.getEntity(this.referredEntityId); + if (this.referredEntity ==null){ + System.err.println("unexpected: referred Entity is null" + this.referredEntityId + this.text+this.id); + } + } + } + + /** + * Set type of the expression + * @param type + * @param referredEntity + * @param bindingResolver + */ + public void setType(TypeEntity type, Entity referredEntity, IBindingResolver bindingResolver) { + if (this.getReferredEntity()==null && referredEntity!=null) { + this.setReferredEntity(referredEntity); + } + + boolean changedType = false; + if (this.type==null && type!=null) { + this.type = type; + for (VarEntity var:deducedTypeVars) { + if (var!=null) { + var.setType(this.type); + } + } + for (FunctionEntity func:deducedTypeFunctions) { + if (func!=null) { + func.addReturnType(this.type); + } + } + changedType = true; + } + if (this.referredEntity==null) + this.setReferredEntity(this.type); + + if (changedType) + deduceTheParentType(bindingResolver); + } + + + /** + * deduce type of parent based on child's type + * @param bindingResolver + */ + private void deduceTheParentType(IBindingResolver bindingResolver) { + if (this.type==null) return; + if (this.parent==null) return; + Expression parent = this.parent; + if (parent.type != null)return; + if (!parent.deriveTypeFromChild) return; + //parent's type depends on first child's type + if (parent.deduceTypeBasedId!=this.id) return; + + //if child is a built-in/external type, then parent must also a built-in/external type + if (this.type.equals(TypeEntity.buildInType)) { + parent.setType(TypeEntity.buildInType,TypeEntity.buildInType, bindingResolver); + return; + } + + /* if it is a logic expression, the return type/type is boolean. */ + if (parent.isLogic) { + parent.setType(TypeEntity.buildInType,null, bindingResolver); + } + /* if it is a.b, and we already get a's type, b's type could be identified easily */ + else if (parent.isDot) { + if (parent.isCall()) { + List funcs = this.getType().lookupFunctionInVisibleScope(parent.identifier); + setReferredFunctions(bindingResolver, parent, funcs); + }else { + Entity var = this.getType().lookupVarInVisibleScope(parent.identifier); + if (var!=null) { + parent.setType(var.getType(),var, bindingResolver); + parent.setReferredEntity(var); + }else { + List funcs = this.getType().lookupFunctionInVisibleScope(parent.identifier); + setReferredFunctions(bindingResolver,parent,funcs); + } + } + if (parent.getType()==null) { + parent.setType(bindingResolver.inferTypeFromName(this.getType(), parent.identifier),null, bindingResolver); + } + } + /* if other situation, simple make the parent and child type same */ + else { + parent.setType(type, null, bindingResolver); + } + if (parent.getReferredEntity()==null) + parent.setReferredEntity(parent.type); + } + + private void setReferredFunctions(IBindingResolver bindingResolver, Expression expr, List funcs) { + if (funcs ==null ||funcs.size()==0) return; + Entity func = funcs.get(0); + if (funcs.size()==1){ + expr.setType(func.getType(), func, bindingResolver); + expr.setReferredEntity(func); + return; + } + MultiDeclareEntities m = new MultiDeclareEntities(func, bindingResolver.getRepo().generateId()); + bindingResolver.getRepo().add(m); + for (int i = 1; i< funcs.size(); i++) { + m.add(funcs.get(i)); + } + expr.setType(func.getType(), m, bindingResolver); + expr.setReferredEntity(m); + } + + private void setReferredEntity(Entity referredEntity) { + this.referredEntity = referredEntity; + if (this.referredEntity!=null) { + this.referredEntityId = referredEntity.getId(); + } + } + + public void addDeducedTypeVar(VarEntity var) { + this.deducedTypeVars.add(var); + this.deducedTypeVarsId.add(var.getId()); + } + + public void addDeducedTypeFunction(FunctionEntity function) { + this.deducedTypeFunctions.add(function); + this.deducedTypeFunctionsId.add(function.id); + } + + public void setParent(Expression parent) { + this.parent = parent; + if (parent!=null) + this.parentId = parent.id; + if (parent!=null) { + if (parent.deduceTypeBasedId==null) + parent.deduceTypeBasedId = id; + if (parent.isSet) { + parent.deduceTypeBasedId = id; + } + } + } + + + public GenericName getIdentifier() { + return this.identifier; + } + + public GenericName getRawType() { + return this.rawType; + } + + public void setIdentifier(String name) { + if (!validName(name)){ + return; + } + this.identifier = GenericName.build(name); + } + + public void setIdentifier(GenericName name) { + if (name==null) return; + if (!validName(name.getName())){ + return; + } + this.identifier = name; + } + + public void setRawType(GenericName name) { + if (name==null) return; + if (!validName(name.getName())){ + return; + } + this.rawType = name; + + } + + public void setRawType(String name) { + if (name==null) return; + if (!validName(name)){ + return; + } + this.rawType = GenericName.build(name); + } + + public Expression getParent() { + return this.parent; + } + + public void setText(String text) { + this.text = text; + } + + + public boolean isCall() { + return isCall; + } + + public boolean isSet() { + return isSet; + } + + public void setSet(boolean isSet) { + this.isSet = isSet; + } + + public boolean isDot() { + return isDot; + } + + public void setDot(boolean isDot) { + this.isDot = isDot; + } + + public boolean isLogic() { + return isLogic; + } + + public void setLogic(boolean isLogic) { + this.isLogic = isLogic; + } + + public boolean isCreate() { + return isCreate; + } + + public void setCreate(boolean isCreate) { + this.isCreate = isCreate; + } + + public boolean isCast() { + return isCast; + } + + public void setCast(boolean isCast) { + this.isCast = isCast; + } + + public boolean isThrow() { + return isThrow; + } + + public void setThrow(boolean isThrow) { + this.isThrow = isThrow; + } + + public boolean isStatement() { + return isStatement; + } + + public void setStatement(boolean isStatement) { + this.isStatement = isStatement; + } + + public void setCall(boolean isCall) { + this.isCall = isCall; + } + + public void disableDriveTypeFromChild() { + deriveTypeFromChild = false ; + } + + public Entity getReferredEntity() { + return referredEntity; + } + + public TypeEntity getType() { + return type; + } + + + private boolean validName(String name) { + if (name==null) return false; + if (name.toLowerCase().equals("")) return true; + if (name.toLowerCase().equals("")) return true; + return true; + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("[").append(text).append("]").append("|") + .append("rawType:").append(rawType).append("|") + .append("identifier:").append(identifier).append("|") + .append("prop:").append(isDot?"[dot]":"") + .append(isSet?"[set]":"") + .append(isLogic?"[bool]":"") + .append(isCall?"[call]":"") + .append(isCreate?"[new]":"") + .append(isThrow?"[throw]":"").append("|") + .append("parent:").append(parent==null?"nil":parent.text).append("|") + .append("type:").append(type).append("|"); + return s.toString(); + } + + public void setLine(int lineNumber) { + this.location.setLine(lineNumber); + } + + public Location getLocation() { + return location; + } +} \ No newline at end of file diff --git a/src/main/java/com/educoder/bridge/tmp/ExpressionUsage.java b/src/main/java/com/educoder/bridge/tmp/ExpressionUsage.java new file mode 100644 index 0000000..2d9762a --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/ExpressionUsage.java @@ -0,0 +1,298 @@ +package depends.extractor.python.union; + +import depends.entity.*; +import depends.entity.repo.IdGenerator; +import depends.extractor.HandlerContext; +import depends.extractor.python.PythonHandlerContext; +import depends.extractor.python.PythonParser.*; +import depends.extractor.python.PythonParserBaseVisitor; +import depends.relations.IBindingResolver; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.RuleContext; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +public class ExpressionUsage { + HandlerContext context; + IdGenerator idGenerator; + private boolean exprStarted=false; + private IBindingResolver bindingResolver; + public ExpressionUsage(PythonHandlerContext context, IdGenerator idGenerator, IBindingResolver bindingResolver) { + this.context = context; + this.idGenerator = idGenerator; + this.bindingResolver = bindingResolver; + } + + /** + * Auto deduce variable type from assignment. for example: c = new C() then c is + * type of C + * + */ + private void deduceVarTypeInCaseOfAssignment(Expr_stmtContext expr, Expression expression) { + List names = getName(expr.testlist_star_expr()); + // TODO: should handle list properly; + String varName = null; + if (names.size() == 1) + varName = names.get(0); + if (varName == null) + return; + VarEntity var = context.lastContainer().lookupVarLocally(varName); + if (var != null) { + expression.addDeducedTypeVar(var); + } + } + + private List getName(Testlist_star_exprContext testlist_star_expr) { + List names = new ArrayList<>(); + testlist_star_expr.accept(new NameCollector(names)); + return names; + } + + public void foundExpression(ParserRuleContext ctx) { + if (!isStartOfContainerRule(ctx)) { + return ; + } + if (context.lastContainer().containsExpression(ctx)) return; + if (ctx.getParent() instanceof TrailerContext) return; + + Expression parent = findParentInStack(ctx); + Expression expression = parent; + + if (ctx.getParent().getChildCount()==1 && parent!=null) { + //如果就是自己,则无需创建新的Expression + }else { + /* create expression and link it with parent*/ + expression = new Expression(idGenerator.generateId()); + expression.setLine(ctx.getStart().getLine()); + + expression.setText(ctx.getText()); + context.lastContainer().addExpression(ctx,expression); + expression.setParent(parent); + } + + + if (ctx instanceof Expr_stmtContext) { + Expr_stmtContext exprAssign = (Expr_stmtContext)ctx; + if (exprAssign.assign_part()!=null) { + expression.setSet(true); + expression.setIdentifier(exprAssign.testlist_star_expr().getText()); + if (isValidIdentifier(expression.getIdentifier())) { + makeSureVarExist(expression.getIdentifier(), ctx); + } + deduceVarTypeInCaseOfAssignment((Expr_stmtContext)ctx,expression); + } + } + if (ctx instanceof Raise_stmtContext) { + expression.setThrow (true); + } + if (ctx instanceof Return_stmtContext) { + deduceReturnTypeInCaseOfReturn((Return_stmtContext)ctx,expression); + } + if (ctx instanceof ExprContext) { + processExprContext((ExprContext)ctx, expression); + } + + } + + private void deduceReturnTypeInCaseOfReturn(Return_stmtContext ctx, Expression expression) { + FunctionEntity currentFunction = context.currentFunction(); + if (currentFunction == null) + return; + expression.addDeducedTypeFunction(currentFunction); + } + + + private void makeSureVarExist(GenericName identifier, ParserRuleContext ctx) { + if (null==context.foundEntityWithName(identifier)) { + VarEntity var = context.foundVarDefinition(context.lastContainer(), identifier.getName(),ctx.getStart().getLine()); + var.setLine(ctx.getStart().getLine()); + + } + } + + private boolean isValidIdentifier(GenericName identifier) { + Pattern p = Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*"); + Matcher m = p.matcher(identifier.getName()); + return m.matches(); + } + + + + private void processExprContext(ExprContext exprCtx, Expression expression) { + //func_call, member_access, subscript member, and atom + Expression lastExpression = null; + if (exprCtx.atom()!=null) { + //atom + Expression atomExpr = new Expression(idGenerator.generateId()); + atomExpr.setLine(exprCtx.atom().getStart().getLine()); + atomExpr.setParent(expression); + atomExpr.setText(exprCtx.atom().getText()); + atomExpr.setIdentifier(exprCtx.atom().getText()); + context.lastContainer().addExpression(exprCtx.atom(),atomExpr); + processAtom(exprCtx.atom(),atomExpr); + lastExpression = atomExpr; + if (exprCtx.trailer()==null || exprCtx.trailer().size()==0) { + //do nothing; it is just an id; + }else { + for (TrailerContext trailer:exprCtx.trailer()) { + if (trailer.name()!=null) { + Expression trailerExpr = new Expression(idGenerator.generateId()); + trailerExpr.setLine(trailer.getStart().getLine()); + trailerExpr.setText(trailer.getText()); + context.lastContainer().addExpression(trailer,trailerExpr); + trailerExpr.setParent(expression); + + //doted name = member access or method call + trailerExpr.setDot(true);; + trailerExpr.setIdentifier(trailer.name().getText()); + if (trailer.arguments()!=null) { + if (trailer.arguments().OPEN_PAREN()!=null) { + foundCallStyleExpressionWithDot(trailerExpr,lastExpression.getIdentifier(), trailer); + }else { + //subscript list, do nothing + } + } + lastExpression.setParent(trailerExpr); + lastExpression = trailerExpr; + }else { + //direct call, or direct data access + if (trailer.arguments()!=null) { + if (trailer.arguments().OPEN_PAREN()!=null) { + foundCallStyleExpressionWithoutDot(lastExpression, trailer.arguments()); + }else { + //subscript list, do nothing + } + } + } + } + } + }else { +/** expr + | expr op=POWER expr + | op=(ADD | MINUS | NOT_OP) expr + | expr op=(STAR | DIV | MOD | IDIV | AT) expr + | expr op=(ADD | MINUS) expr + | expr op=(LEFT_SHIFT | RIGHT_SHIFT) expr + | expr op=AND_OP expr + | expr op=XOR expr + | expr op=OR_OP expr + ;*/ + + } + } + + + private boolean isStartOfContainerRule(ParserRuleContext ctx) { + if (this.exprStarted) return true; + return ctx instanceof ExprContext || + ctx instanceof Expr_stmtContext || + ctx instanceof Del_stmtContext || + ctx instanceof Return_stmtContext || + ctx instanceof Raise_stmtContext || + ctx instanceof Raise_stmtContext || + ctx instanceof Yield_stmtContext || + ctx instanceof Assert_stmtContext; + } + + + + private void foundCallStyleExpressionWithDot(Expression theExpression, GenericName varName, ParserRuleContext ctx) { + GenericName funcName = theExpression.getIdentifier(); + Entity prefixEntity = context.foundEntityWithName(varName); + if (prefixEntity instanceof VarEntity) { + ((VarEntity) prefixEntity).addFunctionCall(funcName); + } + Entity typeEntity = context.foundEntityWithName(funcName); + if (typeEntity instanceof TypeEntity && typeEntity.getId() > 0) { + theExpression.setCreate(true); + theExpression.setType(typeEntity.getType(), typeEntity, bindingResolver); + theExpression.setRawType(typeEntity.getRawName()); + return; + } + theExpression.setCall(true); + } + + + + private void foundCallStyleExpressionWithoutDot(Expression theExpression, ParserRuleContext ctx) { + GenericName funcName = theExpression.getIdentifier(); + Entity typeEntity = context.foundEntityWithName(funcName); + if (typeEntity instanceof TypeEntity && typeEntity.getId() > 0) { + theExpression.getParent().setCreate(true); + theExpression.setType(typeEntity.getType(), typeEntity, bindingResolver); + theExpression.getParent().setRawType(typeEntity.getRawName()); + return; + } + theExpression.setCall(true); + } + + + private void processAtom(AtomContext atom, Expression expression) { + if (atom.name()!=null) { + expression.setIdentifier(atom.getText()); + return; + } + if (atom.STRING()!=null + || atom.NONE()!=null + || atom.number()!=null) { + expression.setRawType(""); + expression.setIdentifier(""); + return; + } + + if (atom.EXEC()!=null + || atom.PRINT()!=null + || atom.ELLIPSIS()!=null) { + return; + } +// : OPEN_PAREN (yield_expr | testlist_comp)? CLOSE_PAREN +// | OPEN_BRACKET testlist_comp? CLOSE_BRACKET +// | OPEN_BRACE dictorsetmaker? CLOSE_BRACE +// | REVERSE_QUOTE testlist COMMA? REVERSE_QUOTE + return; + } + + + + + + private Expression findParentInStack(RuleContext ctx) { + if (ctx==null) return null; + if (ctx.parent==null) return null; + if (context.lastContainer()==null) { + return null; + } + if (context.lastContainer().expressions().containsKey(ctx.parent)) + return context.lastContainer().expressions().get(ctx.parent); + return findParentInStack(ctx.parent); + } + + + + public void startExpr() { + this.exprStarted = true; + } + + + + public void stopExpr() { + this.exprStarted = false; + } + + +} + +class NameCollector extends PythonParserBaseVisitor{ + private List names; + NameCollector(List names){ + this.names = names; + } + @Override + public Void visitAtom(AtomContext ctx) { + if (ctx.name()!=null) + names.add(ctx.name().getText()); + return super.visitAtom(ctx); + } +} \ No newline at end of file diff --git a/src/main/java/com/educoder/bridge/tmp/ExtractText.java b/src/main/java/com/educoder/bridge/tmp/ExtractText.java new file mode 100644 index 0000000..4ba8951 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/ExtractText.java @@ -0,0 +1,129 @@ +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.lang.StringBuffer; + + +// https://svn.apache.org/repos/asf/poi/trunk/src/ooxml/testcases/org/apache/poi/extractor/TestExtractorFactory.java +import org.apache.poi.POIOLE2TextExtractor; +import org.apache.poi.POITextExtractor; +//import org.apache.poi.POIDataSamples; +//import org.apache.poi.extractor.*; +import org.apache.poi.extractor.ExtractorFactory; +import org.apache.poi.hdgf.extractor.VisioTextExtractor; +import org.apache.poi.hpbf.extractor.PublisherTextExtractor; +import org.apache.poi.hslf.extractor.PowerPointExtractor; +import org.apache.poi.hsmf.extractor.OutlookTextExtactor; +import org.apache.poi.hssf.extractor.EventBasedExcelExtractor; +import org.apache.poi.hssf.extractor.ExcelExtractor; +import org.apache.poi.hwpf.extractor.Word6Extractor; +import org.apache.poi.hwpf.extractor.WordExtractor; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.xslf.extractor.XSLFPowerPointExtractor; +import org.apache.poi.xssf.extractor.XSSFEventBasedExcelExtractor; +import org.apache.poi.xssf.extractor.XSSFExcelExtractor; +import org.apache.poi.xwpf.extractor.XWPFWordExtractor; + +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.poifs.filesystem.OfficeXmlFileException; + +import org.apache.poi.xslf.usermodel.XMLSlideShow; // pptx 2007, http://poi.apache.org/apidocs/org/apache/poi/xslf/ +import org.apache.poi.xwpf.usermodel.XWPFDocument; // docx 2007, http://poi.apache.org/apidocs/org/apache/poi/xwpf/ +import org.apache.poi.xssf.usermodel.XSSFWorkbook; // xlsx 2007, http://poi.apache.org/apidocs/org/apache/poi/xssf/ + +class ExtractText +{ + public static String file(String path) { + try { return pptx(new FileInputStream(path)); } catch(Exception e) { } + try { return docx(new FileInputStream(path)); } catch(Exception e) { } + try { return xlsx(new FileInputStream(path)); } catch(Exception e) { } + return ""; + } + public static String pptx(InputStream in) throws Exception { + XSLFPowerPointExtractor o = new XSLFPowerPointExtractor( new XMLSlideShow(in) ); + o.setSlidesByDefault(true); + o.setNotesByDefault(true); + return o.getText(); + } + public static String docx(InputStream in) throws Exception { + XWPFWordExtractor o = new XWPFWordExtractor(new XWPFDocument(in)); + return o.getText(); + } + public static String xlsx(InputStream in) throws Exception { + XSSFExcelExtractor o = new XSSFExcelExtractor(new XSSFWorkbook(in)); + return o.getText(); + } + public static void main(String argv[]) { + try { + InputStream in = null; + if (argv.length < 1) + in = System.in; + else + in = new FileInputStream(argv[0]); + StringBuffer output = new StringBuffer(); + POITextExtractor textExtractor = ExtractorFactory.createExtractor(in); + + if (textExtractor instanceof ExcelExtractor) // xls, excel 97-2003 + { + ExcelExtractor extractor = (ExcelExtractor) textExtractor; + output.append(extractor.getText()); + } + else if (textExtractor instanceof XSSFExcelExtractor) // xlsx, excel 2007 + { + XSSFExcelExtractor extractor = (XSSFExcelExtractor) textExtractor; + output.append(extractor.getText()); + } + else if (textExtractor instanceof Word6Extractor) // doc, word 95 + { + Word6Extractor extractor = (Word6Extractor) textExtractor; + output.append(extractor.getText()); + } + else if (textExtractor instanceof WordExtractor) // doc, word 97-2003 + { + WordExtractor extractor = (WordExtractor) textExtractor; + output.append(extractor.getText()); + } + else if (textExtractor instanceof XWPFWordExtractor) // docx, word 2007 + { + XWPFWordExtractor extractor = (XWPFWordExtractor) textExtractor; + output.append(extractor.getText()); + } + else if (textExtractor instanceof PowerPointExtractor) // ppt, ppt 97-2003 + { + PowerPointExtractor extractor = (PowerPointExtractor) textExtractor; + output.append(extractor.getText()); + output.append(extractor.getNotes()); + } + else if (textExtractor instanceof XSLFPowerPointExtractor ) // pptx, powerpoint 2007 + { + XSLFPowerPointExtractor extractor = (XSLFPowerPointExtractor) textExtractor; + extractor.setSlidesByDefault(true); + extractor.setNotesByDefault(true); + output.append(extractor.getText()); + } + else if (textExtractor instanceof VisioTextExtractor) // vsd, visio + { + VisioTextExtractor extractor = (VisioTextExtractor) textExtractor; + output.append(extractor.getText()); + } + else if (textExtractor instanceof PublisherTextExtractor) // pub, publisher + { + PublisherTextExtractor extractor = (PublisherTextExtractor) textExtractor; + output.append(extractor.getText()); + } + else if (textExtractor instanceof OutlookTextExtactor) // msg, outlook + { + OutlookTextExtactor extractor = (OutlookTextExtactor) textExtractor; + output.append(extractor.getText()); + } + System.out.println(output.toString().replaceAll( "[\n\t\r ]+"," ")); + } + catch (Exception e) + { + // TODO Auto-generated catch block + //e.printStackTrace(); + //System.out.println(e); + } + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/FileEntity.java b/src/main/java/com/educoder/bridge/tmp/FileEntity.java new file mode 100644 index 0000000..8765a3a --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/FileEntity.java @@ -0,0 +1,169 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.entity; + +import depends.importtypes.Import; +import depends.relations.IBindingResolver; + +import java.util.*; + +public class FileEntity extends TypeEntity { + private List importedNames = new ArrayList<>(); + private boolean isInProjectScope = false; + private Collection importedRelationEntities = new ArrayList<>(); + private Collection importedFiles = new ArrayList<>(); + private Collection importedTypes = new ArrayList<>(); + private List declaredTypes = new ArrayList<>(); + private ImportedFileCollector importedFileCollector = null; + public FileEntity() {} + + public FileEntity(String fullName, int fileId, boolean isInProjectScope) { + super(GenericName.build(fullName), null, fileId); + setQualifiedName(fullName); + this.isInProjectScope = isInProjectScope; + } + + public FileEntity(String fullName, int fileId) { + this(fullName, fileId, true); + } + + public void addImport(Import imported) { + if (!importedNames.contains(imported)) + importedNames.add(imported); + } + + /** + * To match the imported name by suffix + * for example: + * import a.b.ClassX; + * the b.ClassX, ClassX , a.b.classX should be matched + * @param lastName + * @return + */ + public String importedSuffixMatch(String lastName) { + if (!lastName.startsWith(".")) + lastName = "." + lastName; + for (Entity imported : this.importedTypes) { + String name = imported.getQualifiedName(true); + if (!name.startsWith(".")) + name = "." + name; + if (imported.getQualifiedName(true).endsWith(lastName)) + return imported.getQualifiedName(true); + } + return null; + } + + + @Override + public String getQualifiedName(boolean overrideFileWithPackage) { + if (!overrideFileWithPackage) { + return super.getQualifiedName(); + } + if (this.getParent() == null) { + return ""; + } + if (this.getParent() instanceof PackageEntity) + return this.getParent().getQualifiedName(); + else + return super.getQualifiedName(); + } + + @Override + public void inferLocalLevelEntities(IBindingResolver bindingResolver) { + this.importedRelationEntities = bindingResolver.getImportedRelationEntities(importedNames); + this.importedTypes = bindingResolver.getImportedTypes(importedNames,this); + this.importedFiles = bindingResolver.getImportedFiles(importedNames); + + super.inferLocalLevelEntities(bindingResolver); + } + + public boolean isInProjectScope() { + return isInProjectScope; + } + + public void setInProjectScope(boolean isInProjectScope) { + this.isInProjectScope = isInProjectScope; + } + + public Collection getImportedRelationEntities() { + return importedRelationEntities; + } + + public Collection getImportedFiles() { + return importedFiles; + } + + public Collection getImportedTypes() { + return importedTypes; + } + + public List getDeclaredTypes() { + return this.declaredTypes; + } + + public void addType(TypeEntity currentTypeEntity) { + this.declaredTypes.add(currentTypeEntity); + } + + public Set getImportedFilesInAllLevel() { + if (importedFileCollector==null) + importedFileCollector = new ImportedFileCollector(this); + + return importedFileCollector.getFiles(); + } + + public List getImportedNames() { + return importedNames; + } + + public void cacheAllExpressions() { + this.cacheChildExpressions(); + } + + + @Override + public Entity getByName(String name, HashSet searched) { + Entity entity = super.getByName(name, searched); + if (entity!=null) return entity; + for (TypeEntity type:getDeclaredTypes()) { + if (type.getRawName().getName().equals(name)|| + suffixMatch(name,type.getQualifiedName())) { + return type; + } + } + return null; + } + + private boolean suffixMatch(String name, String qualifiedName) { + if (qualifiedName.contains(".")) { + if (!name.startsWith(".")) name = "." +name; + return qualifiedName.endsWith(name); + } + else { + return qualifiedName.equals(name); + } + } + +} diff --git a/src/main/java/com/educoder/bridge/tmp/FormalParameterListContextHelper.java b/src/main/java/com/educoder/bridge/tmp/FormalParameterListContextHelper.java new file mode 100644 index 0000000..4c24ca9 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/FormalParameterListContextHelper.java @@ -0,0 +1,99 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.extractor.java.context; + +import java.util.ArrayList; +import java.util.List; + +import org.antlr.v4.runtime.tree.TerminalNode; + +import depends.entity.FunctionEntity; +import depends.entity.GenericName; +import depends.entity.VarEntity; +import depends.entity.repo.IdGenerator; +import depends.extractor.java.JavaParser.FormalParameterContext; +import depends.extractor.java.JavaParser.FormalParameterListContext; +import depends.extractor.java.JavaParser.FormalParametersContext; +import depends.extractor.java.JavaParser.LastFormalParameterContext; +import depends.extractor.java.JavaParser.TypeTypeContext; +import depends.extractor.java.JavaParser.VariableModifierContext; + +public class FormalParameterListContextHelper { + + FormalParameterListContext context; + private IdGenerator idGenerator; + private List annotations; + private FunctionEntity container; + + public FormalParameterListContextHelper(FormalParameterListContext formalParameterListContext,FunctionEntity container, IdGenerator idGenerator) { + this.context = formalParameterListContext; + this.container = container; + annotations = new ArrayList<>(); + this.idGenerator = idGenerator; + if (context!=null) + extractParameterTypeList(); + } + + public FormalParameterListContextHelper(FormalParametersContext formalParameters,FunctionEntity container, IdGenerator idGenerator) { + this(formalParameters.formalParameterList(),container,idGenerator); + } + + + + public void extractParameterTypeList() { + if (context != null) { + if (context.formalParameter() != null) { + for (FormalParameterContext p : context.formalParameter()) { + foundParameterDefintion(p.typeType(),p.variableDeclaratorId().IDENTIFIER(),p.variableModifier()); + } + if (context.lastFormalParameter()!=null) { + LastFormalParameterContext p = context.lastFormalParameter(); + foundParameterDefintion(p.typeType(),p.variableDeclaratorId().IDENTIFIER(),p.variableModifier()); + } + } + } + return; + } + + private void foundParameterDefintion(TypeTypeContext typeType, TerminalNode identifier, List variableModifier) { + GenericName type = GenericName.build(ClassTypeContextHelper.getClassName(typeType)); + GenericName varName = GenericName.build(identifier.getText()); + VarEntity varEntity = new VarEntity(varName,type,container,idGenerator.generateId()); + container.addParameter(varEntity); + + for ( VariableModifierContext modifier:variableModifier) { + if (modifier.annotation()!=null) { + this.annotations.add(QualitiedNameContextHelper.getName(modifier.annotation().qualifiedName())); + } + } + + } + + public List getAnnotations() { + return annotations; + } + + +} diff --git a/src/main/java/com/educoder/bridge/tmp/FunctionEntity.java b/src/main/java/com/educoder/bridge/tmp/FunctionEntity.java new file mode 100644 index 0000000..91f7e7a --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/FunctionEntity.java @@ -0,0 +1,149 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.entity; + +import depends.relations.IBindingResolver; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class FunctionEntity extends ContainerEntity{ + private List returnTypeIdentifiers = new ArrayList<>(); + Collection parameters; + Collection throwTypesIdentifiers = new ArrayList<>(); + private Collection returnTypes = new ArrayList<>(); + private Collection throwTypes = new ArrayList<>(); + public FunctionEntity() { + this.parameters = new ArrayList<>(); + } + public FunctionEntity(GenericName simpleName, Entity parent, Integer id, GenericName returnType) { + super(simpleName, parent,id); + this.returnTypes = new ArrayList<>(); + returnTypeIdentifiers = new ArrayList<>(); + this.parameters = new ArrayList<>(); + throwTypesIdentifiers = new ArrayList<>(); + addReturnType(returnType); + } + public Collection getReturnTypes() { + return returnTypes; + } + + @Override + public TypeEntity getType() { + if (returnTypes.size()>0){ + Object type = returnTypes.iterator().next(); + if (type instanceof TypeEntity) + return (TypeEntity)type; + } + return null; + } + + public void addReturnType(GenericName returnType) { + if (returnType==null) return; + this.returnTypeIdentifiers.add(returnType); + } + + public void addReturnType(TypeEntity returnType) { + if (returnType==null) return; + if (!this.returnTypeIdentifiers.contains(returnType.rawName)){ + this.returnTypeIdentifiers.add(returnType.rawName); + this.returnTypes.add(returnType); + } + } + + public void addThrowTypes(List throwedType) { + throwTypesIdentifiers.addAll(throwedType); + } + + @Override + public void inferLocalLevelEntities(IBindingResolver bindingResolver) { + for (VarEntity param:parameters) { + param.fillCandidateTypes(bindingResolver); + param.inferLocalLevelEntities(bindingResolver); + } + if (returnTypes.size() typeEntities = typeParametersToEntities(bindingResolver, returnTypeName); + this.appendTypeParameters(typeEntities); + } + } + if (throwTypes.size() typeParametersToEntities(IBindingResolver bindingResolver, GenericName name) { + ArrayList r = new ArrayList<>(); + for (GenericName typeParameter:name.getArguments()) { + toEntityList(bindingResolver, r,typeParameter); + } + return r; + } + + + public Collection getParameters() { + return parameters; + } + public Collection getThrowTypes() { + return throwTypes; + } + @Override + public Entity lookupVarInVisibleScope(GenericName varName) { + for (VarEntity param:parameters) { + if (varName.equals(param.getRawName())) { + return param; + } + } + return super.lookupVarInVisibleScope(varName); + } + public void addParameter(VarEntity var) { + this.parameters.add(var); + } + @Override + public String getDisplayName() { + FileEntity f = (FileEntity) this.getAncestorOfType(FileEntity.class); + return f.getRawName()+"("+this.getQualifiedName()+")"; + } + @Override + public VarEntity lookupVarLocally(GenericName varName) { + for (VarEntity var:this.parameters) { + if (var.getRawName().equals(varName)) + return var; + } + return super.lookupVarLocally(varName); + } + + public void linkReturnToLastExpression() { + if (expressionList()==null) return; + for (int i = expressionList().size() - 1; i >= 0; i--) { + Expression expr = expressionList().get(i); + if (expr.isStatement()) + expr.addDeducedTypeFunction(this); + } + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/GenericName.java b/src/main/java/com/educoder/bridge/tmp/GenericName.java new file mode 100644 index 0000000..1645f52 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/GenericName.java @@ -0,0 +1,117 @@ +package depends.entity; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class GenericName implements Serializable{ + private static final long serialVersionUID = 1L; + private char[] name; + List arguments; + public GenericName(String name) { + this.name = name.toCharArray(); + } + public GenericName(String name, List arguments) { + this.name = name.toCharArray(); + this.arguments = arguments; + } + public boolean contains(String rawType) { + if (new String(name).contains(rawType)) return true; + return false; + } + public String getName() { + return new String(name); + } + public List getArguments() { + if (arguments==null) return new ArrayList<>(); + return arguments; + } + + @Override + public String toString() { + return new String(name) + (getArguments().size()>0?"(" + arguments + ")":""); + } + + public GenericName replace(String from, String to) { + name = new String(name).replace(from, to).toCharArray(); + for (GenericName arg:getArguments()) { + arg.replace(from, to); + } + return this; + } + + public boolean startsWith(String prefix) { + if (name==null) return false; + return new String(name).startsWith(prefix); + } + public String uniqName() { + if (getArguments().size()==0) return new String(name); + StringBuffer sb = new StringBuffer(); + sb.append(name); + if (getArguments().size()>0) { + for (GenericName arg:getArguments()) { + sb.append("__").append(arg.uniqName()).append("__"); + } + } + return sb.toString(); + } + public GenericName substring(int start) { + return new GenericName(new String(this.name).substring(start)); + } + public boolean isNull() { + return name==null; + } + public static GenericName build(String name) { + if (name==null) return null; + return new GenericName(name); + } + public static GenericName build(String name, List arguments) { + return new GenericName(name,arguments); + } + public boolean find(GenericName rawType) { + //if (this.equals(rawType)) return true; + for (GenericName subType:this.getArguments()) { + if (subType.equals(rawType)) return true; + boolean found = subType.find(rawType); + if (found) return true; + } + return false; + } + public void appendArguments(List parameters) { + if (this.arguments==null) this.arguments = new ArrayList<>(); + this.arguments.addAll(parameters); + } + public void appendArguments(GenericName parameter) { + if (this.arguments==null) this.arguments = new ArrayList<>(); + this.arguments.add(parameter); + } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((arguments == null) ? 0 : arguments.hashCode()); + result = prime * result + Arrays.hashCode(name); + return result; + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + GenericName other = (GenericName) obj; + if (this.getArguments() == null) { + if (other.getArguments() != null) + return false; + } else if (!getArguments().equals(other.getArguments())) + return false; + if (!Arrays.equals(name, other.name)) + return false; + return true; + } + + +} diff --git a/src/main/java/com/educoder/bridge/tmp/GoImportLookupStrategy.java b/src/main/java/com/educoder/bridge/tmp/GoImportLookupStrategy.java new file mode 100644 index 0000000..cda04d7 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/GoImportLookupStrategy.java @@ -0,0 +1,103 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.extractor.golang; + +import depends.entity.Entity; +import depends.entity.FileEntity; +import depends.entity.PackageEntity; +import depends.entity.repo.EntityRepo; +import depends.extractor.UnsolvedBindings; +import depends.importtypes.Import; +import depends.relations.ImportLookupStrategy; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class GoImportLookupStrategy extends ImportLookupStrategy{ + public GoImportLookupStrategy(EntityRepo repo) { + super(repo); + } + + public Entity lookupImportedType(String name, FileEntity fileEntity) { + //Java Strategy + String importedString = fileEntity.importedSuffixMatch(name); + if (importedString==null) return null; + return repo.getEntity(importedString); + } + + + @Override + public List getImportedRelationEntities(List importedList) { + ArrayList result = new ArrayList<>(); + for (Import importedItem:importedList) { + Entity imported = repo.getEntity(importedItem.getContent()); + if (imported==null) continue; + if (imported instanceof PackageEntity) { + //ignore wildcard import relation + }else { + result.add(imported); + } + } + return result; + } + + @Override + public List getImportedTypes(List importedList, Set unsolvedBindings) { + ArrayList result = new ArrayList<>(); + for (Import importedItem:importedList) { + Entity imported = repo.getEntity(importedItem.getContent()); + if (imported==null) { + unsolvedBindings.add(new UnsolvedBindings(importedItem.getContent(),null)); + continue; + } + if (imported instanceof PackageEntity) { + //expand import of package to all classes under the package due to we dis-courage the behavior + for (Entity child:imported.getChildren()) { + if (child instanceof FileEntity) { + child.getChildren().forEach(item->result.add(item)); + }else { + result.add(child); + } + } + }else { + result.add(imported); + } + } + return result; + } + + @Override + public List getImportedFiles(List importedList) { + return new ArrayList(); + } + + + @Override + public boolean supportGlobalNameLookup() { + return true; + } + +} diff --git a/src/main/java/com/educoder/bridge/tmp/GoListener.java b/src/main/java/com/educoder/bridge/tmp/GoListener.java new file mode 100644 index 0000000..b8fafe7 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/GoListener.java @@ -0,0 +1,136 @@ +package depends.extractor.golang; + +import depends.entity.FunctionEntity; +import depends.entity.GenericName; +import depends.entity.VarEntity; +import depends.entity.repo.EntityRepo; +import depends.relations.IBindingResolver; +import org.antlr.v4.runtime.tree.TerminalNode; + +import java.util.ArrayList; + +public class GoListener extends GoParserBaseListener { + private final EntityRepo entityRepo; + GoHandlerContext context; + @Override + public void enterSourceFile(GoParser.SourceFileContext ctx) { + super.enterSourceFile(ctx); + } + + public GoListener(String fileFullPath, EntityRepo entityRepo, IBindingResolver bindingResolver) { + context = new GoHandlerContext(entityRepo, bindingResolver); + context.startFile(fileFullPath); + this.entityRepo = entityRepo; + } + + @Override + public void enterFunctionDecl(GoParser.FunctionDeclContext ctx) { + String funcName = ctx.IDENTIFIER().getText(); + context.foundMethodDeclarator(funcName,ctx.getStart().getLine()); + foundFuncSignature(ctx.signature()); + super.enterFunctionDecl(ctx); + } + + @Override + public void exitFunctionDecl(GoParser.FunctionDeclContext ctx) { + context.exitLastedEntity(); + super.exitFunctionDecl(ctx); + } + + @Override + public void enterPackageClause(GoParser.PackageClauseContext ctx) { + context.foundPackageDeclaration(ctx.IDENTIFIER().getText()); + super.enterPackageClause(ctx); + } + + private void foundFuncSignature(GoParser.SignatureContext signature) { + FunctionEntity func = (FunctionEntity) context.lastContainer(); + if (signature.parameters()!=null) { + for (GoParser.ParameterDeclContext param : signature.parameters().parameterDecl()) { + if (param.identifierList() != null) { + TypeDefHelper typeDefHelper = new TypeDefHelper(param.type_()); + for (TerminalNode id : param.identifierList().IDENTIFIER()) { + VarEntity varEntity = new VarEntity(GenericName.build(id.getText()), + GenericName.build(typeDefHelper.getTypeRefName()), context.lastContainer(), + entityRepo.generateId()); + func.addParameter(varEntity); + } + } else/* with ... parameters*/ { + + } + } + } + if (signature.result()!=null){ + if(signature.result().parameters()!=null){ + for (GoParser.ParameterDeclContext paramDecl:signature.result().parameters().parameterDecl()){ + TypeDefHelper typeDefHelper = new TypeDefHelper(paramDecl.type_()); + if (typeDefHelper.isTypeRef()) { + func.addReturnType(GenericName.build(typeDefHelper.getTypeRefName())); + }else{ + System.err.println("TODO: unsupport return type"); + } + } + } + if (signature.result().type_()!=null){ + TypeDefHelper typeDefHelper = new TypeDefHelper(signature.result().type_()); + if (typeDefHelper.isTypeRef()) { + func.addReturnType(GenericName.build(typeDefHelper.getTypeRefName())); + }else{ + System.err.println("TODO: unsupport return type"); + } + } + System.err.println(signature.result().getText()); + } + } + + @Override + public void enterTypeSpec(GoParser.TypeSpecContext ctx) { + TypeSpecHelper specHelper = new TypeSpecHelper(ctx); + if (specHelper.getTypeDefHelper().isTypeRef()){ + context.foundNewAlias(specHelper.getIdentifier(),specHelper.getTypeDefHelper().getTypeRefName()); + }else if (specHelper.getTypeDefHelper().isStruct()){ + context.foundNewType(specHelper.getIdentifier(),ctx.getStart().getLine()); + } + super.enterTypeSpec(ctx); + } + + @Override + public void exitTypeSpec(GoParser.TypeSpecContext ctx) { + TypeSpecHelper specHelper = new TypeSpecHelper(ctx); + if (specHelper.getTypeDefHelper().isStruct()){ + context.exitLastedEntity(); + } + super.exitTypeSpec(ctx); + } + +// fieldDecl +// : ({noTerminatorBetween(2)}? identifierList type_ | anonymousField) string_? +// ; + + @Override + public void enterFieldDecl(GoParser.FieldDeclContext ctx) { + FieldDeclHelper fieldDeclHelper = new FieldDeclHelper(ctx); + String typeName = fieldDeclHelper.getTypeName(); + + if (fieldDeclHelper.getIdentifiers()!=null){ + for (String id:fieldDeclHelper.getIdentifiers()){ + if (typeName==null){ + System.err.println("TODO: unsupport fieldDecl without type"); + } + context.foundVarDefinition(id, GenericName.build(typeName),new ArrayList<>(),ctx.getStart().getLine()); + } + } + + if (fieldDeclHelper.getAnonymousField()!=null){ + System.err.println("TODO: unsupport anonymousField"); + } + if (fieldDeclHelper.getString()!=null){ + System.err.println("TODO: unsupport field with string " + fieldDeclHelper.getString()); + } + super.enterFieldDecl(ctx); + } + + public void done() { + + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/GoParserBase.java b/src/main/java/com/educoder/bridge/tmp/GoParserBase.java new file mode 100644 index 0000000..9f3c099 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/GoParserBase.java @@ -0,0 +1,132 @@ +package depends.extractor.golang; + +import org.antlr.v4.runtime.*; + +import java.util.List; + +/** + * All parser methods that used in grammar (p, prev, notLineTerminator, etc.) + * should start with lower case char similar to parser rules. + */ +public abstract class GoParserBase extends Parser +{ + protected GoParserBase(TokenStream input) { + super(input); + } + + /** + * Returns {@code true} iff on the current index of the parser's + * token stream a token exists on the {@code HIDDEN} channel which + * either is a line terminator, or is a multi line comment that + * contains a line terminator. + * + * @return {@code true} iff on the current index of the parser's + * token stream a token exists on the {@code HIDDEN} channel which + * either is a line terminator, or is a multi line comment that + * contains a line terminator. + */ + protected boolean lineTerminatorAhead() { + // Get the token ahead of the current index. + int possibleIndexEosToken = this.getCurrentToken().getTokenIndex() - 1; + + if (possibleIndexEosToken == -1) + { + return true; + } + + Token ahead = _input.get(possibleIndexEosToken); + if (ahead.getChannel() != Lexer.HIDDEN) { + // We're only interested in tokens on the HIDDEN channel. + return false; + } + + if (ahead.getType() == GoLexer.TERMINATOR) { + // There is definitely a line terminator ahead. + return true; + } + + if (ahead.getType() == GoLexer.WS) { + // Get the token ahead of the current whitespaces. + possibleIndexEosToken = this.getCurrentToken().getTokenIndex() - 2; + ahead = _input.get(possibleIndexEosToken); + } + + // Get the token's text and type. + String text = ahead.getText(); + int type = ahead.getType(); + + // Check if the token is, or contains a line terminator. + return (type == GoLexer.COMMENT && (text.contains("\r") || text.contains("\n"))) || + (type == GoLexer.TERMINATOR); + } + + /** + * Returns {@code true} if no line terminator exists between the specified + * token offset and the prior one on the {@code HIDDEN} channel. + * + * @return {@code true} if no line terminator exists between the specified + * token offset and the prior one on the {@code HIDDEN} channel. + */ + protected boolean noTerminatorBetween(int tokenOffset) { + BufferedTokenStream stream = (BufferedTokenStream)_input; + List tokens = stream.getHiddenTokensToLeft(stream.LT(tokenOffset).getTokenIndex()); + + if (tokens == null) { + return true; + } + + for (Token token : tokens) { + if (token.getText().contains("\n")) + return false; + } + + return true; + } + + /** + * Returns {@code true} if no line terminator exists after any encounterd + * parameters beyond the specified token offset and the next on the + * {@code HIDDEN} channel. + * + * @return {@code true} if no line terminator exists after any encounterd + * parameters beyond the specified token offset and the next on the + * {@code HIDDEN} channel. + */ + protected boolean noTerminatorAfterParams(int tokenOffset) { + BufferedTokenStream stream = (BufferedTokenStream)_input; + int leftParams = 1; + int rightParams = 0; + int valueType; + + if (stream.LT(tokenOffset).getType() == GoLexer.L_PAREN) { + // Scan past parameters + while (leftParams != rightParams) { + tokenOffset++; + valueType = stream.LT(tokenOffset).getType(); + + if (valueType == GoLexer.L_PAREN){ + leftParams++; + } + else if (valueType == GoLexer.R_PAREN) { + rightParams++; + } + } + + tokenOffset++; + return noTerminatorBetween(tokenOffset); + } + + return true; + } + + protected boolean checkPreviousTokenText(String text) + { + BufferedTokenStream stream = (BufferedTokenStream)_input; + String prevTokenText = stream.LT(1).getText(); + + if (prevTokenText == null) + return false; + + return prevTokenText.equals(text); + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/HandlerContext.java b/src/main/java/com/educoder/bridge/tmp/HandlerContext.java new file mode 100644 index 0000000..a18547c --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/HandlerContext.java @@ -0,0 +1,333 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.extractor; + +import depends.entity.*; +import depends.entity.repo.EntityRepo; +import depends.entity.repo.IdGenerator; +import depends.importtypes.Import; +import depends.relations.IBindingResolver; + +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; +import java.util.stream.Collectors; + +public abstract class HandlerContext { + protected EntityRepo entityRepo; + protected IdGenerator idGenerator; + + protected FileEntity currentFileEntity; + protected IBindingResolver bindingResolver; + + + public HandlerContext(EntityRepo entityRepo, IBindingResolver bindingResolver) { + this.entityRepo = entityRepo; + this.idGenerator = entityRepo; + entityStack = new Stack(); + this.bindingResolver = bindingResolver; + } + + public FileEntity startFile(String fileName) { + currentFileEntity = new FileEntity(fileName, idGenerator.generateId(),true); + pushToStack(currentFileEntity); + addToRepo(currentFileEntity); + return currentFileEntity; + } + + public TypeEntity foundNewType(GenericName name, Integer startLine) { + TypeEntity currentTypeEntity = new TypeEntity(name, this.latestValidContainer(), + idGenerator.generateId()); + currentTypeEntity.setLine(startLine); + pushToStack(currentTypeEntity); + addToRepo(currentTypeEntity); + currentFileEntity.addType(currentTypeEntity); + return currentTypeEntity; + } + + /** + * Tell the context object that a new type founded. + * @param name + * @param startLine + * @return + */ + public TypeEntity foundNewType(String name, Integer startLine) { + return foundNewType(GenericName.build(name),startLine); + } + + public AliasEntity foundNewAlias(String aliasName, String originalName) { + if (aliasName.equals(originalName)) return null; //it is a tricky, we treat same name no different. + //indeed it is not perfect -> the right match should depends on no-bare format like "struct a" instead of "a" + AliasEntity currentTypeEntity = new AliasEntity(GenericName.build(aliasName), this.latestValidContainer(), + idGenerator.generateId(),GenericName.build(originalName) ); + addToRepo(currentTypeEntity); + return currentTypeEntity; + } + + public AliasEntity foundNewAlias(GenericName aliasName, Entity referToEntity) { + AliasEntity currentTypeEntity = new AliasEntity(aliasName, this.latestValidContainer(), + idGenerator.generateId(),aliasName); + currentTypeEntity.setReferToEntity(referToEntity); + addToRepo(currentTypeEntity); + return currentTypeEntity; + } + + /** + * Tell the context that a new method was found. + * Do not forget to tell the context leave the method when you finish + * the process of the method + * @param methodName + * @param returnType - if no return type information avaliable, keep it as null; + * @param throwedType - if no throwed type information avaliable, keep it as empty list; + * @return the new function enity + */ + public FunctionEntity foundMethodDeclarator(String methodName, String returnType, List throwedType, Integer startLine) { + FunctionEntity functionEntity = new FunctionEntity(GenericName.build(methodName), this.latestValidContainer(), + idGenerator.generateId(),GenericName.build(returnType)); + functionEntity.setLine(startLine); + addToRepo(functionEntity); + this.typeOrFileContainer().addFunction(functionEntity); + pushToStack(functionEntity); + functionEntity.addThrowTypes(throwedType.stream().map(item->GenericName.build(item)).collect(Collectors.toList())); + return functionEntity; + } + + public FunctionEntity foundMethodDeclarator(String methodName, Integer startLine) { + FunctionEntity functionEntity = new FunctionEntity(GenericName.build(methodName), this.latestValidContainer(), + idGenerator.generateId(),null); + functionEntity.setLine(startLine); + addToRepo(functionEntity); + this.typeOrFileContainer().addFunction(functionEntity); + pushToStack(functionEntity); + return functionEntity; + } + + + public FunctionEntity foundMethodDeclarator(ContainerEntity containerEntity, String methodName, Integer startLine) { + FunctionEntity functionEntity = new FunctionEntity(GenericName.build(methodName), containerEntity, + idGenerator.generateId(),null); + functionEntity.setLine(startLine); + addToRepo(functionEntity); + containerEntity.addFunction(functionEntity); + pushToStack(functionEntity); + functionEntity.addThrowTypes(new ArrayList<>()); + return functionEntity; + } + + public void foundNewImport(Import imported) { + currentFileEntity.addImport(imported); + } + + public TypeEntity currentType() { + for (int i = entityStack.size() - 1; i >= 0; i--) { + Entity t = entityStack.get(i); + if (t instanceof TypeEntity) + return (TypeEntity) t; + } + return null; + } + + public ContainerEntity typeOrFileContainer() { + for (int i = entityStack.size() - 1; i >= 0; i--) { + Entity t = entityStack.get(i); + if (t instanceof TypeEntity) + return (ContainerEntity) t; + if (t instanceof FileEntity) { + return (ContainerEntity)t; + } + } + return null; + } + + + public FunctionEntity currentFunction() { + for (int i = entityStack.size() - 1; i >= 0; i--) { + Entity t = entityStack.get(i); + if (t instanceof FunctionEntity) + return (FunctionEntity) t; + } + return null; + } + + public FileEntity currentFile() { + return currentFileEntity; + } + + public ContainerEntity globalScope() { + Entity global = entityRepo.getEntity(EntityRepo.GLOBAL_SCOPE_NAME); + if (global==null) { + global = new PackageEntity(EntityRepo.GLOBAL_SCOPE_NAME,idGenerator.generateId()); + addToRepo(global); + } + return (ContainerEntity)global; + } + + public Entity latestValidContainer() { + for (int i = entityStack.size() - 1; i >= 0; i--) { + Entity t = entityStack.get(i); + if (t instanceof FunctionEntity) + return t; + if (t instanceof TypeEntity) + return t; + if (t instanceof FileEntity) + return t; + } + return null; + } + + public ContainerEntity lastContainer() { + for (int i = entityStack.size() - 1; i >= 0; i--) { + Entity t = entityStack.get(i); + if (t instanceof ContainerEntity) + return (ContainerEntity) t; + } + return null; + } + + public void foundImplements(GenericName typeName) { + currentType().addImplements(typeName); + } + + public void foundExtends(String className) { + foundExtends(GenericName.build(className)); + } + + public void foundExtends(GenericName typeName) { + if (currentType()==null) { + System.out.println("error: type do not exist"); + return ; + } + currentType().addExtends(typeName); + } + + public void foundMixin(String name) { + foundMixin(GenericName.build(name)); + + } + + public void foundMixin(GenericName name) { + lastContainer().addMixin(name); + + } + + public void foundTypeParametes(GenericName typeName) { + lastContainer().addTypeParameter(typeName); + } + + + public List foundVarDefinitions(List varNames, String type, List typeArguments, Integer line) { + return varNames.stream().map(item->foundVarDefinition(item,GenericName.build(type),typeArguments,line)).collect(Collectors.toList()); + } + + public VarEntity foundVarDefinition(ContainerEntity container,String varName,Integer line) { + if (container==null) { + System.out.println("fallback to file container for var " + varName + " in file "+ currentFile().getRawName()); + container = currentFile(); + } + + VarEntity var = getVarInLocalFile(container,GenericName.build(varName)); + if (var!=null) return var; + var = new VarEntity(GenericName.build(varName), null, container, idGenerator.generateId()); + var.setLine(line); + container.addVar(var); + addToRepo(var); + + return var; + } + + public VarEntity foundGlobalVarDefinition(ContainerEntity container,String varName,Integer line) { + if (container==null) { + System.out.println("fallback to file container for var " + varName + " in file "+ currentFile().getRawName()); + container = currentFile(); + } + + VarEntity var = getVarInLocalFile(container,GenericName.build(varName)); + if (var!=null) return var; + var = new VarEntity(GenericName.build(varName), null, container, idGenerator.generateId()); + container.addVar(var); + var.setLine(line); + var.setQualifiedName(var.getRawName().toString()); + addToRepo(var); + return var; + } + + public VarEntity foundVarDefinition(String varName, GenericName type, List typeArguments,Integer line) { + VarEntity var = new VarEntity(GenericName.build(varName), type, lastContainer(), idGenerator.generateId()); + var.setLine(line); + var.addTypeParameter(typeArguments); + lastContainer().addVar(var); + addToRepo(var); + return var; + } + + public VarEntity addMethodParameter(String paramName) { + if (currentFunction()==null) return null; + VarEntity varEntity = new VarEntity(GenericName.build(paramName),null,currentFunction(),idGenerator.generateId()); + currentFunction().addParameter(varEntity); + addToRepo(varEntity); + return varEntity; + } + + public VarEntity foundEnumConstDefinition(String varName,Integer line) { + GenericName type = lastContainer().getRawName(); + return foundVarDefinition(varName,type,new ArrayList<>(),line); + } + + protected Stack entityStack = new Stack(); + + protected void pushToStack(Entity entity) { + entityStack.push(entity); + } + + + public void exitLastedEntity() { + //we never pop up the lastest one (FileEntity) + if (entityStack.size()>1) + entityStack.pop(); + } + + private VarEntity getVarInLocalFile(ContainerEntity container, GenericName varName) { + Entity entity = bindingResolver.resolveName(container, varName, false); + if (entity ==null ) return null; + Entity fileEntity = entity.getAncestorOfType(FileEntity.class); + if (fileEntity ==null ){ + //may not exist in fileEntity, for example global vars + }else{ + if (!fileEntity.equals(currentFileEntity)) return null; + if (entity instanceof VarEntity) return (VarEntity)entity; + } + return null; + } + + public Entity foundEntityWithName(GenericName rawName) { + return bindingResolver.resolveName(lastContainer(), rawName, true); + } + + public void addToRepo(Entity entity) { + entityRepo.add(entity); + } + + +} \ No newline at end of file diff --git a/src/main/java/com/educoder/bridge/tmp/InMemoryEntityRepo.java b/src/main/java/com/educoder/bridge/tmp/InMemoryEntityRepo.java new file mode 100644 index 0000000..435676b --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/InMemoryEntityRepo.java @@ -0,0 +1,135 @@ +package depends.entity.repo; + +import depends.entity.Entity; +import depends.entity.FileEntity; +import depends.entity.GenericName; +import depends.entity.MultiDeclareEntities; +import multilang.depends.util.file.FileUtil; + +import java.util.*; +import java.util.Map.Entry; + + +public class InMemoryEntityRepo extends SimpleIdGenerator implements EntityRepo { + + public class EntityMapIterator implements Iterator{ + + private Iterator> entryIterator; + + public EntityMapIterator(Set> entries) { + this.entryIterator = entries.iterator(); + } + @Override + public boolean hasNext() { + return entryIterator.hasNext(); + } + + @Override + public Entity next() { + return entryIterator.next().getValue(); + } + + } + + private Map allEntieisByName; + private Map allEntitiesById; + private List allFileEntitiesByOrder; + + public InMemoryEntityRepo() { + allEntieisByName = new TreeMap<>(); + allEntitiesById = new TreeMap<>(); + allFileEntitiesByOrder = new LinkedList<>(); + } + + @Override + public Entity getEntity(String entityName) { + return allEntieisByName.get(entityName); + } + + @Override + public Entity getEntity(Integer entityId) { + return allEntitiesById.get(entityId); + } + + @Override + public void add(Entity entity) { + allEntitiesById.put(entity.getId(), entity); + String name = entity.getRawName().uniqName(); + if (entity.getQualifiedName() != null && !(entity.getQualifiedName().isEmpty())) { + name = entity.getQualifiedName(); + } + if (allEntieisByName.containsKey(name)) { + Entity existedEntity = allEntieisByName.get(name); + if (existedEntity instanceof MultiDeclareEntities) { + ((MultiDeclareEntities) existedEntity).add(entity); + } else { + MultiDeclareEntities eMultiDeclare = new MultiDeclareEntities(existedEntity, this.generateId()); + eMultiDeclare.add(entity); + allEntieisByName.put(name, eMultiDeclare); + } + } else { + allEntieisByName.put(name, entity); + } + if (entity.getParent() != null) + Entity.setParent(entity, entity.getParent()); + } + + @Override + public Iterator entityIterator() { + return new EntityMapIterator(allEntitiesById.entrySet()); + } + + + @Override + public void update(Entity entity) { + } + + @Override + public Entity getEntity(GenericName rawName) { + return this.getEntity(rawName.uniqName()); + } + + @Override + public Collection getFileEntities() { + return allFileEntitiesByOrder; + } + + @Override + public Iterator sortedFileIterator() { + return allFileEntitiesByOrder.iterator(); + } + + @Override + public void clear() { + allEntieisByName.clear(); + allEntitiesById.clear(); + allFileEntitiesByOrder.clear(); + } + + @Override + public FileEntity getFileEntity(String fileFullPath) { + fileFullPath = FileUtil.uniqFilePath(fileFullPath); + Entity entity = this.getEntity(fileFullPath); + if (entity ==null) return null; + if (entity instanceof FileEntity) return (FileEntity) entity; + if (entity instanceof MultiDeclareEntities){ + MultiDeclareEntities multiDeclare = (MultiDeclareEntities) entity; + for (Entity theEntity: multiDeclare.getEntities()){ + if (theEntity instanceof FileEntity){ + return (FileEntity) theEntity; + } + } + } + return null; + } + + @Override + public void completeFile(String fileFullPath) { + FileEntity fileEntity = getFileEntity(fileFullPath); + // in case of parse error(throw exception), the file entity may not exists + if (fileEntity!=null) { + fileEntity.cacheAllExpressions(); + allFileEntitiesByOrder.add(fileEntity); + } + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/IncludeRelationTest.java b/src/main/java/com/educoder/bridge/tmp/IncludeRelationTest.java new file mode 100644 index 0000000..3356fb4 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/IncludeRelationTest.java @@ -0,0 +1,109 @@ +package depends.extractor.cpp; +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import depends.deptypes.DependencyType; +import depends.entity.AliasEntity; +import multilang.depends.util.file.FileUtil; + +public class IncludeRelationTest extends CppParserTest{ + @Before + public void setUp() { + super.init(); + } + + @Test + public void test_includefiles_should_be_imported_relations() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/cpp-code-examples/includesTest/EntryFile.cpp", + "./src/test/resources/cpp-code-examples/includesTest/LocalHeader.h", + "./src/test/resources/cpp-code-examples/includesTest/IndirectIncluded.h", + "./src/test/resources/cpp-code-examples/includesTest/RelativeInclude.h", + "./src/test/resources/cpp-code-examples/includesTest/path/Header.h", + }; + + for (String src:srcs) { + CppFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + File f = new File(srcs[0]); + assertEquals(3, entityRepo.getEntity(f.getCanonicalPath()).getRelations().size()); + } + + + @Test + public void test_could_found_files_in_include_path() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/cpp-code-examples/includesTest/EntryFileIncludePathTest.cpp", + "./src/test/resources/cpp-code-examples/includesTest/path/HeadersWithoutPath.h", + }; + + List includePaths = new ArrayList<>(); + includePaths.add("./src/test/resources/cpp-code-examples/includesTest/path/"); + for (String src:srcs) { + CppFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + File f = new File(srcs[0]); + assertEquals(1, entityRepo.getEntity(f.getCanonicalPath()).getRelations().size()); + } + + + @Test + public void test_type_t_should_be_treat_as_structure() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/cpp-code-examples/typedeftest2.cpp", + }; + + for (String src:srcs) { + CppFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + assertEquals("abc",((AliasEntity) entityRepo.getEntity("abc_t")).getOriginType().getRawName().uniqName()); + + } + + @Test + public void test_call_should_only_in_relations_with_include() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/cpp-code-examples/includeTest2/f0.cpp", + "./src/test/resources/cpp-code-examples/includeTest2/f_with_include.cpp", + "./src/test/resources/cpp-code-examples/includeTest2/f_without_include.cpp", + }; + + for (String src:srcs) { + CppFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + this.assertContainsRelation(this.entityRepo.getEntity("foo"), DependencyType.CALL, "bar"); + this.assertNotContainsRelation(this.entityRepo.getEntity("foo2"), DependencyType.CALL, "bar"); + } + + @Test + public void should_find_include_relation_in_conditional_macro_block() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/cpp-code-examples/includeTest3/inc_macro_test.c", + "./src/test/resources/cpp-code-examples/includeTest3/fx.h", + "./src/test/resources/cpp-code-examples/includeTest3/fy.h", + }; + + for (String src:srcs) { + CppFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + this.assertContainsRelation(this.entityRepo.getEntity(FileUtil.uniqFilePath(srcs[0])), DependencyType.IMPORT, FileUtil.uniqFilePath(srcs[1])); + this.assertContainsRelation(this.entityRepo.getEntity(FileUtil.uniqFilePath(srcs[0])), DependencyType.IMPORT, FileUtil.uniqFilePath(srcs[2])); + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/JDataBuilder.java b/src/main/java/com/educoder/bridge/tmp/JDataBuilder.java new file mode 100644 index 0000000..5e44240 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/JDataBuilder.java @@ -0,0 +1,88 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.format.json; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import depends.format.FileAttributes; +import depends.matrix.core.DependencyDetail; +import depends.matrix.core.DependencyMatrix; +import depends.matrix.core.DependencyPair; +import depends.matrix.core.DependencyValue; + +public class JDataBuilder { + public JDepObject build(DependencyMatrix dependencyMatrix, FileAttributes attribute) { + ArrayList files = dependencyMatrix.getNodes(); + Collection dependencyPairs = dependencyMatrix.getDependencyPairs(); + ArrayList cellObjects = buildCellObjects(dependencyPairs); // transform finalRes into cellObjects + + JDepObject depObject = new JDepObject(); + depObject.setVariables(files); + depObject.setName(attribute.getAttributeName()); + depObject.setSchemaVersion(attribute.getSchemaVersion()); + depObject.setCells(cellObjects); + + return depObject; + } + + private ArrayList buildCellObjects(Collection dependencyPairs) { + ArrayList cellObjects = new ArrayList(); + + for (DependencyPair dependencyPair : dependencyPairs) { + Map valueObject = buildValueObject(dependencyPair.getDependencies()); + List details = buildDetails(dependencyPair.getDependencies()); + JCellObject cellObject = new JCellObject(); + cellObject.setSrc(dependencyPair.getFrom()); + cellObject.setDest(dependencyPair.getTo()); + cellObject.setValues(valueObject); + cellObject.setDetails(details); + cellObjects.add(cellObject); + } + return cellObjects; + } + + private List buildDetails(Collection dependencies) { + List r = new ArrayList<>(); + for (DependencyValue dependency : dependencies) { + for (DependencyDetail detail:dependency.getDetails()) { + r.add(new DetailItem(detail.getSrc(),detail.getDest(),dependency.getType())); + } + } + if (r.size()==0) return null; + return r; + } + + private Map buildValueObject(Collection dependencies) { + Map valueObject = new HashMap(); + for (DependencyValue dependency : dependencies) { + valueObject.put(dependency.getType(), (float) dependency.getWeight()); + } + return valueObject; + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/JRubyVisitor.java b/src/main/java/com/educoder/bridge/tmp/JRubyVisitor.java new file mode 100644 index 0000000..052b755 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/JRubyVisitor.java @@ -0,0 +1,281 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.extractor.ruby.jruby; + +import depends.entity.*; +import depends.entity.repo.EntityRepo; +import depends.extractor.ParserCreator; +import depends.extractor.IncludedFileLocator; +import depends.extractor.ruby.RubyHandlerContext; +import depends.relations.IBindingResolver; +import org.jrubyparser.ast.*; +import org.jrubyparser.util.NoopVisitor; + +import java.util.ArrayList; +import java.util.Collection; + +public class JRubyVisitor extends NoopVisitor { + + private RubyHandlerContext context; + RubyParserHelper helper = RubyParserHelper.getInst(); + private ExpressionUsage expressionUsage; + + public JRubyVisitor(String fileFullPath, EntityRepo entityRepo, IncludedFileLocator includedFileLocator, + IBindingResolver bindingResolver, ParserCreator parserCreator) { + this.context = new RubyHandlerContext(entityRepo, includedFileLocator, bindingResolver, parserCreator); + expressionUsage = new ExpressionUsage(context, entityRepo, helper, bindingResolver); + context.startFile(fileFullPath); + + } + + @Override + public Object visitAliasNode(AliasNode node) { + context.foundNewAlias(node.getNewNameString(), node.getOldNameString()); + return super.visitAliasNode(node); + } + + @Override + public Object visitModuleNode(ModuleNode node) { + String name = helper.getName(node.getCPath()); + context.foundNamespace(name,node.getPosition().getStartLine()+1); + super.visitModuleNode(node); + context.exitLastedEntity(); + return null; + } + + @Override + public Object visitClassNode(ClassNode node) { + TypeEntity type = context.foundNewType(helper.getName(node.getCPath()),node.getPosition().getStartLine()+1); + Node superNode = node.getSuper(); + + if (superNode instanceof ConstNode || + superNode instanceof SymbolNode || + superNode instanceof Colon2ConstNode || + superNode instanceof Colon3Node) { + String superName = helper.getName(superNode); + context.foundExtends(superName); + }else{ + if (superNode != null) { + System.err.println("cannot support the super node style" + superNode.toString()); + } + } + + super.visitClassNode(node); + context.exitLastedEntity(); + return null; + } + + @Override + public Object visitRootNode(RootNode node) { + return super.visitRootNode(node); + } + + @Override + public Object visitFCallNode(FCallNode node) { + String fname = helper.getName(node); + Collection params = getParams(node); + context.processSpecialFuncCall(fname, params, node.getPosition().getStartLine()+1); + return super.visitFCallNode(node); + } + + private Collection getParams(IArgumentNode node) { + Node args = node.getArgs(); + Collection params = new ArrayList<>(); + if (args instanceof ArrayNode) { + ArrayNode argArray = (ArrayNode) args; + for (Node arg : argArray.childNodes()) { + if (arg instanceof StrNode) { + params.add(((StrNode) arg).getValue()); + } else if (arg instanceof ConstNode) { + params.add(((ConstNode) arg).getName()); + } + } + } + return params; + } + + @Override + public Object visitCallNode(CallNode node) { + String fname = helper.getName(node); + Collection params = getParams(node); + addCallToReceiverVar(node, fname); + context.processSpecialFuncCall(fname, params, node.getPosition().getStartLine()+1); + return super.visitCallNode(node); + } + + private void addCallToReceiverVar(CallNode node, String fname) { + if (helper.isCommonOperator(fname))return; + Node varNode = node.getReceiver(); + + GenericName varName = GenericName.build(helper.getName(varNode)); + if (varName==null) return; + Entity var = context.foundEntityWithName(varName); + if (var != null && var instanceof VarEntity) { + VarEntity varEntity = (VarEntity) var; + varEntity.addFunctionCall(GenericName.build(fname)); + } + } + + @Override + public Object visitUnaryCallNode(UnaryCallNode node) { + String fname = helper.getName(node); + Collection params = new ArrayList<>(); + context.processSpecialFuncCall(fname, params, node.getPosition().getStartLine()+1); + return super.visitUnaryCallNode(node); + } + + /** + * VCallNode is just a function call without parameter + */ + @Override + public Object visitVCallNode(VCallNode node) { + return super.visitVCallNode(node); + } + + @Override + public Object visitDefnNode(DefnNode node) { + FunctionEntity method = context.foundMethodDeclarator(node.getName(),node.getPosition().getStartLine()+1); + method.setLine(node.getPosition().getStartLine()+1); + + super.visitDefnNode(node); + context.exitLastedEntity(); + return null; + } + + @Override + public Object visitDefsNode(DefsNode node) { + boolean handled = false; + Node varNode = node.getReceiver(); + if (varNode instanceof SelfNode) { + //will be handled by context.foundMethodDeclarator(node.getName(), null, new ArrayList<>()); + } else if (varNode instanceof ConstNode) { + String className = ((INameNode) varNode).getName(); + Entity entity = context.foundEntityWithName(GenericName.build(className)); + if (entity != null && entity instanceof ContainerEntity) { + FunctionEntity method = context.foundMethodDeclarator(((ContainerEntity) entity), node.getName(),node.getPosition().getStartLine()+1); + method.setLine(node.getPosition().getStartLine()+1); + handled = true; + } + + } else if (varNode instanceof INameNode) { + String varName = ((INameNode) varNode).getName(); + Entity var = context.foundEntityWithName(GenericName.build(varName)); + if (var != null && var instanceof ContainerEntity) { + FunctionEntity method = context.foundMethodDeclarator(((ContainerEntity) var), node.getName(),node.getPosition().getStartLine()+1); + method.setLine(node.getPosition().getStartLine()+1); + handled = true; + } + } + + if (!handled) { + // fallback to add it to last container + FunctionEntity method = context.foundMethodDeclarator(node.getName(),node.getPosition().getStartLine()+1); + method.setLine(node.getPosition().getStartLine()+1); + } + super.visitDefsNode(node); + context.exitLastedEntity(); + return null; + } + + @Override + public Object visitGlobalVarNode(GlobalVarNode node) { + context.foundVarDefinition(context.globalScope(), node.getName(),node.getPosition().getStartLine()+1); + return super.visitGlobalVarNode(node); + } + + @Override + public Object visitInstVarNode(InstVarNode node) { + context.foundVarDefinition(context.currentType(), node.getName(),node.getPosition().getStartLine()+1); + return super.visitInstVarNode(node); + } + + @Override + public Object visitClassVarAsgnNode(ClassVarAsgnNode node) { + context.foundVarDefinition(helper.getScopeOfVar(node,context), node.getName(),node.getPosition().getStartLine()+1); + return super.visitClassVarAsgnNode(node); + } + + @Override + public Object visitClassVarDeclNode(ClassVarDeclNode node) { + context.foundVarDefinition(context.currentType(), node.getName(),node.getPosition().getStartLine()+1); + return super.visitClassVarDeclNode(node); + } + + @Override + public Object visitClassVarNode(ClassVarNode node) { + context.foundVarDefinition(context.currentType(), node.getName(),node.getPosition().getStartLine()+1); + return super.visitClassVarNode(node); + } + + @Override + public Object visitLocalVarNode(LocalVarNode node) { + return super.visitLocalVarNode(node); + } + + @Override + public Object visitDVarNode(DVarNode node) { + context.foundVarDefinition(context.lastContainer(), node.getName(),node.getPosition().getStartLine()+1); + return super.visitDVarNode(node); + } + + @Override + public Object visitDAsgnNode(DAsgnNode node) { + context.foundVarDefinition(helper.getScopeOfVar(node,context), node.getName(),node.getPosition().getStartLine()+1); + expressionUsage.foundExpression(node.getValue()); + return super.visitDAsgnNode(node); + } + + @Override + public Object visitGlobalAsgnNode(GlobalAsgnNode node) { + context.foundVarDefinition(helper.getScopeOfVar(node,context), node.getName(),node.getPosition().getStartLine()+1); + return super.visitGlobalAsgnNode(node); + } + + @Override + public Object visitInstAsgnNode(InstAsgnNode node) { + context.foundVarDefinition(helper.getScopeOfVar(node,context), node.getName(),node.getPosition().getStartLine()+1); + return super.visitInstAsgnNode(node); + } + + @Override + public Object visitArgumentNode(ArgumentNode node) { + String paramName = node.getName(); + context.addMethodParameter(paramName); + return super.visitArgumentNode(node); + } + + @Override + public Object visitLocalAsgnNode(LocalAsgnNode node) { + context.foundVarDefinition(helper.getScopeOfVar(node,context), node.getName(),node.getPosition().getStartLine()+1); + return super.visitLocalAsgnNode(node); + } + + @Override + protected Object visit(Node node) { + expressionUsage.foundExpression(node); + return super.visit(node); + } + +} diff --git a/src/main/java/com/educoder/bridge/tmp/JavaBuiltInType.java b/src/main/java/com/educoder/bridge/tmp/JavaBuiltInType.java new file mode 100644 index 0000000..7402f58 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/JavaBuiltInType.java @@ -0,0 +1,84 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.extractor.java; + +import depends.entity.repo.BuiltInType; + +public class JavaBuiltInType extends BuiltInType{ + + @Override + protected String[] getBuiltInTypeName() { + return new String[]{ + "void","int","double","char","byte","boolean","long","short","float", + "BigDecimal","Integer","Double","Char","Byte","Boolean","Long","Short","Float", + "String","Object","Class","Exception","StringBuilder", + "Appendable","AutoCloseable","Cloneable","Comparable","Iterable","Readable", + "Runnable","Thread.UncaughtExceptionHandler","Boolean","Byte","Character","Character.Subset", + "Character.UnicodeBlock","ClassLoader","ClassValue","Compiler","Double","Enum", + "InheritableThreadLocal","Math","Number","Package","Process", + "ProcessBuilder","ProcessBuilder.Redirect","Runtime","RuntimePermission", + "SecurityManager","StackTraceElement","StrictMath","StringBuffer", + "System","Thread","ThreadGroup","ThreadLocal","Throwable","Void","ProcessBuilder.Redirect.Type", + "Thread.State","ArithmeticException","ArrayIndexOutOfBoundsException", + "ArrayStoreException","ClassCastException","ClassNotFoundException","CloneNotSupportedException", + "EnumConstantNotPresentException","Exception","IllegalAccessException","IllegalArgumentException", + "IllegalMonitorStateException","IllegalStateException","IllegalThreadStateException", + "IndexOutOfBoundsException","InstantiationException","InterruptedException", + "NegativeArraySizeException","NoSuchFieldException","NoSuchMethodException","NullPointerException", + "NumberFormatException","ReflectiveOperationException","RuntimeException","SecurityException", + "StringIndexOutOfBoundsException","TypeNotPresentException","UnsupportedOperationException","AbstractMethodError", + "AssertionError","BootstrapMethodError","ClassCircularityError","ClassFormatError","Error","ExceptionInInitializerError", + "IllegalAccessError","IncompatibleClassChangeError","InstantiationError","InternalError","LinkageError","NoClassDefFoundError" + ,"NoSuchFieldError","NoSuchMethodError","OutOfMemoryError","StackOverflowError","ThreadDeath","UnknownError", + "UnsatisfiedLinkError","UnsupportedClassVersionError","VerifyError","VirtualMachineError","Deprecated","Override", + "SafeVarargs","SuppressWarnings", + "Collection","Comparator","Deque","Enumeration","EventListener","Formattable","Iterator","List", + "ListIterator","Map","Map.Entry","NavigableMap","NavigableSet","Observer","Queue","RandomAccess", + "Set","SortedMap","SortedSet","AbstractCollection","AbstractList","AbstractMap","AbstractMap.SimpleEntry", + "AbstractMap.SimpleImmutableEntry","AbstractQueue","AbstractSequentialList","AbstractSet","ArrayDeque", + "ArrayList","Arrays","BitSet","Calendar","Collections","Currency","Date","Dictionary","EnumMap","EnumSet", + "EventListenerProxy","EventObject","FormattableFlags","Formatter","GregorianCalendar","HashMap","HashSet", + "Hashtable","IdentityHashMap","LinkedHashMap","LinkedHashSet","LinkedList","ListResourceBundle","Locale", + "Locale.Builder","Objects","Observable","PriorityQueue","Properties","PropertyPermission", + "PropertyResourceBundle","Random","ResourceBundle","ResourceBundle.Control","Scanner", + "ServiceLoader","SimpleTimeZone","Stack","StringTokenizer","Timer","TimerTask","TimeZone", + "TreeMap","TreeSet","UUID","Vector","WeakHashMap","Formatter.BigDecimalLayoutForm", + "Locale.Category","ConcurrentModificationException","DuplicateFormatFlagsException", + "EmptyStackException","FormatFlagsConversionMismatchException","FormatterClosedException", + "IllegalFormatCodePointException","IllegalFormatConversionException","IllegalFormatException", + "IllegalFormatFlagsException","IllegalFormatPrecisionException","IllegalFormatWidthException", + "IllformedLocaleException","InputMismatchException","InvalidPropertiesFormatException","MissingFormatArgumentException", + "MissingFormatWidthException","MissingResourceException","NoSuchElementException","TooManyListenersException", + "UnknownFormatConversionException","UnknownFormatFlagsException","ServiceConfigurationError", + "" + }; + } + @Override + protected String[] getBuiltInTypePrefix() { + return new String[]{ + "java.","javax.","com.sun." + }; + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/JavaImportLookupStrategy.java b/src/main/java/com/educoder/bridge/tmp/JavaImportLookupStrategy.java new file mode 100644 index 0000000..d791697 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/JavaImportLookupStrategy.java @@ -0,0 +1,105 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.extractor.java; + +import depends.entity.Entity; +import depends.entity.FileEntity; +import depends.entity.PackageEntity; +import depends.entity.repo.EntityRepo; +import depends.extractor.UnsolvedBindings; +import depends.importtypes.Import; +import depends.relations.ImportLookupStrategy; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class JavaImportLookupStrategy extends ImportLookupStrategy{ + + public JavaImportLookupStrategy(EntityRepo repo) { + super(repo); + } + + @Override + public Entity lookupImportedType(String name, FileEntity fileEntity) { + //Java Strategy + String importedString = fileEntity.importedSuffixMatch(name); + if (importedString==null) return null; + return repo.getEntity(importedString); + } + + + @Override + public List getImportedRelationEntities(List importedList) { + ArrayList result = new ArrayList<>(); + for (Import importedItem:importedList) { + Entity imported = repo.getEntity(importedItem.getContent()); + if (imported==null) continue; + if (imported instanceof PackageEntity) { + //ignore wildcard import relation + }else { + result.add(imported); + } + } + return result; + } + + @Override + public List getImportedTypes(List importedList,Set unsolvedBindings) { + ArrayList result = new ArrayList<>(); + for (Import importedItem:importedList) { + Entity imported = repo.getEntity(importedItem.getContent()); + if (imported==null) { + unsolvedBindings.add(new UnsolvedBindings(importedItem.getContent(),null)); + continue; + } + if (imported instanceof PackageEntity) { + //expand import of package to all classes under the package due to we dis-courage the behavior + for (Entity child:imported.getChildren()) { + if (child instanceof FileEntity) { + child.getChildren().forEach(item->result.add(item)); + }else { + result.add(child); + } + } + }else { + result.add(imported); + } + } + return result; + } + + @Override + public List getImportedFiles(List importedList) { + return new ArrayList(); + } + + + @Override + public boolean supportGlobalNameLookup() { + return true; + } + +} diff --git a/src/main/java/com/educoder/bridge/tmp/JavaListener.java b/src/main/java/com/educoder/bridge/tmp/JavaListener.java new file mode 100644 index 0000000..326e788 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/JavaListener.java @@ -0,0 +1,337 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.extractor.java; + +import depends.entity.*; +import depends.entity.repo.EntityRepo; +import depends.extractor.java.JavaParser.*; +import depends.extractor.java.context.*; +import depends.importtypes.ExactMatchImport; +import depends.relations.IBindingResolver; + +import java.util.ArrayList; +import java.util.List; + +public class JavaListener extends JavaParserBaseListener { + private final JavaHandlerContext context; + private final AnnotationProcessor annotationProcessor; + private final ExpressionUsage expressionUsage; + private final EntityRepo entityRepo; + + public JavaListener(String fileFullPath, EntityRepo entityRepo, IBindingResolver bindingResolver) { + this.context = new JavaHandlerContext(entityRepo, bindingResolver); + this.entityRepo = entityRepo; + annotationProcessor = new AnnotationProcessor(); + expressionUsage = new ExpressionUsage(context,entityRepo); + context.startFile(fileFullPath); + } + + //////////////////////// + // Package + @Override + public void enterPackageDeclaration(PackageDeclarationContext ctx) { + context.foundNewPackage(QualitiedNameContextHelper.getName(ctx.qualifiedName())); + super.enterPackageDeclaration(ctx); + } + + + //////////////////////// + // Import + @Override + public void enterImportDeclaration(ImportDeclarationContext ctx) { + context.foundNewImport(new ExactMatchImport(ctx.qualifiedName().getText())); + super.enterImportDeclaration(ctx); + } + + /////////////////////// + // Class or Interface + // classDeclaration | enumDeclaration | interfaceDeclaration | + /////////////////////// annotationTypeDeclaration + @Override + public void enterClassDeclaration(ClassDeclarationContext ctx) { + if (ctx.IDENTIFIER()==null) return; + context.foundNewType(GenericName.build(ctx.IDENTIFIER().getText()), ctx.getStart().getLine()); + // implements + if (ctx.typeList() != null) { + for (int i = 0; i < ctx.typeList().typeType().size(); i++) { + context.foundImplements(GenericName.build(ClassTypeContextHelper.getClassName(ctx.typeList().typeType().get(i)))); + } + } + // extends relation + if (ctx.typeType() != null) { + context.foundExtends(GenericName.build(ClassTypeContextHelper.getClassName(ctx.typeType()))); + } + + if (ctx.typeParameters() != null) { + foundTypeParametersUse(ctx.typeParameters()); + } + annotationProcessor.processAnnotationModifier(ctx, TypeDeclarationContext.class ,"classOrInterfaceModifier.annotation",context.lastContainer()); + super.enterClassDeclaration(ctx); + } + + @Override + public void exitClassDeclaration(ClassDeclarationContext ctx) { + exitLastEntity(); + super.exitClassDeclaration(ctx); + } + + @Override + public void enterEnumDeclaration(EnumDeclarationContext ctx) { + context.foundNewType(GenericName.build(ctx.IDENTIFIER().getText()), ctx.getStart().getLine()); + annotationProcessor.processAnnotationModifier(ctx, TypeDeclarationContext.class ,"classOrInterfaceModifier.annotation",context.lastContainer()); + super.enterEnumDeclaration(ctx); + } + + @Override + public void enterAnnotationTypeDeclaration(AnnotationTypeDeclarationContext ctx) { + context.foundNewType(GenericName.build(ctx.IDENTIFIER().getText()), ctx.getStart().getLine()); + annotationProcessor.processAnnotationModifier(ctx, TypeDeclarationContext.class ,"classOrInterfaceModifier.annotation",context.lastContainer()); + super.enterAnnotationTypeDeclaration(ctx); + } + + @Override + public void exitEnumDeclaration(EnumDeclarationContext ctx) { + exitLastEntity(); + super.exitEnumDeclaration(ctx); + } + + /** + * interfaceDeclaration : INTERFACE IDENTIFIER typeParameters? (EXTENDS + * typeList)? interfaceBody ; + */ + @Override + public void enterInterfaceDeclaration(InterfaceDeclarationContext ctx) { + context.foundNewType(GenericName.build(ctx.IDENTIFIER().getText()), ctx.getStart().getLine()); + // type parameters + if (ctx.typeParameters() != null) { + foundTypeParametersUse(ctx.typeParameters()); + } + // extends relation + if (ctx.typeList() != null) { + for (int i = 0; i < ctx.typeList().typeType().size(); i++) { + context.foundExtends(ClassTypeContextHelper.getClassName(ctx.typeList().typeType().get(i))); + } + } + annotationProcessor.processAnnotationModifier(ctx, TypeDeclarationContext.class ,"classOrInterfaceModifier.annotation",context.lastContainer()); + super.enterInterfaceDeclaration(ctx); + } + + @Override + public void exitInterfaceDeclaration(InterfaceDeclarationContext ctx) { + exitLastEntity(); + super.exitInterfaceDeclaration(ctx); + } + + + + @Override + public void exitAnnotationTypeDeclaration(AnnotationTypeDeclarationContext ctx) { + exitLastEntity(); + super.exitAnnotationTypeDeclaration(ctx); + } + + ///////////////////////// + // Method + @Override + public void enterMethodDeclaration(MethodDeclarationContext ctx) { + List throwedType = QualitiedNameContextHelper.getNames(ctx.qualifiedNameList()); + String methodName = ctx.IDENTIFIER().getText(); + String returnedType = ClassTypeContextHelper.getClassName(ctx.typeTypeOrVoid()); + FunctionEntity method = context.foundMethodDeclarator(methodName, returnedType, throwedType,ctx.getStart().getLine()); + new FormalParameterListContextHelper(ctx.formalParameters(), method, entityRepo); + if (ctx.typeParameters() != null) { + List parameters = TypeParameterContextHelper.getTypeParameters(ctx.typeParameters()); + method.addTypeParameter(parameters); + } + annotationProcessor.processAnnotationModifier(ctx, ClassBodyDeclarationContext.class,"modifier.classOrInterfaceModifier.annotation",context.lastContainer()); + super.enterMethodDeclaration(ctx); + } + + @Override + public void exitMethodDeclaration(MethodDeclarationContext ctx) { + exitLastEntity(); + super.exitMethodDeclaration(ctx); + } + + private void exitLastEntity() { + context.exitLastedEntity(); + } + +// interfaceMethodDeclaration +// : interfaceMethodModifier* (typeTypeOrVoid | typeParameters annotation* typeTypeOrVoid) +// IDENTIFIER formalParameters ('[' ']')* (THROWS qualifiedNameList)? methodBody + + @Override + public void enterInterfaceMethodDeclaration(InterfaceMethodDeclarationContext ctx) { + List throwedType = QualitiedNameContextHelper.getNames(ctx.qualifiedNameList()); + FunctionEntity method = context.foundMethodDeclarator(ctx.IDENTIFIER().getText(), + ClassTypeContextHelper.getClassName(ctx.typeTypeOrVoid()), throwedType,ctx.getStart().getLine()); + new FormalParameterListContextHelper(ctx.formalParameters(), method, entityRepo); + if (ctx.typeParameters() != null) { + foundTypeParametersUse(ctx.typeParameters()); + } + annotationProcessor.processAnnotationModifier(ctx, InterfaceBodyDeclarationContext.class,"modifier.classOrInterfaceModifier.annotation",context.lastContainer()); + super.enterInterfaceMethodDeclaration(ctx); + } + + @Override + public void exitInterfaceMethodDeclaration(InterfaceMethodDeclarationContext ctx) { + exitLastEntity(); + super.exitInterfaceMethodDeclaration(ctx); + } + + @Override + public void enterConstructorDeclaration(ConstructorDeclarationContext ctx) { + List throwedType = QualitiedNameContextHelper.getNames(ctx.qualifiedNameList()); + FunctionEntity method = context.foundMethodDeclarator(ctx.IDENTIFIER().getText(), ctx.IDENTIFIER().getText(), + throwedType,ctx.getStart().getLine()); + new FormalParameterListContextHelper(ctx.formalParameters(), method, entityRepo); + method.addReturnType(context.currentType()); + annotationProcessor.processAnnotationModifier(ctx, ClassBodyDeclarationContext.class,"modifier.classOrInterfaceModifier.annotation",context.lastContainer()); + super.enterConstructorDeclaration(ctx); + } + + @Override + public void exitConstructorDeclaration(ConstructorDeclarationContext ctx) { + exitLastEntity(); + super.exitConstructorDeclaration(ctx); + } + + ///////////////////////////////////////////////////////// + // Field + @Override + public void enterFieldDeclaration(FieldDeclarationContext ctx) { + List varNames = VariableDeclaratorsContextHelper.getVariables(ctx.variableDeclarators()); + String type = ClassTypeContextHelper.getClassName(ctx.typeType()); + List typeArguments = ClassTypeContextHelper.getTypeArguments(ctx.typeType()); + List vars = context.foundVarDefinitions(varNames, type,typeArguments,ctx.getStart().getLine()); + annotationProcessor.processAnnotationModifier(ctx, ClassBodyDeclarationContext.class,"modifier.classOrInterfaceModifier.annotation",vars); + super.enterFieldDeclaration(ctx); + } + + + + @Override + public void enterConstDeclaration(ConstDeclarationContext ctx) { + List typeArguments = ClassTypeContextHelper.getTypeArguments(ctx.typeType()); + List vars = context.foundVarDefinitions(VariableDeclaratorsContextHelper.getVariables(ctx.constantDeclarator()), + ClassTypeContextHelper.getClassName(ctx.typeType()),typeArguments, ctx.getStart().getLine()); + + annotationProcessor.processAnnotationModifier(ctx, InterfaceBodyDeclarationContext.class,"modifier.classOrInterfaceModifier.annotation",vars); + super.enterConstDeclaration(ctx); + } + + @Override + public void enterEnumConstant(EnumConstantContext ctx) { + if (ctx.IDENTIFIER() != null) { + context.foundEnumConstDefinition(ctx.IDENTIFIER().getText(),ctx.getStart().getLine()); + } + super.enterEnumConstant(ctx); + } + + @Override + public void enterAnnotationMethodRest(AnnotationMethodRestContext ctx) { + context.foundMethodDeclarator(ctx.IDENTIFIER().getText(), ClassTypeContextHelper.getClassName(ctx.typeType()), + new ArrayList<>(),ctx.getStart().getLine()); + super.enterAnnotationMethodRest(ctx); + } + + @Override + public void exitAnnotationMethodRest(AnnotationMethodRestContext ctx) { + exitLastEntity(); + super.exitAnnotationMethodRest(ctx); + } + + @Override + public void enterAnnotationConstantRest(AnnotationConstantRestContext ctx) { + // TODO: no variable type defined in annotation const? + context.foundVarDefinitions(VariableDeclaratorsContextHelper.getVariables(ctx.variableDeclarators()), "", new ArrayList<>(), ctx.getStart().getLine()); + super.enterAnnotationConstantRest(ctx); + } + + /////////////////////////////////////////// + // variables + // TODO: all modifier have not processed yet. + @Override + public void enterLocalVariableDeclaration(LocalVariableDeclarationContext ctx) { + List typeArguments = ClassTypeContextHelper.getTypeArguments(ctx.typeType()); + context.foundVarDefinitions(VariableDeclaratorsContextHelper.getVariables((ctx.variableDeclarators())), + ClassTypeContextHelper.getClassName(ctx.typeType()), typeArguments, ctx.getStart().getLine()); + + super.enterLocalVariableDeclaration(ctx); + } + + public void enterEnhancedForControl(EnhancedForControlContext ctx) { + List typeArguments = ClassTypeContextHelper.getTypeArguments(ctx.typeType()); + context.foundVarDefinitions(VariableDeclaratorsContextHelper.getVariable((ctx.variableDeclaratorId())), + ClassTypeContextHelper.getClassName(ctx.typeType()), typeArguments, ctx.getStart().getLine()); + super.enterEnhancedForControl(ctx); + } + +// resource +// : variableModifier* classOrInterfaceType variableDeclaratorId '=' expression +// ; + @Override + public void enterResource(ResourceContext ctx) { + List typeArguments = ClassTypeContextHelper.getTypeArguments(ctx.classOrInterfaceType()); + context.foundVarDefinition(ctx.variableDeclaratorId().IDENTIFIER().getText(), + GenericName.build(IdentifierContextHelper.getName(ctx.classOrInterfaceType().IDENTIFIER())), typeArguments,ctx.getStart().getLine()); + super.enterResource(ctx); + } + + @Override + public void enterExpression(ExpressionContext ctx) { + Expression expr = expressionUsage.foundExpression(ctx); + expr.setLine(ctx.getStart().getLine()); + super.enterExpression(ctx); + } + + ///////////////////////////////////////////// + // Block + @Override + public void enterBlock(BlockContext ctx) { + // TODO support block in java + super.enterBlock(ctx); + } + + @Override + public void exitBlock(BlockContext ctx) { + // TODO support block in java + super.exitBlock(ctx); + } + + /* type parameters , <> treat as USE */ + private void foundTypeParametersUse(TypeParametersContext typeParameters) { + for (int i = 0; i < typeParameters.typeParameter().size(); i++) { + TypeParameterContext typeParam = typeParameters.typeParameter(i); + if (typeParam.typeBound() != null) { + for (int j = 0; j < typeParam.typeBound().typeType().size(); j++) { + context.foundTypeParametes(GenericName.build(ClassTypeContextHelper.getClassName(typeParam.typeBound().typeType(j)))); + } + } + context.currentType().addTypeParameter(GenericName.build(typeParam.IDENTIFIER().getText())); + } + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/JavaVarResolveTest.java b/src/main/java/com/educoder/bridge/tmp/JavaVarResolveTest.java new file mode 100644 index 0000000..4660844 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/JavaVarResolveTest.java @@ -0,0 +1,100 @@ +package depends.extractor.java; + +import depends.deptypes.DependencyType; +import depends.entity.ContainerEntity; +import depends.entity.Entity; +import depends.entity.FunctionEntity; +import depends.entity.TypeEntity; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; + +public class JavaVarResolveTest extends JavaParserTest{ + @Before + public void setUp() { + super.init(); + } + + @Test + public void test_field_var_should_be_parsed() throws IOException { + String src = "./src/test/resources/java-code-examples/FieldVar.java"; + JavaFileParser parser = createParser(); + parser.parse(src); + resolveAllBindings(); + Entity classEntity = entityRepo.getEntity("FieldVar"); + assertEquals(3,((TypeEntity)classEntity).getVars().size()); + } + + @Test + public void test_local_var_should_be_parsed() throws IOException { + String src = "./src/test/resources/java-code-examples/LocalVar.java"; + JavaFileParser parser = createParser(); + parser.parse(src); + resolveAllBindings(); + assertEquals(1,((TypeEntity)entityRepo.getEntity("LocalVar")).getVars().size()); + assertEquals(1,((FunctionEntity)entityRepo.getEntity("LocalVar.foo")).getVars().size()); + } + + @Test + public void test_local_var_type_could_be_inferred() throws IOException { + String src = "./src/test/resources/java-code-examples/LocalVarInferExample.java"; + JavaFileParser parser = createParser(); + parser.parse(src); + resolveAllBindings(); + ContainerEntity e = (ContainerEntity) entityRepo.getEntity("LocalVarInferExample.setExample"); + this.assertContainsRelation(e, DependencyType.CONTAIN, "MyInteger"); + } + + @Test + public void test_field_access_could_be_inferred() throws IOException { + String src = "./src/test/resources/java-code-examples/ComplexExpressionExample.java"; + JavaFileParser parser = createParser(); + parser.parse(src); + resolveAllBindings(); + Entity e = entityRepo.getEntity("test.ComplexExpressionExample.setExample"); + this.assertContainsRelation(e, DependencyType.PARAMETER, "test.ClassA"); + this.assertContainsRelation(e, DependencyType.CREATE, "test.ClassA"); + this.assertContainsRelation(e, DependencyType.CALL, "test.ClassA"); + this.assertContainsRelation(e, DependencyType.CAST, "test.ClassA"); + this.assertContainsRelation(e, DependencyType.CALL, "test.ClassA.foo"); + this.assertContainsRelation(e, DependencyType.CALL, "test.ClassA"); + this.assertContainsRelation(e, DependencyType.USE, "test.ComplexExpressionExample.setExample.a3"); + this.assertContainsRelation(e, DependencyType.USE, "test.ClassX.m"); + this.assertContainsRelation(e, DependencyType.USE, "test.ComplexExpressionExample.setExample.a2"); + this.assertContainsRelation(e, DependencyType.USE, "test.ClassA.x"); + this.assertContainsRelation(e, DependencyType.USE, "test.ComplexExpressionExample.setExample.a"); + } + + @Test + public void test_long_static_function_should_be_inferred() throws IOException { + String src = "./src/test/resources/java-code-examples/LongExpressionWithAbsolutePath.java"; + JavaFileParser parser = createParser(); + parser.parse(src); + resolveAllBindings(); + assertEquals(5,entityRepo.getEntity("x.LongExpressionWithAbsolutePath.setExample").getRelations().size()); + } + + + + @Test + public void test_call_should_be_referred() throws IOException { + String src = "./src/test/resources/java-code-examples/ExpressionCallTest.java"; + JavaFileParser parser = createParser(); + parser.parse(src); + resolveAllBindings(); + assertEquals(10,entityRepo.getEntity("ValidateAll.validate").getRelations().size()); + } + + @Test + public void test_could_detect_type_argument_in_field() throws IOException { + String src = "./src/test/resources/java-code-examples/TypeArgument.java"; + JavaFileParser parser = createParser(); + parser.parse(src); + resolveAllBindings(); + this.assertContainsRelation(entityRepo.getEntity("JDepObject.cells"),DependencyType.PARAMETER, "JCellObject"); + } + +} diff --git a/src/main/java/com/educoder/bridge/tmp/JchService.java b/src/main/java/com/educoder/bridge/tmp/JchService.java new file mode 100644 index 0000000..8657d1d --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/JchService.java @@ -0,0 +1,261 @@ +package com.educoder.bridge.service; + +import com.alibaba.fastjson.JSONObject; +import com.educoder.bridge.model.SSHInfo; +import com.educoder.bridge.model.SSHSession; +import com.educoder.bridge.utils.Base64Util; +import com.jcraft.jsch.ChannelShell; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.UserInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Service +public class JchService { + + private static List sshSessionQueue = new CopyOnWriteArrayList<>(); + private ExecutorService executorService = Executors.newCachedThreadPool(); + private Logger logger = LoggerFactory.getLogger(getClass()); + + com.jcraft.jsch.Logger jschLogger = new com.jcraft.jsch.Logger() { + + @Override + public boolean isEnabled(int arg0) { + return true; + } + + @Override + public void log(int arg0, String arg1) { + if (logger.isTraceEnabled()) { + logger.trace("JSch Log [Level " + arg0 + "]: " + arg1); + } + } + }; + + + /** + * 在webSocket连接时,初始化一个ssh连接 + * + * @param webSocketSession webSocket连接 + */ + public void add(WebSocketSession webSocketSession) { + + SSHSession sshSession = new SSHSession(); + sshSession.setWebSocketSession(webSocketSession); + + sshSessionQueue.add(sshSession); + } + + /** + * 处理客户端发过来的数据 + * @param buffer 数据 + * @param webSocketSession webSocket连接 + */ + public void recv(String buffer, WebSocketSession webSocketSession) { + + SSHSession sshSession = null; + try { + logger.debug("webSocketSessionID: {}, 信息: {}", webSocketSession.getId(), buffer); + JSONObject info = JSONObject.parseObject(buffer); + String tp = info.getString("tp"); + sshSession = findByWebSocketSession(webSocketSession); + + //初始化连接 + if ("init".equals(tp)) { +// {"tp":"init","data":{"host":"127.0.0.1","port":"41080","username":"root","password":"123123"}} + SSHInfo sshInfo = info.getObject("data", SSHInfo.class); + sshSession.setSSHInfo(sshInfo); + + if (sshSession != null) { + SSHSession finalSSHSession = sshSession; + + // 新开一个线程建立连接,连接开启之后以一直监听来自客户端的输入 + executorService.execute(() -> { + connectTossh(finalSSHSession); + }); + } + } else if ("client".equals(tp)) { + String data = info.getString("data"); + + // 将网页输入的数据传送给后端服务器 + if (sshSession != null) { + transTossh(sshSession.getOutputStream(), data); + } + } + } catch (Exception e) { + logger.error("转发命令到ssh出错: {}", e); + + close(sshSession); + } + + } + + /** + * 将数据传送给服务端作为SSH的输入 + * + * @param outputStream + * @param data + * @throws IOException + */ + private void transTossh(OutputStream outputStream, String data) throws IOException { + if (outputStream != null) { + outputStream.write(data.getBytes()); + outputStream.flush(); + } + } + + /** + * 连接ssh + * + * @param sshSession ssh连接需要的信息 + */ + private void connectTossh(SSHSession sshSession){ + Session jschSession = null; + SSHInfo SSHInfo = sshSession.getSSHInfo(); + try { + JSch jsch = new JSch(); + JSch.setLogger(jschLogger); + + //启动线程 + java.util.Properties config = new java.util.Properties(); + config.put("StrictHostKeyChecking", "no"); + jschSession = jsch.getSession(SSHInfo.getUsername(), SSHInfo.getHost(), SSHInfo.getPort()); + + jschSession.setConfig(config); + jschSession.setPassword(SSHInfo.getPassword()); + jschSession.setUserInfo(new UserInfo() { + @Override + public String getPassphrase() { + return null; + } + + @Override + public String getPassword() { + return null; + } + + @Override + public boolean promptPassword(String s) { + return false; + } + + @Override + public boolean promptPassphrase(String s) { + return false; + } + + @Override + public boolean promptYesNo(String s) { + return true; + } // Accept all server keys + + @Override + public void showMessage(String s) { + } + }); + + jschSession.connect(); + ChannelShell channel = (ChannelShell) jschSession.openChannel("shell"); + channel.setPtyType("xterm"); + + channel.connect(); + + sshSession.setChannel(channel); + InputStream inputStream = channel.getInputStream(); + sshSession.setOutputStream(channel.getOutputStream()); + + sshSession.setSSHInfo(SSHInfo); + logger.debug("主机: {} 连接成功!", SSHInfo.getHost()); + + // 循环读取,jsch的输入为服务器执行命令之后的返回数据 + byte[] buf = new byte[1024]; + while (true) { + int length = inputStream.read(buf); + if (length < 0) { + close(sshSession); + throw new Exception("读取出错,数据长度:" + length); + } + sendMsg(sshSession.getWebSocketSession(), Arrays.copyOfRange(buf, 0, length)); + } + + } catch (Exception e) { + logger.error("ssh连接出错, e: {}", e); + } finally { + logger.info("连接关闭, {}", SSHInfo.getHost()); + if (jschSession != null) { + jschSession.disconnect(); + } + + close(sshSession); + } + } + + + /** + * 发送数据回websocket + * + * @param webSocketSession webSocket连接 + * @param buffer 数据 + * @throws IOException + */ + public void sendMsg(WebSocketSession webSocketSession, byte[] buffer) throws IOException { + logger.debug("服务端返回的数据: {}", new String(buffer, "UTF-8")); + + webSocketSession.sendMessage(new TextMessage(Base64Util.encodeBytes(buffer))); + } + + /** + * 通过webSocket连接在队列中找到对应的SSH连接 + * + * @param webSocketSession webSocket连接 + */ + public SSHSession findByWebSocketSession(WebSocketSession webSocketSession) { + Optional optional = sshSessionQueue.stream().filter(webscoketObj -> webscoketObj.getWebSocketSession() == webSocketSession).findFirst(); + if (optional.isPresent()) { + return optional.get(); + } + return null; + } + + /** + * 关闭ssh和websocket连接 + * + * @param sshSession ssh连接 + */ + private void close(SSHSession sshSession) { + if (sshSession != null) { + sshSession.getChannel().disconnect(); + try { + sshSession.getWebSocketSession().close(); + sshSession.getOutputStream().close(); + } catch (IOException e) { + logger.error("连接关闭失败!e: {}", e); + } + + sshSessionQueue.remove(sshSession); + } + } + + /** + * 通过webSocketSession关闭ssh与webSocket连接 + * + * @param webSocketSession + */ + public void closeByWebSocket(WebSocketSession webSocketSession) { + close(findByWebSocketSession(webSocketSession)); + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/Main.java b/src/main/java/com/educoder/bridge/tmp/Main.java new file mode 100644 index 0000000..260ae11 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/Main.java @@ -0,0 +1,197 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends; + +import depends.addons.DV8MappingFileBuilder; +import depends.entity.repo.EntityRepo; +import depends.extractor.AbstractLangProcessor; +import depends.extractor.LangProcessorRegistration; +import depends.extractor.UnsolvedBindings; +import depends.format.DependencyDumper; +import depends.format.detail.UnsolvedSymbolDumper; +import depends.generator.DependencyGenerator; +import depends.generator.FileDependencyGenerator; +import depends.generator.FunctionDependencyGenerator; +import depends.generator.StructureDependencyGenerator; +import depends.matrix.core.DependencyMatrix; +import depends.matrix.transform.MatrixLevelReducer; +import depends.relations.BindingResolver; +import depends.relations.IBindingResolver; +import depends.relations.RelationCounter; +import edu.emory.mathcs.backport.java.util.Arrays; +import multilang.depends.util.file.FileUtil; +import multilang.depends.util.file.FolderCollector; +import multilang.depends.util.file.TemporaryFile; +import multilang.depends.util.file.path.*; +import multilang.depends.util.file.strip.LeadingNameStripper; +import net.sf.ehcache.CacheManager; +import org.codehaus.plexus.util.StringUtils; +import picocli.CommandLine; +import picocli.CommandLine.PicocliException; + +import java.io.File; +import java.util.List; +import java.util.Set; + +/** + * The entry pooint of depends + */ +public class Main { + + public static void main(String[] args) { + try { + LangRegister langRegister = new LangRegister(); + langRegister.register(); + DependsCommand appArgs = CommandLine.populateCommand(new DependsCommand(), args); + if (appArgs.help) { + CommandLine.usage(new DependsCommand(), System.out); + System.exit(0); + } + executeCommand(appArgs); + } catch (Exception e) { + if (e instanceof PicocliException) { + CommandLine.usage(new DependsCommand(), System.out); + } else if (e instanceof ParameterException){ + System.err.println(e.getMessage()); + }else { + System.err.println("Exception encountered. If it is a design error, please report issue to us." ); + e.printStackTrace(); + } + System.exit(0); + } + } + + @SuppressWarnings("unchecked") + private static void executeCommand(DependsCommand args) throws ParameterException { + String lang = args.getLang(); + String inputDir = args.getSrc(); + String[] includeDir = args.getIncludes(); + String outputName = args.getOutputName(); + String outputDir = args.getOutputDir(); + String[] outputFormat = args.getFormat(); + + inputDir = FileUtil.uniqFilePath(inputDir); + + if (args.isAutoInclude()) { + includeDir = appendAllFoldersToIncludePath(inputDir, includeDir); + } + + AbstractLangProcessor langProcessor = LangProcessorRegistration.getRegistry().getProcessorOf(lang); + if (langProcessor == null) { + System.err.println("Not support this language: " + lang); + return; + } + + IBindingResolver bindingResolver = new BindingResolver(langProcessor, args.isOutputExternalDependencies(), args.isDuckTypingDeduce()); + + long startTime = System.currentTimeMillis(); + //step1: build data + EntityRepo entityRepo = langProcessor.buildDependencies(inputDir, includeDir, bindingResolver); + + new RelationCounter(entityRepo,langProcessor, bindingResolver).computeRelations(); + System.out.println("Dependency done...."); + + //step2: generate dependencies matrix + DependencyGenerator dependencyGenerator = getDependencyGenerator(args, inputDir); + DependencyMatrix matrix = dependencyGenerator.identifyDependencies(entityRepo,args.getTypeFilter()); + //step3: output + if (args.getGranularity().startsWith("L")) { + matrix = new MatrixLevelReducer(matrix,args.getGranularity().substring(1)).shrinkToLevel(); + } + DependencyDumper output = new DependencyDumper(matrix); + output.outputResult(outputName,outputDir,outputFormat); + if (args.isOutputExternalDependencies()) { + Set unsolved = langProcessor.getExternalDependencies(); + UnsolvedSymbolDumper unsolvedSymbolDumper = new UnsolvedSymbolDumper(unsolved,args.getOutputName(),args.getOutputDir(), + new LeadingNameStripper(args.isStripLeadingPath(),inputDir,args.getStrippedPaths())); + unsolvedSymbolDumper.output(); + } + long endTime = System.currentTimeMillis(); + TemporaryFile.getInstance().delete(); + CacheManager.create().shutdown(); + System.out.println("Consumed time: " + (float) ((endTime - startTime) / 1000.00) + " s, or " + + (float) ((endTime - startTime) / 60000.00) + " min."); + if ( args.isDv8map()) { + DV8MappingFileBuilder dv8MapfileBuilder = new DV8MappingFileBuilder(langProcessor.supportedRelations()); + dv8MapfileBuilder.create(outputDir+ File.separator+"depends-dv8map.mapping"); + } + } + + private static String[] appendAllFoldersToIncludePath(String inputDir, String[] includeDir) { + FolderCollector includePathCollector = new FolderCollector(); + List additionalIncludePaths = includePathCollector.getFolders(inputDir); + additionalIncludePaths.addAll(Arrays.asList(includeDir)); + includeDir = additionalIncludePaths.toArray(new String[] {}); + return includeDir; + } + + private static DependencyGenerator getDependencyGenerator(DependsCommand app, String inputDir) throws ParameterException { + FilenameWritter filenameWritter = new EmptyFilenameWritter(); + if (!StringUtils.isEmpty(app.getNamePathPattern())) { + if (app.getNamePathPattern().equals("dot")|| + app.getNamePathPattern().equals(".")) { + filenameWritter = new DotPathFilenameWritter(); + }else if (app.getNamePathPattern().equals("unix")|| + app.getNamePathPattern().equals("/")) { + filenameWritter = new UnixPathFilenameWritter(); + }else if (app.getNamePathPattern().equals("windows")|| + app.getNamePathPattern().equals("\\")) { + filenameWritter = new WindowsPathFilenameWritter(); + }else{ + throw new ParameterException("Unknown name pattern paremater:" + app.getNamePathPattern()); + } + } + + + /* by default use file dependency generator */ + DependencyGenerator dependencyGenerator = new FileDependencyGenerator(); + if (!StringUtils.isEmpty(app.getGranularity())) { + /* method parameter means use method generator */ + if (app.getGranularity().equals("method")) + dependencyGenerator = new FunctionDependencyGenerator(); + else if (app.getGranularity().equals("structure")) + dependencyGenerator = new StructureDependencyGenerator(); + else if (app.getGranularity().equals("file")) + /*no action*/; + else if (app.getGranularity().startsWith("L")) + /*no action*/; + else + throw new ParameterException("Unknown granularity parameter:" + app.getGranularity()); + } + + if (app.isStripLeadingPath() || + app.getStrippedPaths().length>0) { + dependencyGenerator.setLeadingStripper(new LeadingNameStripper(app.isStripLeadingPath(), inputDir, app.getStrippedPaths())); + } + + if (app.isDetail()) { + dependencyGenerator.setGenerateDetail(true); + } + + dependencyGenerator.setFilenameRewritter(filenameWritter); + return dependencyGenerator; + } + +} diff --git a/src/main/java/com/educoder/bridge/tmp/MatrixLevelReducer.java b/src/main/java/com/educoder/bridge/tmp/MatrixLevelReducer.java new file mode 100644 index 0000000..4cb0d5d --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/MatrixLevelReducer.java @@ -0,0 +1,131 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.matrix.transform; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; + +import depends.matrix.core.DependencyMatrix; +import depends.matrix.core.DependencyPair; +import depends.matrix.core.DependencyValue; + +public class MatrixLevelReducer { + + private DependencyMatrix origin; + private int level; + HashMap nodesMap = new HashMap<>(); + + public MatrixLevelReducer(DependencyMatrix matrix, String levelString) { + this.origin = matrix; + this.level = stringToPositiveInt(levelString); + } + + public DependencyMatrix shrinkToLevel() { + if (level < 0) + return origin; + + ArrayList reMappedNodes = new ArrayList<>(); + for (String node : origin.getNodes()) { + String newNode = calcuateNodeAtLevel(node, level); + if (!reMappedNodes.contains(newNode)) { + reMappedNodes.add(newNode); + } + } + // sort nodes by name + reMappedNodes.sort(new Comparator() { + @Override + public int compare(String o1, String o2) { + return o1.compareTo(o2); + } + }); + DependencyMatrix ordered = new DependencyMatrix(); + for (int id=0;id 0) { + if (sb.length()>0) + sb.append(splitter); + sb.append(segments[i]); + count++; + } + } + return prefix + sb.toString(); + } + + private Integer translateToNewId(Integer id) { + String newNode = calcuateNodeAtLevel(origin.getNodeName(id), level); + return nodesMap.get(newNode); + } + + private int stringToPositiveInt(String level) { + int result = -1; + try { + result = Integer.parseInt(level); + } catch (Exception e) { + result = -1; + } + if (result <= 0) { + result = -1; + } + return result; + } + +} diff --git a/src/main/java/com/educoder/bridge/tmp/ParserTest.java b/src/main/java/com/educoder/bridge/tmp/ParserTest.java new file mode 100644 index 0000000..4df73d0 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/ParserTest.java @@ -0,0 +1,109 @@ +package depends.extractor; + +import depends.entity.ContainerEntity; +import depends.entity.Entity; +import depends.entity.FunctionEntity; +import depends.entity.VarEntity; +import depends.entity.repo.EntityRepo; +import depends.relations.BindingResolver; +import depends.relations.IBindingResolver; +import depends.relations.Relation; +import depends.relations.RelationCounter; +import multilang.depends.util.file.TemporaryFile; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Set; + +import static org.junit.Assert.fail; + +public abstract class ParserTest { + protected EntityRepo entityRepo ; + protected IBindingResolver bindingResolver; + protected AbstractLangProcessor langProcessor; + + protected void init(){ + entityRepo = langProcessor.getEntityRepo(); + bindingResolver = new BindingResolver(langProcessor,true,false); + langProcessor.bindingResolver = bindingResolver; + TemporaryFile.reset(); + } + + protected void init(boolean duckTypingDeduce){ + entityRepo = langProcessor.getEntityRepo(); + bindingResolver = new BindingResolver(langProcessor,false,duckTypingDeduce); + langProcessor.bindingResolver = bindingResolver; + TemporaryFile.reset(); + } + + public Set resolveAllBindings() { + Set result = bindingResolver.resolveAllBindings(langProcessor.isEagerExpressionResolve()); + new RelationCounter(entityRepo,langProcessor, bindingResolver).computeRelations(); + return result; + } + + protected Set resolveAllBindings(boolean callAsImpl) { + Set result = bindingResolver.resolveAllBindings(langProcessor.isEagerExpressionResolve()); + new RelationCounter(entityRepo,langProcessor, bindingResolver).computeRelations(); + return result; + } + + protected void assertNotContainsRelation(Entity inEntity, String dependencyType, String dependedEntityFullName) { + for (Relation r:inEntity.getRelations()) { + if (r.getType().equals(dependencyType)) { + if (r.getEntity().getQualifiedName().equals(dependedEntityFullName)) { + fail("found unexpected relation: type = " + dependencyType + " to entity " + dependedEntityFullName); + } + } + } + } + + protected void assertContainsRelation(Entity inEntity, String dependencyType, String dependedEntityFullName) { + Relation relation = null; + for (Relation r:inEntity.getRelations()) { + if (r.getType().equals(dependencyType)) { + relation = r; + if (r.getEntity()==null) continue; + if (r.getEntity().getQualifiedName().equals(dependedEntityFullName)) + return; + } + } + if (relation==null) { + fail("cannot found relation type of "+ dependencyType); + }else { + fail("cannot found relation type of " + dependencyType + " to entity " + dependedEntityFullName); + } + } + + protected void assertContainsVarWithRawName(Entity entity, String name) { + ContainerEntity container = (ContainerEntity)entity; + ArrayList vars = container.getVars(); + for (VarEntity var:vars) { + if (var.getRawName().uniqName().equals(name)) { + return; + } + } + fail("cannot found var with rawname " + name); + } + + protected void assertContainsParametersWithRawName(FunctionEntity function, String name) { + Collection vars = function.getParameters(); + for (VarEntity var:vars) { + if (var.getRawName().uniqName().equals(name)) { + return; + } + } + fail("cannot found parameter with rawname " + name); + } + + protected void assertContainReturnType(FunctionEntity function, String name) { + Collection types = function.getReturnTypes(); + for (Entity type:types) { + if (type.getRawName().uniqName().equals(name)) { + return; + } + } + fail("cannot found return type with rawname " + name); + } + +} diff --git a/src/main/java/com/educoder/bridge/tmp/PomListener.java b/src/main/java/com/educoder/bridge/tmp/PomListener.java new file mode 100644 index 0000000..8bcd2f1 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/PomListener.java @@ -0,0 +1,165 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.extractor.pom; + +import depends.entity.Expression; +import depends.entity.GenericName; +import depends.entity.VarEntity; +import depends.entity.repo.EntityRepo; +import depends.extractor.FileParser; +import depends.extractor.xml.XMLParser.ElementContext; +import depends.extractor.xml.XMLParserBaseListener; +import depends.relations.IBindingResolver; +import org.antlr.v4.runtime.ParserRuleContext; + +import java.io.IOException; +import java.util.List; +import java.util.Stack; + +public class PomListener extends XMLParserBaseListener { + private PomHandlerContext context; + private EntityRepo entityRepo; + PomArtifactEntity currentEntity; + private VarEntity currentVar; + Expression currentExpression; + private PomParent pomParent; + private PomProcessor parseCreator; + private List includePaths; + private IBindingResolver IBindingResolver; + private Stack pomCoords= new Stack<>(); + + public PomListener(String fileFullPath, EntityRepo entityRepo, List includePaths, PomProcessor parseCreator, + IBindingResolver bindingResolver) { + this.context = new PomHandlerContext(entityRepo); + this.entityRepo = entityRepo; + this.parseCreator = parseCreator; + this.includePaths = includePaths; + this.IBindingResolver = bindingResolver; + context.startFile(fileFullPath); + } + + @Override + public void enterElement(ElementContext ctx) { + String name = ctx.Name(0).getText(); + if (name.equals("project")) { + pomCoords.push(new PomCoords()); + currentEntity = new PomArtifactEntity("", context.currentFile(), entityRepo.generateId()); + } else if (name.equals("plugin")) { + pomCoords.push(new PomCoords()); + currentExpression = new Expression(entityRepo.generateId()); + currentExpression.setRawType(""); + } else if (name.equals("dependency")) { + pomCoords.push(new PomCoords()); + currentVar = new VarEntity(GenericName.build(""), GenericName.build(""), + currentEntity, entityRepo.generateId()); + } else if (name.equals("parent")) { + pomCoords.push(new PomCoords()); + pomParent = new PomParent(""); + } + + // Add attribute + else if (name.equals("groupId")) { + peekPomCoords().groupId = getContentValueOf(ctx); + } else if (name.equals("artifactId")) { + peekPomCoords().artifactId = getContentValueOf(ctx); + } else if (name.equals("version")) { + peekPomCoords().version = getContentValueOf(ctx); + } else if ("properties".equals(getParentElementName(ctx))) { + if (ctx.content() != null) { + currentEntity.addProperty(name, getContentValueOf(ctx)); + } + } + super.enterElement(ctx); + } + + private PomCoords peekPomCoords() { + return pomCoords.peek(); + } + + private String getContentValueOf(ElementContext ctx) { + String text = ctx.content().getText(); + if (text == null) + return ""; + if (text.contains("${")) + text = currentEntity.replaceProperty(text); + return text; + } + + @Override + public void exitElement(ElementContext ctx) { + String name = ctx.Name(0).getText(); + if (name.equals("project")) { + if (pomParent != null) { + peekPomCoords().fillFromIfNull(pomParent); + } + currentEntity.setRawName(peekPomCoords().getGenericNamePath()); + currentEntity.setQualifiedName(currentEntity.getRawName().uniqName()); + entityRepo.add(currentEntity); + pomCoords.pop(); + } else if (name.equals("plugin")) { + peekPomCoords().sureFillVersion(includePaths); + currentExpression.setRawType(peekPomCoords().getGenericNamePath()); + currentEntity.addExpression(ctx, currentExpression); + pomCoords.pop(); + } else if (name.equals("dependency")) { + peekPomCoords().sureFillVersion(includePaths); + currentVar.setRawType(peekPomCoords().getGenericNamePath()); + //TODO: Depends currently has a limitation: var name cannot be same as var type + //To be fixed in future + currentVar.setRawName(new GenericName("_" + currentVar.getRawType().getName())); + currentEntity.addVar(currentVar); + pomCoords.pop(); + } else if (name.equals("parent")) { + pomParent.buildFrom(peekPomCoords()); + context.currentFile().addImport(pomParent); + String parentFileName = new PomLocator(includePaths, pomParent).getLocation(); + if (parentFileName != null) { + FileParser importedParser = parseCreator.createFileParser(); + try { + importedParser.parse(parentFileName); + } catch (IOException e) { + System.err.println("error occurred during parse "+ parentFileName); + } + } + pomCoords.pop(); + context.currentFile().inferLocalLevelEntities(IBindingResolver); + } + super.exitElement(ctx); + } + + + private String getParentElementName(ParserRuleContext node) { + node = node.getParent(); + if (node == null) + return "project"; + node = node.getParent(); + if (!(node instanceof ElementContext)) + return "project"; + + ElementContext p = (ElementContext) node; + String name = p.Name().get(0).getText(); + return name; + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/PreprocessorHandler.java b/src/main/java/com/educoder/bridge/tmp/PreprocessorHandler.java new file mode 100644 index 0000000..decaedf --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/PreprocessorHandler.java @@ -0,0 +1,126 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.extractor.cpp.cdt; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +import org.eclipse.cdt.core.dom.ast.IASTFileLocation; +import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIncludeStatement; +import org.eclipse.cdt.core.dom.ast.IASTPreprocessorStatement; +import org.eclipse.cdt.internal.core.parser.scanner.ScannerUtility; + +import multilang.depends.util.file.FileTraversal; +import multilang.depends.util.file.FileTraversal.IFileVisitor; +import multilang.depends.util.file.FileUtil; + +public class PreprocessorHandler { + private List includePaths; + private String inputSrcPath; + private HashSet allFiles = new HashSet<>(); + public PreprocessorHandler(String inputSrcPath, List includePaths){ + this.inputSrcPath = inputSrcPath; + this.includePaths = includePaths; + buildAllFiles(); + } + + class AllFileVisitor implements IFileVisitor{ + @Override + public void visit(File file) { + try { + allFiles.add(file.getCanonicalPath()); + } catch (IOException e) { + } + } + } + private void buildAllFiles() { + allFiles = new HashSet<>(); + AllFileVisitor v = new AllFileVisitor(); + if (inputSrcPath!=null) { + FileTraversal ft = new FileTraversal(v,false,true); + ft.travers(inputSrcPath); + } + for (String includePath:includePaths) { + FileTraversal ft = new FileTraversal(v,false,true); + ft.travers(includePath); + } + } + + private boolean existFile(String checkPath) { + checkPath = FileUtil.uniformPath(checkPath); + return allFiles.contains(checkPath); + } + + public List getDirectIncludedFiles(IASTPreprocessorStatement[] statements, String fileLocation) { + ArrayList includedFullPathNames = new ArrayList<>(); + for (int statementIndex=0;statementIndex searchPath = new ArrayList<>(); + searchPath.add(locationDir); + searchPath.addAll(includePaths); + for (String includePath:searchPath) { + String checkPath = ScannerUtility.createReconciledPath(includePath,path); + if (existFile(checkPath)) { + return FileUtil.uniqFilePath(checkPath); + } + } + return ""; + } + + + public List getIncludePaths() { + return includePaths; + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/PythonBuiltInType.java b/src/main/java/com/educoder/bridge/tmp/PythonBuiltInType.java new file mode 100644 index 0000000..934c870 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/PythonBuiltInType.java @@ -0,0 +1,96 @@ +package depends.extractor.python; + +import depends.entity.FunctionCall; +import depends.entity.FunctionEntity; +import depends.entity.GenericName; +import depends.entity.TypeEntity; +import depends.entity.repo.BuiltInType; +import depends.relations.FunctionMatcher; + +import java.util.ArrayList; +import java.util.List; + +public class PythonBuiltInType extends BuiltInType { + + public static String[] BUILT_IN_FUNCTIONS = { "abs", "delattr", "hash", "memoryview", "set", "all", "dict", "help", + "min", "setattr", "any", "dir", "hex", "next", "slice", "exit", "ascii", "divmod", "id", "object", "sorted", + "bin", "enumerate", "input", "oct", "staticmethod", "bool", "eval", "int", "open", "str", "breakpoint", + "exec", "isinstance", "ord", "sum", "bytearray", "filter", "issubclass", "pow", "super", "bytes", "float", + "iter", "print", "tuple", "callable", "format", "len", "property", "type", "chr", "frozenset", "list", + "range", "vars", "classmethod", "getattr", "locals", "repr", "zip", "compile", "globals", "map", "reversed", + "__import__", "complex", "hasattr", "max", "round" }; + + /** + * methods of built-in String + */ + public static String[] BUILT_IN_STRING_METHODS = { "capitalize", "center", "casefold", "count", "endswith", + "expandtabs", "encode", "find", "format", "index", "isalnum", "isalpha", "isdecimal", "isdigit", + "isidentifier", "islower", "isnumeric", "isprintable", "isspace", "istitle", "isupper", "join", "ljust", + "rjust", "lower", "upper", "swapcase", "lstrip", "rstrip", "strip", "partition", "maketrans", "rpartition", + "translate", "replace", "rfind", "rindex", "split", "rsplit", "splitlines", "startswith", "title", "zfill", + "format_map" }; + + /** + * methods of built-in List + */ + public static String[] BUILT_IN_LIST_METHODS = { "index", "append", "extend", "insert", "remove", "count", "pop", + "reverse", "sort", "copy", "clear" }; + + /** + * methods of built-in Tuple + */ + public static String[] BUILT_IN_TUPLE_METHODS = { "index", "count" }; + + /** + * methods of built-in Dict + */ + public static String[] BUILT_IN_DICT_METHODS = { "clear", "copy", "fromkeys", "get", "items", "keys", "popitem", + "setdefault", "pop", "values", "update", }; + + /** + * methods of built-in Set + */ + public static String[] BUILT_IN_SET_METHODS = { "remove", "add", "copy", "clear", "difference", "difference_update", + "discard", "intersection", "intersection_update", "isdisjoint", "issubset", "pop", "symmetric_difference", + "symmetric_difference_update", "union", "update" }; + + /** + * methods of built-in File + */ + public static String[] BUILT_IN_FILE_METHOD = { "close", "flush", "fileno", "isatty", "next", "read", "readline", + "readlines", "seek", "tell", "truncate", "write", "writelines" }; + + List buildInTypes = new ArrayList<>(); + + + + public PythonBuiltInType() { + addBuildInType(BUILT_IN_FILE_METHOD); + addBuildInType(BUILT_IN_SET_METHODS); + addBuildInType(BUILT_IN_DICT_METHODS); + addBuildInType(BUILT_IN_TUPLE_METHODS); + addBuildInType(BUILT_IN_LIST_METHODS); + addBuildInType(BUILT_IN_STRING_METHODS); + } + + private void addBuildInType(String[] methods) { + TypeEntity type = new TypeEntity(); + for (String method:methods) { + FunctionEntity func = new FunctionEntity(GenericName.build(method),type,-1,GenericName.build("")); + type.addFunction(func); + } + buildInTypes.add(type); + } + + @Override + public boolean isBuildInTypeMethods(List functionCalls) { + for (TypeEntity type:buildInTypes) { + FunctionMatcher functionMatcher = new FunctionMatcher(type.getFunctions()); + if (functionMatcher.containsAll(functionCalls)) { + return true; + } + } + return false; + } + +} diff --git a/src/main/java/com/educoder/bridge/tmp/PythonCodeListener.java b/src/main/java/com/educoder/bridge/tmp/PythonCodeListener.java new file mode 100644 index 0000000..e406a13 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/PythonCodeListener.java @@ -0,0 +1,371 @@ +package depends.extractor.python.union; + +import depends.entity.*; +import depends.entity.repo.EntityRepo; +import depends.extractor.python.NameAliasImport; +import depends.extractor.python.PythonHandlerContext; +import depends.extractor.python.PythonParser.*; +import depends.extractor.python.PythonParserBaseListener; +import depends.extractor.IncludedFileLocator; +import depends.importtypes.FileImport; +import depends.relations.IBindingResolver; +import multilang.depends.util.file.FileUtil; +import org.antlr.v4.runtime.ParserRuleContext; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class PythonCodeListener extends PythonParserBaseListener{ + private final PythonHandlerContext context; + private final ExpressionUsage expressionUsage; + private final EntityRepo entityRepo; + private final IncludedFileLocator includeFileLocator; + private final PythonProcessor pythonProcessor; + private final IBindingResolver bindingResolver; + public PythonCodeListener(String fileFullPath, EntityRepo entityRepo, IBindingResolver bindingResolver, + IncludedFileLocator includeFileLocator, PythonProcessor pythonProcessor) { + this.context = new PythonHandlerContext(entityRepo, bindingResolver); + this.expressionUsage = new ExpressionUsage(context, entityRepo, bindingResolver); + FileEntity fileEntity = context.startFile(fileFullPath); + this.entityRepo = entityRepo; + this.includeFileLocator = includeFileLocator; + this.bindingResolver = bindingResolver; + this.pythonProcessor = pythonProcessor; + + String dir = FileUtil.uniqFilePath(FileUtil.getLocatedDir(fileFullPath)); + if (entityRepo.getEntity(dir) == null) { + PackageEntity pacakgeEntity = new PackageEntity(dir, entityRepo.generateId()); + entityRepo.add(pacakgeEntity); + } + + PackageEntity packageEntity = (PackageEntity) entityRepo.getEntity(dir); + String moduleName = fileEntity.getRawName().uniqName().substring(packageEntity.getRawName().uniqName().length() + 1); + if (moduleName.endsWith(".py")) + moduleName.substring(0, moduleName.length() - ".py".length()); + Entity.setParent(fileEntity, packageEntity); + packageEntity.addChild(FileUtil.getShortFileName(fileEntity.getRawName().uniqName()).replace(".py", ""), fileEntity); + } + @Override + public void enterImport_stmt(Import_stmtContext ctx) { + String moduleName = null; + for(Dotted_as_nameContext dotted_as_name:ctx.dotted_as_names().dotted_as_name()){ + moduleName = getName(dotted_as_name.dotted_name()); + String aliasName = moduleName; + if (dotted_as_name.name()!=null) { + aliasName = dotted_as_name.name().getText(); + } + List fullNames = foundImportedModuleOrPackage(0,moduleName); + + for (String fullName:fullNames) { + if (FileUtil.existFile(fullName) && !(FileUtil.isDirectory(fullName))) { + context.foundNewImport(new FileImport(fullName)); + } + context.foundNewImport(new NameAliasImport(fullName, entityRepo.getEntity(fullName), aliasName)); + } + } + super.enterImport_stmt(ctx); + } + @Override + public void enterFrom_stmt(From_stmtContext ctx) { + String moduleName = null; + if (ctx.dotted_name() != null) { + moduleName = ctx.dotted_name().getText(); + } + int prefixDotCount = getDotCounter(ctx); + + List fullNames = foundImportedModuleOrPackage(prefixDotCount, moduleName); + for (String fullName:fullNames) { + + if (ctx.import_as_names() == null) {// import * + ContainerEntity moduleEntity = (ContainerEntity) (entityRepo.getEntity(fullName)); + if (moduleEntity != null) { + for (Entity child:moduleEntity.getChildren()) { + context.foundNewImport(new NameAliasImport(fullName, child, child.getRawName().uniqName())); + context.foundNewAlias(child.getRawName(), child); + } + if (moduleEntity instanceof PackageEntity) { + for (Entity file : moduleEntity.getChildren()) { + if (file instanceof FileEntity) { + String fileName = file.getRawName().uniqName().substring(fullName.length()); + context.foundNewImport(new NameAliasImport(file.getRawName().uniqName(), file, fileName)); + context.foundNewAlias(GenericName.build(FileUtil.getShortFileName(fileName).replace(".py", "")), file); + }else { + context.foundNewImport(new NameAliasImport(file.getRawName().uniqName(), file, file.getRawName().uniqName())); + context.foundNewAlias(GenericName.build(FileUtil.getShortFileName(file.getRawName().uniqName())), file); + } + } + } + if (moduleEntity instanceof FileEntity) { + String fileName = moduleEntity.getRawName().uniqName().substring(fullName.length()); + context.foundNewImport(new NameAliasImport(moduleEntity.getRawName().uniqName(), moduleEntity, fileName)); + } + } + } else { + for (Import_as_nameContext item : ctx.import_as_names().import_as_name()) { + String name = item.name(0).getText(); + String alias = name; + if (item.name().size() > 1) + alias = item.name(1).getText(); + if (FileUtil.isDirectory(fullName)) { + String fileName = fullName + File.separator + name + ".py"; + if (FileUtil.existFile(fileName) && !(FileUtil.isDirectory(fileName))) { + context.foundNewImport(new FileImport(fileName)); + } + } + if (FileUtil.existFile(fullName) && !(FileUtil.isDirectory(fullName))) { + context.foundNewImport(new FileImport(fullName)); + } + Entity itemEntity = bindingResolver.resolveName(entityRepo.getEntity(fullName), GenericName.build(name), true); + if (itemEntity != null) { + context.foundNewAlias(GenericName.build(alias), itemEntity); + context.foundNewImport(new NameAliasImport(itemEntity.getQualifiedName(), itemEntity, alias)); + } + } + } + } + super.enterFrom_stmt(ctx); + } + + + private int getDotCounter(From_stmtContext ctx) { + int total = 0; + if (ctx.DOT()!=null){ + total = ctx.DOT().size(); + } + if (ctx.ELLIPSIS()!=null) { + total += ctx.ELLIPSIS().size()*3; + } + return total; + } + private List foundImportedModuleOrPackage(int prefixDotCount, String originalName) { + String dir = FileUtil.getLocatedDir(context.currentFile().getRawName().uniqName()); + String preFix = ""; + for (int i = 0; i < prefixDotCount - 1; i++) { + preFix = preFix + ".." + File.separator; + } + dir = dir + File.separator + preFix; + String fullName = null; + if (originalName != null) { + String importedName = originalName.replace(".", File.separator); + fullName = includeFileLocator.uniqFileName(dir, importedName); + if (fullName == null) { + fullName = includeFileLocator.uniqFileName(dir, importedName + ".py"); + } + } else { + fullName = FileUtil.uniqFilePath(dir); + } + if (fullName != null) { + if (FileUtil.isDirectory(fullName)) { + if (!FileUtil.uniqFilePath(fullName).equals(FileUtil.uniqFilePath(dir))) { + File d = new File(fullName); + File[] files = d.listFiles(); + for (File file : files) { + if (!file.isDirectory()) { + if (file.getAbsolutePath().endsWith(".py")) { + visitIncludedFile(FileUtil.uniqFilePath(file.getAbsolutePath())); + } + } + } + } + } else { + visitIncludedFile(fullName); + } + } + ArrayList r = new ArrayList<>(); + if (fullName==null) return r; + r.add(fullName); + if (FileUtil.existFile(fullName+File.separator + "__init__.py")) { + r.add( fullName+File.separator +"__init__.py"); + } + return r; + } + + private void visitIncludedFile(String fullName) { + PythonFileParser importedParser = new PythonFileParser(entityRepo, includeFileLocator, bindingResolver, + pythonProcessor); + try { + importedParser.parse(fullName); + } catch (IOException e) { + e.printStackTrace(); + } + } + @Override + public void enterFuncdef(FuncdefContext ctx) { + String functionName =""; + String name = getName(ctx.name()); + if (name!=null) { + functionName = name; + } + + FunctionEntity method = context.foundMethodDeclarator(functionName,ctx.getStart().getLine()); + if (ctx.typedargslist()!=null) { + List parameters = getParameterList(ctx.typedargslist().def_parameters()); + for (String param : parameters) { + VarEntity paramEntity = context.addMethodParameter(param); + if (param.equals("self")) { + paramEntity.setType(context.currentType()); + } + } + } + super.enterFuncdef(ctx); + } + + @Override + public void exitFuncdef(FuncdefContext ctx) { + context.exitLastedEntity(); + super.exitFuncdef(ctx); + } + + + @Override + public void enterClassdef(ClassdefContext ctx) { + String name = getName(ctx.name()); + TypeEntity type = context.foundNewType(name, ctx.getStart().getLine()); + List baseClasses = getArgList(ctx.arglist()); + baseClasses.forEach(base -> type.addExtends(GenericName.build(base))); + + super.enterClassdef(ctx); + } + + + @Override + public void exitClassdef(ClassdefContext ctx) { + context.exitLastedEntity(); + super.exitClassdef(ctx); + } + + private List getParameterList(List def_parameters) { + List result = new ArrayList<>(); + for (Def_parametersContext params:def_parameters) { + for (Def_parameterContext param:params.def_parameter()) { + String p = getName( param.named_parameter().name()); + result.add(p); + } + + } + return result; + } + private String getName(NameContext name) { + return name.getText(); + } + + private String getName(Dotted_nameContext dotted_name) { + return dotted_name.getText(); + } + + private String getDecoratedName(Class_or_func_def_stmtContext ctx) { + if (ctx.classdef()!=null) { + return getName(ctx.classdef().name()); + }else if (ctx.funcdef()!=null) { + return getName(ctx.funcdef().name()); + } + return null; + } + + private List getArgList(ArglistContext arglist) { + List r = new ArrayList<>(); + if (arglist==null) return r; + if (arglist.argument() == null) return r; + if (arglist.argument().isEmpty()) return r; + arglist.argument().forEach(arg->r.add(arg.getText())); + return r; + } + + + /** + * class_or_func_def_stmt: decorator+ (classdef | funcdef); + */ + @Override + public void exitClass_or_func_def_stmt(Class_or_func_def_stmtContext ctx) { + String decoratedName = getDecoratedName(ctx); + if (decoratedName!=null) { + Entity entity = context.foundEntityWithName(GenericName.build(decoratedName)); + entity.setLine(ctx.getStart().getLine()); + + if (entity instanceof DecoratedEntity) { + for (DecoratorContext decorator: ctx.decorator()) { + String decoratorName = getName(decorator.dotted_name()); + ((DecoratedEntity) entity).addAnnotation(GenericName.build(decoratorName)); + } + } + } + super.exitClass_or_func_def_stmt(ctx); + } + + + @Override + public void enterGlobal_stmt(Global_stmtContext ctx) { + for (NameContext name:ctx.name()){ + VarEntity var = context.foundGlobalVarDefinition(context.currentFile(), name.getText(),ctx.getStart().getLine()); + } + super.enterGlobal_stmt(ctx); + } + + @Override + public void enterEveryRule(ParserRuleContext ctx) { + expressionUsage.foundExpression(ctx); + super.enterEveryRule(ctx); + } + @Override + public void enterExpr_stmt(Expr_stmtContext ctx) { + expressionUsage.startExpr(); + super.enterExpr_stmt(ctx); + } + @Override + public void exitExpr_stmt(Expr_stmtContext ctx) { + expressionUsage.stopExpr(); + super.exitExpr_stmt(ctx); + } + @Override + public void enterDel_stmt(Del_stmtContext ctx) { + expressionUsage.startExpr(); + super.enterDel_stmt(ctx); + } + @Override + public void exitDel_stmt(Del_stmtContext ctx) { + expressionUsage.stopExpr(); + super.exitDel_stmt(ctx); + } + @Override + public void enterReturn_stmt(Return_stmtContext ctx) { + expressionUsage.startExpr(); + super.enterReturn_stmt(ctx); + } + @Override + public void exitReturn_stmt(Return_stmtContext ctx) { + expressionUsage.stopExpr(); + super.exitReturn_stmt(ctx); + } + @Override + public void enterRaise_stmt(Raise_stmtContext ctx) { + expressionUsage.startExpr(); + super.enterRaise_stmt(ctx); + } + @Override + public void exitRaise_stmt(Raise_stmtContext ctx) { + expressionUsage.stopExpr(); + super.exitRaise_stmt(ctx); + } + @Override + public void enterYield_stmt(Yield_stmtContext ctx) { + expressionUsage.startExpr(); + super.enterYield_stmt(ctx); + } + @Override + public void exitYield_stmt(Yield_stmtContext ctx) { + expressionUsage.stopExpr(); + super.exitYield_stmt(ctx); + } + @Override + public void enterAssert_stmt(Assert_stmtContext ctx) { + expressionUsage.startExpr(); + super.enterAssert_stmt(ctx); + } + @Override + public void exitAssert_stmt(Assert_stmtContext ctx) { + expressionUsage.stopExpr(); + super.exitAssert_stmt(ctx); + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/PythonImportTest.java b/src/main/java/com/educoder/bridge/tmp/PythonImportTest.java new file mode 100644 index 0000000..6b36c4f --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/PythonImportTest.java @@ -0,0 +1,284 @@ +package depends.extractor.python; + +import depends.deptypes.DependencyType; +import depends.entity.Entity; +import depends.entity.FileEntity; +import depends.entity.FunctionEntity; +import depends.entity.MultiDeclareEntities; +import depends.extractor.python.union.PythonFileParser; +import multilang.depends.util.file.FileUtil; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; + +public class PythonImportTest extends PythonParserTest { + @Before + public void setUp() { + super.init(); + } + + @Test + public void should_parse_module_in_same_package() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/imported_a.py", + "./src/test/resources/python-code-examples/importing.py", + }; + + for (String src:srcs) { + PythonFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + Entity file = entityRepo.getEntity(FileUtil.uniqFilePath(srcs[1])); + this.assertContainsRelation(file, DependencyType.IMPORT,FileUtil.uniqFilePath(srcs[0])); + } + + + @Test + public void should_parse_module_in_same_package_order_robust() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/importing.py", + "./src/test/resources/python-code-examples/imported_a.py", + }; + + for (String src:srcs) { + PythonFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + Entity file = entityRepo.getEntity(FileUtil.uniqFilePath(srcs[0])); + this.assertContainsRelation(file, DependencyType.IMPORT,FileUtil.uniqFilePath(srcs[1])); + this.assertContainsRelation(file, DependencyType.CALL,withPackageName(srcs[0],"foo")); + } + + @Test + public void should_parse_module_in_same_package_with_alias() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/importing_with_alias.py", + "./src/test/resources/python-code-examples/imported_a.py", + }; + + for (String src:srcs) { + PythonFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + Entity file = entityRepo.getEntity(FileUtil.uniqFilePath(srcs[0])); + this.assertContainsRelation(file, DependencyType.IMPORT,FileUtil.uniqFilePath(srcs[1])); + this.assertContainsRelation(file, DependencyType.CALL,withPackageName(srcs[0],"foo")); + } + + @Test + public void should_parse_module_in_from_importing() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/from_importing.py", + "./src/test/resources/python-code-examples/imported_a.py", + }; + + for (String src:srcs) { + PythonFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + Entity file = entityRepo.getEntity(FileUtil.uniqFilePath(srcs[0])); + this.assertContainsRelation(file, DependencyType.CALL,withPackageName(srcs[0],"foo")); + this.assertContainsRelation(file, DependencyType.IMPORT,FileUtil.uniqFilePath(srcs[1])); + } + + + @Test + public void should_parse_module_in_from_importing_star() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/from_importing_star.py", + "./src/test/resources/python-code-examples/imported_a.py", + }; + + for (String src:srcs) { + PythonFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + Entity file = entityRepo.getEntity(FileUtil.uniqFilePath(srcs[0])); + this.assertContainsRelation(file, DependencyType.CALL,withPackageName(srcs[0],"foo")); + this.assertContainsRelation(file, DependencyType.IMPORT,FileUtil.uniqFilePath(srcs[1])); + } + + + @Test + public void should_parse_import_with_multi_dots() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/from_importing_multidot.py", + "./src/test/resources/python-code-examples/pkg/imported.py", + }; + + for (String src:srcs) { + PythonFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + Entity file = entityRepo.getEntity(FileUtil.uniqFilePath(srcs[0])); + this.assertContainsRelation(file, DependencyType.IMPORT,FileUtil.uniqFilePath(srcs[1])); + this.assertContainsRelation(file, DependencyType.CALL,withPackageName(srcs[1],"foo")); + } + + @Test + public void should_parse_import_with_prefix_dots() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/import_with_dir/importing.py", + "./src/test/resources/python-code-examples/import_with_dir/imported_a.py", + "./src/test/resources/python-code-examples/import_with_dir/subdir/importing.py", + "./src/test/resources/python-code-examples/import_with_dir/subdir/importing2.py", + }; + + for (String src:srcs) { + PythonFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + this.assertContainsRelation(entityRepo.getEntity(FileUtil.uniqFilePath(srcs[0])), DependencyType.IMPORT,FileUtil.uniqFilePath(srcs[1])); + this.assertContainsRelation(entityRepo.getEntity(FileUtil.uniqFilePath(srcs[2])), DependencyType.IMPORT,FileUtil.uniqFilePath(srcs[1])); + this.assertContainsRelation(entityRepo.getEntity(FileUtil.uniqFilePath(srcs[3])), DependencyType.IMPORT,FileUtil.uniqFilePath(srcs[1])); + } + + @Test + public void should_parse_import_with_prefix_dots2() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/import_with_dir/subdir/importing2.py", + }; + + for (String src:srcs) { + PythonFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + } + + + @Test + public void should_import_from_package__init__file() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/import_from_init/importing.py", + "./src/test/resources/python-code-examples/import_from_init/pkg/__init__.py", + }; + + for (String src:srcs) { + PythonFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + this.assertContainsRelation(entityRepo.getEntity(FileUtil.uniqFilePath(srcs[0])), DependencyType.IMPORT,FileUtil.uniqFilePath(srcs[1])); + } + + + @Test + public void should_not_bypass_import_in_same_dir() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/import_of_same_dir/pkg/importing.py", + "./src/test/resources/python-code-examples/import_of_same_dir/pkg/a.py", + }; + + for (String src:srcs) { + PythonFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + this.assertContainsRelation(entityRepo.getEntity(FileUtil.uniqFilePath(srcs[0])), DependencyType.IMPORT,FileUtil.uniqFilePath(srcs[1])); + } + + + @Test + public void should_resolve_symbols_of_imported_in_same_dir() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/import_of_same_dir/pkg/importing.py", + "./src/test/resources/python-code-examples/import_of_same_dir/pkg/a.py", + }; + + for (String src:srcs) { + PythonFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + FunctionEntity func = (FunctionEntity) entityRepo.getEntity(withPackageName(srcs[0],"test")); + this.assertContainsRelation(func, DependencyType.CALL, withPackageName(srcs[1],"foo")); + } + + + + @Test + public void should_resolve_symbols_of_ducktyping() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/duck_typing/forest.py", + "./src/test/resources/python-code-examples/duck_typing/animals.py", + "./src/test/resources/python-code-examples/duck_typing/controller.py", + }; + + for (String src:srcs) { + PythonFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + + MultiDeclareEntities funcs = (MultiDeclareEntities) entityRepo.getEntity(withPackageName(srcs[1],"in_the_forest")); + Entity func = funcs.getEntities().get(0); + + this.assertContainsRelation(func, DependencyType.CALL, withPackageName(srcs[1],"Duck.quack")); + this.assertContainsRelation(func, DependencyType.CALL, withPackageName(srcs[1],"Doge.quack")); + this.assertContainsRelation(func, DependencyType.CALL, withPackageName(srcs[1],"Bird.quack")); + } + + @Test + public void should_resolve_symbols_of_ducktyping2() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/duck_typing/animals.py", + "./src/test/resources/python-code-examples/duck_typing/forest.py", + "./src/test/resources/python-code-examples/duck_typing/controller.py", + }; + + for (String src:srcs) { + PythonFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + + MultiDeclareEntities funcs = (MultiDeclareEntities) entityRepo.getEntity(withPackageName(srcs[1],"in_the_forest")); + Entity func = funcs.getEntities().get(0); + this.assertContainsRelation(func, DependencyType.CALL, withPackageName(srcs[0],"Duck.quack")); + this.assertContainsRelation(func, DependencyType.CALL, withPackageName(srcs[0],"Bird.quack")); + this.assertContainsRelation(func, DependencyType.CALL, withPackageName(srcs[0],"Doge.quack")); + } + + @Test + public void should_resolve_imported_symbols() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/import_from_init/use_imported.py", + "./src/test/resources/python-code-examples/import_from_init/pkg/__init__.py", + "./src/test/resources/python-code-examples/import_from_init/pkg/core.py", + }; + + for (String src:srcs) { + PythonFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + FunctionEntity func = (FunctionEntity) entityRepo.getEntity(withPackageName(srcs[0],"bar")); + this.assertContainsRelation(func, DependencyType.CALL, withPackageName(srcs[2],"C")); + } + + @Test + public void should_resolve_imported_vars() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/import_from_var/use_imported.py", + "./src/test/resources/python-code-examples/import_from_var/pkg/core.py", + }; + + for (String src:srcs) { + PythonFileParser parser = createParser(); + parser.parse(src); + } + resolveAllBindings(); + FileEntity f = (FileEntity) entityRepo.getEntity(FileUtil.uniqFilePath(srcs[0])); + this.assertContainsRelation(f, DependencyType.CALL, withPackageName(srcs[1],"Core.foo")); + } + +} diff --git a/src/main/java/com/educoder/bridge/tmp/PythonLexerBase.java b/src/main/java/com/educoder/bridge/tmp/PythonLexerBase.java new file mode 100644 index 0000000..ca98458 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/PythonLexerBase.java @@ -0,0 +1,186 @@ +package depends.extractor.python.union; + +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CommonToken; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.Token; + +import depends.extractor.python.PythonLexer; + +import java.util.Stack; + +public abstract class PythonLexerBase extends Lexer { + public static int TabSize = 8; + + // The amount of opened braces, brackets and parenthesis. + private int _opened; + + // The stack that keeps track of the indentation level. + private Stack _indents = new Stack<>(); + + // A circular buffer where extra tokens are pushed on (see the NEWLINE and WS lexer rules). + private int _firstTokensInd; + private int _lastTokenInd; + private Token[] _buffer = new Token[32]; + private Token _lastToken; + + protected PythonLexerBase(CharStream input) { + super(input); + } + + @Override + public void emit(Token token) { + super.setToken(token); + + if (_buffer[_firstTokensInd] != null) + { + _lastTokenInd = IncTokenInd(_lastTokenInd); + + if (_lastTokenInd == _firstTokensInd) + { + // Enlarge buffer + Token[] newArray = new Token[_buffer.length * 2]; + int destInd = newArray.length - (_buffer.length - _firstTokensInd); + + System.arraycopy(_buffer, 0, newArray, 0, _firstTokensInd); + System.arraycopy(_buffer, _firstTokensInd, newArray, destInd, _buffer.length - _firstTokensInd); + + _firstTokensInd = destInd; + _buffer = newArray; + } + } + + _buffer[_lastTokenInd] = token; + _lastToken = token; + } + + @Override + public Token nextToken() { + // Check if the end-of-file is ahead and there are still some DEDENTS expected. + if (_input.LA(1) == EOF && _indents.size() > 0) + { + if (_buffer[_lastTokenInd] == null || _buffer[_lastTokenInd].getType() != PythonLexer.LINE_BREAK) + { + // First emit an extra line break that serves as the end of the statement. + emit(PythonLexer.LINE_BREAK); + } + + // Now emit as much DEDENT tokens as needed. + while (_indents.size() != 0) + { + emit(PythonLexer.DEDENT); + _indents.pop(); + } + } + + Token next = super.nextToken(); + + if (_buffer[_firstTokensInd] == null) + { + return next; + } + + Token result = _buffer[_firstTokensInd]; + _buffer[_firstTokensInd] = null; + + if (_firstTokensInd != _lastTokenInd) + { + _firstTokensInd = IncTokenInd(_firstTokensInd); + } + + return result; + } + + protected void HandleNewLine() { + emit(PythonLexer.NEWLINE, HIDDEN, getText()); + + char next = (char) _input.LA(1); + + // Process whitespaces in HandleSpaces + if (next != ' ' && next != '\t' && IsNotNewLineOrComment(next)) + { + ProcessNewLine(0); + } + } + + protected void HandleSpaces() { + char next = (char) _input.LA(1); + + if ((_lastToken == null || _lastToken.getType() == PythonLexer.NEWLINE) && IsNotNewLineOrComment(next)) + { + // Calculates the indentation of the provided spaces, taking the + // following rules into account: + // + // "Tabs are replaced (from left to right) by one to eight spaces + // such that the total number of characters up to and including + // the replacement is a multiple of eight [...]" + // + // -- https://docs.python.org/3.1/reference/lexical_analysis.html#indentation + + int indent = 0; + String text = getText(); + + for (int i = 0; i < text.length(); i++) { + indent += text.charAt(i) == '\t' ? TabSize - indent % TabSize : 1; + } + + ProcessNewLine(indent); + } + + emit(PythonLexer.WS, HIDDEN, getText()); + } + + protected void IncIndentLevel() { + _opened++; + } + + protected void DecIndentLevel() { + if (_opened > 0) { + --_opened; + } + } + + private boolean IsNotNewLineOrComment(char next) { + return _opened == 0 && next != '\r' && next != '\n' && next != '\f' && next != '#'; + } + + private void ProcessNewLine(int indent) { + emit(PythonLexer.LINE_BREAK); + + int previous = _indents.size() == 0 ? 0 : _indents.peek(); + + if (indent > previous) + { + _indents.push(indent); + emit(PythonLexer.INDENT); + } + else + { + // Possibly emit more than 1 DEDENT token. + while (_indents.size() != 0 && _indents.peek() > indent) + { + emit(PythonLexer.DEDENT); + _indents.pop(); + } + } + } + + private int IncTokenInd(int ind) { + return (ind + 1) % _buffer.length; + } + + private void emit(int tokenType) { + emit(tokenType, DEFAULT_TOKEN_CHANNEL, ""); + } + + private void emit(int tokenType, int channel, String text) { + int charIndex = getCharIndex(); + CommonToken token = new CommonToken(_tokenFactorySourcePair, tokenType, channel, charIndex - text.length(), charIndex); + token.setLine(getLine()); + token.setCharPositionInLine(getCharPositionInLine()); + token.setText(text); + + emit(token); + } +} + diff --git a/src/main/java/com/educoder/bridge/tmp/PythonParameterTypeDedudceTest.java b/src/main/java/com/educoder/bridge/tmp/PythonParameterTypeDedudceTest.java new file mode 100644 index 0000000..8fcb6fd --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/PythonParameterTypeDedudceTest.java @@ -0,0 +1,118 @@ +package depends.extractor.python; + +import depends.entity.CandidateTypes; +import depends.entity.FunctionEntity; +import depends.entity.TypeEntity; +import depends.entity.VarEntity; +import depends.extractor.FileParser; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.*; + +public class PythonParameterTypeDedudceTest extends PythonParserTest { + @Before + public void setUp() { + super.init(); + } + + @Test + public void test_deduce_type_of_parameter() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/deducetype_parameter.py", + }; + + for (String src:srcs) { + FileParser parser = createFileParser(); + parser.parse(src); + } + resolveAllBindings(); + String name = withPackageName(srcs[0],"test"); + FunctionEntity function = (FunctionEntity)( entityRepo.getEntity(name)); + VarEntity var = function.lookupVarLocally("t1"); + TypeEntity type = var.getType(); + assertTrue(type instanceof CandidateTypes); + assertEquals(2,((CandidateTypes)type).getCandidateTypes().size()); + } + + + @Test + public void test_deduce_type_of_builtIn() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/deducetype_builtin.py", + }; + + for (String src:srcs) { + FileParser parser = createFileParser(); + parser.parse(src); + } + resolveAllBindings(); + String name = withPackageName(srcs[0],"test"); + FunctionEntity function = (FunctionEntity)( entityRepo.getEntity(name)); + VarEntity var = function.lookupVarLocally("t1"); + TypeEntity type = var.getType(); + assertTrue(type == null); + } + + + @Test + public void test_deduce_type_of_builtIn_cannot_override() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/deducetype_builtin.py", + }; + + for (String src:srcs) { + FileParser parser = createFileParser(); + parser.parse(src); + } + resolveAllBindings(); + String name = withPackageName(srcs[0],"test2"); + FunctionEntity function = (FunctionEntity)( entityRepo.getEntity(name)); + VarEntity var = function.lookupVarLocally("t1"); + TypeEntity type = var.getType(); + assertTrue(type instanceof CandidateTypes); + assertEquals(1,((CandidateTypes)type).getCandidateTypes().size()); + } + + + @Ignore + public void test_deduce_type_of_non_param_var() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/deducetype_nonparam.py", + }; + + for (String src:srcs) { + FileParser parser = createFileParser(); + parser.parse(src); + } + resolveAllBindings(); + String name = withPackageName(srcs[0],"test"); + FunctionEntity function = (FunctionEntity)( entityRepo.getEntity(name)); + VarEntity var = function.lookupVarLocally("t2"); + TypeEntity type = var.getType(); + assertTrue(type instanceof CandidateTypes); + assertEquals(2,((CandidateTypes)type).getCandidateTypes().size()); + } + + @Test + public void test_expression_count_should_be_() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/python-code-examples/expression_reload_issue_test.py", + }; + + for (String src:srcs) { + FileParser parser = createFileParser(); + parser.parse(src); + } + resolveAllBindings(); + String name = withPackageName(srcs[0],"test_expression"); + FunctionEntity function = (FunctionEntity)( entityRepo.getEntity(name)); + assertNotNull(function); + } + + +} + diff --git a/src/main/java/com/educoder/bridge/tmp/RelationCounter.java b/src/main/java/com/educoder/bridge/tmp/RelationCounter.java new file mode 100644 index 0000000..c3a163f --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/RelationCounter.java @@ -0,0 +1,246 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.relations; + +import depends.deptypes.DependencyType; +import depends.entity.*; +import depends.entity.repo.EntityRepo; +import depends.extractor.AbstractLangProcessor; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +public class RelationCounter { + + private Collection entities; + private IBindingResolver bindingResolver; + private EntityRepo repo; + private boolean callAsImpl; + private AbstractLangProcessor langProcessor; + + public RelationCounter(EntityRepo repo, AbstractLangProcessor langProcessor, IBindingResolver bindingResolver) { + this.entities = repo.getFileEntities(); + this.bindingResolver = bindingResolver; + this.repo = repo; + this.callAsImpl = langProcessor.supportCallAsImpl(); + this.langProcessor = langProcessor; + } + + public void computeRelations() { + entities.forEach(entity-> + computeRelationOf(entity)); + } + + private void computeRelationOf(Entity entity) { + if (!entity.inScope()) + return; + if (entity instanceof FileEntity) { + computeImports((FileEntity)entity); + } + else if (entity instanceof FunctionEntity) { + computeFunctionRelations((FunctionEntity)entity); + } + else if (entity instanceof TypeEntity) { + computeTypeRelations((TypeEntity)entity); + } + if (entity instanceof ContainerEntity) { + computeContainerRelations((ContainerEntity)entity); + } + entity.getChildren().forEach(child->computeRelationOf(child)); + } + + + + private void computeContainerRelations(ContainerEntity entity) { + for (VarEntity var:entity.getVars()) { + if (var.getType()!=null) + entity.addRelation(buildRelation(entity,DependencyType.CONTAIN,var.getType(),var.getLocation())); + for (Entity type:var.getResolvedTypeParameters()) { + var.addRelation(buildRelation(var, DependencyType.PARAMETER,type,type.getLocation())); + } + } + for (Entity type:entity.getResolvedAnnotations()) { + entity.addRelation(buildRelation(entity,DependencyType.ANNOTATION,type)); + } + for (Entity type:entity.getResolvedTypeParameters()) { + entity.addRelation(buildRelation(entity,DependencyType.USE,type)); + } + for (ContainerEntity mixin:entity.getResolvedMixins()) { + entity.addRelation(buildRelation(entity,DependencyType.MIXIN,mixin)); + } + + entity.reloadExpression(repo); + if (!bindingResolver.isEagerExpressionResolve()) + { + entity.resolveExpressions(bindingResolver); + } + for (Expression expression:entity.expressionList()){ + if (expression.isStatement()) { + continue; + } + Entity referredEntity = expression.getReferredEntity(); + addRelationFromExpression(entity, expression, referredEntity); + } + entity.clearExpressions(); + } + + private void addRelationFromExpression(ContainerEntity entity, Expression expression, Entity referredEntity) { + + if (referredEntity==null) { + return; + } + if (referredEntity.getId()<0){ + return; + } + if (referredEntity instanceof MultiDeclareEntities) { + for (Entity e:((MultiDeclareEntities)referredEntity).getEntities()) { + addRelationFromExpression(entity,expression,e); + } + return; + } + + boolean matched = false; + if (expression.isCall()) { + /* if it is a FunctionEntityProto, add Relation to all Impl Entities*/ + if (callAsImpl && referredEntity instanceof FunctionEntityProto) { + if (entity.getAncestorOfType(FileEntity.class).getId().equals(referredEntity.getAncestorOfType(FileEntity.class).getId())){ + entity.addRelation(buildRelation(entity,DependencyType.CALL,referredEntity,expression.getLocation())); + }else { + Entity multiDeclare = repo.getEntity(referredEntity.getQualifiedName()); + if (multiDeclare instanceof MultiDeclareEntities) { + MultiDeclareEntities m = (MultiDeclareEntities) multiDeclare; + List entities = m.getEntities().stream().filter(item -> (item instanceof FunctionEntityImpl)) + .collect(Collectors.toList()); + for (Entity e : entities) { + entity.addRelation(expression, buildRelation(entity, DependencyType.IMPLLINK, e, expression.getLocation())); + matched = true; + } + } + } + } + entity.addRelation(buildRelation(entity,DependencyType.CALL,referredEntity,expression.getLocation())); + matched = true; + + } + if (expression.isCreate()) { + entity.addRelation(buildRelation(entity,DependencyType.CREATE,referredEntity,expression.getLocation())); + matched = true; + } + if (expression.isThrow()) { + entity.addRelation(buildRelation(entity,DependencyType.THROW,referredEntity,expression.getLocation())); + matched = true; + } + if (expression.isCast()) { + entity.addRelation(buildRelation(entity,DependencyType.CAST,referredEntity,expression.getLocation())); + matched = true; + } + if (!matched) { + if (callAsImpl && repo.getEntity(referredEntity.getQualifiedName()) instanceof MultiDeclareEntities && + (referredEntity instanceof VarEntity ||referredEntity instanceof FunctionEntity)) { + if (entity.getAncestorOfType(FileEntity.class).getId().equals(referredEntity.getAncestorOfType(FileEntity.class).getId())){ + entity.addRelation(buildRelation(entity,DependencyType.USE,referredEntity,expression.getLocation())); + }else { + MultiDeclareEntities m = (MultiDeclareEntities) (repo.getEntity(referredEntity.getQualifiedName())); + for (Entity e : m.getEntities()) { + if (e == referredEntity) { + entity.addRelation(expression, buildRelation(entity, DependencyType.USE, e, expression.getLocation())); + } else { + entity.addRelation(expression, buildRelation(entity, DependencyType.IMPLLINK, e, expression.getLocation())); + } + matched = true; + } + } + } + else { + entity.addRelation(expression,buildRelation(entity,DependencyType.USE,referredEntity,expression.getLocation())); + } + } + } + + private Relation buildRelation(Entity from, String type, Entity referredEntity) { + return buildRelation(from,type,referredEntity,from.getLocation()); + } + + private Relation buildRelation(Entity from, String type, Entity referredEntity,Location location) { + if (referredEntity instanceof AliasEntity) { + if (from.getAncestorOfType(FileEntity.class).equals(referredEntity.getAncestorOfType(FileEntity.class))) { + AliasEntity alias = ((AliasEntity) referredEntity); + if (alias.deepResolve()!=null) { + referredEntity = alias.deepResolve(); + } + } + } + if (this.langProcessor==null) + return new Relation(type,referredEntity,location); + return new Relation(langProcessor.getRelationMapping(type),referredEntity,location); + } + + private void computeTypeRelations(TypeEntity type) { + for (TypeEntity superType:type.getInheritedTypes()) { + type.addRelation(buildRelation(type,DependencyType.INHERIT,superType)); + } + for (TypeEntity interfaceType:type.getImplementedTypes()) { + type.addRelation(buildRelation(type,DependencyType.IMPLEMENT,interfaceType)); + } + } + + private void computeFunctionRelations(FunctionEntity func) { + for (Entity returnType:func.getReturnTypes()) { + func.addRelation(buildRelation(func,DependencyType.RETURN,returnType.getActualReferTo())); + } + for (VarEntity parameter:func.getParameters()) { + if (parameter.getType()!=null) + func.addRelation(buildRelation(func,DependencyType.PARAMETER,parameter.getActualReferTo())); + } + for (Entity throwType:func.getThrowTypes()) { + func.addRelation(buildRelation(func,DependencyType.THROW,throwType)); + } + for (Entity type:func.getResolvedTypeParameters()) { + func.addRelation(buildRelation(func,DependencyType.PARAMETER,type)); + } + if (func instanceof FunctionEntityImpl) { + FunctionEntityImpl funcImpl = (FunctionEntityImpl)func; + if(funcImpl.getImplemented()!=null) { + func.addRelation(buildRelation(func,DependencyType.IMPLEMENT,funcImpl.getImplemented())); + } + } + } + + private void computeImports(FileEntity file) { + Collection imports = file.getImportedRelationEntities(); + if (imports==null) return; + for (Entity imported:imports) { + if (imported instanceof FileEntity) + { + if (((FileEntity)imported).isInProjectScope()) + file.addRelation(buildRelation(file,DependencyType.IMPORT,imported)); + }else { + file.addRelation(buildRelation(file,DependencyType.IMPORT,imported)); + } + } + } + +} diff --git a/src/main/java/com/educoder/bridge/tmp/RubyHandlerContext.java b/src/main/java/com/educoder/bridge/tmp/RubyHandlerContext.java new file mode 100644 index 0000000..2ce057c --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/RubyHandlerContext.java @@ -0,0 +1,105 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.extractor.ruby; + +import depends.entity.Entity; +import depends.entity.PackageEntity; +import depends.entity.repo.EntityRepo; +import depends.extractor.FileParser; +import depends.extractor.HandlerContext; +import depends.extractor.IncludedFileLocator; +import depends.extractor.ParserCreator; +import depends.importtypes.FileImport; +import depends.relations.IBindingResolver; +import multilang.depends.util.file.FileUtil; + +import java.util.Collection; + +public class RubyHandlerContext extends HandlerContext { + + private IncludedFileLocator includedFileLocator; + private ParserCreator parserCreator; + public RubyHandlerContext(EntityRepo entityRepo, + IncludedFileLocator includedFileLocator, + IBindingResolver bindingResolver, ParserCreator parserCreator) { + super(entityRepo, bindingResolver); + this.includedFileLocator = includedFileLocator; + this.parserCreator = parserCreator; + } + + public Entity foundNamespace(String nampespaceName, Integer startLine) { + Entity parentEntity = currentFile(); + if (latestValidContainer()!=null) + parentEntity = latestValidContainer(); + PackageEntity pkgEntity = new PackageEntity(nampespaceName, parentEntity,idGenerator.generateId()); + entityRepo.add(pkgEntity); + entityStack.push(pkgEntity); + return pkgEntity; + } + + public void processSpecialFuncCall(String methodName, Collection params, Integer startLine) { + // Handle Import relation + if(methodName.equals("require") || methodName.equals("require_relative")) { + for (String importedFilename:params) { + if (!importedFilename.endsWith(".rb")) importedFilename = importedFilename + ".rb"; + String dir = FileUtil.getLocatedDir(currentFile().getRawName().uniqName()); + String inclFileName = includedFileLocator.uniqFileName(dir,importedFilename); + if (inclFileName==null) { + System.err.println("Warning: cannot found included file " + importedFilename ); + continue; + } + FileParser importedParser = parserCreator.createFileParser(); + try { + importedParser.parse(inclFileName); + } catch (Exception e) { + System.err.println("parsing error in "+inclFileName); + } + foundNewImport(new FileImport(inclFileName)); + } + } + // Handle Extend relation + else if (methodName.equals("extend")) { + for (String moduleName:params) { + foundExtends(moduleName); + } + } + // Handle mixin relation + else if (methodName.equals("include")) { + for (String moduleName:params) { + foundMixin(moduleName); + } + } + // attribute methods + else if (methodName.equals("attr_accessor")||methodName.equals("attr_writer")||methodName.equals("attr_reader")) { + for (String name:params) { + name = name.replace(":", ""); //remove symbol + foundMethodDeclarator(name,startLine); + } + } + } + + + +} diff --git a/src/main/java/com/educoder/bridge/tmp/RubyParserHelper.java b/src/main/java/com/educoder/bridge/tmp/RubyParserHelper.java new file mode 100644 index 0000000..bfb80fb --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/RubyParserHelper.java @@ -0,0 +1,165 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.extractor.ruby.jruby; + +import java.util.ArrayList; +import java.util.List; + +import org.jrubyparser.ast.AssignableNode; +import org.jrubyparser.ast.CallNode; +import org.jrubyparser.ast.ClassVarAsgnNode; +import org.jrubyparser.ast.ClassVarDeclNode; +import org.jrubyparser.ast.Colon2Node; +import org.jrubyparser.ast.Colon3Node; +import org.jrubyparser.ast.ConstDeclNode; +import org.jrubyparser.ast.DAsgnNode; +import org.jrubyparser.ast.DefsNode; +import org.jrubyparser.ast.FCallNode; +import org.jrubyparser.ast.GlobalAsgnNode; +import org.jrubyparser.ast.INameNode; +import org.jrubyparser.ast.InstAsgnNode; +import org.jrubyparser.ast.ListNode; +import org.jrubyparser.ast.LocalAsgnNode; +import org.jrubyparser.ast.MultipleAsgnNode; +import org.jrubyparser.ast.Node; +import org.jrubyparser.ast.UnaryCallNode; +import org.jrubyparser.ast.VCallNode; + +import depends.entity.ContainerEntity; +import depends.extractor.ruby.RubyBuiltInType; +import depends.extractor.ruby.RubyHandlerContext; + +public class RubyParserHelper { + private static RubyParserHelper inst = new RubyParserHelper(); + public static RubyParserHelper getInst() { + return inst; + } + + private RubyBuiltInType buildInType; + + private RubyParserHelper() { + this.buildInType = new RubyBuiltInType(); + } + + + public String getName(Node node) { + String name = ""; + if (node instanceof INameNode) { + name = ((INameNode)node).getName(); + if (node instanceof Colon2Node) { + Node left = ((Colon2Node)node).getLeftNode(); + if (left!=null) { + name = getName(left) + "."+name; + } + }else if (node instanceof Colon3Node) { + name = "."+name; + } + } + return name.length()>0?name:null; + } + + public boolean isFunctionCall(Node ctx) { + return ctx instanceof CallNode || + ctx instanceof FCallNode || + ctx instanceof UnaryCallNode || + ctx instanceof VCallNode; + } + + public List getName(AssignableNode ctx) { + List names = new ArrayList<>(); + if (ctx instanceof ClassVarAsgnNode) { + names.add(((ClassVarAsgnNode)ctx).getName()); + }else if (ctx instanceof ClassVarDeclNode) { + names.add(((ClassVarDeclNode)ctx).getName()); + }else if (ctx instanceof ConstDeclNode) { + names.add(((ConstDeclNode)ctx).getName()); + }else if (ctx instanceof DAsgnNode) { + names.add(((DAsgnNode)ctx).getName()); + }else if (ctx instanceof GlobalAsgnNode) { + names.add(((GlobalAsgnNode)ctx).getName()); + }else if (ctx instanceof InstAsgnNode) { + names.add(((InstAsgnNode)ctx).getName()); + }else if (ctx instanceof LocalAsgnNode) { + names.add(((LocalAsgnNode)ctx).getName()); + }else if (ctx instanceof MultipleAsgnNode) { + ListNode pre = ((MultipleAsgnNode)ctx).getPre(); + if (pre!=null) { + //TODO: support multiple assignment + } + } + return names; + } + + public String getReciever(Node ctx) { + Node receiver = null; + if (ctx instanceof CallNode) { + receiver = ((CallNode)ctx).getReceiver(); + }else if (ctx instanceof DefsNode) { + receiver = ((DefsNode)ctx).getReceiver(); + } + if (receiver==null) { + return null; + } + if (receiver instanceof INameNode) { + return this.getName(receiver); + } + return null; + } + + public ContainerEntity getScopeOfVar(AssignableNode node, RubyHandlerContext context) { + if (node instanceof LocalAsgnNode) return context.lastContainer(); + if (node instanceof InstAsgnNode) return context.currentType(); + if (node instanceof ClassVarAsgnNode) return context.currentType(); + if (node instanceof GlobalAsgnNode) return context.globalScope(); + if (node instanceof DAsgnNode) return context.lastContainer(); + return context.lastContainer(); + } + + public boolean isArithMeticOperator(String name) { + return name.equals("+") || + name.equals("-") || + name.equals("*") || + name.equals("/") || + name.equals("**") || + name.equals("%") || + name.equals("&") || + name.equals("<") || + name.equals("<=") || + name.equals(">") || + name.equals(">=") || + name.equals("==") || + name.equals("!=") || + name.equals("===") || + name.equals("<<") || + name.equals(">>") || + name.equals("~") || + name.equals("!") || + name.equals("^"); + } + + public boolean isCommonOperator(String name) { + return this.buildInType.isBuildInMethod(name); + } +} diff --git a/src/main/java/com/educoder/bridge/tmp/RubyVarTest.java b/src/main/java/com/educoder/bridge/tmp/RubyVarTest.java new file mode 100644 index 0000000..fcd7dcc --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/RubyVarTest.java @@ -0,0 +1,172 @@ +package depends.extractor.ruby; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import org.junit.Before; +import org.junit.Test; + +import depends.entity.ContainerEntity; +import depends.entity.FunctionEntity; +import depends.entity.TypeEntity; +import depends.entity.repo.EntityRepo; +import depends.extractor.FileParser; + +public class RubyVarTest extends RubyParserTest { + @Before + public void setUp() { + super.init(); + } + + @Test + public void test_parameter_should_be_created() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/ruby-code-examples/auto_var.rb", + }; + + for (String src:srcs) { + FileParser parser = createFileParser(); + parser.parse(src); + } + FunctionEntity function = (FunctionEntity)(entityRepo.getEntity("method1")); + assertEquals(1,function.getParameters().size()); + assertContainsParametersWithRawName(function, "param1"); + } + + + @Test + public void test_var_should_be_created_if_not_declared() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/ruby-code-examples/auto_var.rb", + }; + + for (String src:srcs) { + FileParser parser = createFileParser(); + parser.parse(src); + } + FunctionEntity function = (FunctionEntity)(entityRepo.getEntity("method_with_local_var")); + assertEquals(1,function.getVars().size()); + assertContainsVarWithRawName(function, "var_1"); + } + + + @Test + public void test_var_should_only_create_once() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/ruby-code-examples/auto_var.rb", + }; + + for (String src:srcs) { + FileParser parser = createFileParser(); + parser.parse(src); + } + FunctionEntity function = (FunctionEntity)(entityRepo.getEntity("method_with_local_var_2times")); + assertEquals(1,function.getVars().size()); + } + + @Test + public void test_var_should_not_be_created_if_it_actually_parameter() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/ruby-code-examples/auto_var.rb", + }; + + for (String src:srcs) { + FileParser parser = createFileParser(); + parser.parse(src); + } + FunctionEntity function = (FunctionEntity)(entityRepo.getEntity("method_without_local_var_and_param")); + assertEquals(0,function.getVars().size()); + } + + + @Test + public void test_var_should_not_be_created_if_it_actually_a_file_level_var() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/ruby-code-examples/auto_var.rb", + }; + + for (String src:srcs) { + FileParser parser = createFileParser(); + parser.parse(src); + } + FunctionEntity function = (FunctionEntity)(entityRepo.getEntity("method_access_file_level_var")); + assertEquals(0,function.getVars().size()); + } + + @Test + public void test_var_should_not_be_created_with_a_lot_of_levels() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/ruby-code-examples/auto_var.rb", + }; + + for (String src:srcs) { + FileParser parser = createFileParser(); + parser.parse(src); + } + FunctionEntity function = (FunctionEntity)(entityRepo.getEntity("M.C.method")); + assertEquals(1,function.getVars().size()); + } + @Test + public void test_var_should_not_be_created_not_in_scope() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/ruby-code-examples/auto_var.rb", + }; + + for (String src:srcs) { + FileParser parser = createFileParser(); + parser.parse(src); + } + FunctionEntity function = (FunctionEntity)(entityRepo.getEntity("M.C.method2")); + assertEquals(1,function.getVars().size()); + } + + + @Test + public void test_var_should_created_at_class_level() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/ruby-code-examples/auto_var.rb", + }; + + for (String src:srcs) { + FileParser parser = createFileParser(); + parser.parse(src); + } + TypeEntity c = (TypeEntity)(entityRepo.getEntity("M.C")); + assertEquals(3,c.getVars().size()); + assertContainsVarWithRawName(c,"class_level_var"); + assertContainsVarWithRawName(c,"class_var"); + assertContainsVarWithRawName(c,"instance_var"); + } + + @Test + public void test_var_in_block() throws IOException { + String[] srcs = new String[] { + "./src/test/resources/ruby-code-examples/auto_var.rb", + }; + + for (String src:srcs) { + FileParser parser = createFileParser(); + parser.parse(src); + } + ContainerEntity c = (ContainerEntity)(entityRepo.getEntity("Block")); + assertEquals(1,c.getVars().size()); + assertContainsVarWithRawName(c,"a"); + } + + @Test + public void test_global_var()throws IOException { + String[] srcs = new String[] { + "./src/test/resources/ruby-code-examples/auto_var.rb", + }; + + for (String src:srcs) { + FileParser parser = createFileParser(); + parser.parse(src); + } + ContainerEntity c = (ContainerEntity)(entityRepo.getEntity(EntityRepo.GLOBAL_SCOPE_NAME)); + assertEquals(1,c.getVars().size()); + assertContainsVarWithRawName(c,"global_var"); + } +} + diff --git a/src/main/java/com/educoder/bridge/tmp/TypeEntity.java b/src/main/java/com/educoder/bridge/tmp/TypeEntity.java new file mode 100644 index 0000000..6282ef4 --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/TypeEntity.java @@ -0,0 +1,204 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.entity; + +import depends.relations.IBindingResolver; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; + +public class TypeEntity extends ContainerEntity { + static final public TypeEntity buildInType = new TypeEntity(GenericName.build("built-in"), null, -1); + static final public TypeEntity genericParameterType = new TypeEntity(GenericName.build("T"), null, -3); + Collection inheritedTypes = new ArrayList<>(); + Collection implementedTypes = new ArrayList<>(); + Collection inhertedTypeIdentifiers; + Collection implementedIdentifiers; + TypeEntity inheritedType; + public TypeEntity() {} + public TypeEntity(GenericName simpleName, Entity parent, Integer id) { + super(simpleName, parent, id); + inhertedTypeIdentifiers = new ArrayList<>(); + implementedIdentifiers = new ArrayList<>(); + } + + @Override + public void inferLocalLevelEntities(IBindingResolver bindingResolver) { + inheritedTypes = new ArrayList<>(); + Collection r = identiferToEntities(bindingResolver, this.inhertedTypeIdentifiers); + if (r!=null) { + r.forEach(item -> { + Entity typeItem = getTypeEntity(item); + if (typeItem !=null) { + inheritedTypes.add((TypeEntity) typeItem); + }else { + System.err.println(item.getRawName() + " expected a type, but actually it is "+ item.getClass().getSimpleName()); + } + }); + } + inheritedTypes.remove(this); + + implementedTypes = new ArrayList<>(); + r = identiferToEntities(bindingResolver, this.implementedIdentifiers); + if (r!=null) { + r.forEach(item -> { + Entity typeItem = getTypeEntity(item); + if (typeItem !=null) { + implementedTypes.add((TypeEntity) typeItem); + }else { + System.err.println(item.getRawName() + " expected a type, but actually it is "+ item.getClass().getSimpleName()); + } + }); + } + implementedTypes.remove(this); + if (inheritedTypes.size() > 0) + inheritedType = inheritedTypes.iterator().next(); + super.inferLocalLevelEntities(bindingResolver); + } + + private Entity getTypeEntity(Entity item) { + if (item==null) return null; + if (item instanceof TypeEntity) return item; + if (item instanceof MultiDeclareEntities) return ((MultiDeclareEntities)item).getType(); + if (item instanceof AliasEntity) return item.getType(); + return null; + } + public void addImplements(GenericName typeName) { + if (typeName==null) { + return; + } + if (typeName.equals(this.getRawName())) + return; + if (implementedIdentifiers.contains(typeName)) + return; + if (typeName.equals(this.rawName)) + return; + this.implementedIdentifiers.add(typeName); + } + + public void addExtends(GenericName typeName) { + if (typeName==null) { + return; + } + if (typeName.equals(this.getRawName())) + return; + if (inhertedTypeIdentifiers.contains(typeName)) + return; + if (typeName.equals(this.rawName)) + return; + this.inhertedTypeIdentifiers.add(typeName); + } + + public Collection getInheritedTypes() { + return inheritedTypes; + } + + public Collection getImplementedTypes() { + return implementedTypes; + } + + public TypeEntity getInheritedType() { + return inheritedType; + } + + @Override + public FunctionEntity lookupFunctionLocally(GenericName functionName) { + Collection searchedTypes = new ArrayList<>(); + return lookupFunctionLocally(functionName,searchedTypes); + } + + private FunctionEntity lookupFunctionLocally(GenericName functionName, Collection searched) { + if (searched.contains(this)) return null; + searched.add(this); + FunctionEntity func = super.lookupFunctionLocally(functionName); + if (func != null) + return func; + for (TypeEntity inhertedType : getInheritedTypes()) { + func = inhertedType.lookupFunctionLocally(functionName, searched); + if (func != null) + break; + } + if (func != null) + return func; + for (TypeEntity implType : getImplementedTypes()) { + func = implType.lookupFunctionLocally(functionName,searched); + if (func != null) + break; + } + return func; + } + + @Override + public VarEntity lookupVarLocally(GenericName varName) { + Collection searchedTypes = new ArrayList<>(); + return lookupVarLocally(varName,searchedTypes); + } + + private VarEntity lookupVarLocally(GenericName varName, Collection searched) { + if (searched.contains(this)) return null; + searched.add(this); + VarEntity var = super.lookupVarLocally(varName); + if (var != null) + return var; + for (TypeEntity inhertedType : getInheritedTypes()) { + var = inhertedType.lookupVarLocally(varName,searched); + if (var != null) + break; + } + if (var != null) + return var; + for (TypeEntity implType : getImplementedTypes()) { + var = implType.lookupVarLocally(varName,searched); + if (var != null) + break; + } + return var; + } + + @Override + public TypeEntity getType() { + return this; + } + @Override + public Entity getByName(String name, HashSet searched) { + Entity entity = super.getByName(name, searched); + if (entity!=null) + return entity; + for (TypeEntity child:getInheritedTypes()) { + if (searched.contains(child)) continue; + entity = child.getByName(name, searched); + if (entity!=null) return entity; + } + for (TypeEntity child:getImplementedTypes()) { + if (searched.contains(child)) continue; + entity = child.getByName(name,searched); + if (entity!=null) return entity; + } + return null; + } + + +} diff --git a/src/main/java/com/educoder/bridge/tmp/UnsolvedSymbolDumper.java b/src/main/java/com/educoder/bridge/tmp/UnsolvedSymbolDumper.java new file mode 100644 index 0000000..874369f --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/UnsolvedSymbolDumper.java @@ -0,0 +1,96 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.format.detail; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.util.HashSet; +import java.util.Set; +import java.util.TreeMap; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import depends.extractor.UnsolvedBindings; +import multilang.depends.util.file.strip.LeadingNameStripper; + +public class UnsolvedSymbolDumper{ + private Set unsolved; + private String name; + private String outputDir; + private LeadingNameStripper leadingNameStripper; + + public UnsolvedSymbolDumper(Set unsolved, String name, String outputDir, LeadingNameStripper leadingNameStripper) { + this.unsolved = unsolved; + this.name = name; + this.outputDir = outputDir; + this.leadingNameStripper = leadingNameStripper; + } + + public void output() { + outputDetail(); + outputGrouped(); + } + + private void outputGrouped() { + TreeMap> grouped = new TreeMap>(); + for (UnsolvedBindings symbol: unsolved) { + String depended = symbol.getRawName(); + String from = leadingNameStripper.stripFilename(symbol.getSourceDisplay()); + Set list = grouped.get(depended); + if (list==null) { + list = new HashSet<>(); + grouped.put(depended, list); + } + list.add(from); + } + ObjectMapper om = new ObjectMapper(); + om.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false); + om.configure(SerializationFeature.INDENT_OUTPUT, true); + om.setSerializationInclusion(Include.NON_NULL); + try { + om.writerWithDefaultPrettyPrinter().writeValue(new File(outputDir + File.separator + name +"-PotentialExternalDependencies.json"), grouped); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void outputDetail() { + PrintWriter writer; + try { + writer = new PrintWriter(outputDir + File.separator + name +"-PotentialExternalDependencies.txt"); + for (UnsolvedBindings symbol: unsolved) { + String source = leadingNameStripper.stripFilename(symbol.getSourceDisplay()); + writer.println(""+symbol.getRawName()+", "+source); + } + writer.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/com/educoder/bridge/tmp/VarEntity.java b/src/main/java/com/educoder/bridge/tmp/VarEntity.java new file mode 100644 index 0000000..f81bdbc --- /dev/null +++ b/src/main/java/com/educoder/bridge/tmp/VarEntity.java @@ -0,0 +1,106 @@ +/* +MIT License + +Copyright (c) 2018-2019 Gang ZHANG + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package depends.entity; + +import depends.relations.IBindingResolver; + +import java.util.ArrayList; +import java.util.List; + +public class VarEntity extends ContainerEntity { + private GenericName rawType; + private TypeEntity type; + private List functionCalls; + public VarEntity() { + + } + public VarEntity(GenericName simpleName, GenericName rawType, Entity parent, int id) { + super(simpleName, parent,id); + this.rawType = rawType; + } + + public void setRawType(GenericName rawType) { + this.rawType =rawType; + } + + public GenericName getRawType() { + return rawType; + } + + @Override + public TypeEntity getType() { + return type; + } + + public void setType(TypeEntity type) { + this.type = type; + } + + @Override + public void inferLocalLevelEntities(IBindingResolver bindingResolver) { + super.inferLocalLevelEntities(bindingResolver); + Entity entity = bindingResolver.resolveName(this, rawType, true); + if (entity!=null) { + this.setActualReferTo(entity); + type = entity.getType(); + if (type==null) { + if (((ContainerEntity)getParent()).isGenericTypeParameter(rawType)) { + type = TypeEntity.genericParameterType; + } + } + } + if (type==null) { + fillCandidateTypes(bindingResolver); + } + } + + public List getCalledFunctions() { + if (this.functionCalls!=null) + return functionCalls; + return new ArrayList<>(); + } + + public void addFunctionCall(GenericName fname) { + if (this.functionCalls==null) + { + functionCalls = new ArrayList<>(); + } + this.functionCalls.add(new FunctionCall(fname)); + } + + public void fillCandidateTypes(IBindingResolver bindingResolver) { + if (!bindingResolver.isEagerExpressionResolve()) return; + if (type!=null && !(type instanceof CandidateTypes)) return ; //it is a strong type lang, do not need deduce candidate types + if (functionCalls==null) return; + if (functionCalls.size()==0) return; //no information avaliable for type deduction + if (this.rawType==null) { + List candidateTypes = bindingResolver.calculateCandidateTypes(this,this.functionCalls); + if (candidateTypes.size()>0) { + this.type = new CandidateTypes(candidateTypes, bindingResolver.getRepo().generateId()); + bindingResolver.getRepo().add(this.type); + } + } + } +} diff --git a/src/main/java/com/educoder/bridge/utils/Base64Util.java b/src/main/java/com/educoder/bridge/utils/Base64Util.java new file mode 100644 index 0000000..3ef71ed --- /dev/null +++ b/src/main/java/com/educoder/bridge/utils/Base64Util.java @@ -0,0 +1,52 @@ +package com.educoder.bridge.utils; + + +import org.apache.commons.codec.binary.Base64; + +import java.nio.charset.StandardCharsets; + +/** + * Created by guange on 23/02/2017. + */ +public class Base64Util { + + /** + * base64编码 + * + * @param code + * @return + */ + public static String encode(String code) { + byte[] encode = Base64.encodeBase64URLSafe(code.getBytes(StandardCharsets.UTF_8)); + return new String(encode, StandardCharsets.UTF_8); + } + + public static byte[] encodeBytes(byte[] codes) { + return Base64.encodeBase64(codes); + } + + + /** + * base64解码 + * + * @param code + * @return + */ + public static String decode(String code) { + byte[] decode = Base64.decodeBase64(code); + return new String(decode, StandardCharsets.UTF_8); + } + + /** + * base64再解码,把原本的非URL safe编码转换为URL safe编码 + * + * @param code + * @return + */ + public static String reencode(String code) { + String str = decode(code); + str = str.replace("\n", "\r\n"); + return encode(str); + } + +} diff --git a/src/main/resources/applicationContext.xml b/src/main/resources/applicationContext.xml new file mode 100644 index 0000000..620ed30 --- /dev/null +++ b/src/main/resources/applicationContext.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + 0 + UTF-8 + 0.########## + yyyy-MM-dd HH:mm:ss + true + ignore + + + + + + + + + + + + diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..fe3a44e --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,47 @@ + + + + + + + + + %d{MM-dd HH:mm:ss} [%thread] %-5level %logger{30} %M %L - %msg%n + + + + DEBUG + + + + + + UTF-8 + + %d{MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{30} %M - %msg%n%L + + + ERROR + + + ${log_path}error.%d{MM-dd}.log + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/mvc-dispatcher-servlet.xml b/src/main/webapp/WEB-INF/mvc-dispatcher-servlet.xml new file mode 100644 index 0000000..d81863c --- /dev/null +++ b/src/main/webapp/WEB-INF/mvc-dispatcher-servlet.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + .ftl + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/pages/index.ftl b/src/main/webapp/WEB-INF/pages/index.ftl new file mode 100644 index 0000000..bd2f036 --- /dev/null +++ b/src/main/webapp/WEB-INF/pages/index.ftl @@ -0,0 +1,61 @@ + + + + + + + JWebssh + + + + + + + + + + + + + + +
+

JWebssh

+
+
+ +
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+
+ +
+ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..e71e27e --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,63 @@ + + + + educoder bridge + + + + contextConfigLocation + classpath:applicationContext.xml + + + + + org.springframework.web.context.ContextLoaderListener + + + + + + logbackConfigLocation + classpath:logback.xml + + + + ch.qos.logback.ext.spring.web.LogbackConfigListener + + + + mvc-dispatcher + org.springframework.web.servlet.DispatcherServlet + 1 + + + + mvc-dispatcher + / + + + + encodingFilter + org.springframework.web.filter.CharacterEncodingFilter + + encoding + UTF-8 + + + forceEncoding + true + + + + encodingFilter + /* + + + + + + + diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html new file mode 100644 index 0000000..e8bbf0a --- /dev/null +++ b/src/main/webapp/index.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/webapp/static/css/main.css b/src/main/webapp/static/css/main.css new file mode 100644 index 0000000..d0bb3c4 --- /dev/null +++ b/src/main/webapp/static/css/main.css @@ -0,0 +1,53 @@ +.aside { + text-align: center; + background: #1f8dd6; + height: 100px; + color: #fff; + vertical-align: middle; + line-height: 100px; + font-size: 30px +} + +#main { + margin-top: 20px; +} + +#ratio-group { + float: right; +} + +.pure-item { + margin: 0 auto 10px; + width: 300px; + position: relative; +} + +.pure-radio { + margin-left: 10px; +} + +.pure-item:after { + content: ""; + display: table; + clear: both; +} + +.pure-item label { + float: left; + line-height: 34px; +} + +.pure-item input { + float: right; +} + +.terminal { + float: none; + border: #000 solid 5px; + font-family: "Monaco", "DejaVu Sans Mono", "Liberation Mono", monospace; + font-size: 11px; + color: #f0f0f0; + width: 600px; + background: #000; + box-shadow: rgba(0, 0, 0, 0.8) 2px 2px 20px; +} \ No newline at end of file diff --git a/src/main/webapp/static/css/pure-min.css b/src/main/webapp/static/css/pure-min.css new file mode 100644 index 0000000..f0aa374 --- /dev/null +++ b/src/main/webapp/static/css/pure-min.css @@ -0,0 +1,11 @@ +/*! +Pure v0.6.0 +Copyright 2014 Yahoo! Inc. All rights reserved. +Licensed under the BSD License. +https://github.com/yahoo/pure/blob/master/LICENSE.md +*/ +/*! +normalize.css v^3.0 | MIT License | git.io/normalize +Copyright (c) Nicolas Gallagher and Jonathan Neal +*/ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap;-ms-align-content:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{display:inline-block;zoom:1;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000\9}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129FEA}.pure-form input:not([type]):focus{outline:0;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin solid #129FEA;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked input[type=file],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-list,.pure-menu-item{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-link,.pure-menu-heading{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-separator{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-allow-hover:hover>.pure-menu-children,.pure-menu-active>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;padding:.5em 0}.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar{display:none}.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-link,.pure-menu-disabled,.pure-menu-heading{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent}.pure-menu-active>.pure-menu-link,.pure-menu-link:hover,.pure-menu-link:focus{background-color:#eee}.pure-menu-selected .pure-menu-link,.pure-menu-selected .pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0} \ No newline at end of file diff --git a/src/main/webapp/static/css/reset.css b/src/main/webapp/static/css/reset.css new file mode 100644 index 0000000..248aacf --- /dev/null +++ b/src/main/webapp/static/css/reset.css @@ -0,0 +1,44 @@ + +/* ------- This is the CSS Reset ------- */ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, +abbr, acronym, address, big, cite, code, del, +dfn, em, img, ins, kbd, q, s, samp, small, +strike, strong, sub, sup, tt, var, u, i, center, +dl, dt, dd, ol, ul, li, fieldset, form, label, +legend, table, caption, tbody, tfoot, thead, tr, +th, td, article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, menu, +nav, output, ruby, section, summary, time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} + +/* ------- HTML5 display-role reset for older browsers ------- */ + +article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} + diff --git a/src/main/webapp/static/css/style.css b/src/main/webapp/static/css/style.css new file mode 100644 index 0000000..e2073ba --- /dev/null +++ b/src/main/webapp/static/css/style.css @@ -0,0 +1,187 @@ +/* + * + * Template Name: Fullscreen Login + * Description: Login Template with Fullscreen Background Slideshow + * Author: Anli Zaimi + * Author URI: http://azmind.com + * + */ + + +body { + background: #f8f8f8; + font-family: 'PT Sans', Helvetica, Arial, sans-serif; + text-align: center; + color: #fff; +} + +.page-container { + margin: 120px auto 0 auto; +} + +h1 { + font-size: 30px; + font-weight: 700; + text-shadow: 0 1px 4px rgba(0,0,0,.2); +} + +form { + position: relative; + width: 305px; + margin: 15px auto 0 auto; + text-align: center; +} + +input { + width: 270px; + height: 42px; + margin-top: 25px; + padding: 0 15px; + background: #2d2d2d; /* browsers that don't support rgba */ + background: rgba(45,45,45,.15); + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + border-radius: 6px; + border: 1px solid #3d3d3d; /* browsers that don't support rgba */ + border: 1px solid rgba(255,255,255,.15); + -moz-box-shadow: 0 2px 3px 0 rgba(0,0,0,.1) inset; + -webkit-box-shadow: 0 2px 3px 0 rgba(0,0,0,.1) inset; + box-shadow: 0 2px 3px 0 rgba(0,0,0,.1) inset; + font-family: 'PT Sans', Helvetica, Arial, sans-serif; + font-size: 14px; + color: #fff; + text-shadow: 0 1px 2px rgba(0,0,0,.1); + -o-transition: all .2s; + -moz-transition: all .2s; + -webkit-transition: all .2s; + -ms-transition: all .2s; +} + +input:-moz-placeholder { color: #fff; } +input:-ms-input-placeholder { color: #fff; } +input::-webkit-input-placeholder { color: #fff; } + +input:focus { + outline: none; + -moz-box-shadow: + 0 2px 3px 0 rgba(0,0,0,.1) inset, + 0 2px 7px 0 rgba(0,0,0,.2); + -webkit-box-shadow: + 0 2px 3px 0 rgba(0,0,0,.1) inset, + 0 2px 7px 0 rgba(0,0,0,.2); + box-shadow: + 0 2px 3px 0 rgba(0,0,0,.1) inset, + 0 2px 7px 0 rgba(0,0,0,.2); +} + +button { + cursor: pointer; + width: 300px; + height: 44px; + margin-top: 25px; + padding: 0; + background: #ef4300; + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + border-radius: 6px; + border: 1px solid #ff730e; + -moz-box-shadow: + 0 15px 30px 0 rgba(255,255,255,.25) inset, + 0 2px 7px 0 rgba(0,0,0,.2); + -webkit-box-shadow: + 0 15px 30px 0 rgba(255,255,255,.25) inset, + 0 2px 7px 0 rgba(0,0,0,.2); + box-shadow: + 0 15px 30px 0 rgba(255,255,255,.25) inset, + 0 2px 7px 0 rgba(0,0,0,.2); + font-family: 'PT Sans', Helvetica, Arial, sans-serif; + font-size: 14px; + font-weight: 700; + color: #fff; + text-shadow: 0 1px 2px rgba(0,0,0,.1); + -o-transition: all .2s; + -moz-transition: all .2s; + -webkit-transition: all .2s; + -ms-transition: all .2s; +} + +button:hover { + -moz-box-shadow: + 0 15px 30px 0 rgba(255,255,255,.15) inset, + 0 2px 7px 0 rgba(0,0,0,.2); + -webkit-box-shadow: + 0 15px 30px 0 rgba(255,255,255,.15) inset, + 0 2px 7px 0 rgba(0,0,0,.2); + box-shadow: + 0 15px 30px 0 rgba(255,255,255,.15) inset, + 0 2px 7px 0 rgba(0,0,0,.2); +} + +button:active { + -moz-box-shadow: + 0 15px 30px 0 rgba(255,255,255,.15) inset, + 0 2px 7px 0 rgba(0,0,0,.2); + -webkit-box-shadow: + 0 15px 30px 0 rgba(255,255,255,.15) inset, + 0 2px 7px 0 rgba(0,0,0,.2); + box-shadow: + 0 5px 8px 0 rgba(0,0,0,.1) inset, + 0 1px 4px 0 rgba(0,0,0,.1); + + border: 0px solid #ef4300; +} + +.error { + display: none; + position: absolute; + top: 27px; + right: -55px; + width: 40px; + height: 40px; + background: #2d2d2d; /* browsers that don't support rgba */ + background: rgba(45,45,45,.25); + -moz-border-radius: 8px; + -webkit-border-radius: 8px; + border-radius: 8px; +} + +.error span { + display: inline-block; + margin-left: 2px; + font-size: 40px; + font-weight: 700; + line-height: 40px; + text-shadow: 0 1px 2px rgba(0,0,0,.1); + -o-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + +} + +.connect { + width: 305px; + margin: 35px auto 0 auto; + font-size: 18px; + font-weight: 700; + text-shadow: 0 1px 3px rgba(0,0,0,.2); +} + +.connect a { + display: inline-block; + width: 32px; + height: 35px; + margin-top: 15px; + -o-transition: all .2s; + -moz-transition: all .2s; + -webkit-transition: all .2s; + -ms-transition: all .2s; +} + +.connect a.facebook { background: url(../image/facebook.png) center center no-repeat; } +.connect a.twitter { background: url(../image/twitter.png) center center no-repeat; } + +.connect a:hover { background-position: center bottom; } + + + diff --git a/src/main/webapp/static/css/supersized.css b/src/main/webapp/static/css/supersized.css new file mode 100644 index 0000000..a507e4d --- /dev/null +++ b/src/main/webapp/static/css/supersized.css @@ -0,0 +1,34 @@ +/* + + Supersized - Fullscreen Slideshow jQuery Plugin + Version : 3.2.7 + Site : www.buildinternet.com/project/supersized + + Author : Sam Dunn + Company : One Mighty Roar (www.onemightyroar.com) + License : MIT License / GPL License + +*/ + +* { margin:0; padding:0; } +body { background:#111; height:100%; } + img { border:none; } + + #supersized-loader { position:absolute; top:50%; left:50%; z-index:0; width:60px; height:60px; margin:-30px 0 0 -30px; text-indent:-999em; background:url(../image/progress.gif) no-repeat center center;} + + #supersized { display:block; position:fixed; left:0; top:0; overflow:hidden; z-index:-999; height:100%; width:100%; } + #supersized img { width:auto; height:auto; position:relative; display:none; outline:none; border:none; } + #supersized.speed img { -ms-interpolation-mode:nearest-neighbor; image-rendering: -moz-crisp-edges; } /*Speed*/ + #supersized.quality img { -ms-interpolation-mode:bicubic; image-rendering: optimizeQuality; } /*Quality*/ + + #supersized li { display:block; list-style:none; z-index:-30; position:fixed; overflow:hidden; top:0; left:0; width:100%; height:100%; background:#111; } + #supersized a { width:100%; height:100%; display:block; } + #supersized li.prevslide { z-index:-20; } + #supersized li.activeslide { z-index:-10; } + #supersized li.image-loading { background:#111 url(../image/progress.gif) no-repeat center center; width:100%; height:100%; } + #supersized li.image-loading img{ visibility:hidden; } + #supersized li.prevslide img, #supersized li.activeslide img{ display:inline; } + + +#supersized img { max-width: none !important } + diff --git a/src/main/webapp/static/css/tooltip.css b/src/main/webapp/static/css/tooltip.css new file mode 100644 index 0000000..978506f --- /dev/null +++ b/src/main/webapp/static/css/tooltip.css @@ -0,0 +1,25 @@ +.tooltip{ + position: absolute; + max-width: 300px; + top: 3px; + left: 105%; + padding: 8px 10px; + border-radius: 5px; + color: #fff; + background: #000000; + box-shadow: 0 2px 2px 0 #7F7C7C; + white-space: nowrap; +} +.tooltip:after{ + content: ''; + position: absolute; + top: 35%; + right:100%; + margin-left: 10px; + width: 0; + height: 0; + border: 5px solid transparent; + border-right: 7px #000; + border-left-width: 7px; +} + diff --git a/src/main/webapp/static/css/xterm.css b/src/main/webapp/static/css/xterm.css new file mode 100644 index 0000000..e6f3909 --- /dev/null +++ b/src/main/webapp/static/css/xterm.css @@ -0,0 +1,2273 @@ +/** + * xterm.js: xterm, in the browser + * Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License) + * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) + * https://github.com/chjj/term.js + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Originally forked from (with the author's permission): + * Fabrice Bellard's javascript vt100 for jslinux: + * http://bellard.org/jslinux/ + * Copyright (c) 2011 Fabrice Bellard + * The original design remains. The terminal itself + * has been extended to include xterm CSI codes, among + * other features. + */ + +/* + * Default style for xterm.js + */ +::-webkit-scrollbar { width:10px; height:10px; background-color: #F5F5F5; } +::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); background-color: #F5F5F5; } +::-webkit-scrollbar-thumb { -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); background-color: #ccc; } + +.terminal { + background-color: #000; + color: #fff; + font-family: courier-new, courier, monospace; + font-feature-settings: "liga" 0; + position: relative; + user-select: none; + -ms-user-select: none; + -webkit-user-select: none; +} + +.terminal.focus, +.terminal:focus { + outline: none; +} + +.terminal .xterm-helpers { + position: absolute; + top: 0; +} + +.terminal .xterm-helper-textarea { + /* + * HACK: to fix IE's blinking cursor + * Move textarea out of the screen to the far left, so that the cursor is not visible. + */ + position: absolute; + opacity: 0; + left: -9999em; + top: 0; + width: 0; + height: 0; + z-index: -10; + /** Prevent wrapping so the IME appears against the textarea at the correct position */ + white-space: nowrap; + overflow: hidden; + resize: none; +} + +.terminal a { + color: inherit; + text-decoration: none; +} + +.terminal a:hover { + cursor: pointer; + text-decoration: underline; +} + +.terminal a.xterm-invalid-link:hover { + cursor: text; + text-decoration: none; +} + +.terminal.focus:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar) .terminal-cursor { + background-color: #fff; + color: #000; +} + +.terminal:not(.focus) .terminal-cursor { + outline: 1px solid #fff; + outline-offset: -1px; + background-color: transparent; +} + +.terminal:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus.xterm-cursor-blink-on .terminal-cursor { + background-color: transparent; + color: inherit; +} + +.terminal.xterm-cursor-style-bar .terminal-cursor, +.terminal.xterm-cursor-style-underline .terminal-cursor { + position: relative; +} +.terminal.xterm-cursor-style-bar .terminal-cursor::before, +.terminal.xterm-cursor-style-underline .terminal-cursor::before { + content: ""; + display: block; + position: absolute; + background-color: #fff; +} +.terminal.xterm-cursor-style-bar .terminal-cursor::before { + top: 0; + bottom: 0; + left: 0; + width: 1px; +} +.terminal.xterm-cursor-style-underline .terminal-cursor::before { + bottom: 0; + left: 0; + right: 0; + height: 1px; +} +.terminal.xterm-cursor-style-bar.focus.xterm-cursor-blink.xterm-cursor-blink-on .terminal-cursor::before, +.terminal.xterm-cursor-style-underline.focus.xterm-cursor-blink.xterm-cursor-blink-on .terminal-cursor::before { + background-color: transparent; +} +.terminal.xterm-cursor-style-bar.focus.xterm-cursor-blink .terminal-cursor::before, +.terminal.xterm-cursor-style-underline.focus.xterm-cursor-blink .terminal-cursor::before { + background-color: #fff; +} + +.terminal .composition-view { + background: #000; + color: #FFF; + display: none; + position: absolute; + white-space: nowrap; + z-index: 1; +} + +.terminal .composition-view.active { + display: block; +} + +.terminal .xterm-viewport { + /* On OS X this is required in order for the scroll bar to appear fully opaque */ + background-color: #000; + overflow-y: auto; +} + +.terminal .xterm-wide-char, +.terminal .xterm-normal-char { + display: inline-block; +} + +.terminal .xterm-rows { + position: absolute; + left: 0; + top: 0; +} + +.terminal .xterm-rows > div { + /* Lines containing spans and text nodes ocassionally wrap despite being the same width (#327) */ + white-space: nowrap; +} + +.terminal .xterm-scroll-area { + visibility: hidden; +} + +.terminal .xterm-char-measure-element { + display: inline-block; + visibility: hidden; + position: absolute; + left: -9999em; +} + +.terminal.enable-mouse-events { + /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ + cursor: default; +} + +.terminal .xterm-selection { + position: absolute; + top: 0; + left: 0; + z-index: 1; + opacity: 0.3; + pointer-events: none; +} + +.terminal .xterm-selection div { + position: absolute; + background-color: #fff; +} + +/* + * Determine default colors for xterm.js + */ +.terminal .xterm-bold { + font-weight: bold; +} + +.terminal .xterm-underline { + text-decoration: underline; +} + +.terminal .xterm-blink { + text-decoration: blink; +} + +.terminal .xterm-hidden { + visibility: hidden; +} + +.terminal .xterm-color-0 { + color: #2e3436; +} + +.terminal .xterm-bg-color-0 { + background-color: #2e3436; +} + +.terminal .xterm-color-1 { + color: #cc0000; +} + +.terminal .xterm-bg-color-1 { + background-color: #cc0000; +} + +.terminal .xterm-color-2 { + color: #4e9a06; +} + +.terminal .xterm-bg-color-2 { + background-color: #4e9a06; +} + +.terminal .xterm-color-3 { + color: #c4a000; +} + +.terminal .xterm-bg-color-3 { + background-color: #c4a000; +} + +.terminal .xterm-color-4 { + color: #3465a4; +} + +.terminal .xterm-bg-color-4 { + background-color: #3465a4; +} + +.terminal .xterm-color-5 { + color: #75507b; +} + +.terminal .xterm-bg-color-5 { + background-color: #75507b; +} + +.terminal .xterm-color-6 { + color: #06989a; +} + +.terminal .xterm-bg-color-6 { + background-color: #06989a; +} + +.terminal .xterm-color-7 { + color: #d3d7cf; +} + +.terminal .xterm-bg-color-7 { + background-color: #d3d7cf; +} + +.terminal .xterm-color-8 { + color: #555753; +} + +.terminal .xterm-bg-color-8 { + background-color: #555753; +} + +.terminal .xterm-color-9 { + color: #ef2929; +} + +.terminal .xterm-bg-color-9 { + background-color: #ef2929; +} + +.terminal .xterm-color-10 { + color: #8ae234; +} + +.terminal .xterm-bg-color-10 { + background-color: #8ae234; +} + +.terminal .xterm-color-11 { + color: #fce94f; +} + +.terminal .xterm-bg-color-11 { + background-color: #fce94f; +} + +.terminal .xterm-color-12 { + color: #729fcf; +} + +.terminal .xterm-bg-color-12 { + background-color: #729fcf; +} + +.terminal .xterm-color-13 { + color: #ad7fa8; +} + +.terminal .xterm-bg-color-13 { + background-color: #ad7fa8; +} + +.terminal .xterm-color-14 { + color: #34e2e2; +} + +.terminal .xterm-bg-color-14 { + background-color: #34e2e2; +} + +.terminal .xterm-color-15 { + color: #eeeeec; +} + +.terminal .xterm-bg-color-15 { + background-color: #eeeeec; +} + +.terminal .xterm-color-16 { + color: #000000; +} + +.terminal .xterm-bg-color-16 { + background-color: #000000; +} + +.terminal .xterm-color-17 { + color: #00005f; +} + +.terminal .xterm-bg-color-17 { + background-color: #00005f; +} + +.terminal .xterm-color-18 { + color: #000087; +} + +.terminal .xterm-bg-color-18 { + background-color: #000087; +} + +.terminal .xterm-color-19 { + color: #0000af; +} + +.terminal .xterm-bg-color-19 { + background-color: #0000af; +} + +.terminal .xterm-color-20 { + color: #0000d7; +} + +.terminal .xterm-bg-color-20 { + background-color: #0000d7; +} + +.terminal .xterm-color-21 { + color: #0000ff; +} + +.terminal .xterm-bg-color-21 { + background-color: #0000ff; +} + +.terminal .xterm-color-22 { + color: #005f00; +} + +.terminal .xterm-bg-color-22 { + background-color: #005f00; +} + +.terminal .xterm-color-23 { + color: #005f5f; +} + +.terminal .xterm-bg-color-23 { + background-color: #005f5f; +} + +.terminal .xterm-color-24 { + color: #005f87; +} + +.terminal .xterm-bg-color-24 { + background-color: #005f87; +} + +.terminal .xterm-color-25 { + color: #005faf; +} + +.terminal .xterm-bg-color-25 { + background-color: #005faf; +} + +.terminal .xterm-color-26 { + color: #005fd7; +} + +.terminal .xterm-bg-color-26 { + background-color: #005fd7; +} + +.terminal .xterm-color-27 { + color: #005fff; +} + +.terminal .xterm-bg-color-27 { + background-color: #005fff; +} + +.terminal .xterm-color-28 { + color: #008700; +} + +.terminal .xterm-bg-color-28 { + background-color: #008700; +} + +.terminal .xterm-color-29 { + color: #00875f; +} + +.terminal .xterm-bg-color-29 { + background-color: #00875f; +} + +.terminal .xterm-color-30 { + color: #008787; +} + +.terminal .xterm-bg-color-30 { + background-color: #008787; +} + +.terminal .xterm-color-31 { + color: #0087af; +} + +.terminal .xterm-bg-color-31 { + background-color: #0087af; +} + +.terminal .xterm-color-32 { + color: #0087d7; +} + +.terminal .xterm-bg-color-32 { + background-color: #0087d7; +} + +.terminal .xterm-color-33 { + color: #0087ff; +} + +.terminal .xterm-bg-color-33 { + background-color: #0087ff; +} + +.terminal .xterm-color-34 { + color: #00af00; +} + +.terminal .xterm-bg-color-34 { + background-color: #00af00; +} + +.terminal .xterm-color-35 { + color: #00af5f; +} + +.terminal .xterm-bg-color-35 { + background-color: #00af5f; +} + +.terminal .xterm-color-36 { + color: #00af87; +} + +.terminal .xterm-bg-color-36 { + background-color: #00af87; +} + +.terminal .xterm-color-37 { + color: #00afaf; +} + +.terminal .xterm-bg-color-37 { + background-color: #00afaf; +} + +.terminal .xterm-color-38 { + color: #00afd7; +} + +.terminal .xterm-bg-color-38 { + background-color: #00afd7; +} + +.terminal .xterm-color-39 { + color: #00afff; +} + +.terminal .xterm-bg-color-39 { + background-color: #00afff; +} + +.terminal .xterm-color-40 { + color: #00d700; +} + +.terminal .xterm-bg-color-40 { + background-color: #00d700; +} + +.terminal .xterm-color-41 { + color: #00d75f; +} + +.terminal .xterm-bg-color-41 { + background-color: #00d75f; +} + +.terminal .xterm-color-42 { + color: #00d787; +} + +.terminal .xterm-bg-color-42 { + background-color: #00d787; +} + +.terminal .xterm-color-43 { + color: #00d7af; +} + +.terminal .xterm-bg-color-43 { + background-color: #00d7af; +} + +.terminal .xterm-color-44 { + color: #00d7d7; +} + +.terminal .xterm-bg-color-44 { + background-color: #00d7d7; +} + +.terminal .xterm-color-45 { + color: #00d7ff; +} + +.terminal .xterm-bg-color-45 { + background-color: #00d7ff; +} + +.terminal .xterm-color-46 { + color: #00ff00; +} + +.terminal .xterm-bg-color-46 { + background-color: #00ff00; +} + +.terminal .xterm-color-47 { + color: #00ff5f; +} + +.terminal .xterm-bg-color-47 { + background-color: #00ff5f; +} + +.terminal .xterm-color-48 { + color: #00ff87; +} + +.terminal .xterm-bg-color-48 { + background-color: #00ff87; +} + +.terminal .xterm-color-49 { + color: #00ffaf; +} + +.terminal .xterm-bg-color-49 { + background-color: #00ffaf; +} + +.terminal .xterm-color-50 { + color: #00ffd7; +} + +.terminal .xterm-bg-color-50 { + background-color: #00ffd7; +} + +.terminal .xterm-color-51 { + color: #00ffff; +} + +.terminal .xterm-bg-color-51 { + background-color: #00ffff; +} + +.terminal .xterm-color-52 { + color: #5f0000; +} + +.terminal .xterm-bg-color-52 { + background-color: #5f0000; +} + +.terminal .xterm-color-53 { + color: #5f005f; +} + +.terminal .xterm-bg-color-53 { + background-color: #5f005f; +} + +.terminal .xterm-color-54 { + color: #5f0087; +} + +.terminal .xterm-bg-color-54 { + background-color: #5f0087; +} + +.terminal .xterm-color-55 { + color: #5f00af; +} + +.terminal .xterm-bg-color-55 { + background-color: #5f00af; +} + +.terminal .xterm-color-56 { + color: #5f00d7; +} + +.terminal .xterm-bg-color-56 { + background-color: #5f00d7; +} + +.terminal .xterm-color-57 { + color: #5f00ff; +} + +.terminal .xterm-bg-color-57 { + background-color: #5f00ff; +} + +.terminal .xterm-color-58 { + color: #5f5f00; +} + +.terminal .xterm-bg-color-58 { + background-color: #5f5f00; +} + +.terminal .xterm-color-59 { + color: #5f5f5f; +} + +.terminal .xterm-bg-color-59 { + background-color: #5f5f5f; +} + +.terminal .xterm-color-60 { + color: #5f5f87; +} + +.terminal .xterm-bg-color-60 { + background-color: #5f5f87; +} + +.terminal .xterm-color-61 { + color: #5f5faf; +} + +.terminal .xterm-bg-color-61 { + background-color: #5f5faf; +} + +.terminal .xterm-color-62 { + color: #5f5fd7; +} + +.terminal .xterm-bg-color-62 { + background-color: #5f5fd7; +} + +.terminal .xterm-color-63 { + color: #5f5fff; +} + +.terminal .xterm-bg-color-63 { + background-color: #5f5fff; +} + +.terminal .xterm-color-64 { + color: #5f8700; +} + +.terminal .xterm-bg-color-64 { + background-color: #5f8700; +} + +.terminal .xterm-color-65 { + color: #5f875f; +} + +.terminal .xterm-bg-color-65 { + background-color: #5f875f; +} + +.terminal .xterm-color-66 { + color: #5f8787; +} + +.terminal .xterm-bg-color-66 { + background-color: #5f8787; +} + +.terminal .xterm-color-67 { + color: #5f87af; +} + +.terminal .xterm-bg-color-67 { + background-color: #5f87af; +} + +.terminal .xterm-color-68 { + color: #5f87d7; +} + +.terminal .xterm-bg-color-68 { + background-color: #5f87d7; +} + +.terminal .xterm-color-69 { + color: #5f87ff; +} + +.terminal .xterm-bg-color-69 { + background-color: #5f87ff; +} + +.terminal .xterm-color-70 { + color: #5faf00; +} + +.terminal .xterm-bg-color-70 { + background-color: #5faf00; +} + +.terminal .xterm-color-71 { + color: #5faf5f; +} + +.terminal .xterm-bg-color-71 { + background-color: #5faf5f; +} + +.terminal .xterm-color-72 { + color: #5faf87; +} + +.terminal .xterm-bg-color-72 { + background-color: #5faf87; +} + +.terminal .xterm-color-73 { + color: #5fafaf; +} + +.terminal .xterm-bg-color-73 { + background-color: #5fafaf; +} + +.terminal .xterm-color-74 { + color: #5fafd7; +} + +.terminal .xterm-bg-color-74 { + background-color: #5fafd7; +} + +.terminal .xterm-color-75 { + color: #5fafff; +} + +.terminal .xterm-bg-color-75 { + background-color: #5fafff; +} + +.terminal .xterm-color-76 { + color: #5fd700; +} + +.terminal .xterm-bg-color-76 { + background-color: #5fd700; +} + +.terminal .xterm-color-77 { + color: #5fd75f; +} + +.terminal .xterm-bg-color-77 { + background-color: #5fd75f; +} + +.terminal .xterm-color-78 { + color: #5fd787; +} + +.terminal .xterm-bg-color-78 { + background-color: #5fd787; +} + +.terminal .xterm-color-79 { + color: #5fd7af; +} + +.terminal .xterm-bg-color-79 { + background-color: #5fd7af; +} + +.terminal .xterm-color-80 { + color: #5fd7d7; +} + +.terminal .xterm-bg-color-80 { + background-color: #5fd7d7; +} + +.terminal .xterm-color-81 { + color: #5fd7ff; +} + +.terminal .xterm-bg-color-81 { + background-color: #5fd7ff; +} + +.terminal .xterm-color-82 { + color: #5fff00; +} + +.terminal .xterm-bg-color-82 { + background-color: #5fff00; +} + +.terminal .xterm-color-83 { + color: #5fff5f; +} + +.terminal .xterm-bg-color-83 { + background-color: #5fff5f; +} + +.terminal .xterm-color-84 { + color: #5fff87; +} + +.terminal .xterm-bg-color-84 { + background-color: #5fff87; +} + +.terminal .xterm-color-85 { + color: #5fffaf; +} + +.terminal .xterm-bg-color-85 { + background-color: #5fffaf; +} + +.terminal .xterm-color-86 { + color: #5fffd7; +} + +.terminal .xterm-bg-color-86 { + background-color: #5fffd7; +} + +.terminal .xterm-color-87 { + color: #5fffff; +} + +.terminal .xterm-bg-color-87 { + background-color: #5fffff; +} + +.terminal .xterm-color-88 { + color: #870000; +} + +.terminal .xterm-bg-color-88 { + background-color: #870000; +} + +.terminal .xterm-color-89 { + color: #87005f; +} + +.terminal .xterm-bg-color-89 { + background-color: #87005f; +} + +.terminal .xterm-color-90 { + color: #870087; +} + +.terminal .xterm-bg-color-90 { + background-color: #870087; +} + +.terminal .xterm-color-91 { + color: #8700af; +} + +.terminal .xterm-bg-color-91 { + background-color: #8700af; +} + +.terminal .xterm-color-92 { + color: #8700d7; +} + +.terminal .xterm-bg-color-92 { + background-color: #8700d7; +} + +.terminal .xterm-color-93 { + color: #8700ff; +} + +.terminal .xterm-bg-color-93 { + background-color: #8700ff; +} + +.terminal .xterm-color-94 { + color: #875f00; +} + +.terminal .xterm-bg-color-94 { + background-color: #875f00; +} + +.terminal .xterm-color-95 { + color: #875f5f; +} + +.terminal .xterm-bg-color-95 { + background-color: #875f5f; +} + +.terminal .xterm-color-96 { + color: #875f87; +} + +.terminal .xterm-bg-color-96 { + background-color: #875f87; +} + +.terminal .xterm-color-97 { + color: #875faf; +} + +.terminal .xterm-bg-color-97 { + background-color: #875faf; +} + +.terminal .xterm-color-98 { + color: #875fd7; +} + +.terminal .xterm-bg-color-98 { + background-color: #875fd7; +} + +.terminal .xterm-color-99 { + color: #875fff; +} + +.terminal .xterm-bg-color-99 { + background-color: #875fff; +} + +.terminal .xterm-color-100 { + color: #878700; +} + +.terminal .xterm-bg-color-100 { + background-color: #878700; +} + +.terminal .xterm-color-101 { + color: #87875f; +} + +.terminal .xterm-bg-color-101 { + background-color: #87875f; +} + +.terminal .xterm-color-102 { + color: #878787; +} + +.terminal .xterm-bg-color-102 { + background-color: #878787; +} + +.terminal .xterm-color-103 { + color: #8787af; +} + +.terminal .xterm-bg-color-103 { + background-color: #8787af; +} + +.terminal .xterm-color-104 { + color: #8787d7; +} + +.terminal .xterm-bg-color-104 { + background-color: #8787d7; +} + +.terminal .xterm-color-105 { + color: #8787ff; +} + +.terminal .xterm-bg-color-105 { + background-color: #8787ff; +} + +.terminal .xterm-color-106 { + color: #87af00; +} + +.terminal .xterm-bg-color-106 { + background-color: #87af00; +} + +.terminal .xterm-color-107 { + color: #87af5f; +} + +.terminal .xterm-bg-color-107 { + background-color: #87af5f; +} + +.terminal .xterm-color-108 { + color: #87af87; +} + +.terminal .xterm-bg-color-108 { + background-color: #87af87; +} + +.terminal .xterm-color-109 { + color: #87afaf; +} + +.terminal .xterm-bg-color-109 { + background-color: #87afaf; +} + +.terminal .xterm-color-110 { + color: #87afd7; +} + +.terminal .xterm-bg-color-110 { + background-color: #87afd7; +} + +.terminal .xterm-color-111 { + color: #87afff; +} + +.terminal .xterm-bg-color-111 { + background-color: #87afff; +} + +.terminal .xterm-color-112 { + color: #87d700; +} + +.terminal .xterm-bg-color-112 { + background-color: #87d700; +} + +.terminal .xterm-color-113 { + color: #87d75f; +} + +.terminal .xterm-bg-color-113 { + background-color: #87d75f; +} + +.terminal .xterm-color-114 { + color: #87d787; +} + +.terminal .xterm-bg-color-114 { + background-color: #87d787; +} + +.terminal .xterm-color-115 { + color: #87d7af; +} + +.terminal .xterm-bg-color-115 { + background-color: #87d7af; +} + +.terminal .xterm-color-116 { + color: #87d7d7; +} + +.terminal .xterm-bg-color-116 { + background-color: #87d7d7; +} + +.terminal .xterm-color-117 { + color: #87d7ff; +} + +.terminal .xterm-bg-color-117 { + background-color: #87d7ff; +} + +.terminal .xterm-color-118 { + color: #87ff00; +} + +.terminal .xterm-bg-color-118 { + background-color: #87ff00; +} + +.terminal .xterm-color-119 { + color: #87ff5f; +} + +.terminal .xterm-bg-color-119 { + background-color: #87ff5f; +} + +.terminal .xterm-color-120 { + color: #87ff87; +} + +.terminal .xterm-bg-color-120 { + background-color: #87ff87; +} + +.terminal .xterm-color-121 { + color: #87ffaf; +} + +.terminal .xterm-bg-color-121 { + background-color: #87ffaf; +} + +.terminal .xterm-color-122 { + color: #87ffd7; +} + +.terminal .xterm-bg-color-122 { + background-color: #87ffd7; +} + +.terminal .xterm-color-123 { + color: #87ffff; +} + +.terminal .xterm-bg-color-123 { + background-color: #87ffff; +} + +.terminal .xterm-color-124 { + color: #af0000; +} + +.terminal .xterm-bg-color-124 { + background-color: #af0000; +} + +.terminal .xterm-color-125 { + color: #af005f; +} + +.terminal .xterm-bg-color-125 { + background-color: #af005f; +} + +.terminal .xterm-color-126 { + color: #af0087; +} + +.terminal .xterm-bg-color-126 { + background-color: #af0087; +} + +.terminal .xterm-color-127 { + color: #af00af; +} + +.terminal .xterm-bg-color-127 { + background-color: #af00af; +} + +.terminal .xterm-color-128 { + color: #af00d7; +} + +.terminal .xterm-bg-color-128 { + background-color: #af00d7; +} + +.terminal .xterm-color-129 { + color: #af00ff; +} + +.terminal .xterm-bg-color-129 { + background-color: #af00ff; +} + +.terminal .xterm-color-130 { + color: #af5f00; +} + +.terminal .xterm-bg-color-130 { + background-color: #af5f00; +} + +.terminal .xterm-color-131 { + color: #af5f5f; +} + +.terminal .xterm-bg-color-131 { + background-color: #af5f5f; +} + +.terminal .xterm-color-132 { + color: #af5f87; +} + +.terminal .xterm-bg-color-132 { + background-color: #af5f87; +} + +.terminal .xterm-color-133 { + color: #af5faf; +} + +.terminal .xterm-bg-color-133 { + background-color: #af5faf; +} + +.terminal .xterm-color-134 { + color: #af5fd7; +} + +.terminal .xterm-bg-color-134 { + background-color: #af5fd7; +} + +.terminal .xterm-color-135 { + color: #af5fff; +} + +.terminal .xterm-bg-color-135 { + background-color: #af5fff; +} + +.terminal .xterm-color-136 { + color: #af8700; +} + +.terminal .xterm-bg-color-136 { + background-color: #af8700; +} + +.terminal .xterm-color-137 { + color: #af875f; +} + +.terminal .xterm-bg-color-137 { + background-color: #af875f; +} + +.terminal .xterm-color-138 { + color: #af8787; +} + +.terminal .xterm-bg-color-138 { + background-color: #af8787; +} + +.terminal .xterm-color-139 { + color: #af87af; +} + +.terminal .xterm-bg-color-139 { + background-color: #af87af; +} + +.terminal .xterm-color-140 { + color: #af87d7; +} + +.terminal .xterm-bg-color-140 { + background-color: #af87d7; +} + +.terminal .xterm-color-141 { + color: #af87ff; +} + +.terminal .xterm-bg-color-141 { + background-color: #af87ff; +} + +.terminal .xterm-color-142 { + color: #afaf00; +} + +.terminal .xterm-bg-color-142 { + background-color: #afaf00; +} + +.terminal .xterm-color-143 { + color: #afaf5f; +} + +.terminal .xterm-bg-color-143 { + background-color: #afaf5f; +} + +.terminal .xterm-color-144 { + color: #afaf87; +} + +.terminal .xterm-bg-color-144 { + background-color: #afaf87; +} + +.terminal .xterm-color-145 { + color: #afafaf; +} + +.terminal .xterm-bg-color-145 { + background-color: #afafaf; +} + +.terminal .xterm-color-146 { + color: #afafd7; +} + +.terminal .xterm-bg-color-146 { + background-color: #afafd7; +} + +.terminal .xterm-color-147 { + color: #afafff; +} + +.terminal .xterm-bg-color-147 { + background-color: #afafff; +} + +.terminal .xterm-color-148 { + color: #afd700; +} + +.terminal .xterm-bg-color-148 { + background-color: #afd700; +} + +.terminal .xterm-color-149 { + color: #afd75f; +} + +.terminal .xterm-bg-color-149 { + background-color: #afd75f; +} + +.terminal .xterm-color-150 { + color: #afd787; +} + +.terminal .xterm-bg-color-150 { + background-color: #afd787; +} + +.terminal .xterm-color-151 { + color: #afd7af; +} + +.terminal .xterm-bg-color-151 { + background-color: #afd7af; +} + +.terminal .xterm-color-152 { + color: #afd7d7; +} + +.terminal .xterm-bg-color-152 { + background-color: #afd7d7; +} + +.terminal .xterm-color-153 { + color: #afd7ff; +} + +.terminal .xterm-bg-color-153 { + background-color: #afd7ff; +} + +.terminal .xterm-color-154 { + color: #afff00; +} + +.terminal .xterm-bg-color-154 { + background-color: #afff00; +} + +.terminal .xterm-color-155 { + color: #afff5f; +} + +.terminal .xterm-bg-color-155 { + background-color: #afff5f; +} + +.terminal .xterm-color-156 { + color: #afff87; +} + +.terminal .xterm-bg-color-156 { + background-color: #afff87; +} + +.terminal .xterm-color-157 { + color: #afffaf; +} + +.terminal .xterm-bg-color-157 { + background-color: #afffaf; +} + +.terminal .xterm-color-158 { + color: #afffd7; +} + +.terminal .xterm-bg-color-158 { + background-color: #afffd7; +} + +.terminal .xterm-color-159 { + color: #afffff; +} + +.terminal .xterm-bg-color-159 { + background-color: #afffff; +} + +.terminal .xterm-color-160 { + color: #d70000; +} + +.terminal .xterm-bg-color-160 { + background-color: #d70000; +} + +.terminal .xterm-color-161 { + color: #d7005f; +} + +.terminal .xterm-bg-color-161 { + background-color: #d7005f; +} + +.terminal .xterm-color-162 { + color: #d70087; +} + +.terminal .xterm-bg-color-162 { + background-color: #d70087; +} + +.terminal .xterm-color-163 { + color: #d700af; +} + +.terminal .xterm-bg-color-163 { + background-color: #d700af; +} + +.terminal .xterm-color-164 { + color: #d700d7; +} + +.terminal .xterm-bg-color-164 { + background-color: #d700d7; +} + +.terminal .xterm-color-165 { + color: #d700ff; +} + +.terminal .xterm-bg-color-165 { + background-color: #d700ff; +} + +.terminal .xterm-color-166 { + color: #d75f00; +} + +.terminal .xterm-bg-color-166 { + background-color: #d75f00; +} + +.terminal .xterm-color-167 { + color: #d75f5f; +} + +.terminal .xterm-bg-color-167 { + background-color: #d75f5f; +} + +.terminal .xterm-color-168 { + color: #d75f87; +} + +.terminal .xterm-bg-color-168 { + background-color: #d75f87; +} + +.terminal .xterm-color-169 { + color: #d75faf; +} + +.terminal .xterm-bg-color-169 { + background-color: #d75faf; +} + +.terminal .xterm-color-170 { + color: #d75fd7; +} + +.terminal .xterm-bg-color-170 { + background-color: #d75fd7; +} + +.terminal .xterm-color-171 { + color: #d75fff; +} + +.terminal .xterm-bg-color-171 { + background-color: #d75fff; +} + +.terminal .xterm-color-172 { + color: #d78700; +} + +.terminal .xterm-bg-color-172 { + background-color: #d78700; +} + +.terminal .xterm-color-173 { + color: #d7875f; +} + +.terminal .xterm-bg-color-173 { + background-color: #d7875f; +} + +.terminal .xterm-color-174 { + color: #d78787; +} + +.terminal .xterm-bg-color-174 { + background-color: #d78787; +} + +.terminal .xterm-color-175 { + color: #d787af; +} + +.terminal .xterm-bg-color-175 { + background-color: #d787af; +} + +.terminal .xterm-color-176 { + color: #d787d7; +} + +.terminal .xterm-bg-color-176 { + background-color: #d787d7; +} + +.terminal .xterm-color-177 { + color: #d787ff; +} + +.terminal .xterm-bg-color-177 { + background-color: #d787ff; +} + +.terminal .xterm-color-178 { + color: #d7af00; +} + +.terminal .xterm-bg-color-178 { + background-color: #d7af00; +} + +.terminal .xterm-color-179 { + color: #d7af5f; +} + +.terminal .xterm-bg-color-179 { + background-color: #d7af5f; +} + +.terminal .xterm-color-180 { + color: #d7af87; +} + +.terminal .xterm-bg-color-180 { + background-color: #d7af87; +} + +.terminal .xterm-color-181 { + color: #d7afaf; +} + +.terminal .xterm-bg-color-181 { + background-color: #d7afaf; +} + +.terminal .xterm-color-182 { + color: #d7afd7; +} + +.terminal .xterm-bg-color-182 { + background-color: #d7afd7; +} + +.terminal .xterm-color-183 { + color: #d7afff; +} + +.terminal .xterm-bg-color-183 { + background-color: #d7afff; +} + +.terminal .xterm-color-184 { + color: #d7d700; +} + +.terminal .xterm-bg-color-184 { + background-color: #d7d700; +} + +.terminal .xterm-color-185 { + color: #d7d75f; +} + +.terminal .xterm-bg-color-185 { + background-color: #d7d75f; +} + +.terminal .xterm-color-186 { + color: #d7d787; +} + +.terminal .xterm-bg-color-186 { + background-color: #d7d787; +} + +.terminal .xterm-color-187 { + color: #d7d7af; +} + +.terminal .xterm-bg-color-187 { + background-color: #d7d7af; +} + +.terminal .xterm-color-188 { + color: #d7d7d7; +} + +.terminal .xterm-bg-color-188 { + background-color: #d7d7d7; +} + +.terminal .xterm-color-189 { + color: #d7d7ff; +} + +.terminal .xterm-bg-color-189 { + background-color: #d7d7ff; +} + +.terminal .xterm-color-190 { + color: #d7ff00; +} + +.terminal .xterm-bg-color-190 { + background-color: #d7ff00; +} + +.terminal .xterm-color-191 { + color: #d7ff5f; +} + +.terminal .xterm-bg-color-191 { + background-color: #d7ff5f; +} + +.terminal .xterm-color-192 { + color: #d7ff87; +} + +.terminal .xterm-bg-color-192 { + background-color: #d7ff87; +} + +.terminal .xterm-color-193 { + color: #d7ffaf; +} + +.terminal .xterm-bg-color-193 { + background-color: #d7ffaf; +} + +.terminal .xterm-color-194 { + color: #d7ffd7; +} + +.terminal .xterm-bg-color-194 { + background-color: #d7ffd7; +} + +.terminal .xterm-color-195 { + color: #d7ffff; +} + +.terminal .xterm-bg-color-195 { + background-color: #d7ffff; +} + +.terminal .xterm-color-196 { + color: #ff0000; +} + +.terminal .xterm-bg-color-196 { + background-color: #ff0000; +} + +.terminal .xterm-color-197 { + color: #ff005f; +} + +.terminal .xterm-bg-color-197 { + background-color: #ff005f; +} + +.terminal .xterm-color-198 { + color: #ff0087; +} + +.terminal .xterm-bg-color-198 { + background-color: #ff0087; +} + +.terminal .xterm-color-199 { + color: #ff00af; +} + +.terminal .xterm-bg-color-199 { + background-color: #ff00af; +} + +.terminal .xterm-color-200 { + color: #ff00d7; +} + +.terminal .xterm-bg-color-200 { + background-color: #ff00d7; +} + +.terminal .xterm-color-201 { + color: #ff00ff; +} + +.terminal .xterm-bg-color-201 { + background-color: #ff00ff; +} + +.terminal .xterm-color-202 { + color: #ff5f00; +} + +.terminal .xterm-bg-color-202 { + background-color: #ff5f00; +} + +.terminal .xterm-color-203 { + color: #ff5f5f; +} + +.terminal .xterm-bg-color-203 { + background-color: #ff5f5f; +} + +.terminal .xterm-color-204 { + color: #ff5f87; +} + +.terminal .xterm-bg-color-204 { + background-color: #ff5f87; +} + +.terminal .xterm-color-205 { + color: #ff5faf; +} + +.terminal .xterm-bg-color-205 { + background-color: #ff5faf; +} + +.terminal .xterm-color-206 { + color: #ff5fd7; +} + +.terminal .xterm-bg-color-206 { + background-color: #ff5fd7; +} + +.terminal .xterm-color-207 { + color: #ff5fff; +} + +.terminal .xterm-bg-color-207 { + background-color: #ff5fff; +} + +.terminal .xterm-color-208 { + color: #ff8700; +} + +.terminal .xterm-bg-color-208 { + background-color: #ff8700; +} + +.terminal .xterm-color-209 { + color: #ff875f; +} + +.terminal .xterm-bg-color-209 { + background-color: #ff875f; +} + +.terminal .xterm-color-210 { + color: #ff8787; +} + +.terminal .xterm-bg-color-210 { + background-color: #ff8787; +} + +.terminal .xterm-color-211 { + color: #ff87af; +} + +.terminal .xterm-bg-color-211 { + background-color: #ff87af; +} + +.terminal .xterm-color-212 { + color: #ff87d7; +} + +.terminal .xterm-bg-color-212 { + background-color: #ff87d7; +} + +.terminal .xterm-color-213 { + color: #ff87ff; +} + +.terminal .xterm-bg-color-213 { + background-color: #ff87ff; +} + +.terminal .xterm-color-214 { + color: #ffaf00; +} + +.terminal .xterm-bg-color-214 { + background-color: #ffaf00; +} + +.terminal .xterm-color-215 { + color: #ffaf5f; +} + +.terminal .xterm-bg-color-215 { + background-color: #ffaf5f; +} + +.terminal .xterm-color-216 { + color: #ffaf87; +} + +.terminal .xterm-bg-color-216 { + background-color: #ffaf87; +} + +.terminal .xterm-color-217 { + color: #ffafaf; +} + +.terminal .xterm-bg-color-217 { + background-color: #ffafaf; +} + +.terminal .xterm-color-218 { + color: #ffafd7; +} + +.terminal .xterm-bg-color-218 { + background-color: #ffafd7; +} + +.terminal .xterm-color-219 { + color: #ffafff; +} + +.terminal .xterm-bg-color-219 { + background-color: #ffafff; +} + +.terminal .xterm-color-220 { + color: #ffd700; +} + +.terminal .xterm-bg-color-220 { + background-color: #ffd700; +} + +.terminal .xterm-color-221 { + color: #ffd75f; +} + +.terminal .xterm-bg-color-221 { + background-color: #ffd75f; +} + +.terminal .xterm-color-222 { + color: #ffd787; +} + +.terminal .xterm-bg-color-222 { + background-color: #ffd787; +} + +.terminal .xterm-color-223 { + color: #ffd7af; +} + +.terminal .xterm-bg-color-223 { + background-color: #ffd7af; +} + +.terminal .xterm-color-224 { + color: #ffd7d7; +} + +.terminal .xterm-bg-color-224 { + background-color: #ffd7d7; +} + +.terminal .xterm-color-225 { + color: #ffd7ff; +} + +.terminal .xterm-bg-color-225 { + background-color: #ffd7ff; +} + +.terminal .xterm-color-226 { + color: #ffff00; +} + +.terminal .xterm-bg-color-226 { + background-color: #ffff00; +} + +.terminal .xterm-color-227 { + color: #ffff5f; +} + +.terminal .xterm-bg-color-227 { + background-color: #ffff5f; +} + +.terminal .xterm-color-228 { + color: #ffff87; +} + +.terminal .xterm-bg-color-228 { + background-color: #ffff87; +} + +.terminal .xterm-color-229 { + color: #ffffaf; +} + +.terminal .xterm-bg-color-229 { + background-color: #ffffaf; +} + +.terminal .xterm-color-230 { + color: #ffffd7; +} + +.terminal .xterm-bg-color-230 { + background-color: #ffffd7; +} + +.terminal .xterm-color-231 { + color: #ffffff; +} + +.terminal .xterm-bg-color-231 { + background-color: #ffffff; +} + +.terminal .xterm-color-232 { + color: #080808; +} + +.terminal .xterm-bg-color-232 { + background-color: #080808; +} + +.terminal .xterm-color-233 { + color: #121212; +} + +.terminal .xterm-bg-color-233 { + background-color: #121212; +} + +.terminal .xterm-color-234 { + color: #1c1c1c; +} + +.terminal .xterm-bg-color-234 { + background-color: #1c1c1c; +} + +.terminal .xterm-color-235 { + color: #262626; +} + +.terminal .xterm-bg-color-235 { + background-color: #262626; +} + +.terminal .xterm-color-236 { + color: #303030; +} + +.terminal .xterm-bg-color-236 { + background-color: #303030; +} + +.terminal .xterm-color-237 { + color: #3a3a3a; +} + +.terminal .xterm-bg-color-237 { + background-color: #3a3a3a; +} + +.terminal .xterm-color-238 { + color: #444444; +} + +.terminal .xterm-bg-color-238 { + background-color: #444444; +} + +.terminal .xterm-color-239 { + color: #4e4e4e; +} + +.terminal .xterm-bg-color-239 { + background-color: #4e4e4e; +} + +.terminal .xterm-color-240 { + color: #585858; +} + +.terminal .xterm-bg-color-240 { + background-color: #585858; +} + +.terminal .xterm-color-241 { + color: #626262; +} + +.terminal .xterm-bg-color-241 { + background-color: #626262; +} + +.terminal .xterm-color-242 { + color: #6c6c6c; +} + +.terminal .xterm-bg-color-242 { + background-color: #6c6c6c; +} + +.terminal .xterm-color-243 { + color: #767676; +} + +.terminal .xterm-bg-color-243 { + background-color: #767676; +} + +.terminal .xterm-color-244 { + color: #808080; +} + +.terminal .xterm-bg-color-244 { + background-color: #808080; +} + +.terminal .xterm-color-245 { + color: #8a8a8a; +} + +.terminal .xterm-bg-color-245 { + background-color: #8a8a8a; +} + +.terminal .xterm-color-246 { + color: #949494; +} + +.terminal .xterm-bg-color-246 { + background-color: #949494; +} + +.terminal .xterm-color-247 { + color: #9e9e9e; +} + +.terminal .xterm-bg-color-247 { + background-color: #9e9e9e; +} + +.terminal .xterm-color-248 { + color: #a8a8a8; +} + +.terminal .xterm-bg-color-248 { + background-color: #a8a8a8; +} + +.terminal .xterm-color-249 { + color: #b2b2b2; +} + +.terminal .xterm-bg-color-249 { + background-color: #b2b2b2; +} + +.terminal .xterm-color-250 { + color: #bcbcbc; +} + +.terminal .xterm-bg-color-250 { + background-color: #bcbcbc; +} + +.terminal .xterm-color-251 { + color: #c6c6c6; +} + +.terminal .xterm-bg-color-251 { + background-color: #c6c6c6; +} + +.terminal .xterm-color-252 { + color: #d0d0d0; +} + +.terminal .xterm-bg-color-252 { + background-color: #d0d0d0; +} + +.terminal .xterm-color-253 { + color: #dadada; +} + +.terminal .xterm-bg-color-253 { + background-color: #dadada; +} + +.terminal .xterm-color-254 { + color: #e4e4e4; +} + +.terminal .xterm-bg-color-254 { + background-color: #e4e4e4; +} + +.terminal .xterm-color-255 { + color: #eeeeee; +} + +.terminal .xterm-bg-color-255 { + background-color: #eeeeee; +} \ No newline at end of file diff --git a/src/main/webapp/static/image/backgrounds/1.jpg b/src/main/webapp/static/image/backgrounds/1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e43067cce342404d1dcbd8bcc70c1f363ba84824 GIT binary patch literal 72128 zcmeFYcT|%>v@aT3=q2=CLhro`3IqtfmnbFlj(`+Jksd-Pw9tE%UPVM9gx;iyh@f-< z0Rce~5pF!^-h0k^Wv%<~TkB2M_f6Te_w1Rh`DM?Zo!jNxbpVaNj-Czx4<7(<#eD#` zhX4>H#Kq?c01rS2_}2)yJqA!|xIT1t1Go<0TJUZ^0pb97|K|z+zY@X!PXCh~9}f>l z#XbL<@PAsk9RPqx@TCd33GhGwd=MT12=8_X(1O#Ckl>$ioTmSo2#J8i_yi<)q~v6H z0Q`Tn|3$&&1bF!W=rp(z5djGa0TD3)Ax;@Q+=7632ndO!HGn5(PJ!jLVB%;LWj+&R;Np6|3mR*Qj=Bqqx;s8Wu}1rp0V|HyR{rwcybKN&bFxY2M!;NcU1cxVZw zHO%Oo=ut$04AJFn81S6T$%gsu3V;GP7Cwjo1h@;hz6A{YH`V_C;{QiifV1@nmnq;^ zAgmib1z<~>?u^3gX-pwRY_>}Q{6%>I!2c?A_;J(x>+u#2She=6{(+-)_(|pg6d4M= zo(Rxe_%E^l&rHNApv+IUQY@5>%oK~;41NXv2VS&t2%y2A_fPxu5;8fDe zEQt5IikY%(050`-0G=WRmtyvKT@i{P2xg1S3}CCRtz+ip5`3M2%PAyrdESr{T^wir zu3Ah0v&Md&ln&EaxCiPDX16`ZnLzs#5SQe}9vtH_N8;^!syD9#xiI@0bGZXRg1I@gEdkzsL%5now{7YoD6;3-M3(IJ>|OQjHthhQ^dhGYJ7DOhMJFj0fDhPH7~QIf zSLRDO;Q9a3ur_x}SLW;Vs&Z41(3f%=KY)!oOy+Ts)G}NC9cHIiMZzP>=vQTIT5x|) z;OsJ6OekVt701%5znYgSouu9jK)v9)t@F~@%9G#n5vn9QBW#AorW4=OAFDZa&euQUvB3{zWgtRJ4YS`D zbdhK0of2WY6Rmk?kaXD}LZC`sWqb!QBAy^nZ8zr�+vM>tNSZdm(j*5m6&y4`-7) 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%0DCD*;oub^JXuDdis#gP;_gZeZ0UN`b8 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 literal 0 HcmV?d00001 diff --git a/src/main/webapp/static/image/backgrounds/3.jpg b/src/main/webapp/static/image/backgrounds/3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b3443592c6aee34f0e7341f064a334e5cb56b785 GIT binary patch literal 52931 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 literal 0 HcmV?d00001 diff --git a/src/main/webapp/static/image/progress.gif b/src/main/webapp/static/image/progress.gif new file mode 100644 index 0000000000000000000000000000000000000000..f3e45e0569c02ae3fe7114691e5b5aba892e9ef4 GIT binary patch literal 2608 zcmdVcdr(tX9tZGC9yiG~=H_)naswe?Kn8g&RT|OlKrRHxN(e$}*dR-|NeGA`|R>g+BX>~=c+qutq=_K%)F z?q6r--1*G!e7`3)UM^j)^#Z(ruYu*Yw6w(I@d5(_WipvcrAkjv_wn(O%jG(q4nS4yLK%~ij76zp!h%DIZmvVT|BYh#daYTn6Yt3}8^zjO zZ9(1+tu8ocyI}|5{ftC|T)P67e`gs10GA?*O>ta0l}s$ovPwiF9p-{aN%w1!`U_6! zE6JVvi-WfIr&q1sm>zo7uofN3B%)6&Zwa9g6ev_@7a@CABtbi9C29r?F#&?R05u+= zL(#fY8jGzJf}Ul1Qc5AvynAaQMV6{(adu-ImWf(dc&ZO$7s^eH!6b$$)}$K6U{jpw z%=OczcvGz%V44)4UI!iXvwDy`IS<`>TRmpK@h)*>-liuK2JBW70q9kqt6G@!T`ca( z(S{G8A6HMT{!_AKG1pAK$&5+6y0P+Hg0*z7fMcwDdZ;BJDh(Ak?WeTION$+rofWlK zML13xI8#2ZL||zMYv{RwRKb3o^mv*^_<5rtm6%TY-U#@nQo89~=wse#&(+k*&rR0Q z?7!1SWh;$C9Q~eq7aa3n8USy4611&A^MZ#7!3+(6AYxH;md*u~%VrfcH1NG-l#(2C zPHQVema1WK%$UgYJ;Vbb7*2eEf&B`(Edenv_z~keg%30KSvqoX}8RFwNZ<+aSjomZZOG zVsUsd8ph-BwowR=$E!wVOk6QF1x`55A#-zF^0is_`8i&r`jSI%AK?=R976Ol?F0P7 zLGUqhsY|W?*VA#UC&im0?^jISXsj_GTD$WpX~?4t#-IUftud)J*4kS@T<}(SMdB(2 zWW96PQyS3oC}|k<^H{&aJ1eYoRNcU+$IqKucS)|)`XxRjx&3B{j@)wje48+^;S%JOCf8?Bwu$ouW$2w?Owmy@I~Seg}6M)wjziMBzu~=Vo7fjf}h|&R(-X;KtTSwk#U~6=IBlu6p=~ z@*4lx?bEGgZq$15tJ^)=Mv34tMA!1{YpdHKxc(VQiIrIi2YnMxjQ>`QxX8?m%5hTz z@@8k;9l$3VGJg>fJg{l9K0}2LtC|RZhTI-JkDoLdZ(D~$)VRUTGz7oBCK*?~k$E}c zu%Ge-a1g!gHWD6L!CSZYz~`KHf4U4n-`~8sG-~ssp_~f+n`rn;3%0_7igqDu?P%Ax z^#`G5mPBz(`o%ZC)L-njU86d$@55cABoZUYz8@mn6-hELetG#Hzl&@Q31c)j1C1|o z$)f}D6qzwjiz}GmUPR&QH3NaxiwdpLbsAz*hv2F!oJhQ0_el)G$=qJmr*GmzcdvqY z^&bU9OPBgp{FAN7O8-iGV{=@4G?#0C7J0-OGI|%SQ9lL>?Vla#u7&PY3jq~@vJZr`Ul-8!P*6OYHAGYyi<jkCbCl)bMQK+F02 z`};ff&@Z&7&+GMGMnDg@1W<8~1-Ieo2b_jQLl`*US%ZSL--5wlUp)W@q8dH^f^Cx^ zeKDWUcMZ0WKo4v+ALyY8(GRn2l(n_BH?T|7u~mp(y$j4{^H?Ypx&!(n+p+7V(`l;y zJXeKpQcR5jqGvy}-Dqp)8H~PiJ#e0-WvY|Hpqzc`jYOJbt<~KJt-=Yvd)6S;X zc_b2K;sFo@+wdJ}@wFHXIH= z#?k7hu4F4p8c{n32M4=(wVWIM=RV*|#y1KefVjH~$qpvcV%vU%_h|chA9_{%F zg*iDpJA1vixAzQfe9*((j;Wq=sm8s`Q)1-kO~So+$YjFFmj+7-S`@UzmFYf~^JBIw zw58aZm31P3ii%Fl4Sg5Xcweo5VXmRtkM{rlr`fIp_$9yqU{FmN_6cop00000NkvXX Hu0mjfagVuN literal 0 HcmV?d00001 diff --git a/src/main/webapp/static/js/base64.js b/src/main/webapp/static/js/base64.js new file mode 100644 index 0000000..c4df1c0 --- /dev/null +++ b/src/main/webapp/static/js/base64.js @@ -0,0 +1,103 @@ +function Base64() { + + // private property + _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + + // public method for encoding + this.encode = function (input) { + var output = ""; + var chr1, chr2, chr3, enc1, enc2, enc3, enc4; + var i = 0; + input = _utf8_encode(input); + while (i < input.length) { + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + output = output + + _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + + _keyStr.charAt(enc3) + _keyStr.charAt(enc4); + } + return output; + } + + // public method for decoding + this.decode = function (input) { + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + while (i < input.length) { + enc1 = _keyStr.indexOf(input.charAt(i++)); + enc2 = _keyStr.indexOf(input.charAt(i++)); + enc3 = _keyStr.indexOf(input.charAt(i++)); + enc4 = _keyStr.indexOf(input.charAt(i++)); + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + output = output + String.fromCharCode(chr1); + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + } + output = _utf8_decode(output); + return output; + } + + // private method for UTF-8 encoding + _utf8_encode = function (string) { + string = string.replace(/\r\n/g,"\n"); + var utftext = ""; + for (var n = 0; n < string.length; n++) { + var c = string.charCodeAt(n); + if (c < 128) { + utftext += String.fromCharCode(c); + } else if((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + + } + return utftext; + } + + // private method for UTF-8 decoding + _utf8_decode = function (utftext) { + var string = ""; + var i = 0; + var c = c1 = c2 = 0; + while ( i < utftext.length ) { + c = utftext.charCodeAt(i); + if (c < 128) { + string += String.fromCharCode(c); + i++; + } else if((c > 191) && (c < 224)) { + c2 = utftext.charCodeAt(i+1); + string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + i += 2; + } else { + c2 = utftext.charCodeAt(i+1); + c3 = utftext.charCodeAt(i+2); + string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + } + return string; + } +} \ No newline at end of file diff --git a/src/main/webapp/static/js/formvalid.js b/src/main/webapp/static/js/formvalid.js new file mode 100644 index 0000000..4106442 --- /dev/null +++ b/src/main/webapp/static/js/formvalid.js @@ -0,0 +1,234 @@ +/* + Jquery + janchie 2010.1 + 1.02版 + */ + +var validResult = {}; +var errorMsg = {}; + +(function ($) { + $.fn.extend({ + valid: function () { + if (!$(this).is("form")) return; + + var items = $.isArray(arguments[0]) ? arguments[0] : [], + isBindSubmit = typeof arguments[1] === "boolean" ? arguments[1] : true, + isAlert = typeof arguments[2] === "boolean" ? arguments[2] : false, + + rule = { + "eng": /^[A-Za-z]+$/, + "chn": /^[\u0391-\uFFE5]+$/, + "mail": /\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/, + "url": /^http[s]?:\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+[\/=\?%\-&_~`@[\]\':+!]*([^<>\"\"])*$/, + "currency": /^\d+(\.\d+)?$/, + "number": /^\d+$/, + "int": /^[0-9]{1,30}$/, + "double": /^[-\+]?\d+(\.\d+)?$/, + "username": /^[a-zA-Z]{1}([a-zA-Z0-9]|[._]){3,19}$/, + "password": /^[\w\W]{6,20}$/, + "safe": />|<|,|\[|\]|\{|\}|\?|\/|\+|=|\||\'|\\|\"|:|;|\~|\!|\@|\#|\*|\$|\%|\^|\&|\(|\)|`/i, + "dbc": /[a-zA-Z0-9!@#¥%^&*()_+{}[]|:"';.,/?<>`~ ]/, + "qq": /[1-9][0-9]{4,}/, + "date": /^((((1[6-9]|[2-9]\d)\d{2})-(0?[13578]|1[02])-(0?[1-9]|[12]\d|3[01]))|(((1[6-9]|[2-9]\d)\d{2})-(0?[13456789]|1[012])-(0?[1-9]|[12]\d|30))|(((1[6-9]|[2-9]\d)\d{2})-0?2-(0?[1-9]|1\d|2[0-8]))|(((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))-0?2-29-))$/, + "year": /^(19|20)[0-9]{2}$/, + "month": /^(0?[1-9]|1[0-2])$/, + "day": /^((0?[1-9])|((1|2)[0-9])|30|31)$/, + "hour": /^((0?[1-9])|((1|2)[0-3]))$/, + "minute": /^((0?[1-9])|((1|5)[0-9]))$/, + "second": /^((0?[1-9])|((1|5)[0-9]))$/, + "mobile": /^((\(\d{2,3}\))|(\d{3}\-))?13\d{9}$/, + "phone": /^[+]{0,1}(\d){1,3}[ ]?([-]?((\d)|[ ]){1,12})+$/, + "zipcode": /^[1-9]\d{5}$/, + "IDcard": /^((1[1-5])|(2[1-3])|(3[1-7])|(4[1-6])|(5[0-4])|(6[1-5])|71|(8[12])|91)\d{4}((19\d{2}(0[13-9]|1[012])(0[1-9]|[12]\d|30))|(19\d{2}(0[13578]|1[02])31)|(19\d{2}02(0[1-9]|1\d|2[0-8]))|(19([13579][26]|[2468][048]|0[48])0229))\d{3}(\d|X|x)?$/, + "ip": /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/, + "file": /^[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/, + "image": /.+\.(jpg|gif|png|bmp)$/i, + "word": /.+\.(doc|rtf|pdf)$/i, + + "port": function (port) { + return (!isNaN(port) && port > 0 && port < 65536) ? true : false; + }, + "eq": function (arg1, arg2) { + return arg1 == arg2 ? true : false; + }, + "gt": function (arg1, arg2) { + return arg1 > arg2 ? true : false; + }, + "gte": function (arg1, arg2) { + return arg1 >= arg2 ? true : false; + }, + "lt": function (arg1, arg2) { + return arg1 < arg2 ? true : false; + }, + "lte": function (arg1, arg2) { + return arg1 <= arg2 ? true : false; + } + + }, + + msgSuffix = { + "eng": "only english welcomed", + "chn": "only chinese welcomed", + "mail": "invalid email format", + "url": "invalid url format", + "currency": "invalid number format", + "number": "only number welcomed", + "int": "only integer welcomed", + "double": "only float welcomed", + "username": "invalid username format,4-20 characters", + "password": "warning, you'd better use 6-20 characters", + "safe": "forbidden special characters", + "dbc": "forbidden full width characters", + "qq": "invalid qq format", + "date": "invalid date format", + "year": "invalid year format", + "month": "invalid month format", + "day": "invalid day format", + "hour": "invalid hour format", + "minute": "invalid minute format", + "second": "invalid second format", + "mobile": "invalid mobile format", + "phone": "invalid phone format", + "zipcode": "invalid zipcode format", + "IDcard": "invalid identity format", + "ip": "invalid ip format", + "port": "invalid port format", + "file": "invalid file format", + "image": "invalid image format", + "word": "invalid word file format", + "eq": "not equal", + "gt": "no greater than", + "gte": "no greater than or equal", + "lt": "no smaller than", + "lte": "no smaller than or equal" + }, + + msg = "", formObj = $(this), checkRet = true, isAll, + tipname = function (namestr) { + return "tip_" + namestr.replace(/([a-zA-Z0-9])/g, "-$1"); + }, + + typeTest = function () { + var result = true, args = arguments; + if (rule.hasOwnProperty(args[0])) { + var t = rule[args[0]], v = args[1]; + result = args.length > 2 ? t.apply(arguments, [].slice.call(args, 1)) : ($.isFunction(t) ? t(v) : t.test(v)); + } + return result; + }, + + showError = function (fieldObj, filedName, warnInfo) { + checkRet = false; + var tipObj = $("#" + tipname(filedName)); + if (tipObj.length > 0) tipObj.remove(); + var tipPosition = fieldObj.next().length > 0 ? fieldObj.nextAll().eq(this.length - 1) : fieldObj.eq(this.length - 1); + //tipPosition.after(" " + warnInfo + " "); + validResult[filedName] = false; + errorMsg[filedName] = warnInfo; + if (isAlert && isAll) msg = warnInfo; + }, + + showRight = function (fieldObj, filedName) { + var tipObj = $("#" + tipname(filedName)); + if (tipObj.length > 0) tipObj.remove(); + var tipPosition = fieldObj.next().length > 0 ? fieldObj.nextAll().eq(this.length - 1) : fieldObj.eq(this.length - 1); + //tipPosition.after("correct"); + validResult[filedName] = true; + }, + + findTo = function (objName) { + var find; + $.each(items, function () { + if (this.name == objName && this.simple) { + find = this.simple; + return false; + } + }); + if (!find) find = $("[name='" + objName + "']")[0].name; + return find; + }, + + fieldCheck = function (item) { + var i = item, field = $("[name='" + i.name + "']", formObj[0]); + if (!field[0]) return; + + var warnMsg, fv = $.trim(field.val()), isRq = typeof i.require === "boolean" ? i.require : true; + + if (isRq && ((field.is(":radio") || field.is(":checkbox")) && !field.is(":checked"))) { + warnMsg = i.message || "choice needed"; + showError(field, i.name, warnMsg); + + } else if (isRq && fv == "") { + warnMsg = i.message || ( field.is("select") ? "choice needed" : "not none" ); + showError(field, i.name, warnMsg); + + } else if (fv != "") { + if (i.min || i.max) { + var len = fv.length, min = i.min || 0, max = i.max; + warnMsg = i.message || (max ? "range" + min + "~" + max + "" : "min length" + min); + + if ((max && (len > max || len < min)) || (!max && len < min)) { + showError(field, i.name, warnMsg); + return; + } + } + if (i.type) { + var matchVal = i.to ? $.trim($("[name='" + i.to + "']").val()) : i.value; + var matchRet = matchVal ? typeTest(i.type, fv, matchVal) : typeTest(i.type, fv); + + warnMsg = i.message || msgSuffix[i.type]; + if (matchVal) warnMsg += (i.to ? findTo(i.to) + "value" : i.value); + + if (!matchRet) showError(field, i.name, warnMsg); + else showRight(field, i.name); + + } else { + showRight(field, i.name); + } + + } else if (isRq) { + showRight(field, i.name); + } + + }, + + validate = function () { + $.each(items, function () { + isAll = true; + fieldCheck(this); + }); + + if (isAlert && msg != "") { + alert(msg); + msg = ""; + } + return checkRet; + }; + + $.each(items, function () { + var field = $("[name='" + this.name + "']", formObj[0]); + if (field.is(":hidden")) return; + + var obj = this, toCheck = function () { + isAll = false; + fieldCheck(obj); + }; + if (field.is(":file") || field.is("select")) { + field.change(toCheck); + } else { + field.blur(toCheck); + } + }); + + if (isBindSubmit) { + $(this).submit(validate); + } else { + return validate(); + } + + } + + }); + +})(jQuery); diff --git a/src/main/webapp/static/js/jquerymin.js b/src/main/webapp/static/js/jquerymin.js new file mode 100644 index 0000000..8e16b0b --- /dev/null +++ b/src/main/webapp/static/js/jquerymin.js @@ -0,0 +1,5 @@ +/*! jQuery v2.1.2 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.2",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=hb(),z=hb(),A=hb(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},eb=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fb){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function gb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+rb(o[l]);w=ab.test(a)&&pb(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function hb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ib(a){return a[u]=!0,a}function jb(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function kb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function lb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function nb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function ob(a){return ib(function(b){return b=+b,ib(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pb(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=gb.support={},f=gb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=gb.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",eb,!1):e.attachEvent&&e.attachEvent("onunload",eb)),p=!f(g),c.attributes=jb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=jb(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=jb(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(jb(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),jb(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&jb(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return lb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?lb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},gb.matches=function(a,b){return gb(a,null,null,b)},gb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return gb(b,n,null,[a]).length>0},gb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},gb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},gb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},gb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=gb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=gb.selectors={cacheLength:50,createPseudo:ib,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||gb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&gb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=gb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||gb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ib(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ib(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ib(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ib(function(a){return function(b){return gb(a,b).length>0}}),contains:ib(function(a){return a=a.replace(cb,db),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ib(function(a){return W.test(a||"")||gb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:ob(function(){return[0]}),last:ob(function(a,b){return[b-1]}),eq:ob(function(a,b,c){return[0>c?c+b:c]}),even:ob(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:ob(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:ob(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:ob(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function tb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ub(a,b,c){for(var d=0,e=b.length;e>d;d++)gb(a,b[d],c);return c}function vb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wb(a,b,c,d,e,f){return d&&!d[u]&&(d=wb(d)),e&&!e[u]&&(e=wb(e,f)),ib(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ub(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:vb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=vb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=vb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sb(function(a){return a===b},h,!0),l=sb(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sb(tb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wb(i>1&&tb(m),i>1&&rb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xb(a.slice(i,e)),f>e&&xb(a=a.slice(e)),f>e&&rb(a))}m.push(c)}return tb(m)}function yb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=vb(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&gb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ib(f):f}return h=gb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,yb(e,d)),f.selector=a}return f},i=gb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&pb(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&rb(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&pb(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=jb(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),jb(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||kb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&jb(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||kb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),jb(function(a){return null==a.getAttribute("disabled")})||kb(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),gb}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c) +},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,bb=/<([\w:]+)/,cb=/<|&#?\w+;/,db=/<(?:script|style|link)/i,eb=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/^$|\/(?:java|ecma)script/i,gb=/^true\/(.*)/,hb=/^\s*\s*$/g,ib={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,"script"),g.length>0&&mb(g,!i&&ob(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement("div")),g=(bb.exec(e)||["",""])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=ob(k.appendChild(e),"script"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(ob(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&mb(ob(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(ob(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ab,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(ob(c,"script"),kb),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,ob(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(hb,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function tb(a){var b=l,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(qb||n("