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