[
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/caches/build_file_checksums.ser\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n"
  },
  {
    "path": ".idea/codeStyles/Project.xml",
    "content": "<component name=\"ProjectCodeStyleConfiguration\">\n  <code_scheme name=\"Project\" version=\"173\">\n    <JetCodeStyleSettings>\n      <option name=\"CODE_STYLE_DEFAULTS\" value=\"KOTLIN_OFFICIAL\" />\n    </JetCodeStyleSettings>\n    <Objective-C-extensions>\n      <file>\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Import\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Macro\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Typedef\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Enum\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Constant\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Global\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Struct\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"FunctionPredecl\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Function\" />\n      </file>\n      <class>\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Property\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"Synthesize\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"InitMethod\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"StaticMethod\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"InstanceMethod\" />\n        <option name=\"com.jetbrains.cidr.lang.util.OCDeclarationKind\" value=\"DeallocMethod\" />\n      </class>\n      <extensions>\n        <pair source=\"cpp\" header=\"h\" fileNamingConvention=\"NONE\" />\n        <pair source=\"c\" header=\"h\" fileNamingConvention=\"NONE\" />\n      </extensions>\n    </Objective-C-extensions>\n    <codeStyleSettings language=\"kotlin\">\n      <option name=\"CODE_STYLE_DEFAULTS\" value=\"KOTLIN_OFFICIAL\" />\n    </codeStyleSettings>\n  </code_scheme>\n</component>"
  },
  {
    "path": ".idea/codeStyles/codeStyleConfig.xml",
    "content": "<component name=\"ProjectCodeStyleConfiguration\">\n  <state>\n    <option name=\"USE_PER_PROJECT_SETTINGS\" value=\"true\" />\n  </state>\n</component>"
  },
  {
    "path": ".idea/gradle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"GradleSettings\">\n    <option name=\"linkedExternalProjectsSettings\">\n      <GradleProjectSettings>\n        <option name=\"distributionType\" value=\"DEFAULT_WRAPPED\" />\n        <option name=\"externalProjectPath\" value=\"$PROJECT_DIR$\" />\n        <option name=\"modules\">\n          <set>\n            <option value=\"$PROJECT_DIR$\" />\n            <option value=\"$PROJECT_DIR$/app\" />\n            <option value=\"$PROJECT_DIR$/windowtree_annotation\" />\n            <option value=\"$PROJECT_DIR$/windowtree_compiler\" />\n            <option value=\"$PROJECT_DIR$/windowtree_library\" />\n          </set>\n        </option>\n        <option name=\"resolveModulePerSourceSet\" value=\"false\" />\n      </GradleProjectSettings>\n    </option>\n  </component>\n</project>"
  },
  {
    "path": ".idea/misc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"NullableNotNullManager\">\n    <option name=\"myDefaultNullable\" value=\"android.support.annotation.Nullable\" />\n    <option name=\"myDefaultNotNull\" value=\"android.support.annotation.NonNull\" />\n    <option name=\"myNullables\">\n      <value>\n        <list size=\"7\">\n          <item index=\"0\" class=\"java.lang.String\" itemvalue=\"org.jetbrains.annotations.Nullable\" />\n          <item index=\"1\" class=\"java.lang.String\" itemvalue=\"javax.annotation.Nullable\" />\n          <item index=\"2\" class=\"java.lang.String\" itemvalue=\"javax.annotation.CheckForNull\" />\n          <item index=\"3\" class=\"java.lang.String\" itemvalue=\"edu.umd.cs.findbugs.annotations.Nullable\" />\n          <item index=\"4\" class=\"java.lang.String\" itemvalue=\"android.support.annotation.Nullable\" />\n          <item index=\"5\" class=\"java.lang.String\" itemvalue=\"androidx.annotation.Nullable\" />\n          <item index=\"6\" class=\"java.lang.String\" itemvalue=\"androidx.annotation.RecentlyNullable\" />\n        </list>\n      </value>\n    </option>\n    <option name=\"myNotNulls\">\n      <value>\n        <list size=\"6\">\n          <item index=\"0\" class=\"java.lang.String\" itemvalue=\"org.jetbrains.annotations.NotNull\" />\n          <item index=\"1\" class=\"java.lang.String\" itemvalue=\"javax.annotation.Nonnull\" />\n          <item index=\"2\" class=\"java.lang.String\" itemvalue=\"edu.umd.cs.findbugs.annotations.NonNull\" />\n          <item index=\"3\" class=\"java.lang.String\" itemvalue=\"android.support.annotation.NonNull\" />\n          <item index=\"4\" class=\"java.lang.String\" itemvalue=\"androidx.annotation.NonNull\" />\n          <item index=\"5\" class=\"java.lang.String\" itemvalue=\"androidx.annotation.RecentlyNonNull\" />\n        </list>\n      </value>\n    </option>\n  </component>\n  <component name=\"ProjectRootManager\" version=\"2\" languageLevel=\"JDK_1_7\" default=\"true\" project-jdk-name=\"1.8\" project-jdk-type=\"JavaSDK\">\n    <output url=\"file://$PROJECT_DIR$/build/classes\" />\n  </component>\n  <component name=\"ProjectType\">\n    <option name=\"id\" value=\"Android\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/runConfigurations.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RunConfigurationProducerService\">\n    <option name=\"ignoredProducers\">\n      <set>\n        <option value=\"org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer\" />\n        <option value=\"org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer\" />\n        <option value=\"org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer\" />\n      </set>\n    </option>\n  </component>\n</project>"
  },
  {
    "path": ".idea/vcs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping directory=\"$PROJECT_DIR$\" vcs=\"Git\" />\n  </component>\n</project>"
  },
  {
    "path": "README.md",
    "content": "# WindowTree\n只需使用注解，帮助你轻松维护树形界面的层级关系，管理你的界面结构，当你处于界面的任何位置时，都可以知道，我在哪里，我的父界面是谁，我的子界面是谁。甚至能够自动构建你的界面结构。\n\n## 我们经常会遇一些这样的问题：\n\nhome界面下面有三个子界面A，B，C，他们三个业务功能内都会出现一个或多个未读消息或者通知，那么我们的home界面需要展示的未读消息数就应该是三个子界面未读消息数之和。\n\n刚看到这个问题的时候会感觉很简单，我只需要在每次触发未读消息增减变化的时候，将本级界面和上一级界面的未读数量重新统计即可。当然，简单的需求是可以这么做，但是你有没有考虑过：\n\n1. 当子界面A下级又有三个子界面A1,A2,A3的时候该怎么办？\n\n2. 当用户没有一些界面的查看权限时，缺少了B和A2界面的浏览权限时，该怎么办？\n\n3. 在界面构建时，如果发现用户没有A1,A2,A3的权限，那么其实A界面也不需要被构建，这样的工作，框架可以帮助我完成吗？\n\n考虑到以上问题，如果用面向过程的思考方式出现一个解决一个，必然是事倍功半的，所以WindowTree就这样诞生了。\n\n# WindowTree结构\n![avatar](https://img-blog.csdnimg.cn/20190302184300349.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2thaXh1YW5fZGFzaGVu,size_16,color_FFFFFF,t_70)\n\nwindowTree将应用内的所有界面都视为一个Window，每个window都拥有与之关联的WindowInfo，借助WindowInfo，你可以完成灵活、强大的工作。\n\n目前支持的功能：\n\n1. 维护了父子界面的层级关系，你在应用的任意位置，都可以知道我的父界面是谁，我的子界面有几个等\n2. 灵活的消息通讯，你可以在任意位置发送消息给任意界面，支持双向通讯，你可以知道接收者处理该消息的结果\n3. 支持贯穿全局的未读消息小红点，一个父节点能够智能计算出他所拥有的所有子节点的未读消息之和，借助Kotlin的委托属性实现了数量变化实时通知更新！\n4. 智能的权限管理，当用户未拥有a的子界面a1，a2的权限时，也将失去a的界面权限\n5. 界面自动化搭建，你只需要使用注解定义好界面的层级关系，便可以调用api获取所有子界面，一个循环全部建立\n6. 统一管理界面跳转，便于管理界面和埋点统计，减少重复代码\n7. 支持Activity、Fragment、View、Dialog、PopupWindow...\n\n<img src=\"https://img-blog.csdnimg.cn/20190302194910186.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2thaXh1YW5fZGFzaGVu,size_16,color_FFFFFF,t_70\" width=\"400\" hegiht=\"867\" align=center />\n  \n# WindowTree使用\n\n添加依赖项：\n\n```\napply plugin: 'kotlin-kapt'\n\n...\ndependencies {\n    kapt 'com.kaixuan:windowtree-compiler:1.0.0'\n    implementation 'com.kaixuan:windowtree-library:1.0.0'\n    ...\n}\n```\n\n1. 在你应用的所有界面添加注解@Window，参数parentClass指定该界面的父节点（如当前是顶级节点，则不需要设置该属性），可选添加其他属性，index表示当前界面是父界面的第几个同类界面，name表示当前节点名字  \n\n\n```\n@Window(parentClass = MainActivity::class,index = 2,name = \"联系人\") \nclass ContactsFragment : Fragment() \n```  \n\n<br/>\n2. 在应用启动时，初始化WindowTree  \n\n<br/>\n\n```\nWindowTree.init(MyApp.instance)\n``` \n\n<br/>\n\n3. \n    -  使用kotlin时，可直接在界面类（Activity、Fragment等）中使用扩展属性mWindowInfo拿到当前界面对应的WindowInfo \n    - 使用java时，使用WindowTree.with(this)获取当前界面对应的WindowInfo\n\n<br/>\n\n4. 如需接收消息，需要给当前节点设置setEventListener  \n\n```\nmWindowInfo.setEventListener { sender, sendData ->\n    if (sendData is UnReadCountEvent){\n        tv_log.append(\"未读消息：${sender.name}的未读消息发生变化，数量变化=${sendData.change}\\n\")\n        updateUnReadCount()\n        return@setEventListener \"ok 我已收到并处理完毕\"  // 支持返回给消息发送者一个回信\n    }\n    return@setEventListener null \n}\n```\nwindowTree支持消息接收者返回一个结果的应答，告知事件处理结果\n\n\n<br/>\n\n5. 拿到我的父节点或子节点对象，使用当前的WindowInfo对象可以轻松找到父节点和子节点，如：\n```\nmWindowInfo.parent\nmWindowInfo.child\n```\n\n<br/>\n6. 界面自动构建，如果你使用TabLayout管理你的界面，只需要以下代码即可将当前界面的所有子界面加入到Tab  \n\n\n```\ntabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener{\n    override fun onTabReselected(tab: TabLayout.Tab?) {\n    }\n\n    override fun onTabUnselected(tab: TabLayout.Tab?) {\n    }\n\n    override fun onTabSelected(tab: TabLayout.Tab?) {\n        mWindowInfo.jump(tab!!.position,WindowType.FRAGMENTV4)\n    }\n})\nmWindowInfo.filterChildByWindowType(WindowType.FRAGMENTV4).forEach { window ->\n    tabLayout.addTab(tabLayout.newTab().setText(window.name).setTag(window))\n}\n```\n\n\n<br/>\n7. 界面跳转控制\n\n\n```\nmWindowInfo.jump(0,WindowType.FRAGMENTV4)\n```\n这段代码表示，跳转到我的第1个Fragment类型的子界面\n\n\nDefaultJumpAdapter中默认实现了一些跳转逻辑，你可以继承它实现自己的特殊逻辑\n\n<br/>\n<br/>\n\n如果你有更好的建议欢迎与我联系！thank you！kaixuanapp@163.com\n\ngithub：https://github.com/KaiXuan666/WindowTree\ncsdn：https://blog.csdn.net/kaixuan_dashen\n\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-kapt'\napply plugin: 'kotlin-android-extensions'\n\nandroid {\n    compileSdkVersion 28\n    defaultConfig {\n        applicationId \"com.kaixuan.windowtree\"\n        minSdkVersion 15\n        targetSdkVersion 28\n        versionCode 1\n        versionName \"1.0\"\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n}\n\ndependencies {\n    implementation fileTree(include: ['*.jar'], dir: 'libs')\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version\"\n    implementation 'com.android.support:appcompat-v7:28.0.0'\n    implementation 'com.android.support.constraint:constraint-layout:1.1.3'\n    testImplementation 'junit:junit:4.12'\n    androidTestImplementation 'com.android.support.test:runner:1.0.2'\n    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'\n    implementation project(path: ':windowtree_annotation')\n    kapt   project(':windowtree_compiler')\n    implementation project(':windowtree_library')\n    implementation 'com.android.support:design:28.0.0'\n//    kapt 'com.kaixuan:windowtree-compiler:1.0.0'\n//    implementation 'com.kaixuan:windowtree-library:1.0.0'\n\n}\n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "app/src/androidTest/java/com/kaixuan/windowtree/ExampleInstrumentedTest.kt",
    "content": "package com.kaixuan.windowtree\n\nimport android.support.test.InstrumentationRegistry\nimport android.support.test.runner.AndroidJUnit4\n\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\nimport org.junit.Assert.*\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\n@RunWith(AndroidJUnit4::class)\nclass ExampleInstrumentedTest {\n    @Test\n    fun useAppContext() {\n        // Context of the app under test.\n        val appContext = InstrumentationRegistry.getTargetContext()\n        assertEquals(\"com.kaixuan.windowtree\", appContext.packageName)\n    }\n}\n"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n          package=\"com.kaixuan.windowtree\">\n\n    <application\n            android:allowBackup=\"true\"\n            android:icon=\"@mipmap/ic_launcher\"\n            android:label=\"@string/app_name\"\n            android:name=\".MyApp\"\n            android:roundIcon=\"@mipmap/ic_launcher_round\"\n            android:supportsRtl=\"true\"\n            android:theme=\"@style/AppTheme\">\n        <activity android:name=\".MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n        <activity android:name=\".activity.NewsListActivity\">\n        </activity>\n        <activity android:name=\".activity.EmptyActivity\"/>\n        <activity android:name=\".activity.NewsDetailActivity\"/>\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/java/com/kaixuan/windowtree/KotlinCommon.kt",
    "content": "package com.kaixuan.windowtree\n\nimport android.widget.Toast\n\n\nfun showToast(msg : String){\n    Toast.makeText(MyApp.instance,msg,Toast.LENGTH_SHORT).show()\n}\n"
  },
  {
    "path": "app/src/main/java/com/kaixuan/windowtree/MainActivity.kt",
    "content": "package com.kaixuan.windowtree\n\nimport android.os.Bundle\nimport android.support.design.widget.TabLayout\nimport android.text.method.ScrollingMovementMethod\nimport android.util.Log\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Button\nimport com.kaixuan.windowtree.activity.BaseActivity\nimport com.kaixuan.windowtree_annotation.annotation.Window\nimport com.kaixuan.windowtree_annotation.enums.WindowType\nimport com.kaixuan.windowtreelibrary.WindowInfo\nimport com.kaixuan.windowtreelibrary.WindowTree\nimport com.kaixuan.windowtreelibrary.mWindowInfo\nimport com.kaixuan.windowtreelibrary.model.UnReadCountEvent\nimport kotlinx.android.synthetic.main.activity_main.*\n\n@Window\nclass MainActivity : BaseActivity() {\n\n    lateinit var with: WindowInfo<*>\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n\n        tv_log.movementMethod = ScrollingMovementMethod.getInstance();\n        btnInit.setOnClickListener {\n            init()\n        }\n        btnCreateLayout.setOnClickListener { _ ->\n            WindowTree.instance.hasPageAuthorityFun = { it == -1L }\n            if (WindowTree.hasInit) {\n                bindTabLayout()\n                initActivityButton()\n            } else {\n                showToast(\"请先初始化\")\n            }\n        }\n        btnCreateLayout.setOnLongClickListener { _ ->\n            // 用户的页面权限，一般通过登录接口获取\n            val userAuthority = listOf(-1L, 1L, 2L, 3L, 4L)\n            WindowTree.instance.hasPageAuthorityFun = { userAuthority.contains(it) }\n            if (WindowTree.hasInit) {\n                bindTabLayout()\n                initActivityButton()\n            } else {\n                showToast(\"请先初始化\")\n            }\n            true\n        }\n        btnGc.setOnClickListener { System.gc() }\n        btnDestroy.setOnClickListener {\n            release()\n        }\n    }\n\n    fun init() {\n        if (WindowTree.hasInit) {\n            showToast(\"已经初始化过\")\n            return\n        }\n        WindowTree.init(MyApp.instance)\n        with = mWindowInfo\n        mWindowInfo.frameLayoutId = frameLayout.id\n        initEventListener()\n\n    }\n\n    fun initEventListener() {\n        with.setEventListener { sender, sendData ->\n\n            if (sendData is UnReadCountEvent) {\n                tv_log.append(\"未读消息：${sender.name}的未读消息发生变化，数量变化=${sendData.change}\\n\")\n                updateUnReadCount()\n                return@setEventListener null\n            }\n            when (sender) {\n                // 1、判断是自己的孩子发来的消息\n                in with.child -> {\n                    when (sendData) {\n                        // 2、判断发来消息的数据类型，你也可以定义msgCode或其他数据类型来进行判断，此处我为了偷懒\n                        is String -> {\n                            tv_log.append(\"子模块${sender.name}发来了消息，内容=${sendData}\\n\")\n                        }\n                        is Int -> {\n\n                        }\n                    }\n                }\n            }\n\n            return@setEventListener \"ok 我已收到并处理完毕\" // 支持返回给消息发送者一个回信\n        }\n    }\n\n    fun bindTabLayout() {\n        if (tabLayout.tabCount != 0) {\n            showToast(\"不能重复添加tab\")\n            return\n        }\n        tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {\n            override fun onTabReselected(tab: TabLayout.Tab?) {\n            }\n\n            override fun onTabUnselected(tab: TabLayout.Tab?) {\n            }\n\n            override fun onTabSelected(tab: TabLayout.Tab?) {\n                Log.e(\"onTabSelected\", tab!!.tag.toString())\n                mWindowInfo.jump(tab!!.position, WindowType.FRAGMENTV4)\n            }\n        })\n\n        // 自动布局到TabLayout，两种方式都可以实现\n//        with.child.forEach {\n//            if (it.windowType == WindowType.FRAGMENTV4){\n//                tabLayout.addTab(tabLayout.newTab().setText(it.name))\n//            }\n//        }\n\n        mWindowInfo.filterChildByWindowType(WindowType.FRAGMENTV4).filter { WindowTree.hasAuthority(it.pageAuthority) }\n            .forEach { window ->\n                tabLayout.addTab(tabLayout.newTab().setText(window.name).setTag(window))\n            }\n    }\n\n    fun initActivityButton() {\n        llActivity.visibility = View.VISIBLE\n        if (llActivity.childCount == 0) {\n            // 过滤子Window自动进行布局\n            mWindowInfo.filterChildByWindowType(WindowType.ACTIVITY).forEach { window ->\n                llActivity.addView(Button(this).apply {\n                    text = \"打开 ${window.name}\"\n                    setOnClickListener { with.jump(window) }  // 注：此处不能使用mWindowInfo获取当前windowInfo对象，因为此处的this指代的是View Button\n                }, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n            }\n        }\n    }\n\n    /**\n     * 更新未读消息显示\n     */\n    fun updateUnReadCount() {\n        val calcChildUnReadCount = mWindowInfo.calcChildUnReadCount()\n        supportActionBar!!.title = if (calcChildUnReadCount != 0) \"新消息：$calcChildUnReadCount\" else \"WindowTree\"\n        (0 until tabLayout.tabCount).forEach {\n            val windowInfo = tabLayout.getTabAt(it)!!.tag as WindowInfo<*>\n            val count = windowInfo.calcChildUnReadCount()\n            tabLayout.getTabAt(it)!!.text = windowInfo.name + if (count == 0) \"\" else \"($count)\"\n        }\n        (0 until llActivity.childCount).forEach {\n            val findChildByIndex = mWindowInfo.findChildByIndex<Any>(it, WindowType.ACTIVITY)!!\n            (llActivity.getChildAt(it) as Button).apply {\n                text = \"打开 ${findChildByIndex.name}\"\n                val count = findChildByIndex.calcChildUnReadCount()\n                if (count != 0) {\n                    append(\"($count)\")\n                }\n            }\n        }\n\n    }\n\n    override fun onBackPressed() {\n        super.onBackPressed()\n    }\n\n    fun release() {\n        supportActionBar!!.title = \"WindowTree\"\n        WindowTree.destroy()\n        llActivity.visibility = View.GONE\n        llActivity.removeAllViews()\n        tabLayout.removeAllTabs()\n        tabLayout.clearOnTabSelectedListeners()\n        frameLayout.removeAllViews()\n        System.gc()\n        tv_log.text = \"\"\n    }\n\n    override fun onDestroy() {\n        release()\n        super.onDestroy()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/kaixuan/windowtree/MyApp.kt",
    "content": "package com.kaixuan.windowtree\n\nimport android.app.Application\n\nclass MyApp : Application() {\n\n    companion object {\n        lateinit var instance : MyApp;\n    }\n\n    override fun onCreate() {\n        super.onCreate()\n        instance = this\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/kaixuan/windowtree/WindowTag.kt",
    "content": "package com.kaixuan.windowtree\n\n\nclass WindowTag(\n    var unReadMsgCount : Int = 0,\n    var pageAuthority : Long = 0\n){\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/kaixuan/windowtree/activity/BaseActivity.kt",
    "content": "package com.kaixuan.windowtree.activity\n\nimport android.os.Bundle\nimport android.support.v7.app.AppCompatActivity\nimport android.util.Log\n\nopen class BaseActivity : AppCompatActivity(){\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        Log.i(this.javaClass.simpleName,\"onCreate\")\n    }\n\n    override fun onStart() {\n        super.onStart()\n        Log.i(this.javaClass.simpleName,\"onStart\")\n    }\n\n    override fun onResume() {\n        super.onResume()\n        Log.i(this.javaClass.simpleName,\"onResume\")\n    }\n\n    override fun onPause() {\n        super.onPause()\n        Log.i(this.javaClass.simpleName,\"onPause\")\n    }\n\n    override fun onStop() {\n        super.onStop()\n        Log.i(this.javaClass.simpleName,\"onStop\")\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        Log.i(this.javaClass.simpleName,\"onDestroy\")\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/kaixuan/windowtree/activity/EmptyActivity.kt",
    "content": "package com.kaixuan.windowtree.activity\n\nimport android.support.v7.app.AppCompatActivity\nimport android.os.Bundle\nimport com.kaixuan.windowtree.R\nimport com.kaixuan.windowtree_annotation.annotation.Window\nimport com.kaixuan.windowtreelibrary.mWindowInfo\n\n@Window(parentClassName = \"com.kaixuan.windowtree.MainActivity\",name = \"空白界面\",index = 2)\nclass EmptyActivity : BaseActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.fragment_test)\n        supportActionBar!!.title = mWindowInfo.name\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/kaixuan/windowtree/activity/NewsDetailActivity.kt",
    "content": "package com.kaixuan.windowtree.activity\n\nimport android.os.Bundle\nimport com.kaixuan.windowtree.R\nimport com.kaixuan.windowtree_annotation.annotation.Window\nimport com.kaixuan.windowtreelibrary.mWindowInfo\nimport com.kaixuan.windowtreelibrary.model.UnReadCountEvent\nimport kotlinx.android.synthetic.main.activity_news_detail.*\n\n@Window(parentClass = NewsListActivity::class,name = \"新闻详情\",index = 3)\nclass NewsDetailActivity : BaseActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_news_detail)\n        supportActionBar!!.title = mWindowInfo.name + mWindowInfo.getTag()\n        tv_title.text = mWindowInfo.getTag() as String\n        mWindowInfo.unReadMsgCount = mWindowInfo.bundle.getInt(\"unReadCount\")\n        tv_unReadCount.text = \"未读数量：${mWindowInfo.unReadMsgCount}\"\n        mWindowInfo.setEventListener { sender, sendData ->\n            if (sendData is UnReadCountEvent){\n                tv_unReadCount.text = \"未读数量：${mWindowInfo.unReadMsgCount}\"\n            }\n        }\n        btn_unReadAdd.setOnClickListener { mWindowInfo.unReadMsgCount ++ }\n        btn_dealUnRead.setOnClickListener { mWindowInfo.unReadMsgCount = 0 }\n    }\n\n    override fun onDestroy() {\n        mWindowInfo.release()\n        super.onDestroy()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/kaixuan/windowtree/activity/NewsListActivity.kt",
    "content": "package com.kaixuan.windowtree.activity\n\nimport android.os.Bundle\nimport android.support.v7.recyclerview.extensions.ListAdapter\nimport android.support.v7.widget.LinearLayoutManager\nimport android.support.v7.widget.RecyclerView\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport android.widget.TextView\nimport com.kaixuan.windowtree.MainActivity\nimport com.kaixuan.windowtree.R\nimport com.kaixuan.windowtree_annotation.annotation.Window\nimport com.kaixuan.windowtreelibrary.mWindowInfo\nimport kotlinx.android.synthetic.main.activity_news_list.*\n\n@Window(parentClass = MainActivity::class,name = \"新闻列表\",index = 1)\nclass NewsListActivity : BaseActivity() {\n\n    val newsList = 0..200\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_news_list)\n        supportActionBar!!.title = mWindowInfo.name\n        recyclerView.layoutManager = LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false)\n        recyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>(){\n            override fun onCreateViewHolder(p0: ViewGroup, p1: Int): RecyclerView.ViewHolder {\n                return object : RecyclerView.ViewHolder(LayoutInflater.from(this@NewsListActivity).inflate(R.layout.support_simple_spinner_dropdown_item,p0,false)){}\n            }\n\n            override fun getItemCount(): Int = newsList.count()\n\n            override fun onBindViewHolder(p0: RecyclerView.ViewHolder, p1: Int) {\n                (p0.itemView as TextView).let {textView ->\n                    textView.text = \"Hello，这里是新闻啊啊啊啊啊啊啊啊 ${p1}\"\n                    textView.setOnClickListener {\n                        val childByIndex = mWindowInfo.findChildByIndex<String>(0)!!\n                        childByIndex.setTag(textView.text.toString())\n                        childByIndex.bundle.putInt(\"unReadCount\",p1)\n                        mWindowInfo.jump(childByIndex) }\n                }\n            }\n        }\n    }\n\n    override fun onDestroy() {\n        mWindowInfo.release()\n        super.onDestroy()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/kaixuan/windowtree/fragment/ContactsFragment.kt",
    "content": "package com.kaixuan.windowtree.fragment\n\nimport android.os.Bundle\nimport android.support.v4.app.Fragment\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport com.kaixuan.windowtree.MainActivity\nimport com.kaixuan.windowtree.R\nimport com.kaixuan.windowtree_annotation.annotation.Window\nimport com.kaixuan.windowtreelibrary.mWindowInfo\nimport kotlinx.android.synthetic.main.fragment_test.*\n\n@Window(parentClass = MainActivity::class,index = 2,name = \"联系人\")\nclass ContactsFragment : Fragment() {\n\n    var mView : View? = null\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =\n        mView ?: inflater.inflate(R.layout.fragment_test,container,false).apply { mView = this }\n\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        tv_title.text = \"我是\" + mWindowInfo.getClazz()!!.simpleName\n        btn_send.setOnClickListener {\n            mWindowInfo.unReadMsgCount ++\n            val response = mWindowInfo.sendData(\"hello,我是${javaClass.simpleName}\", mWindowInfo.parent!!)\n            tv_log.append(\"收到了回信：${mWindowInfo.parent!!.getClazz()!!.simpleName}:$response\\n\")\n        }\n        btn_resetUnReadCount.setOnClickListener { mWindowInfo.unReadMsgCount = 0 }\n    }\n\n    override fun onDestroyView() {\n        mWindowInfo.release()\n        super.onDestroyView()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/kaixuan/windowtree/fragment/DynamicFragment.kt",
    "content": "package com.kaixuan.windowtree.fragment\n\nimport android.os.Bundle\nimport android.support.v4.app.Fragment\nimport android.text.method.ScrollingMovementMethod\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport com.kaixuan.windowtree.MainActivity\nimport com.kaixuan.windowtree.R\nimport com.kaixuan.windowtree_annotation.annotation.Window\nimport com.kaixuan.windowtreelibrary.mWindowInfo\nimport kotlinx.android.synthetic.main.fragment_test.*\n\n@Window(parentClass = MainActivity::class,index = 3,name = \"动态\")\nclass DynamicFragment : Fragment() {\n\n    var mView : View? = null\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =\n        mView ?: inflater.inflate(R.layout.fragment_test,container,false).apply { mView = this }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        tv_log.movementMethod = ScrollingMovementMethod.getInstance();\n        tv_title.text = \"我是\" + mWindowInfo.getClazz()!!.simpleName\n        btn_send.setOnClickListener {\n            mWindowInfo.unReadMsgCount ++\n            val response = mWindowInfo.sendData(\"hello,我是${javaClass.simpleName}\", mWindowInfo.parent!!)\n            tv_log.append(\"收到了回信：${mWindowInfo.parent!!.getClazz()!!.simpleName}:$response\\n\")\n        }\n        btn_resetUnReadCount.setOnClickListener { mWindowInfo.unReadMsgCount = 0 }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/kaixuan/windowtree/fragment/MainFragment.kt",
    "content": "package com.kaixuan.windowtree.fragment\n\nimport android.os.Bundle\nimport android.support.v4.app.Fragment\nimport android.text.method.ScrollingMovementMethod\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport com.kaixuan.windowtree.MainActivity\nimport com.kaixuan.windowtree.R\nimport com.kaixuan.windowtree_annotation.annotation.Window\nimport com.kaixuan.windowtreelibrary.WindowInfo\nimport com.kaixuan.windowtreelibrary.WindowTree\nimport com.kaixuan.windowtreelibrary.mWindowInfo\nimport kotlinx.android.synthetic.main.fragment_test.*\n\n@Window(parentClass = MainActivity::class,index = 1,name = \"主页\")\nclass MainFragment : Fragment() {\n\n    lateinit var with : WindowInfo<String>\n    var mView : View? = null\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =\n        mView ?: inflater.inflate(R.layout.fragment_test,container,false).apply { mView = this }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        with = WindowTree.with(this)!!\n        val t = with.setTag(\"\")\n        tv_log.movementMethod = ScrollingMovementMethod.getInstance();\n        tv_title.text = \"我是\" + mWindowInfo!!.getClazz()!!.simpleName\n        btn_send.setOnClickListener {\n            mWindowInfo.unReadMsgCount ++\n            val response = with.sendData(\"hello,我是${javaClass.simpleName}\", with.parent!!)\n            tv_log.append(\"收到了回信：${with.parent!!.getClazz()!!.simpleName}:${response}\\n\")\n        }\n        btn_resetUnReadCount.setOnClickListener { mWindowInfo.unReadMsgCount = 0 }\n\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/kaixuan/windowtree/fragment/VipFragment.kt",
    "content": "package com.kaixuan.windowtree.fragment\n\nimport android.os.Bundle\nimport android.support.v4.app.Fragment\nimport android.text.method.ScrollingMovementMethod\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport com.kaixuan.windowtree.MainActivity\nimport com.kaixuan.windowtree.R\nimport com.kaixuan.windowtree_annotation.annotation.Window\nimport com.kaixuan.windowtreelibrary.mWindowInfo\nimport kotlinx.android.synthetic.main.fragment_test.*\n\n@Window(parentClass = MainActivity::class,index = 3,name = \"Vip隐藏界面\",pageAuthority = 1)\nclass VipFragment : Fragment() {\n\n    var mView : View? = null\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =\n        mView ?: inflater.inflate(R.layout.fragment_test,container,false).apply { mView = this }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        tv_log.movementMethod = ScrollingMovementMethod.getInstance();\n        tv_title.text = \"我是Vip界面，一般人没有权限打开我\"\n        btn_send.setOnClickListener {\n            mWindowInfo.unReadMsgCount ++\n            val response = mWindowInfo.sendData(\"hello,我是${javaClass.simpleName},pageAuthority=${mWindowInfo.pageAuthority}\", mWindowInfo.parent!!)\n            tv_log.append( \"收到了回信：${mWindowInfo.parent!!.getClazz()!!.simpleName}:$response\\n\")\n        }\n        btn_resetUnReadCount.setOnClickListener { mWindowInfo.unReadMsgCount = 0 }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/kaixuan/windowtree/fragment/dynamic/GoodFriendDynamicFragment.kt",
    "content": "package com.kaixuan.windowtree.fragment.dynamic\n\nimport android.os.Bundle\nimport android.support.v4.app.Fragment\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport com.kaixuan.windowtree.R\nimport com.kaixuan.windowtreelibrary.mWindowInfo\nimport kotlinx.android.synthetic.main.fragment_msg.*\n\nclass GoodFriendDynamicFragment : Fragment() {\n    var mView : View? = null\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =\n        mView ?: inflater.inflate(R.layout.fragment_msg,container,false).apply { mView = this }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        updateMsgCount()\n        mWindowInfo.setEventListener { sender, sendData ->\n            mWindowInfo.unReadMsgCount++\n            updateMsgCount()\n        }\n    }\n\n    fun updateMsgCount(){\n        tv_msg_tips.text = \"我有${mWindowInfo.unReadMsgCount}条未读消息\"\n    }\n\n}"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:height=\"108dp\"\n        android:width=\"108dp\"\n        android:viewportHeight=\"108\"\n        android:viewportWidth=\"108\">\n    <path android:fillColor=\"#008577\"\n          android:pathData=\"M0,0h108v108h-108z\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M9,0L9,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,0L19,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M29,0L29,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M39,0L39,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M49,0L49,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M59,0L59,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M69,0L69,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M79,0L79,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M89,0L89,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M99,0L99,108\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,9L108,9\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,19L108,19\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,29L108,29\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,39L108,39\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,49L108,49\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,59L108,59\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,69L108,69\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,79L108,79\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,89L108,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M0,99L108,99\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,29L89,29\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,39L89,39\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,49L89,49\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,59L89,59\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,69L89,69\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M19,79L89,79\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M29,19L29,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M39,19L39,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M49,19L49,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M59,19L59,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M69,19L69,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n    <path android:fillColor=\"#00000000\" android:pathData=\"M79,19L79,89\"\n          android:strokeColor=\"#33FFFFFF\" android:strokeWidth=\"0.8\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:aapt=\"http://schemas.android.com/aapt\"\n        android:width=\"108dp\"\n        android:height=\"108dp\"\n        android:viewportHeight=\"108\"\n        android:viewportWidth=\"108\">\n    <path\n            android:fillType=\"evenOdd\"\n            android:pathData=\"M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z\"\n            android:strokeColor=\"#00000000\"\n            android:strokeWidth=\"1\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                    android:endX=\"78.5885\"\n                    android:endY=\"90.9159\"\n                    android:startX=\"48.7653\"\n                    android:startY=\"61.0927\"\n                    android:type=\"linear\">\n                <item\n                        android:color=\"#44000000\"\n                        android:offset=\"0.0\"/>\n                <item\n                        android:color=\"#00000000\"\n                        android:offset=\"1.0\"/>\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n            android:fillColor=\"#FFFFFF\"\n            android:fillType=\"nonZero\"\n            android:pathData=\"M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z\"\n            android:strokeColor=\"#00000000\"\n            android:strokeWidth=\"1\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\"\n        tools:context=\".MainActivity\">\n\n    <LinearLayout android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\"\n                  android:orientation=\"horizontal\">\n        <LinearLayout android:layout_width=\"0dp\" android:layout_height=\"wrap_content\"\n                      android:orientation=\"vertical\"\n                      android:layout_weight=\"1\">\n            <Button\n                    android:id=\"@+id/btnInit\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:textColor=\"@color/colorAccent\"\n                    android:text=\"初始化WindowTree\"/>\n\n            <Button\n                    android:id=\"@+id/btnCreateLayout\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"WindowTree自动布局\\n（长按使用会员身份构建）\"/>\n\n            <LinearLayout android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\"\n                          android:orientation=\"horizontal\">\n                <Button\n                        android:id=\"@+id/btnDestroy\"\n                        android:layout_width=\"0dp\"\n                        android:layout_weight=\"1\"\n                        android:layout_height=\"wrap_content\"\n                        android:text=\"销毁\"/>\n                <Button\n                        android:id=\"@+id/btnGc\"\n                        android:layout_width=\"0dp\"\n                        android:layout_weight=\"1\"\n                        android:layout_height=\"wrap_content\"\n                        android:text=\"GC\"/>\n            </LinearLayout>\n\n        </LinearLayout>\n        <LinearLayout\n                android:id=\"@+id/llActivity\"\n                android:layout_width=\"0dp\" android:layout_height=\"wrap_content\"\n                android:orientation=\"vertical\"\n                android:visibility=\"gone\"\n                android:layout_weight=\"1\">\n\n        </LinearLayout>\n    </LinearLayout>\n\n    <TextView\n            android:id=\"@+id/tv_log\"\n            android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\"\n            android:scrollbars=\"vertical\"\n            android:lines=\"10\"/>\n\n    <View android:layout_width=\"match_parent\" android:layout_height=\"2px\"\n          android:background=\"@color/design_default_color_primary\"\n    />\n    <FrameLayout\n            android:id=\"@+id/frameLayout\"\n            android:layout_width=\"match_parent\" android:layout_height=\"0dp\"\n            android:layout_weight=\"1\">\n    </FrameLayout>\n    <android.support.design.widget.TabLayout\n            android:layout_alignParentBottom=\"true\"\n            android:id=\"@+id/tabLayout\"\n            android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\"\n            app:tabIndicatorColor=\"@color/colorAccent\"\n            app:tabIndicatorGravity=\"top\">\n\n    </android.support.design.widget.TabLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_news_detail.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\"\n        android:gravity=\"center\"\n        tools:context=\".activity.NewsListActivity\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        >\n        <TextView\n                android:id=\"@+id/tv_title\"\n                android:layout_width=\"match_parent\" android:layout_height=\"match_parent\"\n                android:gravity=\"center\"\n                android:textSize=\"18sp\"\n                android:text=\"\"/>\n        <TextView\n                android:id=\"@+id/tv_unReadCount\"\n                android:layout_width=\"match_parent\" android:layout_height=\"match_parent\"\n                android:gravity=\"center\"\n                android:textSize=\"26sp\"\n                android:text=\"\"/>\n        <Button\n                android:id=\"@+id/btn_unReadAdd\"\n                android:layout_width=\"match_parent\" android:layout_height=\"match_parent\"\n                android:gravity=\"center\"\n                android:textSize=\"26sp\"\n                android:text=\"未读消息+1\"/>\n        <Button\n                android:id=\"@+id/btn_dealUnRead\"\n                android:layout_width=\"match_parent\" android:layout_height=\"match_parent\"\n                android:gravity=\"center\"\n                android:textSize=\"26sp\"\n                android:text=\"全部已读\"/>\n\n    </LinearLayout>\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_news_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\"\n        android:gravity=\"center\"\n        tools:context=\".activity.NewsListActivity\">\n\n    <android.support.v7.widget.RecyclerView\n            android:id=\"@+id/recyclerView\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"></android.support.v7.widget.RecyclerView>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_msg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\"\n        android:gravity=\"center\"\n        tools:context=\".activity.TestActivity\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        >\n        <TextView\n                android:id=\"@+id/tv_msg_tips\"\n                android:layout_width=\"match_parent\" android:layout_height=\"match_parent\"\n                android:gravity=\"center\"\n                android:textSize=\"26sp\"\n                android:text=\"\"/>\n        <Button\n                android:id=\"@+id/btn_add_msg\"\n                android:layout_width=\"match_parent\" android:layout_height=\"match_parent\"\n                android:gravity=\"center\"\n                android:textSize=\"26sp\"\n                android:text=\"本模块消息+1\"/>\n\n    </LinearLayout>\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\"\n        android:gravity=\"center\"\n        tools:context=\".activity.NewsListActivity\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        >\n        <TextView\n                android:id=\"@+id/tv_title\"\n                android:layout_width=\"match_parent\" android:layout_height=\"match_parent\"\n                android:gravity=\"center\"\n                android:textSize=\"26sp\"\n                android:text=\"我是\"/>\n        <Button\n                android:id=\"@+id/btn_send\"\n                android:layout_width=\"match_parent\" android:layout_height=\"match_parent\"\n                android:gravity=\"center\"\n                android:textSize=\"26sp\"\n                android:text=\"点我发消息\"/>\n        <Button\n                android:id=\"@+id/btn_resetUnReadCount\"\n                android:layout_width=\"match_parent\" android:layout_height=\"match_parent\"\n                android:gravity=\"center\"\n                android:textSize=\"26sp\"\n                android:text=\"重置消息数量\"/>\n        <TextView\n                android:id=\"@+id/tv_log\"\n                android:layout_width=\"match_parent\" android:layout_height=\"wrap_content\"\n                android:scrollbars=\"vertical\"\n                android:lines=\"5\"/>\n    </LinearLayout>\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\"/>\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\"/>\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#008577</color>\n    <color name=\"colorPrimaryDark\">#00574B</color>\n    <color name=\"colorAccent\">#D81B60</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">WindowTree</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/src/test/java/com/kaixuan/windowtree/ExampleUnitTest.kt",
    "content": "package com.kaixuan.windowtree\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass ExampleUnitTest {\n    @Test\n    fun addition_isCorrect() {\n        assertEquals(4, 2 + 2)\n    }\n}\n"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    ext.kotlin_version = '1.2.71'\n    repositories {\n        google()\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:3.2.1'\n        classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version\"\n        classpath 'com.novoda:bintray-release:0.9'\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        google()\n        jcenter()\n//        maven { url \"https://dl.bintray.com/kaixuan666/maven\" }\n    }\n    tasks.withType(Javadoc) {\n        options.addStringOption('Xdoclint:none', '-quiet')\n        options.addStringOption('encoding', 'UTF-8')\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n//统一声明配置\next {\n    repoName = 'maven'\n    userOrg = 'kaixuan666'\n    groupId = 'com.kaixuan'\n    uploadName = 'windowtree'\n    publishVersion = '1.0.0'\n    desc = 'WindowTree'\n    website = 'https://github.com/KaiXuan666/WindowTree'\n    licences = ['Apache-2.0']\n}"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-4.6-all.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windows variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':app', ':windowtree_library', ':windowtree_compiler', ':windowtree_annotation'\n"
  },
  {
    "path": "windowtree_annotation/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "windowtree_annotation/build.gradle",
    "content": "apply plugin: 'java-library'\napply plugin: 'com.novoda.bintray-release'\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n}\n\nsourceCompatibility = \"7\"\ntargetCompatibility = \"7\"\ntasks.withType(Javadoc) {\n    options.encoding = \"UTF-8\"\n}\n//添加\npublish {\n    repoName = rootProject.repoName\n    artifactId = 'windowtree-annotation'\n    userOrg = rootProject.userOrg\n    groupId = rootProject.groupId\n    uploadName = rootProject.uploadName\n    publishVersion = rootProject.publishVersion\n    desc = rootProject.desc\n    website = rootProject.website\n    licences = rootProject.licences\n}"
  },
  {
    "path": "windowtree_annotation/src/main/java/com/kaixuan/windowtree_annotation/MyClass.java",
    "content": "package com.kaixuan.windowtree_annotation;\n\npublic class MyClass {\n}\n"
  },
  {
    "path": "windowtree_annotation/src/main/java/com/kaixuan/windowtree_annotation/annotation/Window.java",
    "content": "package com.kaixuan.windowtree_annotation.annotation;\n\nimport com.kaixuan.windowtree_annotation.model.WindowData;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Target({ElementType.TYPE})\n@Retention(RetentionPolicy.CLASS)\npublic @interface Window {\n\n    /**\n     *\n     * @return 父class\n     */\n    Class parentClass() default Window.class;\n\n    /**\n     *\n     * @return 父class完整类名，该属性和parentClass只需要任选实现一个即可，该属性便于模块化场景下无法直接引用目标类的情况\n     */\n    String parentClassName() default \"\";\n\n    /**\n     * 功能名称 可空\n     * @return\n     */\n    String name() default \"\";\n\n    /**\n     * 该窗口是父节点的第几个子窗口\n     * @return\n     */\n    int index() default 0;\n\n    /**\n     * 该窗口在你的项目中，页面权限id是多少\n     * 该值默认为-1时，即该页面无权限控制，任何人都允许加载\n     * @return\n     */\n    long pageAuthority() default -1;\n}\n"
  },
  {
    "path": "windowtree_annotation/src/main/java/com/kaixuan/windowtree_annotation/annotation/WindowTypeAnnotation.java",
    "content": "package com.kaixuan.windowtree_annotation.annotation;\n\n\npublic @interface WindowTypeAnnotation {\n}\n"
  },
  {
    "path": "windowtree_annotation/src/main/java/com/kaixuan/windowtree_annotation/enums/WindowType.java",
    "content": "package com.kaixuan.windowtree_annotation.enums;\n\npublic enum  WindowType {\n\n    ACTIVITY(0, \"android.app.Activity\"),\n    FRAGMENT(1, \"android.app.Fragment\"),\n    FRAGMENTV4(2, \"android.support.v4.app.Fragment\"),\n    VIEW(3, \"android.view.View\"),\n    DIALOG(4, \"android.app.Dialog\"),\n    POPUPWINDOW(5, \"android.widget.PopupWindow\"),\n    UNKNOWN(-1, \"Unknown route type\");\n\n    int id;\n    String className;\n\n    WindowType(int id, String className) {\n        this.id = id;\n        this.className = className;\n    }\n\n    public int getId() {\n        return id;\n    }\n\n    public WindowType setId(int id) {\n        this.id = id;\n        return this;\n    }\n\n    public String getClassName() {\n        return className;\n    }\n\n    public WindowType setClassName(String className) {\n        this.className = className;\n        return this;\n    }\n\n    public static WindowType parse(String name) {\n        for (WindowType windowType : WindowType.values()) {\n            if (windowType.getClassName().equals(name)) {\n                return windowType;\n            }\n        }\n        return UNKNOWN;\n    }\n}\n"
  },
  {
    "path": "windowtree_annotation/src/main/java/com/kaixuan/windowtree_annotation/model/WindowData.java",
    "content": "package com.kaixuan.windowtree_annotation.model;\n\npublic class WindowData {\n}\n"
  },
  {
    "path": "windowtree_annotation/src/main/java/com/kaixuan/windowtree_annotation/model/WindowMeta.java",
    "content": "package com.kaixuan.windowtree_annotation.model;\n\n\nimport com.kaixuan.windowtree_annotation.enums.WindowType;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n\npublic class WindowMeta<T> {\n\n    private Class<?> clazz = null;\n    private String clazzName = \"\";\n    public WindowMeta parent = null;\n    public List<WindowMeta> child = new ArrayList<>();\n    public String name;\n    public int index;\n    private WindowType windowType;\n    public long pageAuthority;\n\n    public WindowMeta(Class<?> clazz, String clazzName, WindowMeta parent, String name, int index) {\n        this(clazz,clazzName,parent,name,index,WindowType.UNKNOWN);\n    }\n    public WindowMeta(Class<?> clazz, String clazzName, WindowMeta parent, String name, int index, WindowType windowType) {\n        this(clazz,clazzName,parent,name,index,WindowType.UNKNOWN,-1);\n    }\n    public WindowMeta(Class<?> clazz, String clazzName, WindowMeta parent, String name, int index, WindowType windowType,long pageAuthority) {\n        this.index = index;\n        this.name = name;\n        this.clazz = clazz;\n        this.clazzName = clazzName;\n        this.parent = parent;\n        this.windowType = windowType;\n        this.pageAuthority = pageAuthority;\n    }\n\n    public void addChild(String clazzName,String name,int index,WindowType windowType){\n        try {\n            child.add(new WindowMeta(Class.forName(clazzName),clazzName,this,name,index,windowType));\n        } catch (ClassNotFoundException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public Class<?> getClazz() {\n        return clazz;\n    }\n\n    public WindowMeta setClazz(Class<?> clazz) {\n        this.clazz = clazz;\n        return this;\n    }\n\n    public String getClazzName() {\n        return clazzName;\n    }\n\n    public WindowMeta setClazzName(String clazzName) {\n        this.clazzName = clazzName;\n        return this;\n    }\n\n    public WindowType getWindowType() {\n        return windowType;\n    }\n\n    public WindowMeta<T> setWindowType(WindowType windowType) {\n        this.windowType = windowType;\n        return this;\n    }\n\n    @Override\n    public String toString() {\n        return \"WindowMeta{\" +\n                \"clazz=\" + clazz +\n                \", clazzName='\" + clazzName + '\\'' +\n                \", name='\" + name + '\\'' +\n                \", index=\" + index +\n                \", windowType=\" + windowType +\n                \", pageAuthority=\" + pageAuthority +\n                '}';\n    }\n}\n"
  },
  {
    "path": "windowtree_compiler/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "windowtree_compiler/build.gradle",
    "content": "apply plugin: 'java-library'\napply plugin:'java'\napply plugin: 'com.novoda.bintray-release'\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n    implementation 'com.google.auto.service:auto-service:1.0-rc3'\n    implementation project(path: ':windowtree_annotation')\n    implementation 'com.squareup:javapoet:1.8.0'\n}\n\nsourceCompatibility = \"7\"\ntargetCompatibility = \"7\"\ntasks.withType(Javadoc) {\n    options.encoding = \"UTF-8\"\n}\n//添加\npublish {\n    repoName = rootProject.repoName\n    artifactId = 'windowtree-compiler'\n    userOrg = rootProject.userOrg\n    groupId = rootProject.groupId\n    uploadName = rootProject.uploadName\n    publishVersion = rootProject.publishVersion\n    desc = rootProject.desc\n    website = rootProject.website\n    licences = rootProject.licences\n}"
  },
  {
    "path": "windowtree_compiler/src/main/java/com/kaixuan/compiler/WindowProcessor.java",
    "content": "package com.kaixuan.compiler;\n\n\nimport com.google.auto.service.AutoService;\n\nimport javax.annotation.processing.*;\nimport javax.lang.model.SourceVersion;\nimport javax.lang.model.element.Element;\nimport javax.lang.model.element.ElementKind;\nimport javax.lang.model.element.Modifier;\nimport javax.lang.model.element.TypeElement;\nimport javax.lang.model.type.MirroredTypeException;\nimport javax.lang.model.type.TypeMirror;\nimport javax.lang.model.util.Elements;\nimport javax.lang.model.util.Types;\nimport javax.tools.Diagnostic;\n\nimport com.kaixuan.windowtree_annotation.enums.WindowType;\nimport com.kaixuan.windowtree_annotation.model.WindowMeta;\nimport com.kaixuan.windowtree_annotation.annotation.Window;\nimport com.squareup.javapoet.*;\n\nimport java.io.IOException;\nimport java.util.*;\n\n@AutoService(Processor.class)\n@SupportedSourceVersion(SourceVersion.RELEASE_7)\n@SupportedAnnotationTypes(\"com.kaixuan.windowtree_annotation.annotation.Window\")\npublic class WindowProcessor extends AbstractProcessor {\n\n    /**\n     * 文件相关的辅助类\n     */\n    private Filer mFiler;\n    /**\n     * 元素相关的辅助类\n     */\n    private Elements mElementUtils;\n    /**\n     * 日志相关的辅助类\n     */\n    private Messager mMessager;\n    /**\n     * 日志相关的辅助类\n     */\n    private Types types;\n\n\n    @Override\n    public synchronized void init(ProcessingEnvironment processingEnvironment) {\n        super.init(processingEnvironment);\n        mFiler = processingEnvironment.getFiler();\n        types = processingEnvironment.getTypeUtils();\n        mElementUtils = processingEnvironment.getElementUtils();\n        mMessager = processingEnvironment.getMessager();\n        mMessager.printMessage(Diagnostic.Kind.WARNING, \"init : \");\n    }\n\n    @Override\n    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {\n        Map<String, List<WindowMeta>> stringListMap = parseWindows(roundEnvironment.getElementsAnnotatedWith(Window.class));\n        List<String> main = new ArrayList<>();\n\n\n        for (String s : stringListMap.keySet()) {\n            MethodSpec.Builder builder = MethodSpec.methodBuilder(\"loadWindowTree\")//定义方面名\n                    .addModifiers(Modifier.PUBLIC)//定义修饰符\n                    .addAnnotation(Override.class)\n                    .returns(void.class)//定义返回结果\n                    .addParameter(ClassName.get(\"com.kaixuan.windowtreelibrary\",\"WindowInfo\"), \"currentWindowMeta\");//添加方法参数\n            List<WindowMeta> windowMetas = stringListMap.get(s);\n            windowMetas.sort(new Comparator<WindowMeta>() {\n                @Override\n                public int compare(WindowMeta windowMeta, WindowMeta t1) {\n                    return windowMeta.index - t1.index;\n                }\n            });\n            mMessager.printMessage(Diagnostic.Kind.WARNING, \"windowMetas windowMetas : \" + windowMetas.toString());\n            for (WindowMeta meta : windowMetas) {\n                mMessager.printMessage(Diagnostic.Kind.WARNING, \"windowMetas meta.index : \" + meta.index);\n                builder.addStatement(\"currentWindowMeta.addChild($S,$S,$L,$L,$L)\", meta.getClazzName(),meta.name,meta.index,meta.getWindowType(),meta.pageAuthority);//添加方法内容\n            }\n            MethodSpec methodSpec = builder.addException(ClassName.get(ClassNotFoundException.class))\n                    .build();\n            String tempClass = s.substring(s.lastIndexOf(\".\") + 1) + \"$Gen\";\n            TypeSpec finderClass = TypeSpec.classBuilder(tempClass)\n                    .addSuperinterface(ClassName.get(mElementUtils.getTypeElement(\"com.kaixuan.windowtreelibrary.template.IWindowTreeLoad\")))\n                    .addModifiers(Modifier.PUBLIC)\n//                .addSuperinterface(ParameterizedTypeName.get(TypeUtil.INJECTOR, TypeName.get(mClassElement.asType())))\n                    .addMethod(methodSpec)\n                    .build();\n            // 创建Java文件\n            main.add(\"com.kaixuan.windowtree.windows.\" + tempClass );\n            JavaFile javaFile = JavaFile.builder(\"com.kaixuan.windowtree.windows\", finderClass)\n                    .addStaticImport(WindowType.class, \"*\").build();\n            try {\n                javaFile.writeTo(mFiler);\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        ParameterizedTypeName map = ParameterizedTypeName.get(ClassName.get(Map.class)\n                , ClassName.get(String.class)\n                , ParameterizedTypeName.get(\n                        ClassName.get(Class.class),\n                        WildcardTypeName.subtypeOf(ClassName.get(mElementUtils.getTypeElement(\"com.kaixuan.windowtreelibrary.template.IWindowTreeLoad\")))\n                ));\n        MethodSpec.Builder builder = MethodSpec.methodBuilder(\"getAllGeneratedFile\")\n                .addModifiers(Modifier.PUBLIC)//定义修饰符\n                .addAnnotation(Override.class)\n                .returns(ParameterizedTypeName.get(ClassName.get(Map.class)\n                        , ClassName.get(String.class)\n                        , ParameterizedTypeName.get(\n                                ClassName.get(Class.class),\n                                WildcardTypeName.subtypeOf(ClassName.get(mElementUtils.getTypeElement(\"com.kaixuan.windowtreelibrary.template.IWindowTreeLoad\")))\n                        )));//定义返回结果\n\n        ClassName mapNew = ClassName.get(\"java.util\", \"HashMap\");\n\n        builder.addStatement(\"$T map = new $T()\",map,mapNew);\n        for (String s : main) {\n            builder.addStatement(\"map.put($S,$L.class)\",s,s);\n        }\n        builder.addStatement(\"return map\");\n\n        TypeSpec mainClass = TypeSpec.classBuilder(  \"Main$Gen\")\n                .addSuperinterface(ClassName.get(mElementUtils.getTypeElement(\"com.kaixuan.windowtreelibrary.template.IMain\")))\n                .addModifiers(Modifier.PUBLIC)\n//                .addSuperinterface(ParameterizedTypeName.get(TypeUtil.INJECTOR, TypeName.get(mClassElement.asType())))\n                .addMethod(builder.build())\n                .build();\n        JavaFile javaFile = JavaFile.builder(\"com.kaixuan.windowtree.windows\", mainClass).build();\n        try {\n            javaFile.writeTo(mFiler);\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        return true;\n    }\n\n    private Map<String, List<WindowMeta>> parseWindows(Set<? extends Element> routeElements) {\n        // 以父节点类名为key平铺存储所有WindowMeta\n        Map<String, List<WindowMeta>> map = new HashMap<>();\n\n        TypeMirror type_Activity = mElementUtils.getTypeElement(WindowType.ACTIVITY.getClassName()).asType();\n        TypeMirror type_Fragment = mElementUtils.getTypeElement(WindowType.FRAGMENT.getClassName()).asType();\n        TypeMirror type_Fragmentv4 = mElementUtils.getTypeElement(WindowType.FRAGMENTV4.getClassName()).asType();\n        TypeMirror type_View = mElementUtils.getTypeElement(WindowType.VIEW.getClassName()).asType();\n        TypeMirror type_Dialog = mElementUtils.getTypeElement(WindowType.DIALOG.getClassName()).asType();\n        TypeMirror type_PopupWindow = mElementUtils.getTypeElement(WindowType.POPUPWINDOW.getClassName()).asType();\n\n        for (Element routeElement : routeElements) {\n            Window annotation = routeElement.getAnnotation(Window.class);\n            if (routeElement.getKind() == ElementKind.CLASS) {\n                mMessager.printMessage(Diagnostic.Kind.WARNING, \"annotation : \" + annotation.toString());\n                mMessager.printMessage(Diagnostic.Kind.WARNING, \"annotation info : \" + annotation.name() + annotation.index());\n                String parentName = annotation.parentClassName();\n                if (parentName.isEmpty()) {\n                    try {\n                        mMessager.printMessage(Diagnostic.Kind.WARNING, \"parentClass : \" + annotation.parentClass());\n                    } catch (MirroredTypeException e) {\n                        parentName = e.getTypeMirror().toString();\n//                       mMessager.printMessage(Diagnostic.Kind.WARNING,\"e.getTypeMirror() : \" + e.getTypeMirror().toString());\n                    }\n                }\n                List<WindowMeta> windowMetas = map.get(parentName);\n                if (windowMetas == null) {\n                    windowMetas = new ArrayList<>();\n                    map.put(parentName, windowMetas);\n                }\n\n//               routeElement.toString()\n                WindowMeta windowMeta = new WindowMeta<Object>(null, routeElement.toString(), null, annotation.name(), annotation.index(),WindowType.UNKNOWN,annotation.pageAuthority());\n                windowMetas.add(windowMeta);\n                // 判断类型\n                TypeMirror tm = routeElement.asType();\n                if (types.isSubtype(tm,type_Activity)){\n                    windowMeta.setWindowType(WindowType.ACTIVITY);\n                }else if (types.isSubtype(tm,type_Fragment)){\n                    windowMeta.setWindowType(WindowType.FRAGMENT);\n                }else if (types.isSubtype(tm,type_Fragmentv4)){\n                    windowMeta.setWindowType(WindowType.FRAGMENTV4);\n                }else if (types.isSubtype(tm,type_View)){\n                    windowMeta.setWindowType(WindowType.VIEW);\n                }else if (types.isSubtype(tm,type_Dialog)){\n                    windowMeta.setWindowType(WindowType.DIALOG);\n                }else if (types.isSubtype(tm,type_PopupWindow)){\n                    windowMeta.setWindowType(WindowType.POPUPWINDOW);\n                }else {\n                    windowMeta.setWindowType(WindowType.UNKNOWN);\n                }\n                mMessager.printMessage(Diagnostic.Kind.WARNING, \"windowMeta : \" + windowMeta.toString());\n            } else {\n                mMessager.printMessage(Diagnostic.Kind.WARNING, \"not class : \" + routeElement.toString());\n            }\n        }\n        mMessager.printMessage(Diagnostic.Kind.WARNING, \"map : \" + map.toString());\n        return map;\n    }\n}\n"
  },
  {
    "path": "windowtree_library/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "windowtree_library/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'com.novoda.bintray-release'\n\nandroid {\n    compileSdkVersion 28\n\n\n\n    defaultConfig {\n        minSdkVersion 15\n        targetSdkVersion 28\n        versionCode 1\n        versionName \"1.0\"\n\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n}\n\ndependencies {\n    implementation fileTree(include: ['*.jar'], dir: 'libs')\n    implementation 'com.android.support:appcompat-v7:28.0.0'\n    testImplementation 'junit:junit:4.12'\n    androidTestImplementation 'com.android.support.test:runner:1.0.2'\n    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version\"\n    implementation project(':windowtree_annotation')\n}\nrepositories {\n    mavenCentral()\n}\ntasks.withType(Javadoc) {\n    options.encoding = \"UTF-8\"\n}\n//添加\npublish {\n    repoName = rootProject.repoName\n    artifactId = 'windowtree-library'\n    userOrg = rootProject.userOrg\n    groupId = rootProject.groupId\n    uploadName = rootProject.uploadName\n    publishVersion = rootProject.publishVersion\n    desc = rootProject.desc\n    website = rootProject.website\n    licences = rootProject.licences\n}"
  },
  {
    "path": "windowtree_library/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "windowtree_library/src/androidTest/java/com/kaixuan/windowtreelibrary/ExampleInstrumentedTest.java",
    "content": "package com.kaixuan.windowtreelibrary;\n\nimport android.content.Context;\nimport android.support.test.InstrumentationRegistry;\nimport android.support.test.runner.AndroidJUnit4;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\n@RunWith(AndroidJUnit4.class)\npublic class ExampleInstrumentedTest {\n    @Test\n    public void useAppContext() {\n        // Context of the app under test.\n        Context appContext = InstrumentationRegistry.getTargetContext();\n\n        assertEquals(\"com.kaixuan.windowtreelibrary.test\", appContext.getPackageName());\n    }\n}\n"
  },
  {
    "path": "windowtree_library/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n          package=\"com.kaixuan.windowtreelibrary\"/>\n"
  },
  {
    "path": "windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/WindowInfo.kt",
    "content": "package com.kaixuan.windowtreelibrary\n\n\nimport android.content.Context\nimport android.os.Bundle\nimport com.kaixuan.windowtree_annotation.enums.WindowType\nimport com.kaixuan.windowtreelibrary.model.UnReadCountEvent\nimport com.kaixuan.windowtreelibrary.util.WindowTreeUtil\nimport java.util.*\nimport kotlin.properties.Delegates\n\n\nclass WindowInfo<T> @JvmOverloads constructor(\n\n    clazz: Class<*>,\n    clazzName: String,\n    parent: WindowInfo<*>?,\n    var name: String? = null,\n    var index: Int = 0,\n    var windowType: WindowType = WindowType.UNKNOWN\n) {\n\n    companion object {\n        const val TAG = \"WindowInfo\"\n    }\n\n    private var clazz: Class<*>? = null\n    private var clazzName = \"\"\n    var parent: WindowInfo<*>? = null\n    val child: MutableList<WindowInfo<*>> = ArrayList()\n    val bundle: Bundle by lazy { Bundle() }\n    private var tag: T? = null\n    /**\n     * 如当前节点需跳转到Fragment或填充View，则需设置该属性\n     */\n    var frameLayoutId = -1\n    /**\n     * 当前节点需要接收其他窗口发来的消息时，请设置该监听\n     */\n    private var onEventListener: ((\n        sender: WindowInfo<*>,\n        sendData: Any?\n    ) -> Any?)? = null\n\n    /**\n     * 该值发生任意变化时，都会通知到当前窗口以及当前窗口的所有父节点\n     */\n    var unReadMsgCount: Int by Delegates.observable(0) { prop, old, new ->\n        if (!WindowTree.hasInit) return@observable  // WindowTree销毁时不再发送未读消息事件\n        var notifyTaget: WindowInfo<*>?\n        notifyTaget = this\n        while (notifyTaget != null) {\n            this.sendData(UnReadCountEvent(this, new - old), notifyTaget)\n            notifyTaget = notifyTaget.parent\n        }\n    }\n\n    var pageAuthority: Long = -1\n\n    init {\n        this.clazz = clazz\n        this.clazzName = clazzName\n        this.parent = parent\n    }\n\n    fun addChild(clazzName: String, name: String, index: Int, windowType: WindowType, pageAuthority: Long) {\n        try {\n            child.add(\n                WindowInfo<Any>(\n                    Class.forName(clazzName),\n                    clazzName,\n                    this,\n                    name,\n                    index,\n                    windowType\n                ).apply { this.pageAuthority = pageAuthority })\n        } catch (e: ClassNotFoundException) {\n            WindowTree.logger.error(\"addChild\", e.toString())\n            e.printStackTrace()\n        }\n\n    }\n\n    fun getClazz(): Class<*>? {\n        return clazz\n    }\n\n    fun setClazz(clazz: Class<*>): WindowInfo<*> {\n        this.clazz = clazz\n        return this\n    }\n\n    fun getClazzName(): String {\n        return clazzName\n    }\n\n    fun setClazzName(clazzName: String): WindowInfo<*> {\n        this.clazzName = clazzName\n        return this\n    }\n\n    fun getTag(): T? {\n        return tag\n    }\n\n    fun setTag(tag: T): WindowInfo<T> {\n        this.tag = tag\n        return this\n    }\n\n    fun sendData(data: Any, receiverClass: Class<*>): Any? {\n        val findWindowInfoByClass =\n            WindowTree.instance.windowMeta!!.findWindowInfoByClass(receiverClass) ?: WindowTree.logger.error(\n                TAG,\n                \"receiverClass，找不到$receiverClass\"\n            ).run { return null }\n        findWindowInfoByClass.onEventListener ?: WindowTree.logger.error(\n            TAG,\n            \"发送失败，目标${receiverClass}未设置监听\"\n        ).run { return null }\n        return findWindowInfoByClass.onEventListener!!.invoke(this, data)\n    }\n\n    fun sendData(data: Any, receiverClazzName: String): Any? {\n        val findWindowInfoByClass =\n            WindowTree.instance.windowMeta!!.findWindowInfoByClass(receiverClazzName) ?: WindowTree.logger.error(\n                TAG,\n                \"发送失败，找不到$receiverClazzName\"\n            ).run { return null }\n        findWindowInfoByClass.onEventListener ?: WindowTree.logger.error(\n            TAG,\n            \"发送失败，目标${receiverClazzName}未设置监听\"\n        ).run { return null }\n        return findWindowInfoByClass.onEventListener!!.invoke(this, data)\n    }\n\n    fun sendData(data: Any, receiver: WindowInfo<*>): Any? {\n        receiver.onEventListener ?: WindowTree.logger.info(TAG, \"发送失败，目标${receiver}未设置监听\").run { return null }\n        WindowTree.logger.info(\"sendData\", \"this = ${this.clazzName}, receiver = ${receiver.clazzName}, data = $data\")\n        return receiver.onEventListener!!.invoke(this, data)\n    }\n\n    fun setEventListener(a: ((sender: WindowInfo<*>, sendData: Any?) -> Any?)?) {\n        onEventListener = a\n    }\n\n    fun getEventListener() = onEventListener\n\n    /**\n     * 跳转至第几个什么类型的子界面\n     * @param index 第几个子界面，该索引值受windowType影响\n     * @param windowType 什么类型的子界面，如不传入windowType，默认以所有类型的子界面获取index\n     */\n    fun jump(index: Int, windowType: WindowType = WindowType.UNKNOWN): Boolean {\n        val tempAdapter = WindowTree.instance.getJumpAdapter() ?: WindowTree.instance.defaultJumpAdapter\n        val taget =\n            findChildByIndex<Any>(index, windowType) ?: throw RuntimeException(\"找不到${windowType}类型的第${index}个窗口\")\n        val context = WindowTreeUtil.findContextByInfo(this)\n            ?: throw RuntimeException(\"当前${this}对应的window未处于打开状态，无法从当前节点获取context，无法跳转\")\n        return tempAdapter.jump(context, taget)\n    }\n\n    fun jump(windowInfo: WindowInfo<*>): Boolean {\n        val tempAdapter = WindowTree.instance.getJumpAdapter() ?: WindowTree.instance.defaultJumpAdapter\n        val context = WindowTreeUtil.findContextByInfo(this)\n            ?: throw RuntimeException(\"当前${this}对应的window未处于打开状态，无法从当前节点获取context，无法跳转\")\n        return tempAdapter.jump(context, windowInfo)\n    }\n\n    fun <T> findChildByIndex(index: Int, windowType: WindowType = WindowType.UNKNOWN): WindowInfo<T>? {\n        val filter = filterChildByWindowType(windowType)\n        return if (index >= filter.size) null else filter[index] as WindowInfo<T>\n    }\n\n    fun filterChildByWindowType(windowType: WindowType): List<WindowInfo<*>> {\n        return child.filter { windowType == WindowType.UNKNOWN || it.windowType == windowType }\n    }\n\n    fun findWindowInfoByClass(clazz: Class<*>?): WindowInfo<*>? {\n        return findWindowInfoByClass(clazz!!.name)\n    }\n\n    /**\n     * 计算当前的所有子节点的未读消息数量（包含当前节点）\n     */\n    fun calcChildUnReadCount(): Int {\n        var count = 0\n        findWindowInfoByCondition {\n            count += it.unReadMsgCount\n            return@findWindowInfoByCondition false\n        }\n        return count\n    }\n\n    fun getContext(): Context? = WindowTreeUtil.findContextByInfo(this)\n\n    fun findWindowInfoByClass(clazzName: String): WindowInfo<*>? {\n        return findWindowInfoByCondition {\n            return@findWindowInfoByCondition it.clazzName == clazzName\n        }\n    }\n\n    tailrec fun findWindowInfoByCondition(condition: (WindowInfo<*>) -> Boolean): WindowInfo<*>? {\n        WindowTree.logger.info(\"findWindowInfoByCondition\", this.toString())\n        if (condition(this)) {\n            return this\n        }\n        for (windowMeta in child) {\n            val findWindowInfoByClass = windowMeta.findWindowInfoByCondition(condition)\n            if (findWindowInfoByClass == null) {\n\n            } else {\n                return findWindowInfoByClass\n            }\n        }\n        return null\n    }\n\n    tailrec fun findWindowInfoByConditionUp(condition: (WindowInfo<*>) -> Boolean): WindowInfo<*>? {\n        WindowTree.logger.info(\"findWindowInfoByCondition\", this.toString())\n        if (condition(this)) {\n            return this\n        }\n\n        if (parent == null) {\n            return null\n        } else {\n            return parent!!.findWindowInfoByConditionUp(condition)\n        }\n    }\n\n    /**\n     * 如果当前节点有设置过onEventListener则需要手动调用该方法释放资源\n     *\n     *  @param clearReadCount 是否清理未读消息数量，可根据业务需求传参\n     */\n    fun release(clearReadCount: Boolean = false) {\n        if (clearReadCount) unReadMsgCount = 0\n        setEventListener(null)\n    }\n\n    /**\n     * 切忌不要打印this或父或子节点，否则将造成\n     */\n    override fun toString(): String {\n        var parentClazzName = \"null str\"\n        parent?.run { parentClazzName = this.clazzName }\n        return \"WindowMeta{\" +\n                \"clazz=\" + clazz +\n                \", clazzName='\" + clazzName + '\\''.toString() +\n                \", parent=\" + parentClazzName +\n                \", child.size=\" + child.size +\n                \", index=\" + index +\n                \", windowType=\" + windowType +\n                \", tag=\" + tag +\n                '}'.toString()\n    }\n}\n\n"
  },
  {
    "path": "windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/WindowTree.kt",
    "content": "package com.kaixuan.windowtreelibrary\n\nimport android.app.Activity\nimport android.app.Application\nimport android.app.Dialog\nimport android.content.Context\nimport android.support.v4.app.Fragment\nimport android.view.View\nimport android.widget.PopupWindow\nimport com.kaixuan.windowtree_annotation.annotation.Window\nimport com.kaixuan.windowtreelibrary.adapter.DefaultJumpAdapter\nimport com.kaixuan.windowtreelibrary.template.IJumpAdapter\nimport com.kaixuan.windowtreelibrary.template.IMain\nimport com.kaixuan.windowtreelibrary.template.IWindowTreeLoad\nimport com.kaixuan.windowtreelibrary.util.Consts\nimport com.kaixuan.windowtreelibrary.util.DefaultLogger\nimport java.util.*\n\nclass WindowTree {\n\n    lateinit var allGeneratedFile:\n            Map<String, Class<out IWindowTreeLoad>>;\n\n    val defaultJumpAdapter: DefaultJumpAdapter by lazy { DefaultJumpAdapter() }\n    private var jumpAdapter: IJumpAdapter? = null\n    var hasPageAuthorityFun: ((Long) -> Boolean)? = null\n\n    /**\n     * 最顶级的WindowInfo\n     */\n    var windowMeta: WindowInfo<Any>? = null;\n\n    val weakHashMap: WeakHashMap<Any, WindowInfo<*>> = WeakHashMap()\n\n    companion object {\n        lateinit var mContext: Context\n        @JvmField\n        @Volatile\n        var debuggable = false\n        @Volatile\n        var hasInit = false\n        @JvmField\n        val logger = DefaultLogger(Consts.TAG)\n        val instance: WindowTree by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { WindowTree() }\n\n        fun init(application: Application) {\n            if (!hasInit) {\n                hasInit = true\n                mContext = application.applicationContext\n                logger.showLog(true)\n                val newInstance =\n                    Class.forName(Consts.GENERATE_FILE_PATH + \".Main\" + Consts.GENERATE_FILE_NAME_END).getConstructor()\n                        .newInstance()\n                instance.allGeneratedFile = (newInstance as IMain).allGeneratedFile\n                val rootWindow =\n                    instance.allGeneratedFile[Consts.GENERATE_FILE_PATH + \".Window\" + Consts.GENERATE_FILE_NAME_END]\n                instance.windowMeta = WindowInfo<Any>(\n                    Window::class.java,\n                    Window::class.java.name,\n                    null\n                );\n                rootWindow!!.getConstructor().newInstance().loadWindowTree(instance.windowMeta)\n                bindWindowTree(instance.windowMeta!!)\n                logger.info(Consts.TAG, instance.allGeneratedFile.toString())\n            }\n        }\n\n        fun bindWindowTree(windowMeta: WindowInfo<*>) {\n            windowMeta.child.forEach { forItem ->\n                val clazz =\n                    instance.allGeneratedFile[Consts.GENERATE_FILE_PATH + \".\" + forItem.getClazz()!!.simpleName + Consts.GENERATE_FILE_NAME_END]\n                if (clazz == null) {\n                    // 找不到，跳过\n\n                } else {\n                    // 找到，继续递归\n                    clazz.getConstructor().newInstance().loadWindowTree(forItem)\n                    bindWindowTree(forItem)\n                }\n            }\n        }\n\n        fun destroy() {\n            if (hasInit) {\n                hasInit = false\n                instance.windowMeta!!.findWindowInfoByCondition {\n                    it.release(true)\n                    return@findWindowInfoByCondition false\n                }\n                instance.weakHashMap.clear()\n                instance.windowMeta = null\n                instance.hasPageAuthorityFun = null\n                instance.defaultJumpAdapter.clear()\n                instance.setJumpAdapter(null)\n            }\n        }\n\n        @JvmStatic\n        fun <T> with(obj: Any): WindowInfo<T>? {\n            if (!hasInit) {\n                throw RuntimeException(\"WindowTree未初始化\")\n            }\n            return instance.weakHashMap[obj] as WindowInfo<T>?\n                ?: instance.windowMeta!!.findWindowInfoByClass(obj.javaClass)?.apply {\n                    instance.weakHashMap[obj] = this\n                } as WindowInfo<T>?\n        }\n\n        @JvmStatic\n        fun hasAuthority(pageAuthorityId: Long): Boolean {\n            if (!hasInit) {\n                throw RuntimeException(\"WindowTree未初始化\")\n            }\n            return instance.hasPageAuthorityFun?.invoke(pageAuthorityId)\n                ?: (pageAuthorityId == -1L) // 不设置权限判断器的情况下，只拥有默认权限的页面\n        }\n    }\n\n    fun setJumpAdapter(jumpAdapter: IJumpAdapter?) {\n        this.jumpAdapter = jumpAdapter\n    }\n\n    fun setJumpAdapter(jumpAdapter: (Context, WindowInfo<*>) -> Boolean) {\n        this.jumpAdapter = jumpAdapter.toJumpAdapter()\n    }\n\n    fun getJumpAdapter(): IJumpAdapter? = this.jumpAdapter\n\n}\n\n/**\n * 简化调用者写法，不需要object :\n */\nfun <T : ((Context, WindowInfo<*>) -> Boolean)> T.toJumpAdapter(): IJumpAdapter = object : IJumpAdapter {\n    override fun jump(formContext: Context, to: WindowInfo<*>): Boolean = this@toJumpAdapter.invoke(formContext, to)\n}\n\nfun getWindowInfo(any: Any): WindowInfo<Any> = WindowTree.with<Any>(any)\n    ?: throw RuntimeException(\"找不到与${any}对应的WindowInfo，请检查。在匿名内部类调用本方法时应谨慎检查this关键字的指代对象。\")\n\nval Activity.mWindowInfo: WindowInfo<Any>\n    get() = getWindowInfo(this)\nval View.mWindowInfo: WindowInfo<Any>\n    get() = getWindowInfo(this)\nval Fragment.mWindowInfo: WindowInfo<Any>\n    get() = getWindowInfo(this)\nval android.app.Fragment.mWindowInfo: WindowInfo<Any>\n    get() = getWindowInfo(this)\nval Dialog.mWindowInfo: WindowInfo<Any>\n    get() = getWindowInfo(this)\nval PopupWindow.mWindowInfo: WindowInfo<Any>\n    get() = getWindowInfo(this)\n"
  },
  {
    "path": "windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/adapter/DefaultJumpAdapter.kt",
    "content": "package com.kaixuan.windowtreelibrary.adapter\n\nimport android.app.Activity\nimport android.content.Context\nimport android.content.Intent\nimport android.support.v4.app.Fragment\nimport android.support.v4.app.FragmentActivity\nimport com.kaixuan.windowtree_annotation.enums.WindowType\nimport com.kaixuan.windowtreelibrary.WindowInfo\nimport com.kaixuan.windowtreelibrary.WindowTree\nimport com.kaixuan.windowtreelibrary.template.IJumpAdapter\nimport java.lang.ref.WeakReference\n\nclass DefaultJumpAdapter : IJumpAdapter {\n\n    //    val weakHashMapFrag : WeakHashMap<String,Any> by lazy { WeakHashMap<String,Any>() }\n    val weakReferenceMap: HashMap<String, WeakReference<Any>> by lazy { HashMap<String, WeakReference<Any>>() }\n\n    fun <T> getCache(className: String, create: () -> T): T {\n\n        val weakReference = weakReferenceMap[className]\n        if (weakReference?.get() == null) {\n            weakReferenceMap[className] = WeakReference(create()) as WeakReference<Any>\n        }\n        return weakReferenceMap[className]!!.get() as T\n    }\n\n    override fun jump(formContext: Context, to: WindowInfo<*>): Boolean {\n        val with = WindowTree.with<Any>(formContext)\n            ?: throw RuntimeException(\"找不到与该FormContext对应的WindowInfo; WindowInfo corresponding to FormContext could not be found\")\n        when (to.windowType) {\n            WindowType.ACTIVITY -> {\n                formContext.startActivity(Intent(formContext, to.getClazz()).apply { putExtras(to.bundle) })\n                return true\n            }\n            WindowType.FRAGMENT -> {\n                (formContext as Activity).fragmentManager.beginTransaction().replace(with.frameLayoutId,\n                    getCache(to.getClazzName()) { to.getClazz()!!.newInstance() } as android.app.Fragment\n                ).commit()\n                return true\n            }\n            WindowType.FRAGMENTV4 -> {\n                (formContext as FragmentActivity).supportFragmentManager.run {\n                    beginTransaction().replace(with.frameLayoutId,\n                        getCache(to.getClazzName()) { to.getClazz()!!.newInstance() } as Fragment\n                    ).commit()\n                }\n                return true\n            }\n            else -> {\n                throw RuntimeException(\"暂时不支持自动跳转至${to.windowType}类型窗口，您可以自己实现IJumpAdapter接口，自定义跳转方法\")\n            }\n        }\n        return false\n    }\n\n    fun clear() {\n        weakReferenceMap.clear()\n    }\n\n}"
  },
  {
    "path": "windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/model/UnReadCountEvent.kt",
    "content": "package com.kaixuan.windowtreelibrary.model\n\nimport com.kaixuan.windowtreelibrary.WindowInfo\n\ndata class UnReadCountEvent(val fromWindowInfo: WindowInfo<*>,val change : Int)"
  },
  {
    "path": "windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/template/IJumpAdapter.kt",
    "content": "package com.kaixuan.windowtreelibrary.template\n\nimport android.content.Context\nimport com.kaixuan.windowtreelibrary.WindowInfo\n\ninterface IJumpAdapter{\n\n    fun jump(formContext: Context, to: WindowInfo<*>) : Boolean\n\n}"
  },
  {
    "path": "windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/template/ILogger.java",
    "content": "package com.kaixuan.windowtreelibrary.template;\n\nimport com.kaixuan.windowtreelibrary.util.Consts;\n\n/**\n * Logger\n *\n * @author 正纬 <a href=\"mailto:zhilong.liu@aliyun.com\">Contact me.</a>\n * @version 1.0\n * @since 16/5/16 下午5:39\n */\npublic interface ILogger {\n\n    boolean isShowLog = false;\n    boolean isShowStackTrace = false;\n    String defaultTag = Consts.TAG;\n\n    void showLog(boolean isShowLog);\n\n    void showStackTrace(boolean isShowStackTrace);\n\n    void debug(String tag, String message);\n\n    void info(String tag, String message);\n\n    void warning(String tag, String message);\n\n    void error(String tag, String message);\n\n    void monitor(String message);\n\n    boolean isMonitorMode();\n\n    String getDefaultTag();\n}\n"
  },
  {
    "path": "windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/template/IMain.java",
    "content": "package com.kaixuan.windowtreelibrary.template;\n\nimport java.util.Map;\n\npublic interface IMain {\n\n   public Map<String, Class<? extends IWindowTreeLoad>> getAllGeneratedFile();\n}\n"
  },
  {
    "path": "windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/template/IWindowTreeLoad.java",
    "content": "package com.kaixuan.windowtreelibrary.template;\n\nimport com.kaixuan.windowtreelibrary.WindowInfo;\n\npublic interface IWindowTreeLoad {\n\n   public void loadWindowTree(WindowInfo currentWindowInfo) throws ClassNotFoundException;\n}\n"
  },
  {
    "path": "windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/thread/DefaultPoolExecutor.java",
    "content": "package com.kaixuan.windowtreelibrary.thread;\n\nimport com.kaixuan.windowtreelibrary.WindowTree;\nimport com.kaixuan.windowtreelibrary.util.Consts;\nimport com.kaixuan.windowtreelibrary.util.TextUtils;\n\nimport java.util.concurrent.*;\n\n/**\n * Executors\n *\n * @author 正纬 <a href=\"mailto:zhilong.liu@aliyun.com\">Contact me.</a>\n * @version 1.0\n * @since 16/4/28 下午4:07\n */\npublic class DefaultPoolExecutor extends ThreadPoolExecutor {\n    //    Thread args\n    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();\n    private static final int INIT_THREAD_COUNT = CPU_COUNT + 1;\n    private static final int MAX_THREAD_COUNT = INIT_THREAD_COUNT;\n    private static final long SURPLUS_THREAD_LIFE = 30L;\n\n    private static volatile DefaultPoolExecutor instance;\n\n    public static DefaultPoolExecutor getInstance() {\n        if (null == instance) {\n            synchronized (DefaultPoolExecutor.class) {\n                if (null == instance) {\n                    instance = new DefaultPoolExecutor(\n                            INIT_THREAD_COUNT,\n                            MAX_THREAD_COUNT,\n                            SURPLUS_THREAD_LIFE,\n                            TimeUnit.SECONDS,\n                            new ArrayBlockingQueue<Runnable>(64),\n                            new DefaultThreadFactory());\n                }\n            }\n        }\n        return instance;\n    }\n\n    private DefaultPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {\n        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, new RejectedExecutionHandler() {\n            @Override\n            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {\n                WindowTree.logger.error(Consts.TAG, \"Task rejected, too many task!\");\n            }\n        });\n    }\n\n    /*\n     *  线程执行结束，顺便看一下有么有什么乱七八糟的异常\n     *\n     * @param r the runnable that has completed\n     * @param t the exception that caused termination, or null if\n     */\n    @Override\n    protected void afterExecute(Runnable r, Throwable t) {\n        super.afterExecute(r, t);\n        if (t == null && r instanceof Future<?>) {\n            try {\n                ((Future<?>) r).get();\n            } catch (CancellationException ce) {\n                t = ce;\n            } catch (ExecutionException ee) {\n                t = ee.getCause();\n            } catch (InterruptedException ie) {\n                Thread.currentThread().interrupt(); // ignore/reset\n            }\n        }\n        if (t != null) {\n            WindowTree.logger.warning(Consts.TAG, \"Running task appeared exception! Thread [\" + Thread.currentThread().getName() + \"], because [\" + t.getMessage() + \"]\\n\" + TextUtils.formatStackTrace(t.getStackTrace()));\n        }\n    }\n}\n"
  },
  {
    "path": "windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/thread/DefaultThreadFactory.java",
    "content": "package com.kaixuan.windowtreelibrary.thread;\n\nimport android.support.annotation.NonNull;\nimport com.kaixuan.windowtreelibrary.WindowTree;\nimport com.kaixuan.windowtreelibrary.util.Consts;\n\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * 线程池工厂类\n *\n * @author zhilong <a href=\"mailto:zhilong.liu@aliyun.com\">Contact me.</a>\n * @version 1.0\n * @since 15/12/25 上午10:51\n */\npublic class DefaultThreadFactory implements ThreadFactory {\n    private static final AtomicInteger poolNumber = new AtomicInteger(1);\n\n    private final AtomicInteger threadNumber = new AtomicInteger(1);\n    private final ThreadGroup group;\n    private final String namePrefix;\n\n    public DefaultThreadFactory() {\n        SecurityManager s = System.getSecurityManager();\n        group = (s != null) ? s.getThreadGroup() :\n                Thread.currentThread().getThreadGroup();\n        namePrefix = \"ARouter task pool No.\" + poolNumber.getAndIncrement() + \", thread No.\";\n    }\n\n    public Thread newThread(@NonNull Runnable runnable) {\n        String threadName = namePrefix + threadNumber.getAndIncrement();\n        WindowTree.logger.info(Consts.TAG, \"Thread production, name is [\" + threadName + \"]\");\n        Thread thread = new Thread(group, runnable, threadName, 0);\n        if (thread.isDaemon()) {   //设为非后台线程\n            thread.setDaemon(false);\n        }\n        if (thread.getPriority() != Thread.NORM_PRIORITY) { //优先级为normal\n            thread.setPriority(Thread.NORM_PRIORITY);\n        }\n\n        // 捕获多线程处理中的异常\n        thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {\n            @Override\n            public void uncaughtException(Thread thread, Throwable ex) {\n                WindowTree.logger.info(Consts.TAG, \"Running task appeared exception! Thread [\" + thread.getName() + \"], because [\" + ex.getMessage() + \"]\");\n            }\n        });\n        return thread;\n    }\n}"
  },
  {
    "path": "windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/util/ClassUtils.java",
    "content": "package com.kaixuan.windowtreelibrary.util;\n\n// Copy from galaxy sdk ${com.alibaba.android.galaxy.utils.ClassUtils}\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageManager;\nimport android.os.Build;\nimport android.util.Log;\nimport com.kaixuan.windowtreelibrary.WindowTree;\nimport com.kaixuan.windowtreelibrary.thread.DefaultPoolExecutor;\nimport dalvik.system.DexFile;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.lang.reflect.Method;\nimport java.util.*;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * Scanner, find out class with any conditions, copy from google source code.\n *\n * @author 正纬 <a href=\"mailto:zhilong.liu@aliyun.com\">Contact me.</a>\n * @version 1.0\n * @since 16/6/27 下午10:58\n */\npublic class ClassUtils {\n    private static final String EXTRACTED_NAME_EXT = \".classes\";\n    private static final String EXTRACTED_SUFFIX = \".zip\";\n\n    private static final String SECONDARY_FOLDER_NAME = \"code_cache\" + File.separator + \"secondary-dexes\";\n\n    private static final String PREFS_FILE = \"multidex.version\";\n    private static final String KEY_DEX_NUMBER = \"dex.number\";\n\n    private static final int VM_WITH_MULTIDEX_VERSION_MAJOR = 2;\n    private static final int VM_WITH_MULTIDEX_VERSION_MINOR = 1;\n\n    private static SharedPreferences getMultiDexPreferences(Context context) {\n        return context.getSharedPreferences(PREFS_FILE, Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB ? Context.MODE_PRIVATE : Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);\n    }\n\n//    public static Set<String> getFileNameByPackage(Context context, final String packageName)  {\n//        final Set<String> classNames = new HashSet<>();\n//    }\n\n    /**\n     * 通过指定包名，扫描包下面包含的所有的ClassName\n     *\n     * @param context     U know\n     * @param packageName 包名\n     * @return 所有class的集合\n     */\n    public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {\n        final Set<String> classNames = new HashSet<>();\n\n        List<String> paths = getSourcePaths(context);\n        final CountDownLatch parserCtl = new CountDownLatch(paths.size());\n\n        for (final String path : paths) {\n            DefaultPoolExecutor.getInstance().execute(new Runnable() {\n                @Override\n                public void run() {\n                    DexFile dexfile = null;\n\n                    try {\n                        if (path.endsWith(EXTRACTED_SUFFIX)) {\n                            //NOT use new DexFile(path), because it will throw \"permission error in /data/dalvik-cache\"\n                            dexfile = DexFile.loadDex(path, path + \".tmp\", 0);\n                        } else {\n                            dexfile = new DexFile(path);\n                        }\n\n                        Enumeration<String> dexEntries = dexfile.entries();\n                        while (dexEntries.hasMoreElements()) {\n                            String className = dexEntries.nextElement();\n                            if (className.startsWith(packageName)) {\n                                classNames.add(className);\n                            }\n                        }\n                    } catch (Throwable ignore) {\n                        Log.e(\"ARouter\", \"Scan map file in dex files made error.\", ignore);\n                    } finally {\n                        if (null != dexfile) {\n                            try {\n                                dexfile.close();\n                            } catch (Throwable ignore) {\n                            }\n                        }\n\n                        parserCtl.countDown();\n                    }\n                }\n            });\n        }\n\n        parserCtl.await();\n\n        Log.d(Consts.TAG, \"Filter \" + classNames.size() + \" classes by packageName <\" + packageName + \">\");\n        return classNames;\n    }\n\n    /**\n     * get all the dex path\n     *\n     * @param context the application context\n     * @return all the dex path\n     * @throws PackageManager.NameNotFoundException\n     * @throws IOException\n     */\n    public static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {\n        ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);\n        File sourceApk = new File(applicationInfo.sourceDir);\n\n        List<String> sourcePaths = new ArrayList<>();\n        sourcePaths.add(applicationInfo.sourceDir); //add the default apk path\n\n        //the prefix of extracted file, ie: test.classes\n        String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;\n\n//        如果VM已经支持了MultiDex，就不要去Secondary Folder加载 Classesx.zip了，那里已经么有了\n//        通过是否存在sp中的multidex.version是不准确的，因为从低版本升级上来的用户，是包含这个sp配置的\n        if (!isVMMultidexCapable()) {\n            //the total dex numbers\n            int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);\n            File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);\n\n            for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {\n                //for each dex file, ie: test.classes2.zip, test.classes3.zip...\n                String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;\n                File extractedFile = new File(dexDir, fileName);\n                if (extractedFile.isFile()) {\n                    sourcePaths.add(extractedFile.getAbsolutePath());\n                    //we ignore the verify zip part\n                } else {\n                    throw new IOException(\"Missing extracted secondary dex file '\" + extractedFile.getPath() + \"'\");\n                }\n            }\n        }\n\n        if (WindowTree.debuggable) { // Search instant run support only debuggable\n            sourcePaths.addAll(tryLoadInstantRunDexFile(applicationInfo));\n        }\n        return sourcePaths;\n    }\n\n    /**\n     * Get instant run dex path, used to catch the branch usingApkSplits=false.\n     */\n    private static List<String> tryLoadInstantRunDexFile(ApplicationInfo applicationInfo) {\n        List<String> instantRunSourcePaths = new ArrayList<>();\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && null != applicationInfo.splitSourceDirs) {\n            // add the split apk, normally for InstantRun, and newest version.\n            instantRunSourcePaths.addAll(Arrays.asList(applicationInfo.splitSourceDirs));\n            Log.d(Consts.TAG, \"Found InstantRun support\");\n        } else {\n            try {\n                // This man is reflection from Google instant run sdk, he will tell me where the dex files go.\n                Class pathsByInstantRun = Class.forName(\"com.android.tools.fd.runtime.Paths\");\n                Method getDexFileDirectory = pathsByInstantRun.getMethod(\"getDexFileDirectory\", String.class);\n                String instantRunDexPath = (String) getDexFileDirectory.invoke(null, applicationInfo.packageName);\n\n                File instantRunFilePath = new File(instantRunDexPath);\n                if (instantRunFilePath.exists() && instantRunFilePath.isDirectory()) {\n                    File[] dexFile = instantRunFilePath.listFiles();\n                    for (File file : dexFile) {\n                        if (null != file && file.exists() && file.isFile() && file.getName().endsWith(\".dex\")) {\n                            instantRunSourcePaths.add(file.getAbsolutePath());\n                        }\n                    }\n                    Log.d(Consts.TAG, \"Found InstantRun support\");\n                }\n\n            } catch (Exception e) {\n                Log.e(Consts.TAG, \"InstantRun support error, \" + e.getMessage());\n            }\n        }\n\n        return instantRunSourcePaths;\n    }\n\n    /**\n     * Identifies if the current VM has a native support for multidex, meaning there is no need for\n     * additional installation by this library.\n     *\n     * @return true if the VM handles multidex\n     */\n    private static boolean isVMMultidexCapable() {\n        boolean isMultidexCapable = false;\n        String vmName = null;\n\n        try {\n            if (isYunOS()) {    // YunOS需要特殊判断\n                vmName = \"'YunOS'\";\n                isMultidexCapable = Integer.valueOf(System.getProperty(\"ro.build.version.sdk\")) >= 21;\n            } else {    // 非YunOS原生Android\n                vmName = \"'Android'\";\n                String versionString = System.getProperty(\"java.vm.version\");\n                if (versionString != null) {\n                    Matcher matcher = Pattern.compile(\"(\\\\d+)\\\\.(\\\\d+)(\\\\.\\\\d+)?\").matcher(versionString);\n                    if (matcher.matches()) {\n                        try {\n                            int major = Integer.parseInt(matcher.group(1));\n                            int minor = Integer.parseInt(matcher.group(2));\n                            isMultidexCapable = (major > VM_WITH_MULTIDEX_VERSION_MAJOR)\n                                    || ((major == VM_WITH_MULTIDEX_VERSION_MAJOR)\n                                    && (minor >= VM_WITH_MULTIDEX_VERSION_MINOR));\n                        } catch (NumberFormatException ignore) {\n                            // let isMultidexCapable be false\n                        }\n                    }\n                }\n            }\n        } catch (Exception ignore) {\n\n        }\n\n        Log.i(Consts.TAG, \"VM with name \" + vmName + (isMultidexCapable ? \" has multidex support\" : \" does not have multidex support\"));\n        return isMultidexCapable;\n    }\n\n    /**\n     * 判断系统是否为YunOS系统\n     */\n    private static boolean isYunOS() {\n        try {\n            String version = System.getProperty(\"ro.yunos.version\");\n            String vmName = System.getProperty(\"java.vm.name\");\n            return (vmName != null && vmName.toLowerCase().contains(\"lemur\"))\n                    || (version != null && version.trim().length() > 0);\n        } catch (Exception ignore) {\n            return false;\n        }\n    }\n}"
  },
  {
    "path": "windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/util/Consts.kt",
    "content": "package com.kaixuan.windowtreelibrary.util\n\nclass Consts{\n    companion object {\n        @JvmField\n        val TAG = \"WindowTree\"\n        val GENERATE_FILE_PATH = \"com.kaixuan.windowtree.windows\"\n        val GENERATE_FILE_NAME_END = \"\\$Gen\"\n    }\n}"
  },
  {
    "path": "windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/util/DefaultLogger.java",
    "content": "package com.kaixuan.windowtreelibrary.util;\n\nimport android.text.TextUtils;\nimport android.util.Log;\nimport com.kaixuan.windowtreelibrary.template.ILogger;\n\n/**\n * Default logger\n *\n * @author zhilong <a href=\"mailto:zhilong.liu@aliyun.com\">Contact me.</a>\n * @version 1.0\n * @since 2015-12-08 21:44:10\n */\npublic class DefaultLogger implements ILogger {\n\n    private static boolean isShowLog = false;\n    private static boolean isShowStackTrace = false;\n    private static boolean isMonitorMode = false;\n\n    private String defaultTag = \"WindowTree\";\n\n    public void showLog(boolean showLog) {\n        isShowLog = showLog;\n    }\n\n    public void showStackTrace(boolean showStackTrace) {\n        isShowStackTrace = showStackTrace;\n    }\n\n    public void showMonitor(boolean showMonitor) {\n        isMonitorMode = showMonitor;\n    }\n\n    public DefaultLogger() {\n    }\n\n    public DefaultLogger(String defaultTag) {\n        this.defaultTag = defaultTag;\n    }\n\n    @Override\n    public void debug(String tag, String message) {\n        if (isShowLog) {\n            StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];\n            Log.d(TextUtils.isEmpty(tag) ? getDefaultTag() : tag, message + getExtInfo(stackTraceElement));\n        }\n    }\n\n    @Override\n    public void info(String tag, String message) {\n        if (isShowLog) {\n            StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];\n            Log.i(TextUtils.isEmpty(tag) ? getDefaultTag() : tag, message + getExtInfo(stackTraceElement));\n        }\n    }\n\n    @Override\n    public void warning(String tag, String message) {\n        if (isShowLog) {\n            StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];\n            Log.w(TextUtils.isEmpty(tag) ? getDefaultTag() : tag, message + getExtInfo(stackTraceElement));\n        }\n    }\n\n    @Override\n    public void error(String tag, String message) {\n        if (isShowLog) {\n            StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];\n            Log.e(TextUtils.isEmpty(tag) ? getDefaultTag() : tag, message + getExtInfo(stackTraceElement));\n        }\n    }\n\n    @Override\n    public void monitor(String message) {\n        if (isShowLog && isMonitorMode()) {\n            StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];\n            Log.d(defaultTag + \"::monitor\", message + getExtInfo(stackTraceElement));\n        }\n    }\n\n    @Override\n    public boolean isMonitorMode() {\n        return isMonitorMode;\n    }\n\n    @Override\n    public String getDefaultTag() {\n        return defaultTag;\n    }\n\n    public static String getExtInfo(StackTraceElement stackTraceElement) {\n\n        String separator = \" & \";\n        StringBuilder sb = new StringBuilder(\"[\");\n\n        if (isShowStackTrace) {\n            String threadName = Thread.currentThread().getName();\n            String fileName = stackTraceElement.getFileName();\n            String className = stackTraceElement.getClassName();\n            String methodName = stackTraceElement.getMethodName();\n            long threadID = Thread.currentThread().getId();\n            int lineNumber = stackTraceElement.getLineNumber();\n\n            sb.append(\"ThreadId=\").append(threadID).append(separator);\n            sb.append(\"ThreadName=\").append(threadName).append(separator);\n            sb.append(\"FileName=\").append(fileName).append(separator);\n            sb.append(\"ClassName=\").append(className).append(separator);\n            sb.append(\"MethodName=\").append(methodName).append(separator);\n            sb.append(\"LineNumber=\").append(lineNumber);\n        }\n\n        sb.append(\" ] \");\n        return sb.toString();\n    }\n}"
  },
  {
    "path": "windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/util/TextUtils.java",
    "content": "package com.kaixuan.windowtreelibrary.util;\n\nimport android.net.Uri;\n\nimport java.util.Collections;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\n/**\n * Text utils\n *\n * @author Alex <a href=\"mailto:zhilong.liu@aliyun.com\">Contact me.</a>\n * @version 1.0\n * @since 16/9/9 14:40\n */\npublic class TextUtils {\n\n    public static boolean isEmpty(final CharSequence cs) {\n        return cs == null || cs.length() == 0;\n    }\n\n    /**\n     * Print thread stack\n     */\n    public static String formatStackTrace(StackTraceElement[] stackTrace) {\n        StringBuilder sb = new StringBuilder();\n        for (StackTraceElement element : stackTrace) {\n            sb.append(\"    at \").append(element.toString());\n            sb.append(\"\\n\");\n        }\n        return sb.toString();\n    }\n\n    /**\n     * Split query parameters\n     * @param rawUri raw uri\n     * @return map with params\n     */\n    public static Map<String, String> splitQueryParameters(Uri rawUri) {\n        String query = rawUri.getEncodedQuery();\n\n        if (query == null) {\n            return Collections.emptyMap();\n        }\n\n        Map<String, String> paramMap = new LinkedHashMap<>();\n        int start = 0;\n        do {\n            int next = query.indexOf('&', start);\n            int end = (next == -1) ? query.length() : next;\n\n            int separator = query.indexOf('=', start);\n            if (separator > end || separator == -1) {\n                separator = end;\n            }\n\n            String name = query.substring(start, separator);\n\n            if (!android.text.TextUtils.isEmpty(name)) {\n                String value = (separator == end ? \"\" : query.substring(separator + 1, end));\n                paramMap.put(Uri.decode(name), Uri.decode(value));\n            }\n\n            // Move start to end of name.\n            start = end + 1;\n        } while (start < query.length());\n\n        return Collections.unmodifiableMap(paramMap);\n    }\n\n    /**\n     * Split key with |\n     *\n     * @param key raw key\n     * @return left key\n     */\n    public static String getLeft(String key) {\n        if (key.contains(\"|\") && !key.endsWith(\"|\")) {\n            return key.substring(0, key.indexOf(\"|\"));\n        } else {\n            return key;\n        }\n    }\n\n    /**\n     * Split key with |\n     *\n     * @param key raw key\n     * @return right key\n     */\n    public static String getRight(String key) {\n        if (key.contains(\"|\") && !key.startsWith(\"|\")) {\n            return key.substring(key.indexOf(\"|\") + 1);\n        } else {\n            return key;\n        }\n    }\n    /**\n     * Split key with |\n     *\n     * @param key raw key\n     * @return right key\n     */\n    public static String getRight(String key,String sub) {\n        if (key.contains(sub) && !key.startsWith(sub)) {\n            return key.substring(key.indexOf(sub) + 1);\n        } else {\n            return key;\n        }\n    }\n}\n"
  },
  {
    "path": "windowtree_library/src/main/java/com/kaixuan/windowtreelibrary/util/WindowTreeUtil.kt",
    "content": "package com.kaixuan.windowtreelibrary.util\n\nimport android.content.Context\nimport com.kaixuan.windowtreelibrary.WindowInfo\nimport com.kaixuan.windowtreelibrary.WindowTree\n\nclass WindowTreeUtil{\n\n    companion object {\n\n        fun <K,V> findKeyByValue(map: Map<K,V>,value : V): K? {\n            return map.entries.find { it.value == value }?.key\n        }\n\n        fun findContextByInfo(windowInfo: WindowInfo<*>): Context? {\n            val findKeyByValue = findKeyByValue(WindowTree.instance.weakHashMap, windowInfo) ?: return null\n            return getContext(findKeyByValue)\n        }\n\n        fun getContext(any: Any): Context? {\n            when(any){\n                is android.app.Activity -> return any\n                is android.app.Fragment -> return any.activity\n                is android.support.v4.app.Fragment -> return any.context\n                is android.view.View -> return any.context\n                is android.app.Dialog -> return any.context\n                is android.widget.PopupWindow -> return any.contentView.context\n                else -> return null\n            }\n        }\n    }\n}"
  },
  {
    "path": "windowtree_library/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">WindowTreeLibrary</string>\n</resources>\n"
  },
  {
    "path": "windowtree_library/src/test/java/com/kaixuan/windowtreelibrary/ExampleUnitTest.java",
    "content": "package com.kaixuan.windowtreelibrary;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\npublic class ExampleUnitTest {\n    @Test\n    public void addition_isCorrect() {\n        assertEquals(4, 2 + 2);\n    }\n}"
  }
]