diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index e99ad7f92..0366a9679 100755
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -41,8 +41,8 @@
+
-
\ No newline at end of file
diff --git a/annotation-processors/.gitignore b/annotation-processors/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/annotation-processors/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/annotation-processors/build.gradle b/annotation-processors/build.gradle
new file mode 100644
index 000000000..8fff3a459
--- /dev/null
+++ b/annotation-processors/build.gradle
@@ -0,0 +1,49 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+plugins {
+ id 'java-library'
+ id 'kotlin'
+ id 'kotlin-kapt'
+}
+
+java {
+ sourceCompatibility = rootProject.ext.javaSourceVersion
+ targetCompatibility = rootProject.ext.javaTargetVersion
+}
+
+compileKotlin {
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+}
+
+compileTestKotlin {
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+}
+
+dependencies {
+ implementation common.jkotlin
+ implementation common.javapoet
+ implementation google.auto_service
+
+ implementation project(path: ':annotations')
+
+ kapt google.auto_service
+ kapt project (path: ':build-tools:framework-stubs')
+}
\ No newline at end of file
diff --git a/annotation-processors/src/main/java/com/itsaky/androidide/annotations/inflater/AttrAdapterAnnotationProcessor.kt b/annotation-processors/src/main/java/com/itsaky/androidide/annotations/inflater/AttrAdapterAnnotationProcessor.kt
new file mode 100644
index 000000000..d956cca8c
--- /dev/null
+++ b/annotation-processors/src/main/java/com/itsaky/androidide/annotations/inflater/AttrAdapterAnnotationProcessor.kt
@@ -0,0 +1,197 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.annotations.inflater
+
+import com.google.auto.service.AutoService
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.CodeBlock
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.JavaFile
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import com.squareup.javapoet.WildcardTypeName
+import java.io.File
+import javax.annotation.processing.AbstractProcessor
+import javax.annotation.processing.Processor
+import javax.annotation.processing.RoundEnvironment
+import javax.lang.model.SourceVersion
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind.CLASS
+import javax.lang.model.element.ElementKind.PACKAGE
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier.FINAL
+import javax.lang.model.element.Modifier.PRIVATE
+import javax.lang.model.element.Modifier.PUBLIC
+import javax.lang.model.element.Modifier.STATIC
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.MirroredTypeException
+import javax.tools.Diagnostic.Kind.ERROR
+
+@Suppress("unused")
+@AutoService(Processor::class)
+class AttrAdapterAnnotationProcessor : AbstractProcessor() {
+
+ private val adapterElement by lazy {
+ processingEnv.elementUtils.getTypeElement(ADAPTER_BASE_CLASS)
+ }
+
+ private val classBuilder = TypeSpec.classBuilder(INDEX_CLASS_NAME).addModifiers(PUBLIC, FINAL)
+ private val addStatements = CodeBlock.builder()
+ init {
+ val wildcard =
+ WildcardTypeName.subtypeOf(ClassName.get(ADAPTER_BASE_CLASS_PCK, ADAPTER_BASE_CLASS_NAME))
+ val klass = ParameterizedTypeName.get(ClassName.get(Class::class.java), wildcard)
+ val type =
+ ParameterizedTypeName.get(
+ ClassName.get(java.util.Map::class.java),
+ ClassName.get(String::class.java),
+ klass
+ )
+ classBuilder.addField(
+ FieldSpec.builder(type, INDEX_MAP_FIELD, PRIVATE, STATIC, FINAL)
+ .initializer("new \$T<>();", java.util.HashMap::class.java)
+ .build()
+ )
+
+ classBuilder.addMethod(
+ MethodSpec.constructorBuilder()
+ .addModifiers(PRIVATE)
+ .addStatement(
+ "throw new \$T(\$S)",
+ UnsupportedOperationException::class.java,
+ "This class cannot be instantiated."
+ )
+ .build()
+ )
+
+ classBuilder.addMethod(
+ MethodSpec.methodBuilder("getAdapter")
+ .addModifiers(PUBLIC, STATIC)
+ .addParameter(String::class.java, "view", FINAL)
+ .returns(klass)
+ .addStatement("return \$L.get(\$L)", INDEX_MAP_FIELD, "view")
+ .build()
+ )
+ }
+
+ companion object {
+ const val ADAPTER_BASE_CLASS_PCK = "com.itsaky.androidide.inflater"
+ const val ADAPTER_BASE_CLASS_NAME = "IAttributeAdapter"
+ const val ADAPTER_BASE_CLASS = "$ADAPTER_BASE_CLASS_PCK.$ADAPTER_BASE_CLASS_NAME"
+ const val INDEX_PACKAGE_NAME = "com.itsaky.androidide.inflater.internal"
+ const val INDEX_CLASS_NAME = "AttributeAdapterIndex"
+ const val INDEX_MAP_FIELD = "adapterMap"
+
+ const val KAPT_KOTLIN_GENERATED = "kapt.kotlin.generated"
+ }
+
+ override fun getSupportedAnnotationTypes(): MutableSet {
+ return mutableSetOf(AttributeAdapter::class.java.name)
+ }
+
+ override fun getSupportedSourceVersion(): SourceVersion {
+ return SourceVersion.latestSupported()
+ }
+
+ override fun process(
+ annotations: MutableSet?,
+ roundEnv: RoundEnvironment
+ ): Boolean {
+ if (roundEnv.processingOver()) {
+ return true
+ }
+ roundEnv.getElementsAnnotatedWith(AttributeAdapter::class.java).forEach {
+ if (it.kind != CLASS) {
+ processingEnv.messager.printMessage(
+ ERROR,
+ "@AttributeAdapter can only be used with classes. Used on $it"
+ )
+ return true
+ }
+ process(it)
+ }
+
+ classBuilder.addStaticBlock(addStatements.build())
+ val file = JavaFile.builder(INDEX_PACKAGE_NAME, classBuilder.build())
+ file
+ .build()
+ .writeTo(
+ File(
+ processingEnv.options[KAPT_KOTLIN_GENERATED]
+ ?: throw IllegalStateException("Cannot find kapt output directory path")
+ )
+ )
+
+ return false
+ }
+
+ private fun process(element: Element) {
+ if (element.enclosingElement?.kind != PACKAGE || element !is TypeElement) {
+ processingEnv.messager.printMessage(
+ ERROR,
+ "@AttributeAdapter can only be used on top-level classes. Used on $element"
+ )
+ return
+ }
+
+ val constructors =
+ processingEnv.elementUtils.getAllMembers(element).filter {
+ "".contentEquals(it.simpleName)
+ }
+ if (constructors.size != 1) {
+ processingEnv.messager.printMessage(
+ ERROR,
+ "An attribute adapter must have only default constructor"
+ )
+ return
+ }
+
+ if ((constructors.first() as ExecutableElement).parameters.isNotEmpty()) {
+ processingEnv.messager.printMessage(
+ ERROR,
+ "Attribute adapter constructor should not have any parameters"
+ )
+ return
+ }
+
+ if (!processingEnv.typeUtils.isSubtype(element.asType(), adapterElement.asType())) {
+ processingEnv.messager.printMessage(ERROR, "Class must be a subtype of $ADAPTER_BASE_CLASS")
+ return
+ }
+
+ val annotation =
+ element.getAnnotation(AttributeAdapter::class.java) ?: throw IllegalStateException()
+ addStatements.addStatement(
+ "\$L.put(\$S, \$T.class)",
+ INDEX_MAP_FIELD,
+ getViewName(annotation),
+ TypeName.get(element.asType())
+ )
+ }
+
+ private fun getViewName(annotation: AttributeAdapter): String {
+ return try {
+ annotation.forClass.qualifiedName
+ ?: throw IllegalStateException("Cannot find type of $annotation")
+ } catch (err: MirroredTypeException) {
+ (processingEnv.typeUtils.asElement(err.typeMirror) as TypeElement).qualifiedName.toString()
+ }
+ }
+}
diff --git a/annotations/.gitignore b/annotations/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/annotations/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/annotations/build.gradle b/annotations/build.gradle
new file mode 100644
index 000000000..7348a2abc
--- /dev/null
+++ b/annotations/build.gradle
@@ -0,0 +1,26 @@
+plugins {
+ id 'java-library'
+ id 'kotlin'
+}
+
+java {
+ sourceCompatibility = rootProject.ext.javaSourceVersion
+ targetCompatibility = rootProject.ext.javaTargetVersion
+}
+
+compileKotlin {
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+}
+
+compileTestKotlin {
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+}
+
+dependencies {
+ implementation common.jkotlin
+ compileOnly project (path: ':build-tools:framework-stubs')
+}
\ No newline at end of file
diff --git a/annotations/src/main/java/com/itsaky/androidide/annotations/inflater/AttributeAdapter.kt b/annotations/src/main/java/com/itsaky/androidide/annotations/inflater/AttributeAdapter.kt
new file mode 100644
index 000000000..20f5a0a5b
--- /dev/null
+++ b/annotations/src/main/java/com/itsaky/androidide/annotations/inflater/AttributeAdapter.kt
@@ -0,0 +1,30 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+package com.itsaky.androidide.annotations.inflater
+
+import android.view.View
+import kotlin.annotation.AnnotationTarget.CLASS
+import kotlin.reflect.KClass
+
+/**
+ * Annotation used to indicate that a class is an attribute adapater for the given view class.
+ *
+ * @author Akash Yadav
+ */
+@Target(CLASS)
+@Retention(AnnotationRetention.SOURCE)
+annotation class AttributeAdapter(val forClass: KClass)
diff --git a/dependencies.gradle b/dependencies.gradle
index 26805d685..a3a8d0514 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -28,7 +28,8 @@ ext {
io : 'commons-io:commons-io:2.11.0',
kotlin : 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.20',
jkotlin : 'org.jetbrains.kotlin:kotlin-stdlib-jdk8',
- eventbus_ap: 'org.greenrobot:eventbus-annotation-processor:3.3.1'
+ eventbus_ap: 'org.greenrobot:eventbus-annotation-processor:3.3.1',
+ javapoet : 'com.squareup:javapoet:1.13.0'
]
androidx = [
@@ -56,6 +57,7 @@ ext {
guava_jre : 'com.google.guava:guava:31.1-jre',
auto_val_annotations: 'com.google.auto.value:auto-value-annotations:1.10',
auto_val_ap : 'com.google.auto.value:auto-value:1.10',
+ auto_service : 'com.google.auto.service:auto-service:1.0.1',
oss_licenses : 'com.google.android.gms:play-services-oss-licenses:17.0.0',
protobuf : 'com.google.protobuf:protobuf-java:3.10.0'
]
diff --git a/lsp/api/build.gradle b/lsp/api/build.gradle
index f6280e001..14c4ff762 100644
--- a/lsp/api/build.gradle
+++ b/lsp/api/build.gradle
@@ -69,7 +69,6 @@ dependencies {
api project (':lsp:models')
api project (':preferences')
- compileOnly project (':sdk-info')
compileOnly project (':actions')
compileOnly project (':common')
}
diff --git a/lsp/xml/build.gradle b/lsp/xml/build.gradle
index 7d570dc5e..81f114b7b 100644
--- a/lsp/xml/build.gradle
+++ b/lsp/xml/build.gradle
@@ -72,7 +72,6 @@ dependencies {
implementation project (':build-tools:xml-dom')
implementation project (':build-tools:xml-formatter')
implementation project (':build-tools:xml-utils')
- implementation project (':sdk-info')
implementation androidx.ktx
implementation common.kotlin
diff --git a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/XmlCompletionProvider.kt b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/XmlCompletionProvider.kt
index 7b6966ba9..cef5aee5e 100644
--- a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/XmlCompletionProvider.kt
+++ b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/XmlCompletionProvider.kt
@@ -59,7 +59,6 @@ import com.itsaky.androidide.lsp.xml.utils.forTransitionAttr
import com.itsaky.androidide.utils.CharSequenceReader
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.StopWatch
-import com.itsaky.xml.INamespace
import io.github.rosemoe.sora.text.ContentReference
import org.eclipse.lemminx.dom.DOMParser
import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager
@@ -94,10 +93,9 @@ class XmlCompletionProvider(settings: IServerSettings) :
}
private fun doComplete(params: CompletionParams): CompletionResult {
- val namespace = INamespace.ANDROID
val contents = toString(contents = params.requireContents())
val document =
- DOMParser.getInstance().parse(contents, namespace.uri, URIResolverExtensionManager())
+ DOMParser.getInstance().parse(contents, "http://schemas.android.com/apk/res/android", URIResolverExtensionManager())
val type = XmlUtils.getNodeType(document, params.position.requireIndex())
if (type == UNKNOWN) {
diff --git a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/utils/XmlUtils.kt b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/utils/XmlUtils.kt
index e4a4b2b52..06ee0fdc7 100644
--- a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/utils/XmlUtils.kt
+++ b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/utils/XmlUtils.kt
@@ -21,18 +21,12 @@ import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType.ATTRIBUTE
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType.ATTRIBUTE_VALUE
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType.TAG
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType.UNKNOWN
-import com.itsaky.androidide.utils.CharSequenceReader
import com.itsaky.androidide.utils.ILogger
-import com.itsaky.xml.INamespace.Resolver
-import java.util.*
import org.antlr.v4.runtime.CharStreams
import org.antlr.v4.runtime.Token
import org.eclipse.lemminx.dom.DOMDocument
import org.eclipse.lemminx.dom.DOMElement
import org.eclipse.lemminx.dom.DOMNode
-import org.xmlpull.v1.XmlPullParser
-import org.xmlpull.v1.XmlPullParserException
-import org.xmlpull.v1.XmlPullParserFactory
/** @author Akash Yadav */
object XmlUtils {
diff --git a/sdk-info/.gitignore b/sdk-info/.gitignore
deleted file mode 100755
index bb0d480e4..000000000
--- a/sdk-info/.gitignore
+++ /dev/null
@@ -1,85 +0,0 @@
-# Built application files
-*.apk
-*.aar
-*.ap_
-*.aab
-
-# Files for the ART/Dalvik VM
-*.dex
-
-# Java class files
-*.class
-
-# Generated files
-/bin/
-/gen/
-/out/
-# Uncomment the following line in case you need and you don't have the release build type files in your app
-# release/
-
-# Gradle files
-.gradle/
-/build/
-
-# Local configuration file (sdk path, etc)
-local.properties
-
-# Proguard folder generated by Eclipse
-proguard/
-
-# Log Files
-*.log
-
-# Android Studio Navigation editor temp files
-.navigation/
-
-# Android Studio captures folder
-captures/
-
-# IntelliJ
-*.iml
-.idea/workspace.xml
-.idea/tasks.xml
-.idea/gradle.xml
-.idea/assetWizardSettings.xml
-.idea/dictionaries
-.idea/libraries
-# Android Studio 3 in .gitignore file.
-.idea/caches
-.idea/modules.xml
-# Comment next line if keeping position of elements in Navigation Editor is relevant for you
-.idea/navEditor.xml
-
-# Keystore files
-# Uncomment the following lines if you do not want to check your keystore files in.
-#*.jks
-#*.keystore
-
-# External native build folder generated in Android Studio 2.2 and later
-.externalNativeBuild
-.cxx/
-
-# Google Services (e.g. APIs or Firebase)
-# google-services.json
-
-# Freeline
-freeline.py
-freeline/
-freeline_project_description.json
-
-# fastlane
-fastlane/report.xml
-fastlane/Preview.html
-fastlane/screenshots
-fastlane/test_output
-fastlane/readme.md
-
-# Version control
-vcs.xml
-
-# lint
-lint/intermediates/
-lint/generated/
-lint/outputs/
-lint/tmp/
-# lint/reports/
diff --git a/sdk-info/build.gradle b/sdk-info/build.gradle
deleted file mode 100755
index cf286ff0b..000000000
--- a/sdk-info/build.gradle
+++ /dev/null
@@ -1,38 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- namespace "com.itsaky.sdkinfo"
- compileSdkVersion rootProject.ext.compileSdk
- buildToolsVersion rootProject.ext.buildTools
-
- defaultConfig {
- minSdkVersion rootProject.ext.minSdk
- targetSdkVersion rootProject.ext.targetSdk
- }
-
- compileOptions {
- sourceCompatibility rootProject.ext.javaSourceVersion
- targetCompatibility rootProject.ext.javaTargetVersion
- }
-
- buildTypes {
- release {
- minifyEnabled true
- }
- }
-}
-
-dependencies {
- implementation fileTree (dir: 'libs', include: ['*.jar'])
- implementation common.utilcode
- implementation common.jsoup
-
- implementation androidx.annotation
- implementation androidx.appcompat
- implementation google.material
-
- implementation project(path: ':resources')
- implementation project(path: ':build-tools:jdt')
-
- compileOnly project (":common")
-}
diff --git a/sdk-info/proguard-rules.pro b/sdk-info/proguard-rules.pro
deleted file mode 100755
index 0448f7080..000000000
--- a/sdk-info/proguard-rules.pro
+++ /dev/null
@@ -1,17 +0,0 @@
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in /Users/bogdan/Library/Android/sdk/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the proguardFiles
-# directive in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
diff --git a/sdk-info/src/main/AndroidManifest.xml b/sdk-info/src/main/AndroidManifest.xml
deleted file mode 100755
index 9c5d2fe0b..000000000
--- a/sdk-info/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/sdk-info/src/main/java/com/itsaky/xml/INamespace.java b/sdk-info/src/main/java/com/itsaky/xml/INamespace.java
deleted file mode 100644
index 284062af4..000000000
--- a/sdk-info/src/main/java/com/itsaky/xml/INamespace.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * This file is part of AndroidIDE.
- *
- * AndroidIDE is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * AndroidIDE is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with AndroidIDE. If not, see .
- */
-package com.itsaky.xml;
-
-import android.os.Parcelable;
-
-import androidx.annotation.NonNull;
-
-import com.itsaky.xml.impl.UiNamespace;
-
-import org.jetbrains.annotations.Contract;
-
-/**
- * Represents a namespace.
- *
- * @author Akash Yadav
- */
-public interface INamespace extends Parcelable {
-
- String URI_PREFIX = "http://schemas.android.com/apk/res/";
-
- /** Used to represent the namespace declarator (xmlns) (don't know what else to call it). */
- INamespace DECLARATOR = new UiNamespace("xmlns", "");
-
- /** The android namespace. */
- INamespace ANDROID =
- new UiNamespace("android", "android", "http://schemas.android.com/apk/res/android");
-
- /** The 'res-auto' namespace. */
- INamespace RES_AUTO = new UiNamespace("", null, "http://schemas.android.com/apk/res-auto");
-
- /** The tools namespace. */
- INamespace TOOLS = new UiNamespace("tools", null, "http://schemas.android.com/tools");
-
- static INamespace forPackageName(@NonNull String packageName) {
- if (ANDROID.getPackageName().equals(packageName)) {
- return ANDROID;
- }
-
- return new UiNamespace("", packageName, INamespace.URI_PREFIX + packageName);
- }
-
- /**
- * Get the package name of this namespace (as used by aapt).
- *
- * @return The package name.
- */
- String getPackageName();
-
- @NonNull
- @Contract(value = "_ -> new", pure = true)
- static INamespace invalid(String name) {
- return new UiNamespace(name, "");
- }
-
- /**
- * Get the prefix (name) of this namespace.
- *
- * @return The name of the namespace.
- */
- String getPrefix();
-
- /**
- * Get the uri of this namespace.
- *
- * @return The uri of this namespace.
- */
- String getUri();
-
- interface Resolver {
- Resolver EMPTY =
- new Resolver() {
- @Override
- public String findUri(String prefix) {
- return null;
- }
-
- @Override
- public String findPrefix(String uri) {
- return null;
- }
- };
-
- String findUri(String prefix);
-
- String findPrefix(String uri);
- }
-}
diff --git a/sdk-info/src/main/java/com/itsaky/xml/impl/UiNamespace.java b/sdk-info/src/main/java/com/itsaky/xml/impl/UiNamespace.java
deleted file mode 100644
index 7797d5afb..000000000
--- a/sdk-info/src/main/java/com/itsaky/xml/impl/UiNamespace.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * This file is part of AndroidIDE.
- *
- * AndroidIDE is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * AndroidIDE is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with AndroidIDE. If not, see .
- */
-package com.itsaky.xml.impl;
-
-import android.os.Parcel;
-
-import androidx.annotation.NonNull;
-
-import com.itsaky.xml.INamespace;
-
-import org.jetbrains.annotations.Contract;
-
-import java.util.Objects;
-
-/**
- * @author Akash Yadav
- */
-public class UiNamespace implements INamespace {
-
- public static final Creator CREATOR =
- new Creator<>() {
- @NonNull
- @Contract("_ -> new")
- @Override
- public UiNamespace createFromParcel(Parcel source) {
- return new UiNamespace(source);
- }
-
- @NonNull
- @Contract(value = "_ -> new", pure = true)
- @Override
- public UiNamespace[] newArray(int size) {
- return new UiNamespace[0];
- }
- };
- private final String prefix;
- private final String packageName;
- private final String uri;
-
- public UiNamespace(String prefix, String uri) {
- this(prefix, null, uri);
- }
-
- public UiNamespace(String prefix, String packageName, String uri) {
- this.prefix = prefix;
- this.packageName = packageName;
- this.uri = uri;
- }
-
- private UiNamespace(@NonNull Parcel in) {
- this.prefix = in.readString();
- this.packageName = in.readString();
- this.uri = in.readString();
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(getUri());
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
-
- if (!(o instanceof UiNamespace)) {
- return false;
- }
-
- UiNamespace that = (UiNamespace) o;
-
- // name doesn't matter while
- // check for URI
- return Objects.equals(getUri(), that.getUri());
- }
-
- @NonNull
- @Override
- public String toString() {
- return "UiNamespace{" + "name='" + prefix + '\'' + ", uri='" + uri + '\'' + '}';
- }
-
- @Override
- public String getPackageName() {
- return packageName;
- }
-
- @Override
- public String getPrefix() {
- return prefix;
- }
-
- @Override
- public String getUri() {
- return uri;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString(prefix);
- dest.writeString(packageName);
- dest.writeString(uri);
- }
-}
diff --git a/settings.gradle b/settings.gradle
index de5204aef..4dec1640e 100755
--- a/settings.gradle
+++ b/settings.gradle
@@ -9,7 +9,6 @@ include ':eventbus-events'
include ':lexers'
include ':logger'
include ':lookup'
-include ':sdk-info'
include ':shared'
include ':treeview'
@@ -45,3 +44,6 @@ include ':build-tools:xml-utils'
include ':editor'
include ':preferences'
include ':resources'
+include ':xml-inflater'
+include ':annotation-processors'
+include ':annotations'
diff --git a/tests/test-home/.androidide/init/model.jar b/tests/test-home/.androidide/init/model.jar
index 6387e73c5..758f910d9 100644
Binary files a/tests/test-home/.androidide/init/model.jar and b/tests/test-home/.androidide/init/model.jar differ
diff --git a/xml-inflater/.gitignore b/xml-inflater/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/xml-inflater/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/xml-inflater/build.gradle b/xml-inflater/build.gradle
new file mode 100644
index 000000000..5af3c249b
--- /dev/null
+++ b/xml-inflater/build.gradle
@@ -0,0 +1,53 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
+
+android {
+ namespace "com.itsaky.androidide.inflater"
+ compileSdkVersion rootProject.ext.compileSdk
+ buildToolsVersion rootProject.ext.buildTools
+
+ defaultConfig {
+ minSdkVersion rootProject.ext.minSdk
+ targetSdkVersion rootProject.ext.targetSdk
+ }
+
+ buildFeatures.viewBinding true
+
+ compileOptions {
+ sourceCompatibility rootProject.ext.javaSourceVersion
+ targetCompatibility rootProject.ext.javaTargetVersion
+ }
+
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled true
+ }
+ }
+
+ testOptions {
+ unitTests {
+ includeAndroidResources = true
+ }
+ }
+}
+
+dependencies {
+ implementation androidx.appcompat
+ implementation common.kotlin
+
+ implementation project(path: ':annotations')
+ implementation project(path: ':build-tools:aaptcompiler')
+ implementation project(path: ':build-tools:projects')
+ implementation project(path: ':build-tools:xml-utils')
+
+ kapt project(path: ':annotation-processors')
+
+ testImplementation tests.junit
+ testImplementation tests.google_truth
+ testImplementation tests.robolectric
+}
\ No newline at end of file
diff --git a/xml-inflater/consumer-rules.pro b/xml-inflater/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/xml-inflater/proguard-rules.pro b/xml-inflater/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/xml-inflater/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/xml-inflater/src/main/AndroidManifest.xml b/xml-inflater/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..e62e7b210
--- /dev/null
+++ b/xml-inflater/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/IAttribute.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/IAttribute.kt
new file mode 100644
index 000000000..591710509
--- /dev/null
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/IAttribute.kt
@@ -0,0 +1,35 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.inflater
+
+/**
+ * Model for an XML attribute.
+ *
+ * @author Akash Yadav
+ */
+interface IAttribute {
+
+ /** The namespace of the attribute. */
+ val namespace: INamespace
+
+ /** The name of the attribute. */
+ val name: String
+
+ /** The value of the attribute. */
+ val value: String
+}
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/IAttributeAdapter.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/IAttributeAdapter.kt
new file mode 100644
index 000000000..f59f30498
--- /dev/null
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/IAttributeAdapter.kt
@@ -0,0 +1,34 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.inflater
+
+/**
+ * An attribute adapter handles the application and removal of attributes on a view.
+ *
+ * @author Akash Yadav
+ */
+abstract class IAttributeAdapter {
+
+ /**
+ * Apply the given attribute to the given view.
+ *
+ * @param view The view to which the attribute must be applied.
+ * @param attribute The attribute to apply.
+ */
+ abstract fun apply(view: IView, attribute: IAttribute)
+}
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/ILayoutInflater.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/ILayoutInflater.kt
new file mode 100644
index 000000000..d95d1fbb2
--- /dev/null
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/ILayoutInflater.kt
@@ -0,0 +1,48 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.inflater
+
+import android.view.ViewGroup
+import com.itsaky.androidide.inflater.internal.LayoutInflaterImpl
+import java.io.File
+
+/**
+ * An [ILayoutInflater] converts (inflates) XML layout to Android view which can be easily rendered.
+ *
+ * @author Akash Yadav
+ */
+abstract class ILayoutInflater {
+
+ /** The listener which listens to layout inflation events. */
+ abstract var inflationEventListener: IInflateEventsListener?
+
+ /**
+ * Inflate the given raw XML file.
+ *
+ * @param file The file to inflate.
+ * @param parent The parent view.
+ */
+ abstract fun inflate(file: File, parent: ViewGroup): IView?
+
+ companion object {
+ @JvmStatic
+ fun newInflater(): ILayoutInflater {
+ return LayoutInflaterImpl()
+ }
+ }
+}
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/INamespace.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/INamespace.kt
new file mode 100644
index 000000000..48b537f79
--- /dev/null
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/INamespace.kt
@@ -0,0 +1,32 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.inflater
+
+/**
+ * An XML namespace declaration.
+ *
+ * @author Akash Yadav
+ */
+interface INamespace {
+
+ /** The prefix of the namespace. */
+ val prefix: String
+
+ /** The namespace URI. */
+ val uri: String
+}
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/IView.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/IView.kt
new file mode 100644
index 000000000..76d2461af
--- /dev/null
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/IView.kt
@@ -0,0 +1,57 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.inflater
+
+import android.view.View
+
+/**
+ * An inflated model for a [View].
+ *
+ * @author Akash Yadav
+ */
+interface IView {
+
+ /** The XML tag. */
+ val tag: String
+
+ /** The fully qualified name of this view. */
+ val name: String
+
+ /** The simple name of this view. Usually same as [tag]. */
+ val simpleName: String
+
+ /** The inflated view. */
+ val view: View
+
+ /** The parent view. */
+ val parent: IViewGroup
+
+ /**
+ * Add and apply the given attribute to this view.
+ *
+ * @param attribute The attribute to apply.
+ */
+ fun addAttribute(attribute: IAttribute)
+
+ /**
+ * Remove the given attribute and update the view accordingly.
+ *
+ * @param attribute The attribute to remove.
+ */
+ fun removeAttribute(attribute: IAttribute)
+}
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/IViewGroup.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/IViewGroup.kt
new file mode 100644
index 000000000..d2254464a
--- /dev/null
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/IViewGroup.kt
@@ -0,0 +1,75 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.inflater
+
+/**
+ * Inflated model for a [ViewGroup][android.view.ViewGroup].
+ *
+ * @author Akash Yadav
+ */
+interface IViewGroup : IView {
+
+ /** The number of children in this view group. */
+ val childCount: Int
+
+ /**
+ * Add the given child.
+ *
+ * @param view The view to add.
+ */
+ fun addChild(view: IView)
+
+ /**
+ * Add the given [view] at the given [index].
+ *
+ * @param index The index at which the child should be added.
+ * @param view The child view to add .
+ */
+ fun addChild(index: Int, view: IView)
+
+ /**
+ * Remove the given child.
+ *
+ * @param view The child to remove.
+ */
+ fun removeChild(view: IView)
+
+ /**
+ * Remove the child at the given index.
+ *
+ * @param index The index of the child to remove.
+ * @return The removed child.
+ */
+ fun removeChild(index: Int): IView
+
+ /**
+ * Get the child at the given index.
+ *
+ * @param index The index of the child.
+ */
+ fun get(index: Int): IView
+
+ /**
+ * Replace the child at the given index.
+ *
+ * @param index The index of the child to replace.
+ * @param view The new view to set.
+ * @return The existing child or `null`.
+ */
+ fun set(index: Int, view: IView): IView
+}
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/InfateException.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/InfateException.kt
new file mode 100644
index 000000000..f027066bf
--- /dev/null
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/InfateException.kt
@@ -0,0 +1,35 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.inflater
+
+/**
+ * Exception thrown by the layout inflater.
+ *
+ * @author Akash Yadav
+ */
+class InfateException : RuntimeException {
+ constructor(message: String?) : super(message)
+ constructor(message: String?, cause: Throwable?) : super(message, cause)
+ constructor(cause: Throwable?) : super(cause)
+ constructor(
+ message: String?,
+ cause: Throwable?,
+ enableSuppression: Boolean,
+ writableStackTrace: Boolean
+ ) : super(message, cause, enableSuppression, writableStackTrace)
+}
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/inflationEvents.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/inflationEvents.kt
new file mode 100644
index 000000000..1dd01f234
--- /dev/null
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/inflationEvents.kt
@@ -0,0 +1,65 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.inflater
+
+/**
+ * A listener which listens for XML layout inflation events.
+ *
+ * @author Akash Yadav
+ */
+fun interface IInflateEventsListener {
+
+ /**
+ * Called when an event occurs. The [event] parameter contains more information about the event.
+ *
+ * @param event The event.
+ */
+ fun onEvent(event: IInflationEvent<*>)
+}
+
+/**
+ * An inflation event.
+ *
+ * @author Akash Yadav
+ */
+abstract class IInflationEvent(val data: T)
+
+/** Indicates start of layout inflation. */
+class InflationStartEvent : IInflationEvent(Unit)
+
+/**
+ * Indicates that the inflation has been finished.
+ *
+ * @property view The inflated view.
+ */
+class InflationFinishEvent(view: IView?) : IInflationEvent(view)
+
+/**
+ * Indicates that the given view has been inflated. The view can be anywhere in the layout.
+ *
+ * @property view The inflated view.
+ */
+class PostInflateViewEvent(view: IView) : IInflationEvent(view)
+
+/**
+ * Indicates that the given [attribute] was added and applied to [view].
+ *
+ * @property view The view to which the attribute was applied.
+ * @property attribute The attribute that was applied.
+ */
+class PostApplyAttributeEvent(view: IView, val attribute: IAttribute) : IInflationEvent(view)
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/AbstractView.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/AbstractView.kt
new file mode 100644
index 000000000..7a276f16c
--- /dev/null
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/AbstractView.kt
@@ -0,0 +1,37 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.inflater.internal
+
+import android.view.View
+import com.itsaky.androidide.inflater.IAttribute
+import com.itsaky.androidide.inflater.IView
+import com.itsaky.androidide.inflater.internal.utils.simpleName
+import com.itsaky.androidide.inflater.internal.utils.tagName
+
+abstract class AbstractView(
+ override val name: String,
+ override val view: View,
+ override val simpleName: String = name.simpleName(),
+ override val tag: String = name.tagName()
+) : IView {
+ protected val attributes = mutableListOf()
+
+ override fun addAttribute(attribute: IAttribute) {
+ AttributeAdapterIndex.getAdapter(name)?.apply { }
+ }
+}
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/AbstractViewGroup.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/AbstractViewGroup.kt
new file mode 100644
index 000000000..bdb76ed2a
--- /dev/null
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/AbstractViewGroup.kt
@@ -0,0 +1,65 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.inflater.internal
+
+import android.view.ViewGroup
+import com.itsaky.androidide.inflater.IView
+import com.itsaky.androidide.inflater.IViewGroup
+
+abstract class AbstractViewGroup(name: String, view: ViewGroup) :
+ AbstractView(name = name, view = view), IViewGroup {
+
+ protected val children = mutableListOf()
+ override val childCount: Int
+ get() = children.size
+
+ // For easy access
+ override val view: ViewGroup
+ get() = super.view as ViewGroup
+
+ override fun addChild(view: IView) {
+ addChild(-1, view)
+ }
+
+ override fun addChild(index: Int, view: IView) {
+ val idx = if (index < 0) childCount else index
+ this.children.add(idx, view)
+ this.view.addView(view.view, idx)
+ }
+
+ override fun removeChild(view: IView) {
+ this.children.remove(view)
+ this.view.removeView(view.view)
+ }
+
+ override fun removeChild(index: Int): IView {
+ val existing = this.children.removeAt(index)
+ this.view.removeViewAt(index)
+ return existing
+ }
+
+ override operator fun get(index: Int): IView {
+ return this.children[index]
+ }
+
+ override operator fun set(index: Int, view: IView): IView {
+ val existing = removeChild(index)
+ addChild(index, view)
+ return existing
+ }
+}
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/AdapterProvider.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/AdapterProvider.kt
new file mode 100644
index 000000000..e0def9484
--- /dev/null
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/AdapterProvider.kt
@@ -0,0 +1,39 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.inflater.internal
+
+import com.itsaky.androidide.inflater.IAttributeAdapter
+import com.itsaky.androidide.utils.ILogger
+
+/**
+ * @author Akash Yadav
+ */
+object AdapterProvider {
+
+ private val log = ILogger.newInstance("AdapterProvider")
+
+ @JvmStatic
+ fun getAdapter(name: String) : IAttributeAdapter? {
+ return try {
+ val klass = AttributeAdapterIndex.getAdapter(name) ?: return null
+ klass.getConstructor().newInstance()
+ } catch (err: Throwable) {
+ null
+ }
+ }
+}
\ No newline at end of file
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/LayoutInflaterImpl.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/LayoutInflaterImpl.kt
new file mode 100644
index 000000000..57e6b9797
--- /dev/null
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/LayoutInflaterImpl.kt
@@ -0,0 +1,106 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.inflater.internal
+
+import android.view.InflateException
+import android.view.ViewGroup
+import com.android.aapt.Resources.SourcePosition
+import com.android.aapt.Resources.XmlElement
+import com.android.aapt.Resources.XmlNode.NodeCase
+import com.android.aapt.Resources.XmlNode.NodeCase.ELEMENT
+import com.android.aaptcompiler.AaptResourceType.LAYOUT
+import com.android.aaptcompiler.BlameLogger
+import com.android.aaptcompiler.ResourceFile
+import com.android.aaptcompiler.ResourceName
+import com.android.aaptcompiler.XmlProcessor
+import com.android.aaptcompiler.extractPathData
+import com.itsaky.androidide.aapt.logging.IDELogger
+import com.itsaky.androidide.inflater.IInflateEventsListener
+import com.itsaky.androidide.inflater.ILayoutInflater
+import com.itsaky.androidide.inflater.IView
+import com.itsaky.androidide.inflater.InflationFinishEvent
+import com.itsaky.androidide.inflater.InflationStartEvent
+import com.itsaky.androidide.projects.ProjectManager
+import com.itsaky.androidide.projects.api.AndroidModule
+import java.io.File
+
+/**
+ * Default implementation of [ILayoutInflater].
+ *
+ * @author Akash Yadav
+ */
+internal class LayoutInflaterImpl : ILayoutInflater() {
+
+ override var inflationEventListener: IInflateEventsListener? = null
+
+ override fun inflate(file: File, parent: ViewGroup): IView? {
+ inflationEventListener?.onEvent(InflationStartEvent())
+ return doInflate(file, parent).apply {
+ inflationEventListener?.onEvent(InflationFinishEvent(this))
+ }
+ }
+
+ private fun doInflate(file: File, parent: ViewGroup): IView? {
+ val pathData = extractPathData(file)
+ if (pathData.type != LAYOUT) {
+ throw InflateException("File is not a layout file.")
+ }
+
+ val module =
+ ProjectManager.findModuleForFile(file) as? AndroidModule
+ ?: throw InflateException(
+ "Cannot find module for given file or the module is not an Android module"
+ )
+ val resFile =
+ ResourceFile(
+ ResourceName(module.packageName, pathData.type!!, pathData.name),
+ pathData.config,
+ pathData.source,
+ ResourceFile.Type.ProtoXml
+ )
+
+ val processor = XmlProcessor(pathData.source, BlameLogger(IDELogger))
+ processor.process(resFile, file.inputStream())
+
+ return doInflate(processor, parent)
+ }
+
+ private fun doInflate(processor: XmlProcessor, parent: ViewGroup): IView? {
+ // TODO(itsaky) : Add test for multiple view as root layout
+ // The inflater should fail in such cases
+ val (file, node) =
+ processor.xmlResources.find { it.file == processor.primaryFile }
+ ?: throw InflateException("Unable to find primary XML resource from XmlProcessor")
+
+ if (node.nodeCase != ELEMENT) {
+ throw InflateException("Found ${node.nodeCase} but $ELEMENT was expected at ${node.source.lineCol()}")
+ }
+
+ val element = node.element
+ val view = onCreateView(element)
+ TODO("Not yet implemented")
+ }
+
+ private fun onCreateView(element: XmlElement): IView {
+ TODO("Not yet implemented")
+ }
+
+ private fun SourcePosition.lineCol() : String {
+ return "line $lineNumber and column $columnNumber"
+ }
+}
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/adapters/ViewAttrAdapter.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/adapters/ViewAttrAdapter.kt
new file mode 100644
index 000000000..c9a94611f
--- /dev/null
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/adapters/ViewAttrAdapter.kt
@@ -0,0 +1,37 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.inflater.internal.adapters
+
+import android.view.View
+import com.itsaky.androidide.annotations.inflater.AttributeAdapter
+import com.itsaky.androidide.inflater.IAttribute
+import com.itsaky.androidide.inflater.IAttributeAdapter
+import com.itsaky.androidide.inflater.IView
+
+/**
+ * Attribute adapter for [View].
+ *
+ * @author Akash Yadav
+ */
+@AttributeAdapter(forClass = View::class)
+open class ViewAttrAdapter : IAttributeAdapter() {
+
+ override fun apply(view: IView, attribute: IAttribute) {
+ TODO("Not yet implemented")
+ }
+}
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/adapters/ViewGroupAttrAdapter.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/adapters/ViewGroupAttrAdapter.kt
new file mode 100644
index 000000000..aaeec5775
--- /dev/null
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/adapters/ViewGroupAttrAdapter.kt
@@ -0,0 +1,33 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.inflater.internal.adapters
+
+import android.view.ViewGroup
+import com.itsaky.androidide.annotations.inflater.AttributeAdapter
+import com.itsaky.androidide.inflater.IAttribute
+import com.itsaky.androidide.inflater.IAttributeAdapter
+import com.itsaky.androidide.inflater.IView
+
+/** @author Akash Yadav */
+@AttributeAdapter(ViewGroup::class)
+class ViewGroupAttrAdapter : IAttributeAdapter() {
+
+ override fun apply(view: IView, attribute: IAttribute) {
+ TODO("Not yet implemented")
+ }
+}
diff --git a/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/ViewUtils.kt b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/ViewUtils.kt
new file mode 100644
index 000000000..a0ca740c7
--- /dev/null
+++ b/xml-inflater/src/main/java/com/itsaky/androidide/inflater/internal/utils/ViewUtils.kt
@@ -0,0 +1,39 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.inflater.internal.utils
+
+import com.android.SdkConstants.ANDROID_PKG
+
+// android.$it.$widget
+val androidPackages = listOf("view", "widget", "gesture")
+
+fun String.simpleName(): String {
+ return if (contains('.')) {
+ substringAfterLast(delimiter = '.')
+ } else this
+}
+
+fun String.tagName(): String {
+ val split = split('.')
+ if (split.size == 3) {
+ if (ANDROID_PKG == split[0] && androidPackages.find { it == split[1] } != null) {
+ return split[2]
+ }
+ }
+ return this
+}
diff --git a/xml-inflater/src/test/java/com/itsaky/androidide/inflater/ExampleUnitTest.kt b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/ExampleUnitTest.kt
new file mode 100644
index 000000000..bcb187e38
--- /dev/null
+++ b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/ExampleUnitTest.kt
@@ -0,0 +1,34 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.inflater
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file