From 54a6856753818fb7f7734e4c19b00a01cda8d6a9 Mon Sep 17 00:00:00 2001 From: Mykola Dzyuba <7852483+mdzyuba@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:06:00 -0800 Subject: [PATCH 1/3] Add Spotless Maven Plugin with Eclipse JDT formatter for AOSP code style Configuration: - Added Spotless Maven Plugin version 2.43.0 - Configured Eclipse JDT formatter with AOSP code style - Added settings/eclipse-aosp-style.xml with AOSP formatting rules - Import ordering: android, androidx, com.android, dalvik, libcore, com, org, java, javax - Removes unused imports automatically - Bound 'check' goal to 'verify' phase Formatting Rules: - 4-space indentation - 100 character line length - Opening braces on same line - Preserved line breaks in method chains (join_wrapped_lines = false) - AOSP import ordering with blank lines between groups Additional Changes: - Expanded README.md with build instructions, usage examples, and formatting documentation - Reformatted Java source files with AOSP style Verification: - Ran 'mvn spotless:check' to verify configuration - Confirmed formatting preserves preferred method chain style Usage: - mvn spotless:check - Verify code formatting - mvn spotless:apply - Auto-format code to match the style --- README.md | 117 ++++++- pom.xml | 28 +- settings/eclipse-aosp-style.xml | 302 ++++++++++++++++++ .../AndroidXmlOutputter.java | 88 ++--- .../bytehamster/androidxmlformatter/Main.java | 6 +- 5 files changed, 496 insertions(+), 45 deletions(-) create mode 100644 settings/eclipse-aosp-style.xml diff --git a/README.md b/README.md index c14e5a5..06b318d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,118 @@ # android-xml-formatter -Formats xml files according to Android Studio's default formatting rules. By default, also re-orders the attributes to specify the `android:id`, `android:layout_width` and `android:layout_height` first. This can be turned off with a command line option. +Formats XML files according to Android Studio's default formatting rules. By default, also re-orders the attributes to specify the `android:id`, `android:layout_width` and `android:layout_height` first. This can be turned off with a command line option. -To view available command line options, execute `java -jar android-xml-formatter.jar --help`. +## Requirements -Can be used as a style check on a CI server by executing and then printing the diff. +- Java 8 or higher +- Maven 3.6 or higher + +## Building + +### Build the project + +```bash +mvn clean package +``` + +This creates an executable JAR file with all dependencies at: +``` +target/android-xml-formatter-1.0-SNAPSHOT-jar-with-dependencies.jar +``` + +### Run tests and verification + +```bash +mvn verify +``` + +This runs all tests and code format checks. + +## Usage + +To view available command line options: + +```bash +java -jar target/android-xml-formatter-1.0-SNAPSHOT-jar-with-dependencies.jar --help +``` + +### Command Line Options + +| Option | Description | +|--------|-------------| +| `--indention ` | Set indentation spaces (default: 4) | +| `--attribute-indention ` | Set attribute indentation spaces (default: 4) | +| `--attribute-order ` | Comma-separated attribute order (default: `id,layout_width,layout_height`) | +| `--attribute-sort` | Sort attributes alphabetically | +| `--namespace-order ` | Comma-separated namespace order (default: `android`) | +| `--namespace-sort` | Sort namespaces alphabetically | + +### Example + +Format an XML file: + +```bash +java -jar target/android-xml-formatter-1.0-SNAPSHOT-jar-with-dependencies.jar path/to/layout.xml +``` + +Format multiple files: + +```bash +java -jar target/android-xml-formatter-1.0-SNAPSHOT-jar-with-dependencies.jar file1.xml file2.xml file3.xml +``` + +## Code Formatting + +This project uses [Spotless Maven Plugin](https://github.com/diffplug/spotless/tree/main/plugin-maven) with Eclipse JDT formatter for Java code formatting, configured to match the [AOSP (Android Open Source Project) code style](https://source.android.com/docs/setup/contribute/code-style). + +### Configuration + +- **Formatter**: Eclipse JDT +- **Style**: AOSP (Android Open Source Project) - see `settings/eclipse-aosp-style.xml` +- **Import Order**: AOSP style (android, androidx, com.android, dalvik, libcore, com, org, java, javax) +- **Check Phase**: Runs automatically during `mvn verify` + +### Check code formatting + +To verify that all Java code follows the formatting rules: + +```bash +mvn spotless:check +``` + +### Apply code formatting + +To automatically format all Java code: + +```bash +mvn spotless:apply +``` + +### Formatting Rules + +The AOSP style enforces: +- 4-space indentation +- 100 character line length limit +- Opening braces on same line +- AOSP import ordering with blank lines between groups +- Proper spacing around operators and keywords +- Preserved line breaks in method chains (won't join manually wrapped lines) + +> **Note:** Always run `mvn spotless:apply` before committing to ensure consistent formatting. + +## CI Integration + +Can be used as a style check on a CI server by executing the formatter and then printing the diff: + +```bash +# Check XML formatting +java -jar android-xml-formatter.jar *.xml +git diff --exit-code + +# Check Java code formatting +mvn spotless:check +``` + +## License + +See [LICENSE](LICENSE) file for details. diff --git a/pom.xml b/pom.xml index 1f124ef..2b712b9 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,32 @@ + + com.diffplug.spotless + spotless-maven-plugin + 2.43.0 + + + + + ${project.basedir}/settings/eclipse-aosp-style.xml + + + + android,androidx,com.android,dalvik,libcore,com,org,java,javax, + + + + + + + + check + + verify + + + maven-assembly-plugin 3.1.1 @@ -59,4 +85,4 @@ - \ No newline at end of file + diff --git a/settings/eclipse-aosp-style.xml b/settings/eclipse-aosp-style.xml new file mode 100644 index 0000000..29426f7 --- /dev/null +++ b/settings/eclipse-aosp-style.xml @@ -0,0 +1,302 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/bytehamster/androidxmlformatter/AndroidXmlOutputter.java b/src/main/java/com/bytehamster/androidxmlformatter/AndroidXmlOutputter.java index 8ffba59..1d2ad14 100644 --- a/src/main/java/com/bytehamster/androidxmlformatter/AndroidXmlOutputter.java +++ b/src/main/java/com/bytehamster/androidxmlformatter/AndroidXmlOutputter.java @@ -1,11 +1,5 @@ package com.bytehamster.androidxmlformatter; -import java.io.IOException; -import java.io.Writer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import org.apache.commons.lang3.StringUtils; import org.jdom.Attribute; import org.jdom.CDATA; @@ -19,6 +13,12 @@ import org.jdom.output.Format; import org.jdom.output.XMLOutputter; +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + public class AndroidXmlOutputter extends XMLOutputter { final String[] namespaceOrder; final String[] attributeNameOrder; @@ -27,8 +27,8 @@ public class AndroidXmlOutputter extends XMLOutputter { final boolean alphabeticalNamespaces; public AndroidXmlOutputter(int indention, int attributeIndention, - String[] namespaceOrder, String[] attributeNameOrder, - boolean alphabeticalAttributes, boolean alphabeticalNamespaces) { + String[] namespaceOrder, String[] attributeNameOrder, + boolean alphabeticalAttributes, boolean alphabeticalNamespaces) { this.attributeIndention = attributeIndention; this.namespaceOrder = namespaceOrder; this.attributeNameOrder = attributeNameOrder; @@ -51,7 +51,8 @@ static private int elementDepth(Element element) { return result; } - private void printNamespace(Writer out, Namespace ns, XMLOutputter.NamespaceStack namespaces) throws IOException { + private void printNamespace(Writer out, Namespace ns, XMLOutputter.NamespaceStack namespaces) + throws IOException { String prefix = ns.getPrefix(); String uri = ns.getURI(); if (!uri.equals(namespaces.getURI(prefix))) { @@ -67,7 +68,9 @@ private void printNamespace(Writer out, Namespace ns, XMLOutputter.NamespaceStac namespaces.push(ns); } } - private void printElementNamespace(Writer out, Element element, XMLOutputter.NamespaceStack namespaces) throws IOException { + + private void printElementNamespace(Writer out, Element element, + XMLOutputter.NamespaceStack namespaces) throws IOException { Namespace ns = element.getNamespace(); if (ns != Namespace.XML_NAMESPACE) { if (ns != Namespace.NO_NAMESPACE || namespaces.getURI("") != null) { @@ -77,11 +80,12 @@ private void printElementNamespace(Writer out, Element element, XMLOutputter.Nam } } - private void printAdditionalNamespaces(Writer out, Element element, XMLOutputter.NamespaceStack namespaces) throws IOException { + private void printAdditionalNamespaces(Writer out, Element element, + XMLOutputter.NamespaceStack namespaces) throws IOException { List list = element.getAdditionalNamespaces(); if (list != null) { - for(int i = 0; i < list.size(); ++i) { - Namespace additional = (Namespace)list.get(i); + for (int i = 0; i < list.size(); ++i) { + Namespace additional = (Namespace) list.get(i); if (attributeIndention > 0) { newline(out); indent(out, elementDepth(element) - 1); @@ -105,7 +109,7 @@ private int skipTrailingWhite(List content, int start) { if (this.currentFormat.getTextMode() == Format.TextMode.TRIM_FULL_WHITE || this.currentFormat.getTextMode() == Format.TextMode.NORMALIZE || this.currentFormat.getTextMode() == Format.TextMode.TRIM) { - while(index >= 0 && this.isAllWhitespace(content.get(index - 1))) { + while (index >= 0 && this.isAllWhitespace(content.get(index - 1))) { --index; } } @@ -116,7 +120,7 @@ private int skipTrailingWhite(List content, int start) { private boolean isAllWhitespace(Object obj) { String str = null; if (obj instanceof String) { - str = (String)obj; + str = (String) obj; } else { if (!(obj instanceof Text)) { if (obj instanceof EntityRef) { @@ -126,10 +130,10 @@ private boolean isAllWhitespace(Object obj) { return false; } - str = ((Text)obj).getText(); + str = ((Text) obj).getText(); } - for(int i = 0; i < str.length(); ++i) { + for (int i = 0; i < str.length(); ++i) { if (!Verifier.isXMLWhitespace(str.charAt(i))) { return false; } @@ -146,7 +150,7 @@ private int skipLeadingWhite(List content, int start) { int index = start; int size = content.size(); if (true) { - while(index < size) { + while (index < size) { if (!this.isAllWhitespace(content.get(index))) { return index; } @@ -166,7 +170,7 @@ private static int nextNonText(List content, int start) { int index = start; int size; - for(size = content.size(); index < size; ++index) { + for (size = content.size(); index < size; ++index) { Object node = content.get(index); if (!(node instanceof Text) && !(node instanceof EntityRef)) { return index; @@ -176,11 +180,12 @@ private static int nextNonText(List content, int start) { return size; } - private void printContentRange(Writer out, List content, int start, int end, int level, XMLOutputter.NamespaceStack namespaces) throws IOException { + private void printContentRange(Writer out, List content, int start, int end, int level, + XMLOutputter.NamespaceStack namespaces) throws IOException { int index = start; - while(true) { - while(index < end) { + while (true) { + while (index < end) { boolean firstNode = index == start; Object next = content.get(index); if (!(next instanceof Text) && !(next instanceof EntityRef)) { @@ -190,11 +195,11 @@ private void printContentRange(Writer out, List content, int start, int end, int this.indent(out, level); if (next instanceof Comment) { - this.printComment(out, (Comment)next); + this.printComment(out, (Comment) next); } else if (next instanceof Element) { - this.printElement(out, (Element)next, level, namespaces); + this.printElement(out, (Element) next, level, namespaces); } else if (next instanceof ProcessingInstruction) { - this.printProcessingInstruction(out, (ProcessingInstruction)next); + this.printProcessingInstruction(out, (ProcessingInstruction) next); } ++index; @@ -233,30 +238,32 @@ private void printTextRange(Writer out, List content, int start, int end) throws if (start < size) { end = this.skipTrailingWhite(content, end); - for(int i = start; i < end; ++i) { + for (int i = start; i < end; ++i) { Object node = content.get(i); String next; if (node instanceof Text) { - next = ((Text)node).getText(); + next = ((Text) node).getText(); } else { if (!(node instanceof EntityRef)) { - throw new IllegalStateException("Should see only CDATA, Text, or EntityRef"); + throw new IllegalStateException( + "Should see only CDATA, Text, or EntityRef"); } - next = "&" + ((EntityRef)node).getValue() + ";"; + next = "&" + ((EntityRef) node).getValue() + ";"; } if (next != null && !"".equals(next)) { - if (previous != null && (this.currentFormat.getTextMode() == Format.TextMode.NORMALIZE - || this.currentFormat.getTextMode() == Format.TextMode.TRIM) + if (previous != null + && (this.currentFormat.getTextMode() == Format.TextMode.NORMALIZE + || this.currentFormat.getTextMode() == Format.TextMode.TRIM) && (this.endsWithWhite(previous) || this.startsWithWhite(next))) { out.write(" "); } if (node instanceof CDATA) { - this.printCDATA(out, (CDATA)node); + this.printCDATA(out, (CDATA) node); } else if (node instanceof EntityRef) { - this.printEntityRef(out, (EntityRef)node); + this.printEntityRef(out, (EntityRef) node); } else { this.printString(out, next); } @@ -273,11 +280,13 @@ private boolean startsWithWhite(String str) { } private boolean endsWithWhite(String str) { - return str != null && str.length() > 0 && Verifier.isXMLWhitespace(str.charAt(str.length() - 1)); + return str != null && str.length() > 0 + && Verifier.isXMLWhitespace(str.charAt(str.length() - 1)); } @Override - protected void printElement(Writer out, Element element, int level, NamespaceStack namespaces) throws IOException { + protected void printElement(Writer out, Element element, int level, NamespaceStack namespaces) + throws IOException { List attributes = element.getAttributes(); List content = element.getContent(); String space = null; @@ -322,7 +331,7 @@ protected void printElement(Writer out, Element element, int level, NamespaceSta out.write(">"); } - while(namespaces.size() > previouslyDeclaredNamespaces) { + while (namespaces.size() > previouslyDeclaredNamespaces) { namespaces.pop(); } @@ -335,13 +344,14 @@ private void newline(Writer out) throws IOException { } private void indent(Writer out, int level) throws IOException { - for(int i = 0; i < level; ++i) { + for (int i = 0; i < level; ++i) { out.write(getFormat().getIndent()); } } @Override - protected void printAttributes(Writer writer, List attribs, Element parent, NamespaceStack ns) throws IOException { + protected void printAttributes(Writer writer, List attribs, Element parent, NamespaceStack ns) + throws IOException { List attributes = new ArrayList<>(); for (Object attribObj : attribs) { attributes.add((Attribute) attribObj); @@ -413,4 +423,4 @@ private void printQualifiedName(Writer out, Element e) throws IOException { } } -} \ No newline at end of file +} diff --git a/src/main/java/com/bytehamster/androidxmlformatter/Main.java b/src/main/java/com/bytehamster/androidxmlformatter/Main.java index 398ea2b..9d4cbad 100644 --- a/src/main/java/com/bytehamster/androidxmlformatter/Main.java +++ b/src/main/java/com/bytehamster/androidxmlformatter/Main.java @@ -63,13 +63,15 @@ public static void main(String[] args) throws Exception { Integer.parseInt(cmd.getOptionValue("indention", "4")), Integer.parseInt(cmd.getOptionValue("attribute-indention", "4")), cmd.getOptionValue("namespace-order", "android").split(","), - cmd.getOptionValue("attribute-order", "id,layout_width,layout_height").split(","), + cmd.getOptionValue("attribute-order", "id,layout_width,layout_height") + .split(","), cmd.hasOption("attribute-sort"), cmd.hasOption("namespace-sort")); ByteArrayOutputStream stream = new ByteArrayOutputStream(); outputter.output(doc, stream); byte[] content = stream.toByteArray(); - new FileOutputStream(filename).write(content, 0, content.length - 2); // Strip double line break + new FileOutputStream(filename).write(content, 0, content.length - 2); // Strip double + // line break } } } From 44e80260f87b8feba0368e09c88b8664a0aeaca1 Mon Sep 17 00:00:00 2001 From: ByteHamster Date: Wed, 14 Jan 2026 08:47:06 +0100 Subject: [PATCH 2/3] Tweak readme --- README.md | 91 ++++++++++++++----------------------------------------- 1 file changed, 22 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index 06b318d..31f6fac 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # android-xml-formatter +[![GitHub check runs](https://img.shields.io/github/check-runs/ByteHamster/android-xml-formatter/develop)](https://github.com/ByteHamster/android-xml-formatter/actions/workflows/checks.yml?query=branch%3Amain) +[![License: GPL v3](https://img.shields.io/github/license/ByteHamster/android-xml-formatter)](https://www.gnu.org/licenses/gpl-3.0) +[![GitHub Release](https://img.shields.io/github/v/release/ByteHamster/android-xml-formatter)](https://github.com/ByteHamster/android-xml-formatter/releases) + Formats XML files according to Android Studio's default formatting rules. By default, also re-orders the attributes to specify the `android:id`, `android:layout_width` and `android:layout_height` first. This can be turned off with a command line option. ## Requirements @@ -9,24 +13,9 @@ Formats XML files according to Android Studio's default formatting rules. By def ## Building -### Build the project - -```bash -mvn clean package -``` - -This creates an executable JAR file with all dependencies at: -``` -target/android-xml-formatter-1.0-SNAPSHOT-jar-with-dependencies.jar -``` - -### Run tests and verification - -```bash -mvn verify -``` - -This runs all tests and code format checks. +- `mvn clean package` to create an executable JAR file with all dependencies + at `target/android-xml-formatter-1.0-SNAPSHOT-jar-with-dependencies.jar`. +- `mvn verify` runs all tests and code format checks. ## Usage @@ -36,16 +25,14 @@ To view available command line options: java -jar target/android-xml-formatter-1.0-SNAPSHOT-jar-with-dependencies.jar --help ``` -### Command Line Options - -| Option | Description | -|--------|-------------| -| `--indention ` | Set indentation spaces (default: 4) | -| `--attribute-indention ` | Set attribute indentation spaces (default: 4) | -| `--attribute-order ` | Comma-separated attribute order (default: `id,layout_width,layout_height`) | -| `--attribute-sort` | Sort attributes alphabetically | -| `--namespace-order ` | Comma-separated namespace order (default: `android`) | -| `--namespace-sort` | Sort namespaces alphabetically | +| Option | Description | +|-----------------------------|----------------------------------------------------------------------------| +| `--indention ` | Set indentation spaces (default: 4) | +| `--attribute-indention ` | Set attribute indentation spaces (default: 4) | +| `--attribute-order ` | Comma-separated attribute order (default: `id,layout_width,layout_height`) | +| `--attribute-sort` | Sort attributes alphabetically | +| `--namespace-order ` | Comma-separated namespace order (default: `android`) | +| `--namespace-sort` | Sort namespaces alphabetically | ### Example @@ -61,56 +48,22 @@ Format multiple files: java -jar target/android-xml-formatter-1.0-SNAPSHOT-jar-with-dependencies.jar file1.xml file2.xml file3.xml ``` -## Code Formatting - -This project uses [Spotless Maven Plugin](https://github.com/diffplug/spotless/tree/main/plugin-maven) with Eclipse JDT formatter for Java code formatting, configured to match the [AOSP (Android Open Source Project) code style](https://source.android.com/docs/setup/contribute/code-style). - -### Configuration - -- **Formatter**: Eclipse JDT -- **Style**: AOSP (Android Open Source Project) - see `settings/eclipse-aosp-style.xml` -- **Import Order**: AOSP style (android, androidx, com.android, dalvik, libcore, com, org, java, javax) -- **Check Phase**: Runs automatically during `mvn verify` - -### Check code formatting - -To verify that all Java code follows the formatting rules: - -```bash -mvn spotless:check -``` - -### Apply code formatting - -To automatically format all Java code: +## Contributing -```bash -mvn spotless:apply -``` +This project uses [Spotless Maven Plugin](https://github.com/diffplug/spotless/tree/main/plugin-maven) +with Eclipse JDT formatter for Java code formatting, configured to match the +[AOSP (Android Open Source Project) code style](https://source.android.com/docs/setup/contribute/code-style). -### Formatting Rules - -The AOSP style enforces: -- 4-space indentation -- 100 character line length limit -- Opening braces on same line -- AOSP import ordering with blank lines between groups -- Proper spacing around operators and keywords -- Preserved line breaks in method chains (won't join manually wrapped lines) - -> **Note:** Always run `mvn spotless:apply` before committing to ensure consistent formatting. +To verify that all Java code follows the formatting rules, run `mvn spotless:check`. +To automatically format all Java code, run `mvn spotless:apply`. ## CI Integration -Can be used as a style check on a CI server by executing the formatter and then printing the diff: +This project can be used as a style check on a CI server by executing the formatter and then printing the diff: ```bash -# Check XML formatting java -jar android-xml-formatter.jar *.xml git diff --exit-code - -# Check Java code formatting -mvn spotless:check ``` ## License From a0b2efd25434efaa590410eed08034d6f4cf1036 Mon Sep 17 00:00:00 2001 From: ByteHamster Date: Wed, 14 Jan 2026 08:49:13 +0100 Subject: [PATCH 3/3] Fix copy-paste in shields url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 31f6fac..5a60f67 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # android-xml-formatter -[![GitHub check runs](https://img.shields.io/github/check-runs/ByteHamster/android-xml-formatter/develop)](https://github.com/ByteHamster/android-xml-formatter/actions/workflows/checks.yml?query=branch%3Amain) +[![GitHub check runs](https://img.shields.io/github/check-runs/ByteHamster/android-xml-formatter/main)](https://github.com/ByteHamster/android-xml-formatter/actions/workflows/checks.yml?query=branch%3Amain) [![License: GPL v3](https://img.shields.io/github/license/ByteHamster/android-xml-formatter)](https://www.gnu.org/licenses/gpl-3.0) [![GitHub Release](https://img.shields.io/github/v/release/ByteHamster/android-xml-formatter)](https://github.com/ByteHamster/android-xml-formatter/releases)