├── .gitignore ├── Config.gradle ├── LICENSE ├── README.md ├── ajsoup ├── .gitignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── zdg │ └── ajsoup │ ├── AJsoupReader.java │ ├── AJsoupReaderContext.java │ ├── annotation │ └── Select.java │ ├── data │ ├── ClassDescriptor.java │ ├── ClassReader.java │ ├── ConstructorDescriptor.java │ ├── GenericArrayTypeImpl.java │ ├── ParameterizedTypeImpl.java │ ├── Resource.java │ └── TypeLiteral.java │ ├── decoder │ ├── BaseDecoder.java │ ├── Decoder.java │ ├── ReflectionArrayDecoder.java │ ├── ReflectionCollectionDecoder.java │ ├── ReflectionDecoderFactory.java │ ├── ReflectionMapDecoder.java │ └── ReflectionObjectDecoder.java │ ├── exception │ └── AJsoupReaderException.java │ └── kit │ ├── AnalysisDecoder.java │ ├── AnnotationAnalysis.java │ └── ReflectKit.java ├── build.gradle ├── converter-ajsoup ├── .gitignore ├── build.gradle └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── zdg │ │ │ └── converter │ │ │ └── ajsoup │ │ │ ├── AJSOUP.java │ │ │ ├── AJsoupConverterFactory.java │ │ │ ├── AJsoupRequestBodyConverter.java │ │ │ └── AJsoupResponseBodyConverter.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── github │ └── zdongcoding │ └── converter │ └── jsoup │ └── ExampleUnitTest.java ├── demo ├── .gitignore ├── build.gradle └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── github │ │ └── zdongcoding │ │ └── jsoup │ │ └── demo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── zdongcoding │ │ │ └── jsoup │ │ │ └── demo │ │ │ ├── Api.java │ │ │ ├── MainActivity.java │ │ │ ├── MediaInfoBean.java │ │ │ ├── WebBean.java │ │ │ └── home │ │ │ ├── HomeBean.java │ │ │ ├── HotTopData.java │ │ │ ├── HotTopTabBean.java │ │ │ ├── LatestPackBean.java │ │ │ ├── LatestVideoBean.java │ │ │ └── NavBean.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── github │ └── zdongcoding │ └── jsoup │ └── demo │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | 11 | /gred* 12 | /gradlew* 13 | gradle.properties 14 | .idea/ 15 | gradle/ -------------------------------------------------------------------------------- /Config.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.jfrog.bintray' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | version = libraryVersion 4 | install { 5 | repositories.mavenInstaller { 6 | // This generates POM.xml with proper parameters 7 | pom { 8 | project { 9 | packaging 'aar' 10 | // Add your description here 11 | name libraryDescription 12 | //项目的描述 你可以多写一点 13 | url siteUrl 14 | // Set your license 15 | licenses { 16 | license { 17 | name 'The Apache Software License, Version 2.0' 18 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 19 | } 20 | } 21 | developers { 22 | developer { 23 | id developerId //填写的一些基本信息 24 | name developerName 25 | email developerEmail 26 | } 27 | } 28 | scm { 29 | connection gitUrl 30 | developerConnection gitUrl 31 | url siteUrl 32 | } 33 | } 34 | } 35 | } 36 | } 37 | 38 | if (project.hasProperty("android")) { // Android libraries 39 | task sourcesJar(type: Jar) { 40 | classifier = 'sources' 41 | from android.sourceSets.main.java.srcDirs 42 | } 43 | 44 | task javadoc(type: Javadoc) { 45 | source = android.sourceSets.main.java.srcDirs 46 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 47 | } 48 | } else { // Java libraries 49 | task sourcesJar(type: Jar, dependsOn: classes) { 50 | classifier = 'sources' 51 | from sourceSets.main.allSource 52 | } 53 | } 54 | 55 | task javadocJar(type: Jar, dependsOn: javadoc) { 56 | classifier = 'javadoc' 57 | from javadoc.destinationDir 58 | } 59 | 60 | artifacts { 61 | // archives javadocJar 62 | archives sourcesJar 63 | } 64 | 65 | // Bintray 66 | Properties properties = new Properties() 67 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 68 | group = publishedGroupId 69 | bintray { 70 | user = properties.getProperty("bintray.user") 71 | key = properties.getProperty("bintray.apikey") 72 | 73 | configurations = ['archives'] 74 | pkg { 75 | repo = bintrayRepo 76 | name = bintrayName 77 | desc = libraryDescription 78 | websiteUrl = siteUrl 79 | vcsUrl = gitUrl 80 | issueTrackerUrl = issueUrl 81 | licenses = allLicenses 82 | labels = alllabels 83 | publish = true 84 | publicDownloadNumbers = true 85 | // githubReleaseNotesFile="README.md" 86 | // githubRepo = 'bintray/gradle-bintray-plugin' //Optional Github repository 87 | // githubReleaseNotesFile = 'README.md' //Optional Github readme file 88 | version { 89 | desc = libraryDescription 90 | gpg { 91 | sign = true //Determines whether to GPG sign the files. The default is false 92 | passphrase = properties.getProperty("bintray.gpg.password") 93 | //Optional. The passphrase for GPG signing' 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 zdong_coding 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AJsoup 2 | 3 | [![Platform](https://img.shields.io/badge/platform-Android-yellow.svg)](https://www.android.com) 4 | [![API](https://img.shields.io/badge/API-14%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=14) 5 | [![author](https://img.shields.io/badge/%E4%BD%9C%E8%80%85-zoudong-blue.svg)](https://github.com/zdongcoding) 6 | 7 | > AJsoup 模块是快速将html 转换成bean 类似gson转换 8 | 9 | > 依赖 [jsoup](https://github.com/jhy/jsoup) 10 | 11 | > **使用前提你了解jsoup并且了解[jsoup的Select](https://jsoup.org/apidocs/index.html?org/jsoup/select/Selector.html)** 12 | 13 | **Gradle** 14 | > compile 'com.github.zdongcoding:ajsoup:0.1.0' 15 | 16 | **Maven** 17 | ``` 18 | 19 | com.github.zdongcoding 20 | ajsoup 21 | 0.1.0 22 | pom 23 | 24 | ``` 25 | 26 | 使用方法如下: 27 | ``` 28 | bean.java 29 | 30 | @Select(select = "body") 31 | public class HomeBean { 32 | @Select(select = "div > div > div.listbox") 33 | public HotTopTabBean hotTopTabBean; //热门数据 34 | @Select(select = "div#nav > ul > li[id]", attr = "id") 35 | public Map navBeans; 36 | @Select(select = "div#nav-under > ul >li:has(a)") //排除无 navUnderBeans; 38 | @Select(select = "div#body > div.left.noborder.clearfix.block1 > ul") 39 | public List latests; 40 | @Select(select = "div#header-in > div > ul#hot-words > li") 41 | public List searchbeans; 42 | } 43 | String html=....; 44 | 45 | HomeBean bean=AJsoupReader.deserialize(Jsoup.parse(html), HomeBean.class); 46 | 47 | ``` 48 | 49 | # AJsoup---->Converter-Ajsoup 50 | 51 | > 使用过Retrofit 一看这个名字就知道做什么的 52 | 53 | **Gradle** 54 | > compile 'com.github.zdongcoding:converter-ajsoup:0.1.0' 55 | 56 | **Maven** 57 | ``` 58 | 59 | com.github.zdongcoding 60 | converter-ajsoup 61 | 0.1.0 62 | pom 63 | 64 | ``` 65 | 66 | 使用方法: 67 | ``` 68 | Api.java 69 | public interface Api { 70 | @GET("{url}") 71 | Observable getPage(@Path(value = "url",encoded = true) String url); 72 | } 73 | api = new Retrofit.Builder().baseUrl(baseUri) 74 | .addConverterFactory(JsoupConverterFactory.create()) 75 | .addConverterFactory(ScalarsConverterFactory.create()) 76 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 77 | .build().create(Api.class); 78 | 79 | api.getPage("").subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber() { 80 | @Override 81 | public void onCompleted() { 82 | } 83 | @Override 84 | public void onError(Throwable e) { 85 | } 86 | @Override 87 | public void onNext(HomeBean homeBean) { 88 | view.setText(homeBean.toString()); 89 | } 90 | }); 91 | 92 | 93 | ``` 94 | 选择器概要(Selector overview) 95 | * Tagname:通过标签查找元素(例如:a) 96 | * ns|tag:通过标签在命名空间查找元素,例如:fb|name查找元素 97 | * #id:通过ID查找元素,例如#logo 98 | * .class:通过类型名称查找元素,例如.masthead 99 | * [attribute]:带有属性的元素,例如[href] 100 | * [^attr]:带有名称前缀的元素,例如[^data-]查找HTML5带有数据集(dataset)属性的元素 101 | * [attr=value]:带有属性值的元素,例如[width=500] 102 | * [attr^=value],[attr$=value],[attr*=value]:包含属性且其值以value开头、结尾或包含value的元素,例* 如[href*=/path/] 103 | * [attr~=regex]:属性值满足正则表达式的元素,例如img[src~=(?i)\.(png|jpe?g)] 104 | * *:所有元素,例如* 105 | * 选择器组合方法 106 | * el#id::带有ID的元素ID,例如div#logo 107 | * el.class:带类型的元素,例如. div.masthead 108 | * el[attr]:包含属性的元素,例如a[href] 109 | * 任意组合:例如a[href].highlight 110 | * ancestor child:继承自某祖(父)元素的子元素,例如.body p查找“body”块下的p元素 111 | * parent > child:直接为父元素后代的子元素,例如: div.content > pf查找p元素,body > * 查找body元素的* 直系子元素 112 | * siblingA + siblingB:查找由同级元素A前导的同级元素,例如div.head + div 113 | * siblingA ~ siblingX:查找同级元素A前导的同级元素X例如h1 ~ p 114 | * el, el, el:多个选择器组合,查找匹配任一选择器的唯一元素,例如div.masthead, div.logo 115 | * 伪选择器(Pseudo selectors) 116 | * :lt(n):查找索引值(即DOM树中相对于其父元素的位置)小于n的同级元素,例如td:lt(3) 117 | * :gt(n):查找查找索引值大于n的同级元素,例如div p:gt(2) 118 | * :eq(n) :查找索引值等于n的同级元素,例如form input:eq(1) 119 | * :has(seletor):查找匹配选择器包含元素的元素,例如div:has(p) 120 | * :not(selector):查找不匹配选择器的元素,例如div:not(.logo) 121 | * :contains(text):查找包含给定文本的元素,大小写铭感,例如p:contains(jsoup) 122 | * :containsOwn(text):查找直接包含给定文本的元素 123 | * :matches(regex):查找其文本匹配指定的正则表达式的元素,例如div:matches((?i)login) 124 | * :matchesOwn(regex):查找其自身文本匹配指定的正则表达式的元素 125 | * 注意:上述伪选择器是0-基数的,亦即第一个元素索引值为0,第二个元素index为1等 126 | 127 | 主要用到以上字段 128 | 129 | **不需要服务器 就可以做一个快速做一个客户端** 130 | 131 | 完毕,就是这么简单 132 | -------------------------------------------------------------------------------- /ajsoup/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /*.iml 3 | /proguard-rules.pro 4 | /libs -------------------------------------------------------------------------------- /ajsoup/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | 4 | 5 | android { 6 | compileSdkVersion 25 7 | buildToolsVersion '26.0.3' 8 | defaultConfig { 9 | minSdkVersion 15 10 | targetSdkVersion 25 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile 'org.jsoup:jsoup:1.11.2' 25 | 26 | } 27 | task makeJar(type: Copy) { 28 | //删除存在的 29 | delete 'build/libs/ajsoup.jar' 30 | //设置拷贝的文件 31 | from('build/intermediates/bundles/release/') 32 | //打进jar包后的文件目录 33 | into('build/libs/') 34 | //将classes.jar放入build/libs/目录下 35 | //include ,exclude参数来设置过滤 36 | //(我们只关心classes.jar这个文件) 37 | include('classes.jar') 38 | //重命名 39 | rename ('classes.jar', 'ajsoup.jar') 40 | } 41 | ext{ 42 | bintrayRepo = 'maven' 43 | bintrayName = 'Ajsoup' 44 | 45 | publishedGroupId = 'com.zdg' 46 | libraryName = 'ajsoup' 47 | artifact = 'ajsoup' 48 | maturity ='Stable' 49 | libraryDescription = 'base on jsoup Automatically parsed into bean' 50 | 51 | siteUrl = 'https://github.com/zdongcoding/jsouplib' 52 | gitUrl = 'https://github.com/zdongcoding/jsouplib.git' 53 | issueUrl='https://github.com/zdongcoding/jsouplib/issues' 54 | libraryVersion = '1.0.0-beta1' 55 | alllabels = ['android','jsoup'] 56 | developerId = 'zdongcoding' 57 | developerName = 'zoudong' 58 | developerEmail = 'zoudongq1990@gmail.com' 59 | 60 | licenseName = 'The Apache Software License, Version 2.0' 61 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 62 | allLicenses = ["Apache-2.0"] 63 | } 64 | //apply from: 'https://raw.githubusercontent.com/zdongcoding/bintrayhelper/master/SimpleBintray.gradle' -------------------------------------------------------------------------------- /ajsoup/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/AJsoupReader.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup; 2 | 3 | 4 | 5 | 6 | import com.zdg.ajsoup.data.ClassDescriptor; 7 | import com.zdg.ajsoup.data.ClassReader; 8 | import com.zdg.ajsoup.data.TypeLiteral; 9 | import com.zdg.ajsoup.kit.AnalysisDecoder; 10 | import com.zdg.ajsoup.kit.AnnotationAnalysis; 11 | 12 | import org.jsoup.Jsoup; 13 | import org.jsoup.nodes.Document; 14 | import org.jsoup.select.Elements; 15 | 16 | 17 | /** 18 | * Created by zoudong on 2017/3/10. 19 | */ 20 | 21 | public class AJsoupReader { 22 | public static final boolean isDebug=false; 23 | public static ThreadLocal jsp = new ThreadLocal() { 24 | @Override 25 | protected AJsoupReader initialValue() { 26 | return new AJsoupReader(); 27 | } 28 | }; 29 | 30 | public static final T deserialize(Document document, Class clazz) { 31 | AJsoupReader context = jsp.get(); 32 | ClassDescriptor classDescriptor = ClassReader.getClassDescriptor(clazz, true); 33 | 34 | if (classDescriptor.clazz_anno == null) 35 | throw new RuntimeException(clazz + " you must used once Annotation "); 36 | Elements elements = AnnotationAnalysis.analysis(document.children(), classDescriptor.clazz_anno); 37 | T val = context.read(clazz, new AJsoupReaderContext(elements, classDescriptor.clazz_anno)); 38 | return val; 39 | } 40 | public static final T deserialize(String document, Class clazz) { 41 | AJsoupReader context = jsp.get(); 42 | ClassDescriptor classDescriptor = ClassReader.getClassDescriptor(clazz, true); 43 | Document parse = Jsoup.parse(document); 44 | if (classDescriptor.clazz_anno == null) 45 | throw new RuntimeException(clazz + " you must used once Annotation "); 46 | if (isDebug) System.out.println("deserialize: "+classDescriptor.clazz_anno[0].toString() ); 47 | Elements elements = AnnotationAnalysis.analysis(parse.children(), classDescriptor.clazz_anno); 48 | if (isDebug) System.out.println("---->" +elements.html()); 49 | T val = context.read(clazz, new AJsoupReaderContext(elements, classDescriptor.clazz_anno)); 50 | return val; 51 | } 52 | @SuppressWarnings("unchecked") 53 | public final T read(Class clazz, AJsoupReaderContext iterator) { 54 | return (T) AnalysisDecoder.getDecoder(TypeLiteral.create(clazz).getDecoderCacheKey(), clazz).decode(iterator); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/AJsoupReaderContext.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup; 2 | 3 | 4 | import com.zdg.ajsoup.annotation.Select; 5 | import com.zdg.ajsoup.data.Resource; 6 | import com.zdg.ajsoup.kit.AnnotationAnalysis; 7 | 8 | import org.jsoup.helper.StringUtil; 9 | import org.jsoup.nodes.Element; 10 | import org.jsoup.select.Elements; 11 | 12 | import java.lang.annotation.Annotation; 13 | import java.lang.reflect.InvocationTargetException; 14 | import java.math.BigDecimal; 15 | import java.math.BigInteger; 16 | 17 | /** 18 | * Created by zoudong on 2017/3/10. 19 | */ 20 | 21 | public class AJsoupReaderContext { 22 | public Elements elements; //数据源 23 | public Resource resource; 24 | public Annotation[] clazzans; //父级的 注解 25 | 26 | public AJsoupReaderContext(Elements elements, Resource resource) { 27 | this.elements = elements; 28 | this.resource = resource; 29 | } 30 | 31 | public AJsoupReaderContext(Elements elements, Annotation[] clazzans) { 32 | this.elements = elements; 33 | this.clazzans = clazzans; 34 | } 35 | 36 | Annotation[] getClazzans() { 37 | return resource != null && resource.annotations != null ? resource.annotations : clazzans; 38 | } 39 | 40 | Select getSelect(Annotation[] annotations) { 41 | for (Annotation annotation : annotations) { 42 | if (annotation instanceof Select) { 43 | return (Select) annotation; 44 | } 45 | } 46 | return null; 47 | } 48 | 49 | public Float readFloat() { 50 | return null; 51 | } 52 | 53 | public Double readDouble() { 54 | return null; 55 | } 56 | 57 | public Boolean readBoolean() { 58 | if (readNull()) { 59 | return false; 60 | } 61 | Annotation[] clazzans = getClazzans(); 62 | // Elements elements1 = AnnotationAnalysis.analysis(elements, clazzans); 63 | Select select = getSelect(clazzans); 64 | if (select.text()) { 65 | for (Element element : elements) { 66 | return element != null && element.hasText(); 67 | } 68 | } 69 | if (!StringUtil.isBlank(select.attr())) { 70 | for (Element element : elements) { 71 | return element != null && element.hasAttr(select.attr()); 72 | } 73 | } 74 | for (Element element : elements) { 75 | return element != null; 76 | } 77 | return true; 78 | } 79 | 80 | public boolean readNull() { 81 | return elements == null || elements.size() == 0; 82 | } 83 | 84 | public Short readShort() { 85 | return new Short("0"); 86 | } 87 | 88 | public Integer readInt() { 89 | return 0; 90 | } 91 | 92 | public Long readLong() { 93 | return 0L; 94 | } 95 | 96 | public BigDecimal readBigDecimal() { 97 | return null; 98 | } 99 | 100 | public BigInteger readBigInteger() { 101 | return null; 102 | } 103 | 104 | public String readString() { 105 | if (readNull()) return null; 106 | Annotation[] clazzans = getClazzans(); 107 | // Elements elements1 = AnnotationAnalysis.analysis(elements, clazzans); 108 | Select select = getSelect(clazzans); 109 | if (select.text()) { 110 | for (Element element : elements) { 111 | if (element != null && element.hasText()) { 112 | return element.text(); 113 | } 114 | } 115 | } 116 | if (!StringUtil.isBlank(select.attr())) { 117 | for (Element element : elements) { 118 | if (element != null && element.hasAttr(select.attr())) { 119 | return element.attr(select.attr()); 120 | } 121 | } 122 | } 123 | return null; 124 | } 125 | 126 | public Object read() { 127 | return null; 128 | } 129 | 130 | public void deserializeChild(Resource target, Object parant) { 131 | int anno = target.annotations != null ? target.annotations.length : 0; 132 | for (int i = 0; i < anno; i++) { 133 | if (target.annotations[i] instanceof Select) { 134 | Elements select = AnnotationAnalysis.analysis(elements, target.annotations); 135 | Object deserialize = target.deserialize(select); 136 | setToBinding(parant, target, deserialize); 137 | break; 138 | } 139 | } 140 | } 141 | 142 | private void setToBinding(Object obj, Resource resource, Object slect) { 143 | if (obj == null) { 144 | return; 145 | } 146 | try { 147 | if (resource.field != null) { 148 | resource.field.set(obj, slect); 149 | } else if (resource.method != null) { 150 | resource.method.invoke(obj, slect); 151 | } 152 | } catch (InvocationTargetException e) { 153 | e.printStackTrace(); 154 | } catch (IllegalAccessException e) { 155 | e.printStackTrace(); 156 | } 157 | 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/annotation/Select.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Created by user on 2017/3/8. 11 | */ 12 | 13 | @Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD}) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Documented 16 | public @interface Select { 17 | 18 | String select(); //没有设置 default 必填 19 | String attr() default ""; //属性 20 | 21 | String key() default ""; //map 使用的 22 | boolean text() default false; //text 23 | } 24 | -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/data/ClassDescriptor.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup.data; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Type; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | public class ClassDescriptor { 10 | 11 | public Class clazz; 12 | public Annotation[] clazz_anno; // 类的注解 13 | public Map lookup; 14 | public ConstructorDescriptor ctor; // 构造函数 15 | public List fields; 16 | public List setters; 17 | public List getters; 18 | 19 | public List allBindings() { 20 | ArrayList resources = new ArrayList(8); 21 | resources.addAll(fields); 22 | if (setters != null) { 23 | resources.addAll(setters); 24 | } 25 | if (getters != null) { 26 | resources.addAll(getters); 27 | } 28 | if (ctor != null) { 29 | resources.addAll(ctor.parameters); 30 | } 31 | return resources; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/data/ClassReader.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup.data; 2 | 3 | import com.zdg.ajsoup.exception.AJsoupReaderException; 4 | import com.zdg.ajsoup.kit.ReflectKit; 5 | 6 | import java.lang.annotation.Annotation; 7 | import java.lang.reflect.Field; 8 | import java.lang.reflect.Method; 9 | import java.lang.reflect.Modifier; 10 | import java.lang.reflect.ParameterizedType; 11 | import java.lang.reflect.Type; 12 | import java.lang.reflect.TypeVariable; 13 | import java.util.ArrayList; 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | 19 | public class ClassReader { 20 | 21 | 22 | 23 | /** 24 | * @param clazz 25 | * @param includingPrivate 26 | * @return 27 | */ 28 | public static ClassDescriptor getClassDescriptor(Class clazz, boolean includingPrivate) { 29 | 30 | ClassDescriptor desc = new ClassDescriptor(); 31 | desc.clazz_anno = clazz.getAnnotations(); 32 | desc.clazz = clazz; 33 | desc.ctor = getCtor(clazz); 34 | 35 | Map lookup = collectTypeVariableLookup(clazz); 36 | desc.lookup = lookup; 37 | desc.fields = getFields(lookup, clazz, includingPrivate); 38 | desc.setters = getSetters(lookup, clazz, includingPrivate); 39 | 40 | for (Resource field : desc.fields) { //变量 41 | if (field.valueType instanceof Class) { 42 | Class valueClazz = (Class) field.valueType; 43 | if (valueClazz.isArray()) { 44 | field.valueCanReuse = false; 45 | continue; 46 | } 47 | } 48 | } 49 | deduplicate(desc); 50 | if (includingPrivate) { 51 | if (desc.ctor.ctor != null) { 52 | desc.ctor.ctor.setAccessible(true); 53 | } 54 | if (desc.ctor.staticFactory != null) { 55 | desc.ctor.staticFactory.setAccessible(true); 56 | } 57 | 58 | } 59 | for (Resource resource : desc.allBindings()) { 60 | if (resource.fromNames == null) { 61 | resource.fromNames = new String[]{resource.name}; 62 | } 63 | if (resource.field != null && includingPrivate) { 64 | resource.field.setAccessible(true); 65 | } 66 | if (resource.method != null && includingPrivate) { 67 | resource.method.setAccessible(true); 68 | } 69 | 70 | } 71 | return desc; 72 | } 73 | 74 | /** 75 | * field 和 seter geter 合并字段 76 | * @param desc 77 | */ 78 | private static void deduplicate(ClassDescriptor desc) { 79 | HashMap byName = new HashMap<>(); 80 | for (Resource field : desc.fields) { 81 | if (!byName.containsKey(field.name)) { //排除重复 变量 82 | byName.put(field.name, field); 83 | } 84 | } 85 | for (Resource setter : desc.setters) { 86 | Resource existing = byName.get(setter.name); 87 | if (existing == null) { 88 | byName.put(setter.name, setter); 89 | continue; 90 | } 91 | if (desc.fields.remove(existing)) { 92 | continue; 93 | } 94 | } 95 | for (Resource param : desc.ctor.parameters) { 96 | Resource existing = byName.get(param.name); 97 | if (existing == null) { 98 | byName.put(param.name, param); 99 | continue; 100 | } 101 | if (desc.fields.remove(existing)) { 102 | continue; 103 | } 104 | if (desc.setters.remove(existing)) { 105 | continue; 106 | } 107 | } 108 | } 109 | 110 | /** 111 | * 获取 clazz 无参构造方法 112 | * 113 | * @param clazz 114 | * @return 115 | */ 116 | private static ConstructorDescriptor getCtor(Class clazz) { 117 | ConstructorDescriptor cctor = new ConstructorDescriptor(); 118 | try { 119 | cctor.ctor = clazz.getDeclaredConstructor(); 120 | } catch (Exception e) { 121 | cctor.ctor = null; 122 | } 123 | return cctor; 124 | } 125 | 126 | private static List getFields(Map lookup, Class clazz, boolean includingPrivate) { 127 | ArrayList resources = new ArrayList(); 128 | for (Field field : ReflectKit.getAllFields(clazz, includingPrivate)) { 129 | Annotation[] annotations = field.getAnnotations(); 130 | if (annotations ==null||annotations.length==0) {//排除未注解变量 131 | continue; 132 | } 133 | if (Modifier.isStatic(field.getModifiers())) { //去掉 static 变量 134 | continue; 135 | } 136 | if (Modifier.isTransient(field.getModifiers())) { 137 | continue; 138 | } 139 | if (!includingPrivate && !Modifier.isPublic(field.getType().getModifiers())) { 140 | continue; 141 | } 142 | if (includingPrivate) { 143 | field.setAccessible(true); 144 | } 145 | Resource resource = createBindingFromField(lookup, clazz, field); 146 | resources.add(resource); 147 | } 148 | return resources; 149 | } 150 | 151 | private static Resource createBindingFromField(Map lookup, Class clazz, Field field) { 152 | try { 153 | Resource resource = new Resource(clazz, lookup, field.getGenericType()); 154 | resource.fromNames = new String[]{field.getName()}; 155 | resource.name = field.getName(); 156 | resource.annotations = field.getAnnotations(); 157 | resource.field = field; 158 | return resource; 159 | } catch (Exception e) { 160 | throw new RuntimeException("failed to onAttachView resource for field: " + field, e); 161 | } 162 | } 163 | 164 | 165 | private static List getSetters(Map lookup, Class clazz, boolean includingPrivate) { 166 | ArrayList setters = new ArrayList(); 167 | for (Method method : ReflectKit.getAllMethods(clazz, includingPrivate)) { 168 | Annotation[] annotations = method.getAnnotations(); 169 | if (annotations ==null||annotations.length==0) {//排除未注解变量 170 | continue; 171 | } 172 | if (Modifier.isStatic(method.getModifiers())) { 173 | continue; 174 | } 175 | String methodName = method.getName(); 176 | if (methodName.length() < 4) { 177 | continue; 178 | } 179 | if (!methodName.startsWith("set")) { 180 | continue; 181 | } 182 | Type[] paramTypes = method.getGenericParameterTypes(); 183 | if (paramTypes.length != 1) { 184 | continue; 185 | } 186 | if (!includingPrivate && !Modifier.isPublic(method.getParameterTypes()[0].getModifiers())) { 187 | continue; 188 | } 189 | if (includingPrivate) { 190 | method.setAccessible(true); 191 | } 192 | try { 193 | String fromName = translateSetterName(methodName); 194 | Resource resource = new Resource(clazz, lookup, paramTypes[0]); 195 | resource.fromNames = new String[]{fromName}; 196 | resource.name = fromName; 197 | resource.method = method; 198 | resource.annotations = method.getAnnotations(); 199 | setters.add(resource); 200 | } catch (Exception e) { 201 | throw new AJsoupReaderException("failed to onAttachView resource from setter: " + method, e); 202 | } 203 | } 204 | return setters; 205 | } 206 | 207 | 208 | private static String translateSetterName(String methodName) { 209 | if (!methodName.startsWith("set")) { 210 | return null; 211 | } 212 | String fromName = methodName.substring("set".length()); 213 | char[] fromNameChars = fromName.toCharArray(); 214 | fromNameChars[0] = Character.toLowerCase(fromNameChars[0]); 215 | fromName = new String(fromNameChars); 216 | return fromName; 217 | } 218 | 219 | private static Map collectTypeVariableLookup(Type type) { 220 | HashMap vars = new HashMap(); 221 | if (null == type) { 222 | return vars; 223 | } 224 | if (type instanceof ParameterizedType) { 225 | ParameterizedType pType = (ParameterizedType) type; 226 | Type[] actualTypeArguments = pType.getActualTypeArguments(); 227 | Class clazz = (Class) pType.getRawType(); 228 | for (int i = 0; i < clazz.getTypeParameters().length; i++) { 229 | TypeVariable variable = clazz.getTypeParameters()[i]; 230 | vars.put(variable.getName() + "@" + clazz.getCanonicalName(), actualTypeArguments[i]); 231 | } 232 | vars.putAll(collectTypeVariableLookup(clazz.getGenericSuperclass())); 233 | return vars; 234 | } 235 | if (type instanceof Class) { 236 | Class clazz = (Class) type; 237 | vars.putAll(collectTypeVariableLookup(clazz.getGenericSuperclass())); 238 | return vars; 239 | } 240 | throw new RuntimeException("unexpected type: " + type); 241 | } 242 | 243 | 244 | } 245 | -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/data/ConstructorDescriptor.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup.data; 2 | 3 | import java.lang.reflect.Constructor; 4 | import java.lang.reflect.Method; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class ConstructorDescriptor { 9 | /** 10 | * set to null if use constructor 11 | * otherwise use static method 12 | */ 13 | public String staticMethodName; 14 | // option 1: use constructor 15 | public Constructor ctor; 16 | // option 2: use static method 17 | public Method staticFactory; 18 | // option 3: onAttachView by extension 19 | // public Extension objectFactory; 20 | 21 | /** 22 | * the parameters to call constructor or static method 23 | */ 24 | public List parameters = new ArrayList(); 25 | 26 | @Override 27 | public String toString() { 28 | return "ConstructorDescriptor{" + 29 | "staticMethodName='" + staticMethodName + '\'' + 30 | ", ctor=" + ctor + 31 | ", staticFactory=" + staticFactory + 32 | ", parameters=" + parameters + 33 | '}'; 34 | } 35 | } -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/data/GenericArrayTypeImpl.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup.data; 2 | 3 | import java.lang.reflect.GenericArrayType; 4 | import java.lang.reflect.Type; 5 | 6 | public class GenericArrayTypeImpl implements GenericArrayType { 7 | 8 | private final Type componentType; 9 | 10 | GenericArrayTypeImpl(Type componentType) { 11 | this.componentType = componentType; 12 | } 13 | 14 | @Override 15 | public Type getGenericComponentType() { 16 | return componentType; 17 | } 18 | 19 | @Override 20 | public boolean equals(Object o) { 21 | if (this == o) return true; 22 | if (o == null || getClass() != o.getClass()) return false; 23 | 24 | GenericArrayTypeImpl that = (GenericArrayTypeImpl) o; 25 | 26 | return componentType != null ? componentType.equals(that.componentType) : that.componentType == null; 27 | 28 | } 29 | 30 | @Override 31 | public int hashCode() { 32 | return componentType != null ? componentType.hashCode() : 0; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return "GenericArrayTypeImpl{" + 38 | "componentType=" + componentType + 39 | '}'; 40 | } 41 | } -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/data/ParameterizedTypeImpl.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup.data; 2 | 3 | import java.lang.reflect.ParameterizedType; 4 | import java.lang.reflect.Type; 5 | import java.util.Arrays; 6 | 7 | public class ParameterizedTypeImpl implements ParameterizedType { 8 | private final Type[] actualTypeArguments; 9 | private final Type ownerType; 10 | private final Type rawType; 11 | 12 | public ParameterizedTypeImpl(Type[] actualTypeArguments, Type ownerType, Type rawType){ 13 | this.actualTypeArguments = actualTypeArguments; 14 | this.ownerType = ownerType; 15 | this.rawType = rawType; 16 | } 17 | 18 | public Type[] getActualTypeArguments() { 19 | return actualTypeArguments; 20 | } 21 | 22 | public Type getOwnerType() { 23 | return ownerType; 24 | } 25 | 26 | public Type getRawType() { 27 | return rawType; 28 | } 29 | 30 | @Override 31 | public boolean equals(Object o) { 32 | if (this == o) return true; 33 | if (o == null || getClass() != o.getClass()) return false; 34 | 35 | ParameterizedTypeImpl that = (ParameterizedTypeImpl) o; 36 | 37 | // Probably incorrect - comparing Object[] arrays with Arrays.equals 38 | if (!Arrays.equals(actualTypeArguments, that.actualTypeArguments)) return false; 39 | if (ownerType != null ? !ownerType.equals(that.ownerType) : that.ownerType != null) return false; 40 | return rawType != null ? rawType.equals(that.rawType) : that.rawType == null; 41 | 42 | } 43 | 44 | @Override 45 | public int hashCode() { 46 | int result = Arrays.hashCode(actualTypeArguments); 47 | result = 31 * result + (ownerType != null ? ownerType.hashCode() : 0); 48 | result = 31 * result + (rawType != null ? rawType.hashCode() : 0); 49 | return result; 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | String rawTypeName = rawType.toString(); 55 | if (rawType instanceof Class) { 56 | Class clazz = (Class) rawType; 57 | rawTypeName = clazz.getName(); 58 | } 59 | return "ParameterizedTypeImpl{" + 60 | "actualTypeArguments=" + Arrays.toString(actualTypeArguments) + 61 | ", ownerType=" + ownerType + 62 | ", rawType=" + rawTypeName + 63 | '}'; 64 | } 65 | 66 | public static boolean isSameClass(Type type, Class clazz) { 67 | if (type == clazz) { 68 | return true; 69 | } 70 | if (type instanceof ParameterizedType) { 71 | ParameterizedType pType = (ParameterizedType) type; 72 | return pType.getRawType() == clazz; 73 | } 74 | return false; 75 | } 76 | 77 | public static Type useImpl(Type type, Class clazz) { 78 | if (type instanceof Class) { 79 | return clazz; 80 | } 81 | if (type instanceof ParameterizedType) { 82 | ParameterizedType pType = (ParameterizedType) type; 83 | return new ParameterizedTypeImpl(pType.getActualTypeArguments(), pType.getOwnerType(), clazz); 84 | } 85 | return null; 86 | // throw new JsonException("can not change impl for: " + type); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/data/Resource.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup.data; 2 | 3 | 4 | import com.zdg.ajsoup.AJsoupReaderContext; 5 | import com.zdg.ajsoup.annotation.Select; 6 | import com.zdg.ajsoup.decoder.Decoder; 7 | import com.zdg.ajsoup.exception.AJsoupReaderException; 8 | 9 | import org.jsoup.select.Elements; 10 | 11 | import java.lang.annotation.Annotation; 12 | import java.lang.reflect.Field; 13 | import java.lang.reflect.GenericArrayType; 14 | import java.lang.reflect.GenericDeclaration; 15 | import java.lang.reflect.Method; 16 | import java.lang.reflect.ParameterizedType; 17 | import java.lang.reflect.Type; 18 | import java.lang.reflect.TypeVariable; 19 | import java.util.Map; 20 | 21 | public class Resource { 22 | // input 23 | public final Class clazz; 24 | public Annotation[] annotations; 25 | public Field field; 26 | public Method method; 27 | public boolean valueCanReuse; 28 | // input/output 29 | public String name; 30 | public Type valueType; 31 | // output 32 | public String[] fromNames; // for decoder 33 | public TypeLiteral valueTypeLiteral; 34 | 35 | private Decoder decoder; // 36 | private TypeLiteral clazzTypeLiteral; 37 | 38 | public Resource(Class clazz, Map lookup, Type valueType) { 39 | this.clazz = clazz; 40 | this.valueType = substituteTypeVariables(lookup, valueType); 41 | this.clazzTypeLiteral = TypeLiteral.create(clazz); 42 | this.valueTypeLiteral = TypeLiteral.create(this.valueType); 43 | } 44 | 45 | private static Type substituteTypeVariables(Map lookup, Type type) { 46 | if (type instanceof TypeVariable) { 47 | return translateTypeVariable(lookup, (TypeVariable) type); 48 | } 49 | if (type instanceof ParameterizedType) { 50 | ParameterizedType pType = (ParameterizedType) type; 51 | Type[] args = pType.getActualTypeArguments(); 52 | for (int i = 0; i < args.length; i++) { 53 | args[i] = substituteTypeVariables(lookup, args[i]); 54 | } 55 | return new ParameterizedTypeImpl(args, pType.getOwnerType(), pType.getRawType()); 56 | } 57 | if (type instanceof GenericArrayType) { 58 | GenericArrayType gaType = (GenericArrayType) type; 59 | return new GenericArrayTypeImpl(substituteTypeVariables(lookup, gaType.getGenericComponentType())); 60 | } 61 | return type; 62 | } 63 | 64 | private static Type translateTypeVariable(Map lookup, TypeVariable var) { 65 | GenericDeclaration declaredBy = var.getGenericDeclaration(); 66 | if (!(declaredBy instanceof Class)) { 67 | // if the is not defined by class, there is no way to get the actual type 68 | return Object.class; 69 | } 70 | Class clazz = (Class) declaredBy; 71 | Type actualType = lookup.get(var.getName() + "@" + clazz.getCanonicalName()); 72 | if (actualType == null) { 73 | // should not happen 74 | return Object.class; 75 | } 76 | if (actualType instanceof TypeVariable) { 77 | // translate to another variable, try again 78 | return translateTypeVariable(lookup, (TypeVariable) actualType); 79 | } 80 | return actualType; 81 | } 82 | 83 | public T getAnnotation(Class annotationClass) { 84 | if (annotations == null) { 85 | return null; 86 | } 87 | for (Annotation annotation : annotations) { 88 | if (annotationClass.isAssignableFrom(annotation.getClass())) { 89 | return (T) annotation; 90 | } 91 | } 92 | return null; 93 | } 94 | 95 | @Override 96 | public boolean equals(Object o) { 97 | if (this == o) return true; 98 | if (o == null || getClass() != o.getClass()) return false; 99 | 100 | Resource resource = (Resource) o; 101 | 102 | if (clazz != null ? !clazz.equals(resource.clazz) : resource.clazz != null) return false; 103 | return name != null ? name.equals(resource.name) : resource.name == null; 104 | } 105 | 106 | @Override 107 | public int hashCode() { 108 | int result = clazz != null ? clazz.hashCode() : 0; 109 | result = 31 * result + (name != null ? name.hashCode() : 0); 110 | return result; 111 | } 112 | 113 | @Override 114 | public String toString() { 115 | return "Resource{" + 116 | "clazz=" + clazz + 117 | ", name='" + name + '\'' + 118 | ", valueType=" + valueType + 119 | '}'; 120 | } 121 | public String decoderCacheKey() { 122 | return this.name + "@" + this.clazzTypeLiteral.getDecoderCacheKey(); 123 | } 124 | 125 | public void setDecoder(Decoder decoder) { 126 | this.decoder = decoder; 127 | } 128 | 129 | public Object deserialize(AJsoupReaderContext context){ 130 | if (context==null) throw new AJsoupReaderException(" not decoder"); 131 | if (decoder != null) { 132 | try { 133 | return decoder.decode(context); 134 | } catch (Exception e) { 135 | e.printStackTrace(); 136 | } 137 | } 138 | return null; 139 | } 140 | public Object deserialize(Elements elements){ 141 | return deserialize(new AJsoupReaderContext(elements, this)); 142 | } 143 | public boolean isHasDecoder() { 144 | return decoder!=null; 145 | } 146 | 147 | private Select getAnnotationIndex(int index) { 148 | if (annotations == null || annotations.length <= index) { 149 | return null; 150 | } 151 | Annotation annotation = annotations[index]; 152 | return annotation instanceof Select ? (Select) annotation : null; 153 | 154 | 155 | } 156 | public String attr(int index) { 157 | Select annotationIndex = getAnnotationIndex(index); 158 | return annotationIndex!=null ? annotationIndex.attr() : null; 159 | } 160 | public String key(int index) { 161 | Select annotationIndex = getAnnotationIndex(index); 162 | return annotationIndex!=null ? annotationIndex.key() : null; 163 | } 164 | public Boolean text(int index) { 165 | Select annotationIndex = getAnnotationIndex(index); 166 | return annotationIndex != null && annotationIndex.text(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/data/TypeLiteral.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup.data; 2 | 3 | 4 | import com.zdg.ajsoup.exception.AJsoupReaderException; 5 | 6 | import java.lang.reflect.GenericArrayType; 7 | import java.lang.reflect.ParameterizedType; 8 | import java.lang.reflect.Type; 9 | import java.math.BigDecimal; 10 | import java.math.BigInteger; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | public class TypeLiteral { 15 | 16 | public enum NativeType { 17 | FLOAT, 18 | DOUBLE, 19 | BOOLEAN, 20 | BYTE, 21 | SHORT, 22 | INT, 23 | CHAR, 24 | LONG, 25 | BIG_DECIMAL, 26 | BIG_INTEGER, 27 | STRING, 28 | OBJECT, 29 | } 30 | 31 | public static Map nativeTypes = new HashMap() {{ 32 | put(float.class, NativeType.FLOAT); 33 | put(Float.class, NativeType.FLOAT); 34 | put(double.class, NativeType.DOUBLE); 35 | put(Double.class, NativeType.DOUBLE); 36 | put(boolean.class, NativeType.BOOLEAN); 37 | put(Boolean.class, NativeType.BOOLEAN); 38 | put(byte.class, NativeType.BYTE); 39 | put(Byte.class, NativeType.BYTE); 40 | put(short.class, NativeType.SHORT); 41 | put(Short.class, NativeType.SHORT); 42 | put(int.class, NativeType.INT); 43 | put(Integer.class, NativeType.INT); 44 | put(char.class, NativeType.CHAR); 45 | put(Character.class, NativeType.CHAR); 46 | put(long.class, NativeType.LONG); 47 | put(Long.class, NativeType.LONG); 48 | put(BigDecimal.class, NativeType.BIG_DECIMAL); 49 | put(BigInteger.class, NativeType.BIG_INTEGER); 50 | put(String.class, NativeType.STRING); 51 | put(Object.class, NativeType.OBJECT); 52 | }}; 53 | 54 | private volatile static Map typeLiteralCache = new HashMap(); 55 | final Type type; 56 | final String decoderCacheKey; 57 | final NativeType nativeType; 58 | 59 | /** 60 | * Constructs a new type literal. Derives represented class from type parameter. 61 | * Clients onAttachView an empty anonymous subclass. Doing so embeds the type parameter in the 62 | * anonymous class's type hierarchy so we can reconstitute it at runtime despite erasure. 63 | */ 64 | @SuppressWarnings("unchecked") 65 | protected TypeLiteral() { 66 | this.type = getSuperclassTypeParameter(getClass()); 67 | nativeType = nativeTypes.get(this.type); 68 | decoderCacheKey = generateDecoderCacheKey(type); 69 | } 70 | 71 | public TypeLiteral(Type type, String decoderCacheKey) { 72 | this.type = type; 73 | nativeType = nativeTypes.get(this.type); 74 | this.decoderCacheKey = decoderCacheKey; 75 | } 76 | 77 | private static String generateDecoderCacheKey(Type type) { 78 | return generateCacheKey(type, "decoder."); 79 | } 80 | 81 | private static String generateCacheKey(Type type, String prefix) { 82 | StringBuilder decoderClassName = new StringBuilder(prefix); 83 | if (type instanceof Class) { 84 | Class clazz = (Class) type; 85 | if (clazz.isAnonymousClass()) { 86 | throw new AJsoupReaderException("anonymous class not supported: " + clazz); 87 | } 88 | if (clazz.isArray()) { 89 | decoderClassName.append(clazz.getCanonicalName().replace("[]", "_array")); 90 | } else { 91 | // for nested class $ 92 | decoderClassName.append(clazz.getName().replace("[]", "_array")); 93 | } 94 | } else if (type instanceof ParameterizedType) { 95 | try { 96 | ParameterizedType pType = (ParameterizedType) type; 97 | Class clazz = (Class) pType.getRawType(); 98 | decoderClassName.append(clazz.getCanonicalName().replace("[]", "_array")); 99 | for (int i = 0; i < pType.getActualTypeArguments().length; i++) { 100 | String typeName = formatTypeWithoutSpecialCharacter(pType.getActualTypeArguments()[i]); 101 | decoderClassName.append('_'); 102 | decoderClassName.append(typeName); 103 | } 104 | } catch (Exception e) { 105 | throw new AJsoupReaderException("failed to generate cache key for: " + type, e); 106 | } 107 | } else if (type instanceof GenericArrayType) { 108 | GenericArrayType gaType = (GenericArrayType) type; 109 | Type compType = gaType.getGenericComponentType(); 110 | decoderClassName.append(formatTypeWithoutSpecialCharacter(compType)); 111 | decoderClassName.append("_array"); 112 | } else { 113 | throw new UnsupportedOperationException("do not know how to handle: " + type); 114 | } 115 | return decoderClassName.toString().replace("$", "_"); 116 | } 117 | 118 | private static String formatTypeWithoutSpecialCharacter(Type type) { 119 | if (type instanceof Class) { 120 | Class clazz = (Class) type; 121 | return clazz.getCanonicalName(); 122 | } 123 | if (type instanceof ParameterizedType) { 124 | ParameterizedType pType = (ParameterizedType) type; 125 | String typeName = formatTypeWithoutSpecialCharacter(pType.getRawType()); 126 | for (Type typeArg : pType.getActualTypeArguments()) { 127 | typeName += "_"; 128 | typeName += formatTypeWithoutSpecialCharacter(typeArg); 129 | } 130 | return typeName; 131 | } 132 | if (type instanceof GenericArrayType) { 133 | GenericArrayType gaType = (GenericArrayType) type; 134 | return formatTypeWithoutSpecialCharacter(gaType.getGenericComponentType()) + "_array"; 135 | } 136 | throw new AJsoupReaderException("unsupported type: " + type + ", of class " + type.getClass()); 137 | } 138 | 139 | static Type getSuperclassTypeParameter(Class subclass) { 140 | Type superclass = subclass.getGenericSuperclass(); 141 | if (superclass instanceof Class) { 142 | throw new AJsoupReaderException("Missing type parameter."); 143 | } 144 | ParameterizedType parameterized = (ParameterizedType) superclass; 145 | return parameterized.getActualTypeArguments()[0]; 146 | } 147 | 148 | public static TypeLiteral create(Type valueType) { 149 | TypeLiteral typeLiteral = typeLiteralCache.get(valueType); 150 | if (typeLiteral != null) { 151 | return typeLiteral; 152 | } 153 | return createNew(valueType); 154 | } 155 | 156 | private synchronized static TypeLiteral createNew(Type valueType) { 157 | TypeLiteral typeLiteral = typeLiteralCache.get(valueType); 158 | if (typeLiteral != null) { 159 | return typeLiteral; 160 | } 161 | HashMap copy = new HashMap(typeLiteralCache); 162 | typeLiteral = new TypeLiteral(valueType, 163 | generateDecoderCacheKey(valueType)); 164 | copy.put(valueType, typeLiteral); 165 | typeLiteralCache = copy; 166 | return typeLiteral; 167 | } 168 | 169 | public Type getType() { 170 | return type; 171 | } 172 | 173 | public String getDecoderCacheKey() { 174 | return decoderCacheKey; 175 | } 176 | 177 | public NativeType getNativeType() { 178 | return nativeType; 179 | } 180 | 181 | @Override 182 | public String toString() { 183 | return "TypeLiteral{" + 184 | "type=" + type + 185 | ", decoderCacheKey='" + decoderCacheKey + '\'' + 186 | '}'; 187 | } 188 | } -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/decoder/BaseDecoder.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup.decoder; 2 | 3 | 4 | import com.zdg.ajsoup.data.ClassDescriptor; 5 | import com.zdg.ajsoup.kit.AnalysisDecoder; 6 | 7 | /** 8 | * Created by zoudong on 2017/3/12. 9 | */ 10 | 11 | public abstract class BaseDecoder implements Decoder { 12 | protected final ClassDescriptor desc; 13 | public BaseDecoder(Class clazz) { 14 | desc = AnalysisDecoder.createDecoder(clazz,null); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/decoder/Decoder.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup.decoder; 2 | 3 | 4 | import com.zdg.ajsoup.AJsoupReaderContext; 5 | 6 | public interface Decoder { 7 | 8 | Object decode(AJsoupReaderContext context) ; 9 | 10 | 11 | abstract class BooleanDecoder implements Decoder { 12 | @Override 13 | public Object decode(AJsoupReaderContext context) { 14 | return Boolean.valueOf(decodeBoolean(context)); 15 | } 16 | 17 | public abstract boolean decodeBoolean(AJsoupReaderContext context) ; 18 | } 19 | 20 | abstract class ShortDecoder implements Decoder { 21 | @Override 22 | public Object decode(AJsoupReaderContext context) { 23 | return Short.valueOf(decodeShort(context)); 24 | } 25 | 26 | public abstract short decodeShort(AJsoupReaderContext context) ; 27 | } 28 | 29 | abstract class IntDecoder implements Decoder { 30 | @Override 31 | public Object decode(AJsoupReaderContext context) { 32 | return Integer.valueOf(decodeInt(context)); 33 | } 34 | 35 | public abstract int decodeInt(AJsoupReaderContext context) ; 36 | } 37 | 38 | abstract class LongDecoder implements Decoder { 39 | @Override 40 | public Object decode(AJsoupReaderContext context) { 41 | return Long.valueOf(decodeLong(context)); 42 | } 43 | 44 | public abstract long decodeLong(AJsoupReaderContext context) ; 45 | } 46 | 47 | abstract class FloatDecoder implements Decoder { 48 | @Override 49 | public Object decode(AJsoupReaderContext context) { 50 | return Float.valueOf(decodeFloat(context)); 51 | } 52 | 53 | public abstract float decodeFloat(AJsoupReaderContext context) ; 54 | } 55 | 56 | abstract class DoubleDecoder implements Decoder { 57 | 58 | @Override 59 | public Object decode(AJsoupReaderContext context) { 60 | return Double.valueOf(decodeDouble(context)); 61 | } 62 | 63 | public abstract double decodeDouble(AJsoupReaderContext context) ; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/decoder/ReflectionArrayDecoder.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup.decoder; 2 | 3 | 4 | import com.zdg.ajsoup.AJsoupReaderContext; 5 | import com.zdg.ajsoup.data.TypeLiteral; 6 | import com.zdg.ajsoup.kit.AnalysisDecoder; 7 | 8 | import org.jsoup.nodes.Element; 9 | import org.jsoup.select.Elements; 10 | 11 | import java.lang.reflect.Constructor; 12 | 13 | class ReflectionArrayDecoder implements Decoder { 14 | 15 | private final Class componentType; 16 | private final Decoder compTypeDecoder; 17 | private Constructor ctor; 18 | 19 | public ReflectionArrayDecoder(Class clazz) { 20 | componentType = clazz.getComponentType(); 21 | try { 22 | ctor = clazz.getConstructor(); 23 | } catch (NoSuchMethodException e) { 24 | e.printStackTrace(); 25 | } 26 | compTypeDecoder = AnalysisDecoder.getDecoder(TypeLiteral.create(componentType).getDecoderCacheKey(), componentType); 27 | } 28 | 29 | @Override 30 | public Object decode(AJsoupReaderContext context) { 31 | Object ctor[] = new Constructor[context.elements.size()]; 32 | for (int i = 0; i < context.elements.size(); i++) { 33 | Element element = context.elements.get(i); 34 | ctor[i]=compTypeDecoder.decode( new AJsoupReaderContext(new Elements(element),context.resource)); 35 | } 36 | return ctor; 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/decoder/ReflectionCollectionDecoder.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup.decoder; 2 | 3 | 4 | import com.zdg.ajsoup.AJsoupReaderContext; 5 | import com.zdg.ajsoup.data.TypeLiteral; 6 | import com.zdg.ajsoup.exception.AJsoupReaderException; 7 | import com.zdg.ajsoup.kit.AnalysisDecoder; 8 | 9 | import org.jsoup.nodes.Element; 10 | import org.jsoup.select.Elements; 11 | 12 | import java.lang.reflect.Constructor; 13 | import java.lang.reflect.Type; 14 | import java.util.Collection; 15 | 16 | class ReflectionCollectionDecoder implements Decoder { 17 | private final Constructor ctor; 18 | private final Decoder compTypeDecoder; 19 | 20 | public ReflectionCollectionDecoder(Class clazz, Type[] typeArgs) { 21 | try { 22 | ctor = clazz.getConstructor(); 23 | } catch (NoSuchMethodException e) { 24 | throw new AJsoupReaderException(e); 25 | } 26 | // List T 的 解析器 27 | compTypeDecoder = AnalysisDecoder.getDecoder(TypeLiteral.create(typeArgs[0]).getDecoderCacheKey(), typeArgs[0]); 28 | } 29 | 30 | @Override 31 | public Object decode(AJsoupReaderContext context) { 32 | return decode_(context); 33 | } 34 | 35 | private Object decode_(AJsoupReaderContext context) { 36 | Collection col = null; 37 | try { 38 | col = (Collection) this.ctor.newInstance(); 39 | for (Element element : context.elements) { // bug 当 List t 为基本类型 40 | Object decode = compTypeDecoder.decode( new AJsoupReaderContext(new Elements(element),context.resource.annotations)); 41 | col.add(decode); 42 | } 43 | } catch (Exception e) { 44 | e.printStackTrace(); 45 | throw new AJsoupReaderException("list decode failure"); 46 | } 47 | 48 | return col; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/decoder/ReflectionDecoderFactory.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup.decoder; 2 | 3 | 4 | 5 | import java.lang.annotation.Annotation; 6 | import java.lang.reflect.Type; 7 | import java.util.Collection; 8 | import java.util.Map; 9 | 10 | public class ReflectionDecoderFactory { 11 | public static Decoder create(Class clazz, Annotation[] annotations, Type... typeArgs) { 12 | if (clazz.isArray()) { 13 | return new ReflectionArrayDecoder(clazz); 14 | } 15 | if (Collection.class.isAssignableFrom(clazz)) { 16 | return new ReflectionCollectionDecoder(clazz, typeArgs); 17 | } 18 | if (Map.class.isAssignableFrom(clazz)) { 19 | return new ReflectionMapDecoder(clazz, typeArgs); 20 | } 21 | if (clazz.isEnum()) { 22 | // return new ReflectionEnumDecoder(clazz); 23 | } 24 | return new ReflectionObjectDecoder(clazz,annotations).create(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/decoder/ReflectionMapDecoder.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup.decoder; 2 | 3 | 4 | 5 | 6 | 7 | import com.zdg.ajsoup.AJsoupReaderContext; 8 | import com.zdg.ajsoup.data.TypeLiteral; 9 | import com.zdg.ajsoup.exception.AJsoupReaderException; 10 | import com.zdg.ajsoup.kit.AnalysisDecoder; 11 | 12 | import org.jsoup.helper.StringUtil; 13 | import org.jsoup.nodes.Element; 14 | import org.jsoup.select.Elements; 15 | 16 | import java.lang.reflect.Constructor; 17 | import java.lang.reflect.InvocationTargetException; 18 | import java.lang.reflect.Type; 19 | import java.util.Map; 20 | 21 | class ReflectionMapDecoder implements Decoder { 22 | 23 | private final Constructor ctor; 24 | private final Decoder valueTypeDecoder; 25 | 26 | public ReflectionMapDecoder(Class clazz, Type[] typeArgs) { 27 | try { 28 | ctor = clazz.getConstructor(); 29 | } catch (NoSuchMethodException e) { 30 | throw new AJsoupReaderException(e); 31 | } 32 | TypeLiteral valueTypeLiteral = TypeLiteral.create(typeArgs[1]); 33 | valueTypeDecoder = AnalysisDecoder.getDecoder(valueTypeLiteral.getDecoderCacheKey(), typeArgs[1]); 34 | } 35 | 36 | @Override 37 | public Object decode(AJsoupReaderContext context) { 38 | return decode_(context); 39 | } 40 | 41 | private Object decode_(AJsoupReaderContext context) { 42 | Map map = (Map) createMap(); 43 | if (map==null||context.readNull()) { 44 | return null; 45 | } 46 | String key = context.resource.attr(0); 47 | if (StringUtil.isBlank(key)) { 48 | throw new AJsoupReaderException("请设置 attr 表示map 中的 key"); 49 | } 50 | for (Element element : context.elements) { 51 | try { 52 | if (element.hasAttr(key)) { 53 | String attr = element.attr(key); 54 | Object decode = valueTypeDecoder.decode(new AJsoupReaderContext(new Elements(element), context.resource)); 55 | map.put(attr, decode); 56 | } 57 | } catch (Exception e) { 58 | e.printStackTrace(); 59 | } 60 | } 61 | return map; 62 | } 63 | private Object createMap(){ 64 | try { 65 | return ctor.newInstance(); 66 | } catch (InstantiationException e) { 67 | e.printStackTrace(); 68 | } catch (IllegalAccessException e) { 69 | e.printStackTrace(); 70 | } catch (InvocationTargetException e) { 71 | e.printStackTrace(); 72 | } 73 | return null; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/decoder/ReflectionObjectDecoder.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup.decoder; 2 | 3 | 4 | import com.zdg.ajsoup.AJsoupReaderContext; 5 | import com.zdg.ajsoup.data.ClassDescriptor; 6 | import com.zdg.ajsoup.data.Resource; 7 | import com.zdg.ajsoup.exception.AJsoupReaderException; 8 | import com.zdg.ajsoup.kit.AnalysisDecoder; 9 | 10 | import java.lang.annotation.Annotation; 11 | import java.lang.reflect.InvocationTargetException; 12 | 13 | 14 | public class ReflectionObjectDecoder { 15 | 16 | protected ClassDescriptor desc; 17 | public ReflectionObjectDecoder(Class clazz, Annotation[] annotations) { 18 | desc= AnalysisDecoder.createDecoder(clazz, annotations); 19 | if (desc==null) throw new AJsoupReaderException("AnalysisDecoder to ClassDescriptor failure"); 20 | } 21 | 22 | public Decoder create() { 23 | if (desc.ctor.parameters.isEmpty()) { 24 | return new OnlyField(); 25 | // if (desc.wrappers.isEmpty()) { 26 | // 27 | // } else { 28 | // return new WithSetter(); 29 | // } 30 | } else { 31 | return new WithCtor(); 32 | } 33 | } 34 | 35 | public class OnlyField implements Decoder { 36 | 37 | public Object decode(AJsoupReaderContext context) { 38 | return decode_(context); 39 | } 40 | 41 | private Object decode_(AJsoupReaderContext context){ 42 | Object obj = createNewObject() ; 43 | if (obj==null) throw new AJsoupReaderException("construction Exception "); 44 | for (Resource field : desc.fields) { 45 | context.deserializeChild(field,obj); 46 | } 47 | for (Resource setter : desc.setters) { 48 | context.deserializeChild(setter,obj); 49 | } 50 | return obj; 51 | } 52 | 53 | } 54 | 55 | public class WithCtor implements Decoder { 56 | 57 | @Override 58 | public Object decode(AJsoupReaderContext context) { 59 | return decode_(context); 60 | } 61 | 62 | private Object decode_(AJsoupReaderContext context){ 63 | 64 | return null; 65 | } 66 | } 67 | 68 | public class WithSetter implements Decoder { 69 | 70 | @Override 71 | public Object decode(AJsoupReaderContext context) { 72 | try { 73 | return decode_(context); 74 | } catch (Exception e) { 75 | throw new AJsoupReaderException(e); 76 | } 77 | } 78 | 79 | private Object decode_(AJsoupReaderContext context) { 80 | return null; 81 | } 82 | } 83 | 84 | 85 | private Object createNewObject(Object... args) { 86 | try { 87 | if (desc.ctor != null) { 88 | if (desc.ctor.staticFactory != null) { 89 | return desc.ctor.staticFactory.invoke(null, args); 90 | } else if (desc.ctor.ctor != null) { 91 | return desc.ctor.ctor.newInstance(args); 92 | } 93 | } 94 | } catch (IllegalAccessException e) { 95 | e.printStackTrace(); 96 | } catch (InstantiationException e) { 97 | e.printStackTrace(); 98 | } catch (InvocationTargetException e) { 99 | e.printStackTrace(); 100 | } 101 | return null; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/exception/AJsoupReaderException.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup.exception; 2 | 3 | 4 | /** 5 | * Created by zoudong on 2017/3/10. 6 | */ 7 | 8 | public class AJsoupReaderException extends RuntimeException { 9 | public AJsoupReaderException() { 10 | } 11 | 12 | public AJsoupReaderException(String msg) { 13 | super(msg); 14 | } 15 | 16 | public AJsoupReaderException(String message, Throwable cause) { 17 | super(message, cause); 18 | } 19 | 20 | public AJsoupReaderException(Throwable cause) { 21 | super(cause); 22 | } 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/kit/AnalysisDecoder.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup.kit; 2 | 3 | 4 | import com.zdg.ajsoup.AJsoupReaderContext; 5 | import com.zdg.ajsoup.data.ClassDescriptor; 6 | import com.zdg.ajsoup.data.ClassReader; 7 | import com.zdg.ajsoup.data.Resource; 8 | import com.zdg.ajsoup.decoder.Decoder; 9 | import com.zdg.ajsoup.decoder.ReflectionDecoderFactory; 10 | 11 | import java.lang.annotation.Annotation; 12 | import java.lang.reflect.ParameterizedType; 13 | import java.lang.reflect.Type; 14 | import java.math.BigDecimal; 15 | import java.math.BigInteger; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | 20 | /** 21 | * Created by zoudong on 2017/3/10. 22 | */ 23 | 24 | public class AnalysisDecoder { 25 | 26 | static volatile Map decoders = new HashMap<>(); //缓存的 decoder 27 | static volatile Map registerDecoder = new HashMap<>(); 28 | static volatile Map cacheClassDescriptor = new HashMap<>(); 29 | 30 | public static Decoder getDecoder(String cacheKey, Type type) { 31 | Decoder decoder = getDecoder(cacheKey); 32 | if (decoder != null) { 33 | return decoder; 34 | } 35 | return gen(cacheKey, type, null); 36 | } 37 | 38 | public static Decoder getDecoder(String cacheKey, Type type, Annotation[] annotations) { 39 | Decoder decoder = getDecoder(cacheKey); 40 | if (decoder != null) { 41 | return decoder; 42 | } 43 | return gen(cacheKey, type, annotations); 44 | } 45 | 46 | public static void registerDecoder(Class clazz, Decoder decoder) { 47 | registerDecoder.put(clazz, decoder); 48 | } 49 | 50 | public static ClassDescriptor createDecoder(Class clazz) { 51 | return createDecoder(clazz, null); 52 | } 53 | 54 | /** 55 | * @param clazz 56 | * @param anns 这个class 当着 变量时的注解 57 | * @return 58 | */ 59 | public static ClassDescriptor createDecoder(Class clazz, Annotation[] anns) { 60 | if (cacheClassDescriptor.get(clazz) != null) { 61 | return cacheClassDescriptor.get(clazz); 62 | } 63 | ClassDescriptor desc = ClassReader.getClassDescriptor(clazz, true); 64 | try { 65 | if (anns != null && anns.length > 0) { //变量注解负责给 class 注解 66 | System.arraycopy(anns, 0, desc.clazz_anno, 0, anns.length - 1); 67 | } 68 | for (Resource resource : desc.ctor.parameters) { 69 | if (!resource.isHasDecoder()) { 70 | resource.setDecoder(AnalysisDecoder.getDecoder(resource)); 71 | } 72 | } 73 | for (Resource resource : desc.fields) { 74 | if (!resource.isHasDecoder()) { 75 | resource.setDecoder(AnalysisDecoder.getDecoder(resource)); 76 | } 77 | } 78 | for (Resource resource : desc.setters) { 79 | if (!resource.isHasDecoder()) { 80 | resource.setDecoder(AnalysisDecoder.getDecoder(resource)); 81 | } 82 | } 83 | 84 | if (!desc.ctor.parameters.isEmpty()) { 85 | // tempCacheKey = "temp@" + clazz.getCanonicalName(); 86 | // ctorArgsCacheKey = "ctor@" + clazz.getCanonicalName(); 87 | } 88 | } finally { 89 | return desc; 90 | } 91 | 92 | } 93 | 94 | /** 95 | * 创建解析器 96 | * 97 | * @param cacheKey 98 | * @param type 99 | * @param annotations 100 | * @return 101 | */ 102 | private static Decoder gen(String cacheKey, Type type, Annotation[] annotations) { 103 | Decoder decoder = getDecoder(cacheKey); 104 | if (decoder != null) { 105 | return decoder; 106 | } 107 | 108 | type = ReflectKit.chooseImpl(type); 109 | 110 | Type[] typeArgs = new Type[0]; 111 | Class clazz; 112 | if (type instanceof ParameterizedType) { 113 | ParameterizedType pType = (ParameterizedType) type; 114 | clazz = (Class) pType.getRawType(); 115 | typeArgs = pType.getActualTypeArguments(); 116 | } else { 117 | clazz = (Class) type; 118 | } 119 | decoder = NATIVE_DECODERS.get(clazz);//基本数据类型 120 | if (decoder != null) { 121 | return decoder; 122 | } 123 | decoder = registerDecoder.get(clazz); //自定义的解析器 124 | if (decoder == null) { 125 | decoder = ReflectionDecoderFactory.create(clazz, annotations, typeArgs); //注解解析器 126 | } 127 | cacheDecoder(cacheKey, decoder); 128 | return decoder; 129 | } 130 | 131 | public static Decoder getDecoder(Resource resource) { 132 | return getDecoder(resource.decoderCacheKey(), resource.valueType, resource.annotations); 133 | } 134 | 135 | private static Decoder getDecoder(String cacheKey) { 136 | return decoders.get(cacheKey); 137 | } 138 | 139 | public synchronized static void cacheDecoder(String cacheKey, Decoder decoder) { 140 | decoders.put(cacheKey, decoder); 141 | } 142 | 143 | 144 | final static Map NATIVE_DECODERS = new HashMap() {{ 145 | put(float.class, new Decoder.FloatDecoder() { 146 | @Override 147 | public float decodeFloat(AJsoupReaderContext context) { 148 | return context.readFloat(); 149 | } 150 | }); 151 | put(Float.class, new Decoder.FloatDecoder() { 152 | @Override 153 | public float decodeFloat(AJsoupReaderContext context) { 154 | return context.readFloat(); 155 | } 156 | }); 157 | put(double.class, new Decoder.DoubleDecoder() { 158 | @Override 159 | public double decodeDouble(AJsoupReaderContext context) { 160 | return context.readDouble(); 161 | } 162 | }); 163 | put(Double.class, new Decoder.DoubleDecoder() { 164 | @Override 165 | public double decodeDouble(AJsoupReaderContext context) { 166 | return context.readDouble(); 167 | } 168 | }); 169 | put(boolean.class, new Decoder.BooleanDecoder() { 170 | @Override 171 | public boolean decodeBoolean(AJsoupReaderContext context) { 172 | return context.readBoolean(); 173 | } 174 | }); 175 | put(Boolean.class, new Decoder.BooleanDecoder() { 176 | @Override 177 | public boolean decodeBoolean(AJsoupReaderContext context) { 178 | return context.readBoolean(); 179 | } 180 | }); 181 | put(byte.class, new Decoder.ShortDecoder() { 182 | @Override 183 | public short decodeShort(AJsoupReaderContext context) { 184 | return context.readShort(); 185 | } 186 | }); 187 | put(Byte.class, new Decoder.ShortDecoder() { 188 | @Override 189 | public short decodeShort(AJsoupReaderContext context) { 190 | return context.readShort(); 191 | } 192 | }); 193 | put(short.class, new Decoder.ShortDecoder() { 194 | @Override 195 | public short decodeShort(AJsoupReaderContext context) { 196 | return context.readShort(); 197 | } 198 | }); 199 | put(Short.class, new Decoder.ShortDecoder() { 200 | @Override 201 | public short decodeShort(AJsoupReaderContext context) { 202 | return context.readShort(); 203 | } 204 | }); 205 | put(int.class, new Decoder.IntDecoder() { 206 | @Override 207 | public int decodeInt(AJsoupReaderContext context) { 208 | return context.readInt(); 209 | } 210 | }); 211 | put(Integer.class, new Decoder.IntDecoder() { 212 | @Override 213 | public int decodeInt(AJsoupReaderContext context) { 214 | return context.readInt(); 215 | } 216 | }); 217 | put(char.class, new Decoder() { 218 | @Override 219 | public Object decode(AJsoupReaderContext context) { 220 | return context.readInt(); 221 | } 222 | }); 223 | put(Character.class, new Decoder() { 224 | @Override 225 | public Object decode(AJsoupReaderContext context) { 226 | return context.readInt(); 227 | } 228 | }); 229 | put(long.class, new Decoder.LongDecoder() { 230 | @Override 231 | public long decodeLong(AJsoupReaderContext context) { 232 | return context.readLong(); 233 | } 234 | }); 235 | put(Long.class, new Decoder.LongDecoder() { 236 | @Override 237 | public long decodeLong(AJsoupReaderContext context) { 238 | return context.readLong(); 239 | } 240 | }); 241 | put(BigDecimal.class, new Decoder() { 242 | @Override 243 | public Object decode(AJsoupReaderContext context) { 244 | return context.readBigDecimal(); 245 | } 246 | }); 247 | put(BigInteger.class, new Decoder() { 248 | @Override 249 | public Object decode(AJsoupReaderContext context) { 250 | return context.readBigInteger(); 251 | } 252 | }); 253 | put(String.class, new Decoder() { 254 | @Override 255 | public Object decode(AJsoupReaderContext context) { 256 | return context.readString(); 257 | } 258 | }); 259 | put(Object.class, new Decoder() { 260 | @Override 261 | public Object decode(AJsoupReaderContext context) { 262 | return context.read(); 263 | } 264 | }); 265 | }}; 266 | 267 | 268 | } 269 | -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/kit/AnnotationAnalysis.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup.kit; 2 | 3 | 4 | 5 | 6 | import com.zdg.ajsoup.annotation.Select; 7 | 8 | import org.jsoup.helper.StringUtil; 9 | import org.jsoup.select.Elements; 10 | 11 | import java.lang.annotation.Annotation; 12 | 13 | /** 14 | * Created by zoudong on 2017/3/12. 15 | */ 16 | 17 | public class AnnotationAnalysis { 18 | 19 | public static Elements analysis(Elements els,Annotation[] ans){ 20 | for (Annotation an : ans) { 21 | els = select(els, an); 22 | } 23 | return els; 24 | } 25 | 26 | private static Elements select(Elements els, Annotation an) { 27 | if (an instanceof Select) { 28 | try { 29 | String select = ((Select) an).select(); 30 | if (!StringUtil.isBlank(select)) { 31 | els = els.select(select.trim()); 32 | } 33 | 34 | } catch (Exception e) { 35 | e.printStackTrace(); 36 | } 37 | } 38 | return els; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ajsoup/src/main/java/com/zdg/ajsoup/kit/ReflectKit.java: -------------------------------------------------------------------------------- 1 | package com.zdg.ajsoup.kit; 2 | 3 | import com.zdg.ajsoup.data.ParameterizedTypeImpl; 4 | 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Method; 7 | import java.lang.reflect.ParameterizedType; 8 | import java.lang.reflect.Type; 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.Collection; 12 | import java.util.HashMap; 13 | import java.util.HashSet; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.Set; 17 | 18 | /** 19 | * Created by zoudong on 2017/5/24. 20 | */ 21 | 22 | public class ReflectKit { 23 | 24 | /** 25 | * 获取所有变量 26 | * @param clazz 27 | * @param includingPrivate 28 | * @return 29 | */ 30 | public static List getAllFields(Class clazz, boolean includingPrivate) { 31 | List temp = Arrays.asList(clazz.getFields()); 32 | List allFields = new ArrayList<>(); 33 | for (Field field : temp) { 34 | if (!isContain(allFields,field)) { 35 | allFields.add(field); 36 | } 37 | } 38 | if (includingPrivate) { 39 | allFields = new ArrayList<>(); 40 | Class current = clazz; 41 | while (current != null) { 42 | List fields = Arrays.asList(current.getDeclaredFields()); 43 | for (Field field : fields) { 44 | if (!isContain(allFields,field)) { 45 | allFields.add(field); 46 | } 47 | } 48 | current = current.getSuperclass(); 49 | } 50 | } 51 | return allFields; 52 | } 53 | /**检测Field List中是否已经包含了目标field 54 | * @param fieldList 55 | * @param field 带检测field 56 | * @return 57 | */ 58 | public static boolean isContain(List fieldList,Field field){ 59 | for(Field temp:fieldList){ 60 | if(temp.getName().equals(field.getName())){ 61 | return true; 62 | } 63 | } 64 | return false; 65 | } 66 | /** 67 | * 获取 所有的 方法 68 | * @param clazz 69 | * @param includingPrivate 70 | * @return 71 | */ 72 | public static List getAllMethods(Class clazz, boolean includingPrivate) { 73 | List allMethods = Arrays.asList(clazz.getMethods()); 74 | if (includingPrivate) { 75 | allMethods = new ArrayList(); 76 | Class current = clazz; 77 | while (current != null) { 78 | allMethods.addAll(Arrays.asList(current.getDeclaredMethods())); 79 | current = current.getSuperclass(); 80 | } 81 | } 82 | return allMethods; 83 | } 84 | public static Type chooseImpl(Type type) { 85 | Type[] typeArgs = new Type[0]; 86 | Class clazz; 87 | if (type instanceof ParameterizedType) { 88 | ParameterizedType pType = (ParameterizedType) type; 89 | clazz = (Class) pType.getRawType(); 90 | typeArgs = pType.getActualTypeArguments(); 91 | } else { 92 | clazz = (Class) type; 93 | } 94 | if (Collection.class.isAssignableFrom(clazz)) { 95 | Type compType = Object.class; 96 | if (typeArgs.length == 0) { 97 | // default to List 98 | } else if (typeArgs.length == 1) { 99 | compType = typeArgs[0]; 100 | } else { 101 | throw new IllegalArgumentException( 102 | "can not onBindData to generic collection without argument types, " + 103 | "try syntax like TypeLiteral>{}"); 104 | } 105 | if (clazz == List.class) { 106 | clazz = ArrayList.class; 107 | } else if (clazz == Set.class) { 108 | clazz = HashSet.class; 109 | } 110 | return new ParameterizedTypeImpl(new Type[]{compType}, null, clazz); 111 | } 112 | if (Map.class.isAssignableFrom(clazz)) { 113 | Type keyType = String.class; 114 | Type valueType = Object.class; 115 | if (typeArgs.length == 0) { 116 | // default to Map 117 | } else if (typeArgs.length == 2) { 118 | keyType = typeArgs[0]; 119 | valueType = typeArgs[1]; 120 | } else { 121 | throw new IllegalArgumentException( 122 | "can not onBindData to generic collection without argument types, " + 123 | "try syntax like TypeLiteral>{}"); 124 | } 125 | if (keyType != String.class) { 126 | throw new IllegalArgumentException("map key must be String"); 127 | } 128 | if (clazz == Map.class) { 129 | clazz = HashMap.class; 130 | } 131 | return new ParameterizedTypeImpl(new Type[]{keyType, valueType}, null, clazz); 132 | } 133 | return type; 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | jcenter() 7 | 8 | google() 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.0.1' 12 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.0' 13 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' 14 | 15 | // NOTE: Do not place your application dependencies here; they belong 16 | // in the individual module build.gradle files 17 | } 18 | } 19 | 20 | allprojects { 21 | repositories { 22 | jcenter() 23 | google() 24 | maven{ 25 | url 'https://dl.bintray.com/zoudongq1990/maven' 26 | } 27 | } 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /converter-ajsoup/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /proguard-rules.pro 3 | -------------------------------------------------------------------------------- /converter-ajsoup/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | 4 | android { 5 | compileSdkVersion 25 6 | buildToolsVersion '26.0.3' 7 | 8 | 9 | defaultConfig { 10 | minSdkVersion 15 11 | targetSdkVersion 25 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | compile fileTree(include: ['*.jar'], dir: 'libs') 27 | compile 'com.squareup.retrofit2:retrofit:2.3.0' 28 | compile project(':ajsoup') 29 | testCompile 'junit:junit:4.12' 30 | } 31 | 32 | ext{ 33 | bintrayRepo = 'maven' 34 | bintrayName = 'Converter-Ajsoup' 35 | 36 | publishedGroupId = 'com.zdg' 37 | libraryName = 'converter-ajsoup' 38 | artifact = 'converter-ajsoup' 39 | artifact_Id = 'converter-ajsoup' 40 | maturity ='Stable' 41 | libraryDescription = 'retrofit jsoup Converter ' 42 | 43 | siteUrl = 'https://github.com/zdongcoding/jsouplib' 44 | gitUrl = 'https://github.com/zdongcoding/jsouplib.git' 45 | issueUrl='https://github.com/zdongcoding/jsouplib/issues' 46 | libraryVersion = '1.0.0-beta1' 47 | alllabels = ['android','retrofit','jsoup','converter'] 48 | developerId = 'zdongcoding' 49 | developerName = 'zoudong' 50 | developerEmail = 'zoudongq1990@gmail.com' 51 | 52 | licenseName = 'The Apache Software License, Version 2.0' 53 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 54 | allLicenses = ["Apache-2.0"] 55 | } 56 | //apply from:'../Config.gradle' -------------------------------------------------------------------------------- /converter-ajsoup/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /converter-ajsoup/src/main/java/com/zdg/converter/ajsoup/AJSOUP.java: -------------------------------------------------------------------------------- 1 | package com.zdg.converter.ajsoup; 2 | 3 | /** 4 | * Created by zoudong on 2017/5/24. 5 | */ 6 | 7 | final public class AJSOUP { 8 | public AJSOUP(T data) { 9 | this.data = data; 10 | } 11 | 12 | public T data; 13 | } 14 | -------------------------------------------------------------------------------- /converter-ajsoup/src/main/java/com/zdg/converter/ajsoup/AJsoupConverterFactory.java: -------------------------------------------------------------------------------- 1 | package com.zdg.converter.ajsoup; 2 | 3 | 4 | import java.lang.annotation.Annotation; 5 | import java.lang.reflect.Type; 6 | 7 | import okhttp3.RequestBody; 8 | import okhttp3.ResponseBody; 9 | import retrofit2.Converter; 10 | import retrofit2.Retrofit; 11 | 12 | /** 13 | * Created by zoudong on 2017/3/3. 14 | */ 15 | 16 | public class AJsoupConverterFactory extends Converter.Factory{ 17 | 18 | 19 | private AJsoupConverterFactory(){ 20 | } 21 | 22 | public static AJsoupConverterFactory create() { 23 | return new AJsoupConverterFactory(); 24 | } 25 | 26 | @Override 27 | public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { 28 | 29 | return AJsoupResponseBodyConverter.create(type); 30 | } 31 | 32 | @Override 33 | public Converter requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { 34 | return super.requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /converter-ajsoup/src/main/java/com/zdg/converter/ajsoup/AJsoupRequestBodyConverter.java: -------------------------------------------------------------------------------- 1 | package com.zdg.converter.ajsoup; 2 | 3 | import java.io.IOException; 4 | 5 | import okhttp3.MediaType; 6 | import okhttp3.RequestBody; 7 | import retrofit2.Converter; 8 | 9 | final class AJsoupRequestBodyConverter implements Converter { 10 | static final AJsoupRequestBodyConverter INSTANCE = new AJsoupRequestBodyConverter<>(); 11 | private static final MediaType MEDIA_TYPE = MediaType.parse("text/plain; charset=UTF-8"); 12 | 13 | private AJsoupRequestBodyConverter() { 14 | } 15 | 16 | @Override 17 | public RequestBody convert(T value) throws IOException { 18 | return RequestBody.create(MEDIA_TYPE, String.valueOf(value)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /converter-ajsoup/src/main/java/com/zdg/converter/ajsoup/AJsoupResponseBodyConverter.java: -------------------------------------------------------------------------------- 1 | package com.zdg.converter.ajsoup; 2 | 3 | 4 | import com.zdg.ajsoup.AJsoupReader; 5 | import com.zdg.ajsoup.annotation.Select; 6 | import com.zdg.ajsoup.kit.ReflectKit; 7 | 8 | 9 | import org.jsoup.Jsoup; 10 | import org.jsoup.nodes.Document; 11 | 12 | import java.io.IOException; 13 | import java.lang.annotation.Annotation; 14 | import java.lang.reflect.ParameterizedType; 15 | import java.lang.reflect.Type; 16 | 17 | import okhttp3.ResponseBody; 18 | import retrofit2.Converter; 19 | 20 | final class AJsoupResponseBodyConverter { 21 | 22 | public static Converter create(Type type) { 23 | if (type instanceof ParameterizedType) { 24 | if (((ParameterizedType) type).getRawType() == AJSOUP.class) { 25 | Type type1 = ReflectKit.chooseImpl(type); 26 | if (type1 instanceof ParameterizedType) { 27 | Type type2 = ((ParameterizedType) type1).getActualTypeArguments()[0]; 28 | return new JSOUPResponseBodyConverter<>((Class) type2); 29 | } 30 | 31 | } 32 | } 33 | if (type instanceof Class) { 34 | Class aClass = (Class) type; 35 | if (aClass == Document.class) { 36 | return new AJsoupResponseBodyConverter.JsoupTResponseBodyConverter<>((Class) type); 37 | } 38 | if (aClass.getAnnotations() != null) { 39 | for (Annotation annotation : aClass.getAnnotations()) { 40 | if (annotation instanceof Select) { 41 | return new JsoupTResponseBodyConverter<>((Class) type); 42 | } 43 | } 44 | } 45 | } 46 | return null; 47 | } 48 | 49 | static final class JSOUPResponseBodyConverter implements Converter> { 50 | 51 | private T mT; 52 | 53 | public JSOUPResponseBodyConverter(T t) { 54 | this.mT = t; 55 | } 56 | 57 | @Override 58 | public AJSOUP convert(ResponseBody value) throws IOException { 59 | try { 60 | return new AJSOUP(AJsoupReader.deserialize(Jsoup.parse(value.string()), (Class) mT)); 61 | } finally { 62 | value.close(); 63 | } 64 | } 65 | 66 | } 67 | 68 | static final class JsoupTResponseBodyConverter implements Converter { 69 | 70 | private T mT; 71 | 72 | public JsoupTResponseBodyConverter(T t) { 73 | this.mT = t; 74 | } 75 | 76 | @Override 77 | public T convert(ResponseBody value) throws IOException { 78 | Document parse = Jsoup.parse(value.string()); 79 | try { 80 | if (mT == Document.class) { 81 | return (T) parse; 82 | } 83 | return AJsoupReader.deserialize(parse, (Class) mT); 84 | } finally { 85 | value.close(); 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /converter-ajsoup/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | converter-jsoup 3 | 4 | -------------------------------------------------------------------------------- /converter-ajsoup/src/test/java/com/github/zdongcoding/converter/jsoup/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.github.zdongcoding.converter.jsoup; 2 | 3 | import com.zdg.converter.ajsoup.AJSOUP; 4 | import com.zdg.ajsoup.kit.ReflectKit; 5 | 6 | import org.junit.Test; 7 | 8 | import java.lang.reflect.ParameterizedType; 9 | import java.lang.reflect.Type; 10 | import java.util.ArrayList; 11 | 12 | /** 13 | * Example local unit test, which will execute on the development machine (host). 14 | * 15 | * @see Testing documentation 16 | */ 17 | public class ExampleUnitTest { 18 | @Test 19 | public void addition_isCorrect() throws Exception { 20 | ArrayList objects = new ArrayList(){}; 21 | AJSOUP jsoup = new AJSOUP<>("sgs"); 22 | Type type = ReflectKit.chooseImpl(jsoup.getClass()); 23 | if (type instanceof ParameterizedType) { 24 | System.out.println(((ParameterizedType) type).getActualTypeArguments()[0]); 25 | } 26 | System.out.println(type); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /build 3 | /libs 4 | demo.iml 5 | proguard-rules.pro -------------------------------------------------------------------------------- /demo/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | 4 | android { 5 | compileSdkVersion 25 6 | buildToolsVersion '26.0.2' 7 | 8 | 9 | defaultConfig { 10 | applicationId "com.github.zdongcoding.jsoup.demo" 11 | minSdkVersion 15 12 | targetSdkVersion 25 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 17 | 18 | } 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | } 26 | 27 | dependencies { 28 | compile fileTree(include: ['*.jar'], dir: 'libs') 29 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 30 | exclude group: 'com.android.support', module: 'support-annotations' 31 | }) 32 | compile 'com.squareup.retrofit2:retrofit:2.3.0' 33 | compile 'com.squareup.retrofit2:converter-scalars:2.3.0' 34 | compile 'com.android.support:appcompat-v7:25.3.1' 35 | testCompile 'junit:junit:4.12' 36 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 37 | compile 'io.reactivex:rxandroid:1.2.1' 38 | compile 'com.squareup.retrofit2:adapter-rxjava:2.3.0' 39 | // compile project(':Ajsoup') 40 | // compile 'com.github.zdongcoding:ajsoup:0.1.0' 41 | // compile 'com.github.zdongcoding:converter-ajsoup:0.1.0' 42 | compile project(path: ':converter-ajsoup') 43 | } 44 | -------------------------------------------------------------------------------- /demo/src/androidTest/java/com/github/zdongcoding/jsoup/demo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.github.zdongcoding.jsoup.demo; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.github.zdongcoding.jsoup.demo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /demo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demo/src/main/java/com/github/zdongcoding/jsoup/demo/Api.java: -------------------------------------------------------------------------------- 1 | package com.github.zdongcoding.jsoup.demo; 2 | 3 | import com.github.zdongcoding.jsoup.demo.home.HomeBean; 4 | 5 | 6 | import retrofit2.http.GET; 7 | import retrofit2.http.Path; 8 | import rx.Observable; 9 | 10 | public interface Api { 11 | @GET("{url}") 12 | Observable getPage(@Path(value = "url",encoded = true) String url); 13 | 14 | @GET("{url}") 15 | Observable getPag1(@Path(value = "url",encoded = true) String url); // 第一种方式 T 必须有Select 的 annotation 16 | // @GET("{url}") 17 | // Observable> getPag1(@Path(value = "url",encoded = true) String url); // 第二种方式 18 | } 19 | -------------------------------------------------------------------------------- /demo/src/main/java/com/github/zdongcoding/jsoup/demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.zdongcoding.jsoup.demo; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.util.Log; 6 | import android.widget.TextView; 7 | 8 | import com.zdg.converter.ajsoup.AJsoupConverterFactory; 9 | import com.github.zdongcoding.jsoup.demo.home.HomeBean; 10 | 11 | import retrofit2.Retrofit; 12 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 13 | import retrofit2.converter.scalars.ScalarsConverterFactory; 14 | import rx.Subscriber; 15 | import rx.android.schedulers.AndroidSchedulers; 16 | import rx.schedulers.Schedulers; 17 | 18 | public class MainActivity extends AppCompatActivity { 19 | 20 | private String baseUri="http://www.80s.tw/"; 21 | private Api api; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_main); 27 | final TextView view= (TextView) findViewById(R.id.text); 28 | api = new Retrofit.Builder().baseUrl(baseUri) 29 | .addConverterFactory(AJsoupConverterFactory.create()) 30 | .addConverterFactory(ScalarsConverterFactory.create()) 31 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 32 | .build().create(Api.class); 33 | // api.getPage("").flatMap(new Func1>() { 34 | // @Override 35 | // public Observable call(String s) { 36 | // Type genericSuperclass = this.getClass().getGenericSuperclass(); 37 | // if (genericSuperclass instanceof ParameterizedType) { 38 | // Type type = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0]; 39 | // Log.e("zoudong", ": "+type); 40 | // } 41 | // return Observable.just(JsoupReader.deserialize(Jsoup.parse(s), HomeBean.class)); 42 | // } 43 | // }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber() { 44 | // @Override 45 | // public void onCompleted() { 46 | // 47 | // } 48 | // 49 | // @Override 50 | // public void onError(Throwable e) { 51 | // 52 | // } 53 | // 54 | // @Override 55 | // public void onNext(HomeBean homeBean) { 56 | // view.setText(homeBean.toString()); 57 | // } 58 | // }); 59 | 60 | api.getPag1("").subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber() { 61 | @Override 62 | public void onCompleted() { 63 | 64 | } 65 | 66 | @Override 67 | public void onError(Throwable e) { 68 | Log.e("zoudong", "onError====" + "e = [" + e + "]"); 69 | } 70 | 71 | @Override 72 | public void onNext(HomeBean homeBean) { 73 | view.setText(homeBean.toString()); 74 | } 75 | }); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /demo/src/main/java/com/github/zdongcoding/jsoup/demo/MediaInfoBean.java: -------------------------------------------------------------------------------- 1 | package com.github.zdongcoding.jsoup.demo; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | import android.text.TextUtils; 6 | 7 | import com.zdg.ajsoup.annotation.Select; 8 | 9 | 10 | /** 11 | * Created by zoudong on 2017/3/3. 12 | */ 13 | @Select(select = "body > div") 14 | public class MediaInfoBean implements Parcelable { 15 | public String pic_url; //数据需要拼接所以使用 set 方法 16 | @Select(select = "span.tip", text = true) 17 | public String tip; 18 | @Select(select = "span[alt]", attr = "alt") 19 | public String media_type; 20 | @Select(select = "a span.poster_score", text = true) 21 | public String poster_score; 22 | @Select(select ="a[href]", attr ="href") 23 | public String url; 24 | @Select(select ="h3 > a[href]", text=true) 25 | public String title; 26 | 27 | @Select(select = "img[_src]", attr = "_src") 28 | public void setPic_url(String pic_url) { 29 | if (!TextUtils.isEmpty(pic_url)) { 30 | this.pic_url = pic_url; 31 | } 32 | } 33 | 34 | // @Select(select ="a[href]", attr ="href") 35 | // public void setUrl(String pic_url) { 36 | // if (!TextUtils.isEmpty(pic_url)) { 37 | // this.url = BeanUtils.getUrl(pic_url); 38 | // } 39 | // } 40 | 41 | @Override 42 | public int describeContents() { 43 | return 0; 44 | } 45 | 46 | @Override 47 | public void writeToParcel(Parcel dest, int flags) { 48 | dest.writeString(this.url); 49 | dest.writeString(this.pic_url); 50 | dest.writeString(this.title); 51 | dest.writeString(this.tip); 52 | dest.writeString(this.media_type); 53 | dest.writeString(this.poster_score); 54 | } 55 | 56 | public MediaInfoBean() { 57 | } 58 | 59 | protected MediaInfoBean(Parcel in) { 60 | this.url = in.readString(); 61 | this.pic_url = in.readString(); 62 | this.title = in.readString(); 63 | this.tip = in.readString(); 64 | this.media_type = in.readString(); 65 | this.poster_score = in.readString(); 66 | } 67 | 68 | public static final Creator CREATOR = new Creator() { 69 | @Override 70 | public MediaInfoBean createFromParcel(Parcel source) { 71 | return new MediaInfoBean(source); 72 | } 73 | 74 | @Override 75 | public MediaInfoBean[] newArray(int size) { 76 | return new MediaInfoBean[size]; 77 | } 78 | }; 79 | 80 | 81 | @Override 82 | public String toString() { 83 | return "MediaInfoBean{" + 84 | "url='" + url + '\'' + 85 | ", pic_url='" + pic_url + '\'' + 86 | ", title='" + title + '\'' + 87 | ", tip='" + tip + '\'' + 88 | ", media_type='" + media_type + '\'' + 89 | '}'; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /demo/src/main/java/com/github/zdongcoding/jsoup/demo/WebBean.java: -------------------------------------------------------------------------------- 1 | package com.github.zdongcoding.jsoup.demo; 2 | 3 | 4 | import com.zdg.ajsoup.annotation.Select; 5 | 6 | /** 7 | * Created by zoudong on 2017/3/5. 8 | */ 9 | 10 | public class WebBean { 11 | @Select(select ="a[href]", attr ="href") 12 | public String url; 13 | @Select(select ="a[href]", text=true) 14 | public String title; 15 | 16 | @Override 17 | public String toString() { 18 | return "WebBean{" + 19 | "url='" + url + '\'' + 20 | ", title='" + title + '\'' + 21 | '}'; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /demo/src/main/java/com/github/zdongcoding/jsoup/demo/home/HomeBean.java: -------------------------------------------------------------------------------- 1 | package com.github.zdongcoding.jsoup.demo.home; 2 | 3 | 4 | 5 | import com.zdg.ajsoup.annotation.Select; 6 | import com.github.zdongcoding.jsoup.demo.WebBean; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * Created by zoudong on 2017/3/3. 13 | */ 14 | @Select(select = "body") 15 | public class HomeBean { 16 | // @Select(select = "div div div.listbox div[id]:not(#list2).lpelmt2.me2li",attr = "id") 17 | // public Map> hotTopVideo; 18 | @Select(select = "div > div > div.listbox") 19 | public HotTopTabBean hotTopTabBean; //热门数据 20 | @Select(select = "div#nav > ul > li[id]", attr = "id") 21 | public Map navBeans; 22 | @Select(select = "div#nav-under > ul >li:has(a)") //排除无 navUnderBeans; 24 | @Select(select = "div#body > div.left.noborder.clearfix.block1 > ul") 25 | public List latests; 26 | @Select(select = "div#header-in > div > ul#hot-words > li") 27 | public List searchbeans; 28 | 29 | @Override 30 | public String toString() { 31 | return "HomeBean{" + 32 | "hotTopTabBean=" + hotTopTabBean + 33 | ", navBeans=" + navBeans + 34 | ", navUnderBeans=" + navUnderBeans + 35 | ", latests=" + latests + 36 | ", searchbeans=" + searchbeans + 37 | '}'; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /demo/src/main/java/com/github/zdongcoding/jsoup/demo/home/HotTopData.java: -------------------------------------------------------------------------------- 1 | package com.github.zdongcoding.jsoup.demo.home; 2 | 3 | 4 | import com.github.zdongcoding.jsoup.demo.MediaInfoBean; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by zoudong on 2017/3/16. 10 | */ 11 | 12 | public class HotTopData { 13 | public String title; 14 | public List lists; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /demo/src/main/java/com/github/zdongcoding/jsoup/demo/home/HotTopTabBean.java: -------------------------------------------------------------------------------- 1 | package com.github.zdongcoding.jsoup.demo.home; 2 | 3 | import com.zdg.ajsoup.annotation.Select; 4 | import com.github.zdongcoding.jsoup.demo.MediaInfoBean; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by zoudong on 2017/3/5. 10 | */ 11 | public class HotTopTabBean { 12 | public static final String TOP_MM = "tt1"; //热门手机电影 13 | public static final String TOP_TV="tt2"; //热门电视剧 14 | public static final String TOP_AM="tt3"; //热门动漫 15 | public static final String TOP_VA="tt4"; //热门综艺 16 | 17 | @Select(select = "div#tt1 div:not(#list2)") 18 | public List top_mm; //热门手机电影 19 | @Select(select = "div#tt2 div:not(#list2)") 20 | public List top_tv; //热门电视剧 21 | @Select(select = "div#tt3 div:not(#list2)") 22 | public List top_am; //热门动漫 23 | @Select(select = "div#tt4 div:not(#list2)") 24 | public List top_va; //热门综艺 25 | 26 | 27 | @Override 28 | public String toString() { 29 | return "HotTopTabBean{" + 30 | "top_mm=" + top_mm + 31 | ", top_tv=" + top_tv + 32 | ", top_am=" + top_am + 33 | ", top_va=" + top_va + 34 | '}'; 35 | } 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /demo/src/main/java/com/github/zdongcoding/jsoup/demo/home/LatestPackBean.java: -------------------------------------------------------------------------------- 1 | package com.github.zdongcoding.jsoup.demo.home; 2 | 3 | /** 4 | * Created by zoudong on 2017/3/5. 5 | */ 6 | 7 | public class LatestPackBean { 8 | public static final int LATEST_MM = 0; //热门手机电影 9 | public static final int LATEST_TV = 1; //热门电视剧 10 | public static final int LATEST_AM = 2; //热门动漫 11 | public static final int LATEST_VA = 3; //热门综艺 12 | 13 | // @Select(select = "div a,div ul,div div") 14 | public LatestVideoBean latestMms; //最新电影 15 | // @Select(select = "div.button_more + a") 16 | public LatestVideoBean latestTvs; //最新电视剧 17 | // @Select(select = "div.button_more + a") 18 | public LatestVideoBean latestVas; //最新综艺 19 | // @Select(select = "div.button_more + a") 20 | public LatestVideoBean latestAms;//最新动漫 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /demo/src/main/java/com/github/zdongcoding/jsoup/demo/home/LatestVideoBean.java: -------------------------------------------------------------------------------- 1 | package com.github.zdongcoding.jsoup.demo.home; 2 | 3 | 4 | import com.zdg.ajsoup.annotation.Select; 5 | import com.github.zdongcoding.jsoup.demo.MediaInfoBean; 6 | import com.github.zdongcoding.jsoup.demo.WebBean; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Created by zoudong on 2017/3/5. 12 | */ 13 | 14 | public class LatestVideoBean extends WebBean { 15 | public String url; 16 | public String title; 17 | 18 | @Select(select = "ul li") 19 | public List mediaInfos; 20 | 21 | @Override 22 | public String toString() { 23 | return "LatestVideoBean{" + 24 | "mediaInfos=" + mediaInfos + 25 | '}'; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /demo/src/main/java/com/github/zdongcoding/jsoup/demo/home/NavBean.java: -------------------------------------------------------------------------------- 1 | package com.github.zdongcoding.jsoup.demo.home; 2 | 3 | 4 | import com.zdg.ajsoup.annotation.Select; 5 | import com.github.zdongcoding.jsoup.demo.WebBean; 6 | 7 | /** 8 | * Created by zoudong on 2017/3/5. 9 | */ 10 | 11 | public class NavBean extends WebBean { 12 | public static final String NAV_MOVIE = "nav_movie";// 电影 13 | public static final String NAV_JU = "nav_ju";// 电视剧 14 | public static final String NAV_DM = "nav_dm";// 动漫 15 | public static final String NAV_ZY = "nav_zy"; // 综艺 16 | public static final String NAV_HOT = "nav_hot"; //热门影片 17 | public static final String NAV_ZHUANTI = "nav_zhuanti"; //电影专题 18 | public static final String NAV_LAST = "nav_last"; //最新更新 19 | @Select(select = "li:has(strong)") 20 | public boolean isclassify; 21 | @Select(select = "a[href]", text = true) 22 | public String title; 23 | 24 | 25 | @Override 26 | public String toString() { 27 | return "NavBean{" + 28 | "isclassify=" + isclassify + 29 | ", title='" + title + '\'' + 30 | '}'; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /demo/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zdongcoding/jsouplib/478251d1e5f0ebac705a62ce99e52ee1690f4883/demo/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zdongcoding/jsouplib/478251d1e5f0ebac705a62ce99e52ee1690f4883/demo/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zdongcoding/jsouplib/478251d1e5f0ebac705a62ce99e52ee1690f4883/demo/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zdongcoding/jsouplib/478251d1e5f0ebac705a62ce99e52ee1690f4883/demo/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zdongcoding/jsouplib/478251d1e5f0ebac705a62ce99e52ee1690f4883/demo/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zdongcoding/jsouplib/478251d1e5f0ebac705a62ce99e52ee1690f4883/demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zdongcoding/jsouplib/478251d1e5f0ebac705a62ce99e52ee1690f4883/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zdongcoding/jsouplib/478251d1e5f0ebac705a62ce99e52ee1690f4883/demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zdongcoding/jsouplib/478251d1e5f0ebac705a62ce99e52ee1690f4883/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zdongcoding/jsouplib/478251d1e5f0ebac705a62ce99e52ee1690f4883/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /demo/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | demo 3 | 4 | -------------------------------------------------------------------------------- /demo/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/src/test/java/com/github/zdongcoding/jsoup/demo/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.github.zdongcoding.jsoup.demo; 2 | 3 | import com.google.gson.TypeAdapter; 4 | import com.google.gson.internal.$Gson$Types; 5 | import com.google.gson.reflect.TypeToken; 6 | 7 | import org.junit.Test; 8 | 9 | import java.lang.reflect.Field; 10 | import java.lang.reflect.Method; 11 | import java.lang.reflect.ParameterizedType; 12 | import java.lang.reflect.Type; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | import static org.junit.Assert.*; 17 | 18 | /** 19 | * Example local unit test, which will execute on the development machine (host). 20 | * 21 | * @see Testing documentation 22 | */ 23 | public class ExampleUnitTest { 24 | @Test 25 | public void addition_isCorrect() throws Exception { 26 | List strings = new ArrayList(){}; 27 | getType(strings.getClass()); 28 | System.out.println(new Type<>(strings).getType()); 29 | // getMethod((Class) new Type("Sglka").getType()); 30 | } 31 | 32 | private void getMethod(Class typeClass) { 33 | try { 34 | Field methods = typeClass.getField("data"); 35 | java.lang.reflect.Type genericType = methods.getGenericType(); 36 | if (genericType instanceof ParameterizedType) { 37 | System.out.println(((ParameterizedType) genericType).getActualTypeArguments()[0]); 38 | } 39 | System.out.println(genericType); 40 | } catch (NoSuchFieldException e) { 41 | e.printStackTrace(); 42 | } 43 | 44 | } 45 | 46 | private void getType(Class t) { 47 | System.out.println(new TypeToken() { 48 | }.getType()); 49 | } 50 | class Type{ 51 | public List mStrings; 52 | public T data; 53 | 54 | public Type(T data) { 55 | this.data = data; 56 | System.out.println(data.getClass()); 57 | } 58 | java.lang.reflect.Type getType(){ 59 | return data.getClass(); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':ajsoup', ':demo', ':converter-ajsoup' 2 | --------------------------------------------------------------------------------