├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── arsceditor
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ ├── mrikso
│ │ └── arsceditor
│ │ │ ├── intrefaces
│ │ │ └── TableChangedListener.java
│ │ │ ├── App.java
│ │ │ ├── valueeditor
│ │ │ ├── ValueType.java
│ │ │ ├── ArscWriter.java
│ │ │ ├── ValueHelper.java
│ │ │ ├── FormatValue.java
│ │ │ └── Converter.java
│ │ │ ├── gui
│ │ │ ├── tree
│ │ │ │ ├── JNode.java
│ │ │ │ ├── ArscTreeCellRenderer.java
│ │ │ │ ├── JTableTreeNode.java
│ │ │ │ ├── ArscNode.java
│ │ │ │ ├── ResourceEntry.java
│ │ │ │ ├── ResourceDirectory.java
│ │ │ │ ├── ArscTableView.java
│ │ │ │ └── ArscTreeView.java
│ │ │ ├── treetable
│ │ │ │ ├── TreeTableModel.java
│ │ │ │ ├── TreeTableModelAdapter.java
│ │ │ │ ├── AbstractCellEditor.java
│ │ │ │ ├── JTreeTable.java
│ │ │ │ ├── TreeTableCellEditor.java
│ │ │ │ ├── AbstractTreeTableModel.java
│ │ │ │ └── DynamicTreeTableModel.java
│ │ │ ├── dialogs
│ │ │ │ ├── ErrorDialog.java
│ │ │ │ ├── AboutDialog.java
│ │ │ │ ├── PackageEditDialog.java
│ │ │ │ └── ResourceEditDialog.java
│ │ │ └── MainWindow.java
│ │ │ ├── model
│ │ │ ├── ResId.java
│ │ │ ├── ResourceTypeTableModel.java
│ │ │ └── ResourceModel.java
│ │ │ └── util
│ │ │ ├── VersionUtils.java
│ │ │ ├── HexUtil.java
│ │ │ ├── AttrNameHelper.java
│ │ │ ├── DecodeGenUtils.java
│ │ │ └── ResourceHelper2.java
│ │ └── google
│ │ └── devrel
│ │ └── gmscore
│ │ └── tools
│ │ └── apk
│ │ └── arsc
│ │ ├── XmlNamespaceEndChunk.java
│ │ ├── XmlNamespaceStartChunk.java
│ │ ├── SerializableResource.java
│ │ ├── XmlChunk.java
│ │ ├── UnknownChunk.java
│ │ ├── XmlNamespaceChunk.java
│ │ ├── ChunkWithChunks.java
│ │ ├── XmlEndElementChunk.java
│ │ ├── XmlCdataChunk.java
│ │ ├── BinaryResourceFile.java
│ │ ├── PackageUtils.java
│ │ ├── XmlResourceMapChunk.java
│ │ ├── ResourceTableChunk.java
│ │ ├── XmlNodeChunk.java
│ │ ├── TypeSpecChunk.java
│ │ ├── BinaryResourceIdentifier.java
│ │ ├── LibraryChunk.java
│ │ ├── XmlAttribute.java
│ │ ├── BinaryResourceValue.java
│ │ ├── XmlStartElementChunk.java
│ │ └── BinaryResourceString.java
└── build.gradle
├── .gitignore
├── README.md
├── gradlew.bat
└── gradlew
/settings.gradle:
--------------------------------------------------------------------------------
1 | include 'arsceditor'
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MrIkso/ArscEditor/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/mrikso/arsceditor/intrefaces/TableChangedListener.java:
--------------------------------------------------------------------------------
1 | package com.mrikso.arsceditor.intrefaces;
2 |
3 | public interface TableChangedListener {
4 |
5 | void tableChanged();
6 | }
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Gradle template
2 | .gradle
3 | /build/
4 | /.idea
5 | /out/
6 |
7 | # Ignore Gradle GUI config
8 | gradle-app.setting
9 |
10 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
11 | !gradle-wrapper.jar
12 |
13 | # Cache of project
14 | .gradletasknamecache
15 |
16 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
17 | # gradle/wrapper/gradle-wrapper.properties
18 |
19 | # Project exclude paths
20 | /arsceditor/build/
21 | /arsceditor/build/classes/java/main/
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/mrikso/arsceditor/App.java:
--------------------------------------------------------------------------------
1 | package com.mrikso.arsceditor;
2 |
3 | import com.mrikso.arsceditor.gui.MainWindow;
4 |
5 | import javax.swing.*;
6 |
7 | public class App {
8 |
9 | public static void main(String[] args) {
10 | try {
11 | // UIManager.setDefaultLookAndFeelDecorated
12 | UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
13 | SwingUtilities.invokeLater(MainWindow::new);
14 | } catch (Exception e) {
15 | e.printStackTrace();
16 | System.exit(1);
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/mrikso/arsceditor/valueeditor/ValueType.java:
--------------------------------------------------------------------------------
1 | package com.mrikso.arsceditor.valueeditor;
2 |
3 | public enum ValueType {
4 | TYPE_STRING(0),
5 | TYPE_REFERENCE(1),
6 | TYPE_ATTRIBUTE(2),
7 | TYPE_COLOR(3),
8 | TYPE_INT_DEC(4),
9 | TYPE_INT_HEX(5),
10 | TYPE_BOOLEAN(6),
11 | TYPE_FLOAT(7),
12 | TYPE_DIMENSION(8),
13 | TYPE_FRACTION(9),
14 | TYPE_DYNAMIC_REFERENCE(10);
15 | private final int value;
16 |
17 | ValueType(int value) {
18 | this.value = value;
19 | }
20 |
21 | public int getValue() {
22 | return value;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/mrikso/arsceditor/gui/tree/JNode.java:
--------------------------------------------------------------------------------
1 | package com.mrikso.arsceditor.gui.tree;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 |
5 | import javax.swing.*;
6 | import javax.swing.tree.DefaultMutableTreeNode;
7 |
8 | public abstract class JNode extends DefaultMutableTreeNode {
9 |
10 | public JNode(Object name, boolean allowsChildren) {
11 | super(name, allowsChildren);
12 | }
13 |
14 | public JNode() {
15 | super();
16 | }
17 |
18 | public JNode(Object name) {
19 | super(name);
20 | }
21 |
22 | public abstract Icon getIcon();
23 |
24 | @NotNull
25 | public abstract String getName();
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/mrikso/arsceditor/valueeditor/ArscWriter.java:
--------------------------------------------------------------------------------
1 | package com.mrikso.arsceditor.valueeditor;
2 |
3 | import com.google.common.io.Files;
4 | import com.google.devrel.gmscore.tools.apk.arsc.ResourceTableChunk;
5 |
6 | import java.io.File;
7 | import java.io.IOException;
8 |
9 | public class ArscWriter {
10 |
11 | private final ResourceTableChunk resourceTableChunk;
12 | private final File out;
13 |
14 | public ArscWriter(ResourceTableChunk chunk, File out){
15 | this.resourceTableChunk = chunk;
16 | this.out = out;
17 | }
18 |
19 | public void write() throws IOException {
20 | byte[] bytes = resourceTableChunk.toByteArray();
21 | Files.write(bytes, out);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/arsceditor/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'application'
3 | id 'com.github.johnrengelman.shadow' version '6.1.0'
4 | }
5 |
6 | repositories {
7 | mavenCentral()
8 | }
9 |
10 | dependencies {
11 | implementation 'com.google.guava:guava:30.1.1-jre'
12 | compileOnly 'org.jetbrains:annotations:20.1.0'
13 |
14 | testCompileOnly 'org.jetbrains:annotations:20.1.0'
15 | testCompile group: 'junit', name: 'junit', version: '4.12'
16 | }
17 |
18 | application {
19 | applicationName = 'arsceditor'
20 | mainClassName = 'com.mrikso.arsceditor.App'
21 | }
22 |
23 | jar{
24 | manifest {
25 | attributes(
26 | "Main-Class": mainClassName,
27 | "Class-Path": configurations.runtimeClasspath.collect { it.getName() }.join(' ')
28 | )
29 | }
30 | }
31 |
32 | shadowJar {
33 | mergeServiceFiles()
34 | }
35 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceEndChunk.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.devrel.gmscore.tools.apk.arsc;
18 |
19 | import javax.annotation.Nullable;
20 | import java.nio.ByteBuffer;
21 |
22 | /** Represents the ending tag of a namespace in an XML document. */
23 | public final class XmlNamespaceEndChunk extends XmlNamespaceChunk {
24 |
25 | protected XmlNamespaceEndChunk(ByteBuffer buffer, @Nullable Chunk parent) {
26 | super(buffer, parent);
27 | }
28 |
29 | @Override
30 | protected Type getType() {
31 | return Type.XML_END_NAMESPACE;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlNamespaceStartChunk.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.devrel.gmscore.tools.apk.arsc;
18 |
19 | import javax.annotation.Nullable;
20 | import java.nio.ByteBuffer;
21 |
22 | /** Represents the starting tag of a namespace in an XML document. */
23 | public final class XmlNamespaceStartChunk extends XmlNamespaceChunk {
24 |
25 | protected XmlNamespaceStartChunk(ByteBuffer buffer, @Nullable Chunk parent) {
26 | super(buffer, parent);
27 | }
28 |
29 | @Override
30 | protected Type getType() {
31 | return Type.XML_START_NAMESPACE;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/mrikso/arsceditor/valueeditor/ValueHelper.java:
--------------------------------------------------------------------------------
1 | package com.mrikso.arsceditor.valueeditor;
2 |
3 | import com.google.devrel.gmscore.tools.apk.arsc.ResourceTableChunk;
4 | import com.google.devrel.gmscore.tools.apk.arsc.StringPoolChunk;
5 | import com.google.devrel.gmscore.tools.apk.arsc.TypeChunk;
6 |
7 | public class ValueHelper {
8 | private static StringPoolChunk stringPoolChunk;
9 |
10 | private static ResourceTableChunk resourceTableChunk;
11 | private static TypeChunk typeChunk;
12 |
13 | public static StringPoolChunk getStringPoolChunk() {
14 | return stringPoolChunk;
15 | }
16 |
17 | public static void setStringPoolChunk(StringPoolChunk stringPoolChunk) {
18 | ValueHelper.stringPoolChunk = stringPoolChunk;
19 | }
20 |
21 | public static ResourceTableChunk getResourceTableChunk() {
22 | return resourceTableChunk;
23 | }
24 |
25 | public static void setResourceTableChunk(ResourceTableChunk resourceTableChunk) {
26 | ValueHelper.resourceTableChunk = resourceTableChunk;
27 | }
28 |
29 | public static TypeChunk getTypeChunk() {
30 | return typeChunk;
31 | }
32 |
33 | public static void setTypeChunk(TypeChunk typeChunk) {
34 | ValueHelper.typeChunk = typeChunk;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Arsc Editor
2 |
3 |
4 | [](https://github.com/MrIkso/ArscEditor/releases) [](LICENSE)
5 |
6 | An open source editor for resources.arsc with GUI.
7 |
8 | This tool used this open source project
9 | - [ArscBlamer](https://github.com/google/android-arscblamer) for parsing and editing resources.arsc files
10 | - [robolectric](https://github.com/robolectric/robolectric) for back convert values to binary format
11 |
12 | ## License
13 |
14 | Copyright (C) 2021 Mr Isko
15 |
16 | This program is free software: you can redistribute it and/or modify
17 | it under the terms of the GNU General Public License as published by
18 | the Free Software Foundation, either version 3 of the License, or
19 | (at your option) any later version.
20 |
21 | This program is distributed in the hope that it will be useful,
22 | but WITHOUT ANY WARRANTY; without even the implied warranty of
23 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 | GNU General Public License for more details.
25 |
26 | You should have received a copy of the GNU General Public License
27 | along with this program. If not, see column.
14 | */
15 | public String getColumnName(int column);
16 |
17 | /**
18 | * Returns the type for column number column.
19 | */
20 | public Class> getColumnClass(int column);
21 |
22 | /**
23 | * Returns the value to be displayed for node node, at column
24 | * number column.
25 | */
26 | public Object getValueAt(Object node, int column);
27 |
28 | /**
29 | * Indicates whether the the value for node node, at column
30 | * number column is editable.
31 | */
32 | public boolean isCellEditable(Object node, int column);
33 |
34 | /**
35 | * Sets the value for node node, at column number
36 | * column.
37 | */
38 | public void setValueAt(Object aValue, Object node, int column);
39 |
40 | /**
41 | * Adds a listener to the list that is notified each time a change to the
42 | * data model occurs.
43 | */
44 | public void addTableModelListener(TableModelListener l);
45 |
46 | /**
47 | * Removes a listener from the list that is notified each time a change to
48 | * the data model occurs.
49 | */
50 | public void removeTableModelListener(TableModelListener l);
51 | }
52 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlChunk.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.devrel.gmscore.tools.apk.arsc;
18 |
19 | import javax.annotation.Nullable;
20 | import java.nio.ByteBuffer;
21 |
22 | /**
23 | * Represents an XML chunk structure.
24 | *
25 | *
An XML chunk can contain many nodes as well as a string pool which contains all of the strings
26 | * referenced by the nodes.
27 | */
28 | public final class XmlChunk extends ChunkWithChunks {
29 |
30 | protected XmlChunk(ByteBuffer buffer, @Nullable Chunk parent) {
31 | super(buffer, parent);
32 | }
33 |
34 | @Override
35 | protected Type getType() {
36 | return Type.XML;
37 | }
38 |
39 | /** Returns a string at the provided (0-based) index if the index exists in the string pool. */
40 | public String getString(int index) {
41 | for (Chunk chunk : getChunks().values()) {
42 | if (chunk instanceof StringPoolChunk) {
43 | return ((StringPoolChunk) chunk).getString(index);
44 | }
45 | }
46 | throw new IllegalStateException("XmlChunk did not contain a string pool.");
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/mrikso/arsceditor/util/VersionUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2021 MrIkso
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see This chunk maps attribute ids to the resource ids of the attribute resource that defines the
30 | * attribute (e.g. type, enum values, etc.).
31 | */
32 | public class XmlResourceMapChunk extends Chunk {
33 |
34 | /** The size of a resource reference for {@code resources} in bytes. */
35 | private static final int RESOURCE_SIZE = 4;
36 |
37 | /**
38 | * Contains a mapping of attributeID to resourceID. For example, the attributeID 2 refers to the
39 | * resourceID returned by {@code resources.get(2)}.
40 | */
41 | private final List Here's an example UTF-8-encoded string of ab©:
57 | * Here's an example UTF-8-encoded string of ab©:
88 | * To support editing of the tree column we can not make the tree
42 | * editable. The reason this doesn't work is that you can not use
43 | * the same component for editing and renderering. The table may have
44 | * the need to paint cells, while a cell is being edited. If the same
45 | * component were used for the rendering and editing the component would
46 | * be moved around, and the contents would change. When editing, this
47 | * is undesirable, the contents of the text field must stay the same,
48 | * including the caret blinking, and selections persisting. For this
49 | * reason the editing is done via a TableCellEditor.
50 | * Another interesting thing to be aware of is how tree positions
51 | * its render and editor. The render/editor is responsible for drawing the
52 | * icon indicating the type of node (leaf, branch...). The tree is
53 | * responsible for drawing any other indicators, perhaps an additional
54 | * +/- sign, or lines connecting the various nodes. So, the renderer
55 | * is positioned based on depth. On the other hand, table always makes
56 | * its editor fill the contents of the cell. To get the allusion
57 | * that the table cell editor is part of the tree, we don't want the
58 | * table cell editor to fill the cell bounds. We want it to be placed
59 | * in the same manner as tree places it editor, and have table message
60 | * the tree to paint any decorations the tree wants. Then, we would
61 | * only have to worry about the editing part. The approach taken
62 | * here is to determine where tree would place the editor, and to override
63 | * the The offset is then set on the TreeTableTextField component
92 | * created in the constructor, and returned.
93 | */
94 | public Component getTableCellEditorComponent(JTable table, Object value,
95 | boolean isSelected, int row,
96 | int column) {
97 | Component component = super.getTableCellEditorComponent(table, value,
98 | isSelected, row, column);
99 | Rectangle bounds = tree.getRowBounds(row);
100 | int offset = bounds.x;
101 | TreeCellRenderer tcr = tree.getCellRenderer();
102 | if (tcr instanceof DefaultTreeCellRenderer) {
103 | Object node = tree.getPathForRow(row).getLastPathComponent();
104 | Icon icon;
105 | if (tree.getModel().isLeaf(node))
106 | icon = ((DefaultTreeCellRenderer) tcr).getLeafIcon();
107 | else if (tree.isExpanded(row))
108 | icon = ((DefaultTreeCellRenderer) tcr).getOpenIcon();
109 | else
110 | icon = ((DefaultTreeCellRenderer) tcr).getClosedIcon();
111 |
112 | if (icon != null) {
113 | offset += ((DefaultTreeCellRenderer) tcr).getIconTextGap() +
114 | icon.getIconWidth();
115 | }
116 | }
117 | ((TreeTableTextField) getComponent()).offset = offset;
118 | return component;
119 | }
120 |
121 | /**
122 | * This is overriden to forward the event to the tree. This will
123 | * return true if the click count >= clickCountToStart, or the event is null.
124 | */
125 | public boolean isCellEditable(EventObject e) {
126 | if (e == null) {
127 | return true;
128 | }
129 | else if (e instanceof MouseEvent) {
130 | return (((MouseEvent) e).getClickCount() >= clickCountToStart);
131 | }
132 |
133 | // e is some other type of event...
134 | return false;
135 | }
136 |
137 | /**
138 | * Component used by TreeTableCellEditor. The only thing this does
139 | * is to override the
62 | * "XmlNamespaceChunk{line=1234, comment=My awesome comment., prefix=foo, uri=com.google.foo}"
63 | *
64 | */
65 | @Override
66 | public String toString() {
67 | return String.format("XmlNamespaceChunk{line=%d, comment=%s, prefix=%s, uri=%s}",
68 | getLineNumber(), getComment(), getPrefix(), getUri());
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/ChunkWithChunks.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.devrel.gmscore.tools.apk.arsc;
18 |
19 | import javax.annotation.Nullable;
20 | import java.io.DataOutput;
21 | import java.io.IOException;
22 | import java.nio.ByteBuffer;
23 | import java.util.LinkedHashMap;
24 | import java.util.Map;
25 |
26 | /** Represents a chunk whose payload is a list of sub-chunks. */
27 | public abstract class ChunkWithChunks extends Chunk {
28 |
29 | private final Map
67 | * "XmlEndElementChunk{line=1234, comment=My awesome comment., namespace=foo, name=bar}"
68 | *
69 | */
70 | @Override
71 | public String toString() {
72 | return String.format("XmlEndElementChunk{line=%d, comment=%s, namespace=%s, name=%s}",
73 | getLineNumber(), getComment(), getNamespace(), getName());
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/mrikso/arsceditor/gui/dialogs/PackageEditDialog.java:
--------------------------------------------------------------------------------
1 | package com.mrikso.arsceditor.gui.dialogs;
2 |
3 | import com.mrikso.arsceditor.gui.tree.ArscNode;
4 |
5 | import javax.swing.*;
6 | import java.awt.*;
7 |
8 | public class PackageEditDialog extends JDialog {
9 |
10 | private ValueChangedListener valueChangedListener;
11 |
12 | public PackageEditDialog(JFrame parent, ArscNode node) {
13 | super(parent);
14 |
15 | JTextField packageNameFiled = new JTextField(20);
16 | packageNameFiled.setText(node.getPackageName());
17 |
18 | JTextField packageIdField = new JTextField(20);
19 | packageIdField.setText(String.valueOf(node.getId()));
20 | packageIdField.setEditable(false);
21 |
22 | // CompactGrid layout is from http://sourceforge.net/projects/swinglib/
23 | JPanel pFields = new JPanel(new CompactGridLayout(2, 6, 12));
24 | pFields.add(new JLabel("Package Name:"));
25 | pFields.add(packageNameFiled);
26 | pFields.add(new JLabel("Package Id:"));
27 | pFields.add(packageIdField);
28 | pFields.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
29 |
30 | JButton okButton = new JButton("OK");
31 | okButton.addActionListener(l -> {
32 | save(packageNameFiled.getText(), Integer.parseInt(packageIdField.getText()));
33 | });
34 | JButton cancelButton = new JButton("Cancel");
35 | cancelButton.addActionListener(l -> {
36 | setVisible(false);
37 | });
38 | JPanel pButtons = new JPanel(new FlowLayout(FlowLayout.CENTER));
39 | pButtons.add(okButton);
40 | pButtons.add(cancelButton);
41 |
42 | JPanel pContent = new JPanel(new BorderLayout());
43 | pContent.add(pFields, BorderLayout.CENTER);
44 | pContent.add(pButtons, BorderLayout.SOUTH);
45 |
46 | setContentPane(pContent);
47 | pack();
48 | // setSize(new Dimension(300, 100));
49 | setTitle("Edit Package");
50 | setLocationRelativeTo(parent);
51 | setModal(true);
52 | setResizable(false);
53 | }
54 |
55 |
56 | private void save(String name, int id) {
57 | if (valueChangedListener != null) {
58 | valueChangedListener.onEdited(name, id);
59 | }
60 | setVisible(false);
61 | }
62 |
63 | public void setValueChangedListener(ValueChangedListener valueChangedListener) {
64 | this.valueChangedListener = valueChangedListener;
65 | }
66 |
67 | public void showDialog() {
68 | setVisible(true);
69 | }
70 |
71 | public interface ValueChangedListener {
72 | public void onEdited(String name, int id);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/XmlCdataChunk.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.devrel.gmscore.tools.apk.arsc;
18 |
19 | import javax.annotation.Nullable;
20 | import java.io.DataOutput;
21 | import java.io.IOException;
22 | import java.nio.ByteBuffer;
23 |
24 | /** Represents an XML cdata node. */
25 | public final class XmlCdataChunk extends XmlNodeChunk {
26 |
27 | /** A string reference to a string containing the raw character data. */
28 | private final int rawValue;
29 |
30 | /** A {@link BinaryResourceValue} instance containing the parsed value. */
31 | private final BinaryResourceValue binaryResourceValue;
32 |
33 | protected XmlCdataChunk(ByteBuffer buffer, @Nullable Chunk parent) {
34 | super(buffer, parent);
35 | rawValue = buffer.getInt();
36 | binaryResourceValue = BinaryResourceValue.create(buffer);
37 | }
38 |
39 | /** Returns a string containing the raw character data of this chunk. */
40 | public String getRawValue() {
41 | return getString(rawValue);
42 | }
43 |
44 | /** Returns a {@link BinaryResourceValue} instance containing the parsed cdata value. */
45 | public BinaryResourceValue getResourceValue() {
46 | return binaryResourceValue;
47 | }
48 |
49 | @Override
50 | protected Type getType() {
51 | return Type.XML_CDATA;
52 | }
53 |
54 | @Override
55 | protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
56 | throws IOException {
57 | super.writePayload(output, header, shrink);
58 | output.writeInt(rawValue);
59 | output.write(binaryResourceValue.toByteArray());
60 | }
61 |
62 | /**
63 | * Returns a brief description of this XML node. The representation of this information is
64 | * subject to change, but below is a typical example:
65 | *
66 | * "XmlCdataChunk{line=1234, comment=My awesome comment., value=1234}"
67 | */
68 | @Override
69 | public String toString() {
70 | return String.format("XmlCdataChunk{line=%d, comment=%s, value=%s}",
71 | getLineNumber(), getComment(), getRawValue());
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceFile.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.devrel.gmscore.tools.apk.arsc;
18 |
19 | import com.google.common.io.ByteArrayDataOutput;
20 | import com.google.common.io.ByteStreams;
21 |
22 | import java.io.IOException;
23 | import java.io.InputStream;
24 | import java.nio.ByteBuffer;
25 | import java.nio.ByteOrder;
26 | import java.util.ArrayList;
27 | import java.util.Collections;
28 | import java.util.List;
29 |
30 | /** Given an arsc file, maps the contents of the file. */
31 | public final class BinaryResourceFile implements SerializableResource {
32 |
33 | /** The chunks contained in this resource file. */
34 | private final List
32 | *
36 | */
37 | public final class ResourceTableChunk extends ChunkWithChunks {
38 |
39 | /** A string pool containing all string resource values in the entire resource table. */
40 | private StringPoolChunk stringPool;
41 |
42 | /** The packages contained in this resource table. */
43 | private final Map"XmlNodeChunk{line=1234, comment=My awesome comment.}"
91 | */
92 | @Override
93 | public String toString() {
94 | return String.format("XmlNodeChunk{line=%d, comment=%s}", getLineNumber(), getComment());
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/mrikso/arsceditor/gui/tree/ResourceDirectory.java:
--------------------------------------------------------------------------------
1 | package com.mrikso.arsceditor.gui.tree;
2 |
3 | import com.mrikso.arsceditor.valueeditor.ValueType;
4 | import com.google.devrel.gmscore.tools.apk.arsc.TypeChunk;
5 |
6 | public class ResourceDirectory extends JTableTreeNode {
7 |
8 | private static final long serialVersionUID = 1L;
9 | private String value;
10 | private String name;
11 | private String decodedName;
12 | private String decodedValue;
13 | private ValueType valueType;
14 | private TypeChunk typeChunk;
15 | private int nameIndex, valueIndex, entryIndex, entryParentIndex = -1;
16 | private boolean isComplex;
17 |
18 | public ResourceDirectory() {
19 | super();
20 | }
21 |
22 | public ResourceDirectory(String id) {
23 | super(id);
24 | }
25 |
26 | @Override
27 | public void setName(String name) {
28 | this.name = name;
29 | }
30 |
31 | @Override
32 | public String getDecodedName() {
33 | return decodedName;
34 | }
35 |
36 | @Override
37 | public void setDecodedName(String name) {
38 | decodedName = name;
39 | }
40 |
41 | @Override
42 | public String getName() {
43 | return name;
44 | }
45 |
46 | @Override
47 | public void setId(String id) {
48 | setUserObject(id);
49 | }
50 |
51 | @Override
52 | public String getId() {
53 | return (String) getUserObject();
54 | }
55 |
56 | @Override
57 | public void setNameIndex(int index) {
58 | this.nameIndex = index;
59 | }
60 |
61 | @Override
62 | public int getNameIndex() {
63 | return nameIndex;
64 | }
65 |
66 | @Override
67 | public void setValueIndex(int index) {
68 | valueIndex = index;
69 | }
70 |
71 | @Override
72 | public int getValueIndex() {
73 | return valueIndex;
74 | }
75 |
76 | @Override
77 | public void setEntryIndex(int index) {
78 | this.entryIndex = index;
79 | }
80 |
81 | @Override
82 | public int getEntryIndex() {
83 | return entryIndex;
84 | }
85 |
86 | @Override
87 | public void setEntryChildrenIndex(int index) {
88 | this.entryParentIndex = index;
89 | }
90 |
91 | @Override
92 | public int getEntryChildrenIndex() {
93 | return entryParentIndex;
94 | }
95 |
96 | @Override
97 | public String getValue() {
98 | return value;
99 | }
100 |
101 | @Override
102 | public void setValue(String value) {
103 | this.value = value;
104 | }
105 |
106 | @Override
107 | public String getDecodeValue() {
108 | return decodedValue;
109 | }
110 |
111 | @Override
112 | public void setDecodedValue(String value) {
113 | decodedValue = value;
114 | }
115 |
116 | @Override
117 | public ValueType getValueType() {
118 | return valueType;
119 | }
120 |
121 | @Override
122 | public void setValueType(ValueType value) {
123 | valueType = value;
124 | }
125 |
126 | /* @Override
127 | public TypeChunk getTypeChunk() {
128 | return typeChunk;
129 | }
130 |
131 | @Override
132 | public void setTypeChunk(TypeChunk value) {
133 | typeChunk = value;
134 | }*/
135 |
136 | @Override
137 | public boolean isComplex() {
138 | return isComplex;
139 | }
140 |
141 | @Override
142 | public void setIsComplex(boolean isComplex) {
143 | this.isComplex = isComplex;
144 | }
145 |
146 | @Override
147 | public boolean isChildren() {
148 | return false;
149 | }
150 |
151 | @Override
152 | public void setIsChildren(boolean isChildren) {
153 |
154 | }
155 | }
156 |
157 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/TypeSpecChunk.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.devrel.gmscore.tools.apk.arsc;
18 |
19 | import com.google.common.base.Preconditions;
20 | import com.google.common.primitives.UnsignedBytes;
21 |
22 | import javax.annotation.Nullable;
23 | import java.io.DataOutput;
24 | import java.io.IOException;
25 | import java.nio.ByteBuffer;
26 |
27 | /** A chunk that contains a collection of resource entries for a particular resource data type. */
28 | public final class TypeSpecChunk extends Chunk {
29 |
30 | /** The id of the resource type that this type spec refers to. */
31 | private final int id;
32 |
33 | /** Resource configuration masks. */
34 | private final int[] resources;
35 |
36 | protected TypeSpecChunk(ByteBuffer buffer, @Nullable Chunk parent) {
37 | super(buffer, parent);
38 | id = UnsignedBytes.toInt(buffer.get());
39 | buffer.position(buffer.position() + 3); // Skip 3 bytes for packing
40 | int resourceCount = buffer.getInt();
41 | resources = new int[resourceCount];
42 |
43 | for (int i = 0; i < resourceCount; ++i) {
44 | resources[i] = buffer.getInt();
45 | }
46 | }
47 |
48 | /**
49 | * Returns the (1-based) type id of the resources that this {@link TypeSpecChunk} has
50 | * configuration masks for.
51 | */
52 | public int getId() {
53 | return id;
54 | }
55 |
56 | /** Returns the number of resource entries that this chunk has configuration masks for. */
57 | public int getResourceCount() {
58 | return resources.length;
59 | }
60 |
61 | @Override
62 | protected Type getType() {
63 | return Type.TABLE_TYPE_SPEC;
64 | }
65 |
66 | /** Returns the name of the type this chunk represents (e.g. string, attr, id). */
67 | public String getTypeName() {
68 | PackageChunk packageChunk = getPackageChunk();
69 | Preconditions.checkNotNull(packageChunk, "%s has no parent package.", getClass());
70 | StringPoolChunk typePool = packageChunk.getTypeStringPool();
71 | Preconditions.checkNotNull(typePool, "%s's parent package has no type pool.", getClass());
72 | return typePool.getString(getId() - 1); // - 1 here to convert to 0-based index
73 | }
74 |
75 | /** Returns the package enclosing this chunk, if any. Else, returns null. */
76 | @Nullable
77 | private PackageChunk getPackageChunk() {
78 | Chunk chunk = getParent();
79 | while (chunk != null && !(chunk instanceof PackageChunk)) {
80 | chunk = chunk.getParent();
81 | }
82 | return chunk != null ? (PackageChunk) chunk : null;
83 | }
84 |
85 | @Override
86 | protected void writeHeader(ByteBuffer output) {
87 | super.writeHeader(output);
88 | // id is an unsigned byte in the range [0-255]. It is guaranteed to be non-negative.
89 | // Because our output is in little-endian, we are making use of the 4 byte packing here
90 | output.putInt(id);
91 | output.putInt(resources.length);
92 | }
93 |
94 | @Override
95 | protected void writePayload(DataOutput output, ByteBuffer header, boolean shrink)
96 | throws IOException {
97 | for (int resource : resources) {
98 | output.writeInt(resource);
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceIdentifier.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.devrel.gmscore.tools.apk.arsc;
18 |
19 | import com.google.common.base.Preconditions;
20 |
21 | import java.util.Objects;
22 |
23 | /**
24 | * Resources in a {@link ResourceTableChunk} are identified by an integer of the form 0xpptteeee,
25 | * where pp is the {@link PackageChunk} id, tt is the {@link TypeChunk} id, and eeee is the index of
26 | * the entry in the {@link TypeChunk}.
27 | */
28 | public class BinaryResourceIdentifier {
29 |
30 | /** The {@link PackageChunk} id mask for a packed resource id of the form 0xpptteeee. */
31 | private static final int PACKAGE_ID_MASK = 0xFF000000;
32 | private static final int PACKAGE_ID_SHIFT = 24;
33 |
34 | /** The {@link TypeChunk} id mask for a packed resource id of the form 0xpptteeee. */
35 | private static final int TYPE_ID_MASK = 0x00FF0000;
36 | private static final int TYPE_ID_SHIFT = 16;
37 |
38 | /** The {@link TypeChunk.Entry} id mask for a packed resource id of the form 0xpptteeee. */
39 | private static final int ENTRY_ID_MASK = 0xFFFF;
40 | private static final int ENTRY_ID_SHIFT = 0;
41 |
42 | private final int packageId;
43 | private final int typeId;
44 | private final int entryId;
45 |
46 | /** Returns a {@link BinaryResourceIdentifier} from a {@code resourceId} of the form 0xpptteeee. */
47 | public static BinaryResourceIdentifier create(int resourceId) {
48 | int packageId = (resourceId & PACKAGE_ID_MASK) >>> PACKAGE_ID_SHIFT;
49 | int typeId = (resourceId & TYPE_ID_MASK) >>> TYPE_ID_SHIFT;
50 | int entryId = (resourceId & ENTRY_ID_MASK) >>> ENTRY_ID_SHIFT;
51 | return create(packageId, typeId, entryId);
52 | }
53 |
54 | /** Returns a {@link BinaryResourceIdentifier} with the given identifiers. */
55 | public static BinaryResourceIdentifier create(int packageId, int typeId, int entryId) {
56 | Preconditions.checkState((packageId & 0xFF) == packageId, "packageId must be <= 0xFF.");
57 | Preconditions.checkState((typeId & 0xFF) == typeId, "typeId must be <= 0xFF.");
58 | Preconditions.checkState((entryId & 0xFFFF) == entryId, "entryId must be <= 0xFFFF.");
59 | return new BinaryResourceIdentifier(packageId, typeId, entryId);
60 | }
61 |
62 | private BinaryResourceIdentifier(int packageId, int typeId, int entryId) {
63 | this.packageId = packageId;
64 | this.typeId = typeId;
65 | this.entryId = entryId;
66 | }
67 |
68 | /** The (1-based) id of the {@link PackageChunk} containing this resource. */
69 | public int packageId() { return packageId; }
70 |
71 | /** The (1-based) id of the {@link TypeChunk} containing this resource. */
72 | public int typeId() { return typeId; }
73 |
74 | /** The (0-based) index of the entry in a {@link TypeChunk} containing this resource. */
75 | public int entryId() { return entryId; }
76 |
77 | /** Returns resource id of the form 0xpptteeee. */
78 | public int resourceId() {
79 | return packageId << PACKAGE_ID_SHIFT | typeId << TYPE_ID_SHIFT | entryId;
80 | }
81 |
82 | @Override
83 | public boolean equals(Object o) {
84 | if (this == o) return true;
85 | if (o == null || getClass() != o.getClass()) return false;
86 | BinaryResourceIdentifier that = (BinaryResourceIdentifier)o;
87 | return packageId == that.packageId &&
88 | typeId == that.typeId &&
89 | entryId == that.entryId;
90 | }
91 |
92 | @Override
93 | public int hashCode() {
94 | return Objects.hash(packageId, typeId, entryId);
95 | }
96 |
97 | @Override
98 | public String toString() {
99 | return String.format("0x%1$08x", resourceId());
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/mrikso/arsceditor/util/AttrNameHelper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2021 MrIkso
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see "XmlAttribute{namespace=foo, name=bar, value=1234}"
142 | */
143 | @Override
144 | public String toString() {
145 | return String.format("XmlAttribute{namespace=%s, name=%s, value=%s}",
146 | namespace(), name(), rawValue());
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/google/devrel/gmscore/tools/apk/arsc/BinaryResourceValue.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.google.devrel.gmscore.tools.apk.arsc;
18 |
19 | import com.google.common.base.Preconditions;
20 | import com.google.common.collect.ImmutableMap;
21 | import com.google.common.collect.ImmutableMap.Builder;
22 | import com.google.common.primitives.UnsignedBytes;
23 |
24 | import java.nio.ByteBuffer;
25 | import java.nio.ByteOrder;
26 | import java.util.Map;
27 | import java.util.Objects;
28 |
29 | /** Represents a single typed resource value. */
30 | public class BinaryResourceValue implements SerializableResource {
31 |
32 | /** Resource type codes. */
33 | public enum Type {
34 | /** {@code data} is either 0 (undefined) or 1 (empty). */
35 | NULL(0x00),
36 | /** {@code data} holds a {@link ResourceTableChunk} entry reference. */
37 | REFERENCE(0x01),
38 | /** {@code data} holds an attribute resource identifier. */
39 | ATTRIBUTE(0x02),
40 | /** {@code data} holds an index into the containing resource table's string pool. */
41 | STRING(0x03),
42 | /** {@code data} holds a single-precision floating point number. */
43 | FLOAT(0x04),
44 | /** {@code data} holds a complex number encoding a dimension value, such as "100in". */
45 | DIMENSION(0x05),
46 | /** {@code data} holds a complex number encoding a fraction of a container. */
47 | FRACTION(0x06),
48 | /** {@code data} holds a dynamic {@link ResourceTableChunk} entry reference. */
49 | DYNAMIC_REFERENCE(0x07),
50 | /** {@code data} holds a dynamic attribute resource identifier. */
51 | DYNAMIC_ATTRIBUTE(0x08),
52 | /** {@code data} is a raw integer value of the form n..n. */
53 | INT_DEC(0x10),
54 | /** {@code data} is a raw integer value of the form 0xn..n. */
55 | INT_HEX(0x11),
56 | /** {@code data} is either 0 (false) or 1 (true). */
57 | INT_BOOLEAN(0x12),
58 | /** {@code data} is a raw integer value of the form #aarrggbb. */
59 | INT_COLOR_ARGB8(0x1c),
60 | /** {@code data} is a raw integer value of the form #rrggbb. */
61 | INT_COLOR_RGB8(0x1d),
62 | /** {@code data} is a raw integer value of the form #argb. */
63 | INT_COLOR_ARGB4(0x1e),
64 | /** {@code data} is a raw integer value of the form #rgb. */
65 | INT_COLOR_RGB4(0x1f);
66 |
67 | private final byte code;
68 |
69 | private static final Map
136 | * "XmlStartElementChunk{line=1234, comment=My awesome comment., namespace=foo, name=bar, ...}"
137 | *
138 | */
139 | @Override
140 | public String toString() {
141 | return String.format(
142 | "XmlStartElementChunk{line=%d, comment=%s, namespace=%s, name=%s, attributes=%s}",
143 | getLineNumber(), getComment(), getNamespace(), getName(), attributes.toString());
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn ( ) {
37 | echo "$*"
38 | }
39 |
40 | die ( ) {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save ( ) {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/mrikso/arsceditor/model/ResourceModel.java:
--------------------------------------------------------------------------------
1 | package com.mrikso.arsceditor.model;
2 |
3 | import com.google.devrel.gmscore.tools.apk.arsc.*;
4 | import com.mrikso.arsceditor.gui.tree.ResourceDirectory;
5 | import com.mrikso.arsceditor.gui.tree.ResourceEntry;
6 | import com.mrikso.arsceditor.util.AttrNameHelper;
7 | import com.mrikso.arsceditor.util.DecodeGenUtils;
8 | import com.mrikso.arsceditor.valueeditor.FormatValue;
9 | import com.mrikso.arsceditor.valueeditor.ValueHelper;
10 | import com.mrikso.arsceditor.valueeditor.ValueType;
11 | import org.jetbrains.annotations.NotNull;
12 |
13 | import java.util.Map;
14 |
15 | public class ResourceModel {
16 |
17 | private final StringPoolChunk stringPool;
18 | private final PackageChunk packageChunk;
19 | private final TypeSpecChunk typeSpec;
20 | private final TypeChunk type;
21 | private final String tableName;
22 | private ResourceDirectory root;
23 |
24 | public ResourceModel(@NotNull StringPoolChunk stringPool, @NotNull PackageChunk packageChunk, @NotNull TypeSpecChunk typeSpec,
25 | @NotNull TypeChunk typeChunk, String tableName) {
26 | this.stringPool = stringPool;
27 | this.packageChunk = packageChunk;
28 | this.typeSpec = typeSpec;
29 | this.type = typeChunk;
30 | this.tableName = tableName;
31 | }
32 |
33 | public void readTable() {
34 | AttrNameHelper nameHelper = AttrNameHelper.getInstance();
35 | root = new ResourceDirectory(String.format("Resource Table (%s)", tableName));
36 | if (type != null) {
37 | for (Map.Entry
58 | * 03 04 61 62 C2 A9 00
59 | * ^ Offset should be here
60 | *
61 | *
62 | * @param buffer The buffer containing the string to decode.
63 | * @param offset Offset into the buffer where the string resides.
64 | * @param type The encoding type that the {@link BinaryResourceString} is encoded in.
65 | * @return The decoded string.
66 | */
67 | public static String decodeString(ByteBuffer buffer, int offset, Type type) {
68 | int length;
69 | int characterCount = decodeLength(buffer, offset, type);
70 | offset += computeLengthOffset(characterCount, type);
71 | // UTF-8 strings have 2 lengths: the number of characters, and then the encoding length.
72 | // UTF-16 strings, however, only have 1 length: the number of characters.
73 | if (type == Type.UTF8) {
74 | length = decodeLength(buffer, offset, type);
75 | offset += computeLengthOffset(length, type);
76 | } else {
77 | length = characterCount * 2;
78 | }
79 | return new String(buffer.array(), offset, length, type.charset());
80 | }
81 |
82 | /**
83 | * Encodes a string in either UTF-8 or UTF-16 and returns the bytes of the encoded string.
84 | * Strings are prefixed by 2 values. The first is the number of characters in the string.
85 | * The second is the encoding length (number of bytes in the string).
86 | *
87 | * 03 04 61 62 C2 A9 00
89 | *
90 | * @param str The string to be encoded.
91 | * @param type The encoding type that the {@link BinaryResourceString} should be encoded in.
92 | * @return The encoded string.
93 | */
94 | public static byte[] encodeString(String str, Type type) {
95 | byte[] bytes = str.getBytes(type.charset());
96 | // The extra 5 bytes is for metadata (character count + byte count) and the NULL terminator.
97 | ByteArrayDataOutput output = ByteStreams.newDataOutput(bytes.length + 5);
98 | encodeLength(output, str.length(), type);
99 | if (type == Type.UTF8) { // Only UTF-8 strings have the encoding length.
100 | encodeLength(output, bytes.length, type);
101 | }
102 | output.write(bytes);
103 | // NULL-terminate the string
104 | if (type == Type.UTF8) {
105 | output.write(0);
106 | } else {
107 | output.writeShort(0);
108 | }
109 | return output.toByteArray();
110 | }
111 |
112 | private static void encodeLength(ByteArrayDataOutput output, int length, Type type) {
113 | if (length < 0) {
114 | output.write(0);
115 | return;
116 | }
117 | if (type == Type.UTF8) {
118 | if (length > 0x7F) {
119 | output.write(((length & 0x7F00) >> 8) | 0x80);
120 | }
121 | output.write(length & 0xFF);
122 | } else { // UTF-16
123 | // TODO(acornwall): Replace output with a little-endian output.
124 | if (length > 0x7FFF) {
125 | int highBytes = ((length & 0x7FFF0000) >> 16) | 0x8000;
126 | output.write(highBytes & 0xFF);
127 | output.write((highBytes & 0xFF00) >> 8);
128 | }
129 | int lowBytes = length & 0xFFFF;
130 | output.write(lowBytes & 0xFF);
131 | output.write((lowBytes & 0xFF00) >> 8);
132 | }
133 | }
134 |
135 | private static int computeLengthOffset(int length, Type type) {
136 | return (type == Type.UTF8 ? 1 : 2) * (length >= (type == Type.UTF8 ? 0x80 : 0x8000) ? 2 : 1);
137 | }
138 |
139 | private static int decodeLength(ByteBuffer buffer, int offset, Type type) {
140 | return type == Type.UTF8 ? decodeLengthUTF8(buffer, offset) : decodeLengthUTF16(buffer, offset);
141 | }
142 |
143 | private static int decodeLengthUTF8(ByteBuffer buffer, int offset) {
144 | // UTF-8 strings use a clever variant of the 7-bit integer for packing the string length.
145 | // If the first byte is >= 0x80, then a second byte follows. For these values, the length
146 | // is WORD-length in big-endian & 0x7FFF.
147 | int length = UnsignedBytes.toInt(buffer.get(offset));
148 | if ((length & 0x80) != 0) {
149 | length = ((length & 0x7F) << 8) | UnsignedBytes.toInt(buffer.get(offset + 1));
150 | }
151 | return length;
152 | }
153 |
154 | private static int decodeLengthUTF16(ByteBuffer buffer, int offset) {
155 | // UTF-16 strings use a clever variant of the 7-bit integer for packing the string length.
156 | // If the first word is >= 0x8000, then a second word follows. For these values, the length
157 | // is DWORD-length in big-endian & 0x7FFFFFFF.
158 | int length = (buffer.getShort(offset) & 0xFFFF);
159 | if ((length & 0x8000) != 0) {
160 | length = ((length & 0x7FFF) << 16) | (buffer.getShort(offset + 2) & 0xFFFF);
161 | }
162 | return length;
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/mrikso/arsceditor/util/DecodeGenUtils.java:
--------------------------------------------------------------------------------
1 | package com.mrikso.arsceditor.util;
2 |
3 | import com.google.devrel.gmscore.tools.apk.arsc.BinaryResourceValue;
4 | import com.google.devrel.gmscore.tools.apk.arsc.StringPoolChunk;
5 | import com.mrikso.arsceditor.valueeditor.FormatValue;
6 | import com.mrikso.arsceditor.valueeditor.ValueType;
7 | import org.jetbrains.annotations.NotNull;
8 | import org.jetbrains.annotations.Nullable;
9 |
10 | import java.text.NumberFormat;
11 | import java.util.Locale;
12 |
13 | public class DecodeGenUtils {
14 |
15 | public static String decodeComplex(int data, boolean isFraction) {
16 | double value = (data & TypedValue.COMPLEX_MANTISSA_MASK << TypedValue.COMPLEX_MANTISSA_SHIFT)
17 | * TypedValue.RADIX_MULTS[data >> TypedValue.COMPLEX_RADIX_SHIFT & TypedValue.COMPLEX_RADIX_MASK];
18 | int unitType = data & TypedValue.COMPLEX_UNIT_MASK;
19 | String unit;
20 | if (isFraction) {
21 | value *= 100;
22 | switch (unitType) {
23 | case TypedValue.COMPLEX_UNIT_FRACTION:
24 | unit = "%";
25 | break;
26 | case TypedValue.COMPLEX_UNIT_FRACTION_PARENT:
27 | unit = "%p";
28 | break;
29 |
30 | default:
31 | unit = "?f" + Integer.toHexString(unitType);
32 | }
33 | } else {
34 | switch (unitType) {
35 | case TypedValue.COMPLEX_UNIT_PX:
36 | unit = "px";
37 | break;
38 | case TypedValue.COMPLEX_UNIT_DIP:
39 | unit = "dp";
40 | break;
41 | case TypedValue.COMPLEX_UNIT_SP:
42 | unit = "sp";
43 | break;
44 | case TypedValue.COMPLEX_UNIT_PT:
45 | unit = "pt";
46 | break;
47 | case TypedValue.COMPLEX_UNIT_IN:
48 | unit = "in";
49 | break;
50 | case TypedValue.COMPLEX_UNIT_MM:
51 | unit = "mm";
52 | break;
53 |
54 | default:
55 | unit = "?d" + Integer.toHexString(unitType);
56 | }
57 | }
58 | return doubleToString(value) + unit;
59 | }
60 |
61 | public static String doubleToString(double value) {
62 | if (Double.compare(value, Math.floor(value)) == 0
63 | && !Double.isInfinite(value)) {
64 | return Integer.toString((int) value);
65 | }
66 | // remove trailing zeroes
67 | NumberFormat f = NumberFormat.getInstance(Locale.ROOT);
68 | f.setMaximumFractionDigits(4);
69 | f.setMinimumIntegerDigits(1);
70 | return f.format(value);
71 | }
72 |
73 | public static String floatToString(float value) {
74 | return doubleToString(value);
75 | }
76 |
77 |
78 | public static FormatValue formatValue(
79 | @NotNull BinaryResourceValue resValue, @Nullable StringPoolChunk stringPool, String packageName) {
80 | int data = resValue.data();
81 | String decoded;
82 |
83 | AttrNameHelper nameHelper = AttrNameHelper.getInstance();
84 | //System.out.println(String.format("data: %d, size: %d", data, resValue.size()));
85 | switch (resValue.type()) {
86 | case NULL:
87 | return new FormatValue(ValueType.TYPE_STRING, null);
88 | case DYNAMIC_ATTRIBUTE:
89 | break;
90 | case DYNAMIC_REFERENCE:
91 | decoded = nameHelper.getName(data, packageName);
92 | if(decoded != null){
93 | return new FormatValue(ValueType.TYPE_DYNAMIC_REFERENCE, String.format(Locale.US, "@0x%1$08x", data),
94 | String.format("@%s", decoded));
95 | }
96 | return new FormatValue(ValueType.TYPE_DYNAMIC_REFERENCE, String.format(Locale.US, "@0x%1$08x", data));
97 | case REFERENCE:
98 | decoded = nameHelper.getName(data, packageName);
99 | if(decoded != null){
100 | return new FormatValue(ValueType.TYPE_REFERENCE, String.format(Locale.US, "@0x%1$08x", data),
101 | String.format("@%s", decoded));
102 | }
103 | return new FormatValue(ValueType.TYPE_REFERENCE, String.format(Locale.US, "@0x%1$08x", data));
104 | case ATTRIBUTE:
105 | decoded = nameHelper.getName(data, packageName);
106 | if(decoded != null){
107 | return new FormatValue(ValueType.TYPE_ATTRIBUTE, String.format(Locale.US, "?0x%1$x", data),
108 | String.format("?%s", decoded));
109 | }
110 | return new FormatValue(ValueType.TYPE_ATTRIBUTE, String.format(Locale.US, "?0x%1$x", data));
111 | case STRING:
112 | if(stringPool != null && stringPool.getStringCount() < data){
113 | return new FormatValue(ValueType.TYPE_STRING, stringPool.getString(data), data);
114 | }else {
115 | // reference string link
116 | decoded = nameHelper.getName(data, packageName);
117 | if(decoded !=null){
118 | return new FormatValue(ValueType.TYPE_REFERENCE, String.format(Locale.US, "@0x%1$x", data), data,
119 | String.format("@%s", decoded));
120 | }
121 | return new FormatValue(ValueType.TYPE_REFERENCE, String.format(Locale.US, "@0x%1$x", data), data);
122 | }
123 | case DIMENSION:
124 | return new FormatValue(ValueType.TYPE_DIMENSION, decodeComplex(data, false));
125 | case FRACTION:
126 | return new FormatValue(ValueType.TYPE_FRACTION, decodeComplex(data, true));
127 | case FLOAT:
128 | return new FormatValue(ValueType.TYPE_FLOAT, floatToString(Float.intBitsToFloat(data)));
129 | case INT_DEC:
130 | return new FormatValue(ValueType.TYPE_INT_DEC, Integer.toString(data));
131 | case INT_HEX:
132 | return new FormatValue(ValueType.TYPE_INT_HEX, "0x" + Integer.toHexString(data));
133 | case INT_BOOLEAN:
134 | return new FormatValue(ValueType.TYPE_BOOLEAN, data == 0 ? "false" : "true");
135 | case INT_COLOR_ARGB8:
136 | return new FormatValue(ValueType.TYPE_COLOR, String.format("#%08x", data));
137 | case INT_COLOR_RGB8:
138 | return new FormatValue(ValueType.TYPE_COLOR, String.format("#%06x", data & 0xFFFFFF));
139 | case INT_COLOR_ARGB4:
140 | return new FormatValue(ValueType.TYPE_COLOR, String.format("#%04x", data & 0xFFFF));
141 | case INT_COLOR_RGB4:
142 | return new FormatValue(ValueType.TYPE_COLOR, String.format("#%03x", data & 0xFFF));
143 | }
144 |
145 | return new FormatValue(ValueType.TYPE_STRING, String.format("@res/0x%x", data));
146 | }
147 |
148 | }
149 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/mrikso/arsceditor/gui/treetable/TreeTableCellEditor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * $Id: TreeTableCellEditor.java,v 1.5 2005/10/13 08:59:59 kleopatra Exp $
3 | *
4 | * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
5 | * Santa Clara, California 95054, U.S.A. All rights reserved.
6 | *
7 | * This library is free software; you can redistribute it and/or
8 | * modify it under the terms of the GNU Lesser General Public
9 | * License as published by the Free Software Foundation; either
10 | * version 2.1 of the License, or (at your option) any later version.
11 | *
12 | * This library is distributed in the hope that it will be useful,
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 | * Lesser General Public License for more details.
16 | *
17 | * You should have received a copy of the GNU Lesser General Public
18 | * License along with this library; if not, write to the Free Software
19 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 | */
21 |
22 | package com.mrikso.arsceditor.gui.treetable;
23 |
24 | import java.util.EventObject;
25 |
26 | import java.awt.Component;
27 | import java.awt.Rectangle;
28 | import java.awt.event.MouseEvent;
29 | import javax.swing.DefaultCellEditor;
30 | import javax.swing.Icon;
31 | import javax.swing.JTable;
32 | import javax.swing.JTextField;
33 | import javax.swing.JTree;
34 | import javax.swing.tree.DefaultTreeCellRenderer;
35 | import javax.swing.tree.TreeCellRenderer;
36 |
37 | /**
38 | * An editor that can be used to edit the tree column. This extends
39 | * DefaultCellEditor and uses a JTextField (actually, TreeTableTextField)
40 | * to perform the actual editing.
41 | * reshape method in the JTextField component to
64 | * nudge the textfield to the location tree would place it. Since
65 | * JXTreeTable will paint the tree behind the editor everything should
66 | * just work. So, that is what we are doing here. Determining of
67 | * the icon position will only work if the TreeCellRenderer is
68 | * an instance of DefaultTreeCellRenderer. If you need custom
69 | * TreeCellRenderers, that don't descend from DefaultTreeCellRenderer,
70 | * and you want to support editing in JXTreeTable, you will have
71 | * to do something similiar.
72 | *
73 | * @author Scott Violet
74 | * @author Ramesh Gupta
75 | */
76 | public class TreeTableCellEditor extends DefaultCellEditor {
77 | public TreeTableCellEditor(JTree tree) {
78 | super(new TreeTableTextField());
79 | if (tree == null) {
80 | throw new IllegalArgumentException("null tree");
81 | }
82 |
83 | this.tree = tree; // immutable
84 | }
85 |
86 | /**
87 | * Overriden to determine an offset that tree would place the
88 | * editor at. The offset is determined from the
89 | * getRowBounds JTree method, and additionaly
90 | * from the icon DefaultTreeCellRenderer will use.
91 | * reshape method, and to ALWAYS
140 | * make the x location be offset.
141 | */
142 | static class TreeTableTextField extends JTextField {
143 | int offset; // changed to package private instead of public
144 |
145 | public void reshape(int x, int y, int width, int height) {
146 | // Allows precise positioning of text field in the tree cell.
147 | //Border border = this.getBorder(); // get this text field's border
148 | //Insets insets = border == null ? null : border.getBorderInsets(this);
149 | //int newOffset = offset - (insets == null ? 0 : insets.left);
150 | int newOffset = offset - getInsets().left;
151 | super.reshape(x + newOffset, y, width - newOffset, height);
152 | }
153 | }
154 |
155 | private final JTree tree; // immutable
156 | }
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/mrikso/arsceditor/gui/tree/ArscTableView.java:
--------------------------------------------------------------------------------
1 | package com.mrikso.arsceditor.gui.tree;
2 |
3 | import com.mrikso.arsceditor.gui.MainWindow;
4 | import com.mrikso.arsceditor.gui.dialogs.ErrorDialog;
5 | import com.mrikso.arsceditor.gui.dialogs.ResourceEditDialog;
6 | import com.mrikso.arsceditor.gui.treetable.JTreeTable;
7 | import com.mrikso.arsceditor.gui.treetable.TreeTableModel;
8 | import com.mrikso.arsceditor.intrefaces.TableChangedListener;
9 | import com.mrikso.arsceditor.valueeditor.Converter;
10 | import com.mrikso.arsceditor.valueeditor.ValueHelper;
11 | import com.mrikso.arsceditor.valueeditor.ValueType;
12 | import org.jetbrains.annotations.Nullable;
13 | import com.google.devrel.gmscore.tools.apk.arsc.BinaryResourceValue;
14 | import com.google.devrel.gmscore.tools.apk.arsc.ResourceTableChunk;
15 | import com.google.devrel.gmscore.tools.apk.arsc.StringPoolChunk;
16 | import com.google.devrel.gmscore.tools.apk.arsc.TypeChunk;
17 |
18 | import javax.swing.*;
19 | import javax.swing.table.DefaultTableCellRenderer;
20 | import javax.swing.tree.TreePath;
21 | import java.awt.*;
22 | import java.awt.event.MouseEvent;
23 | import java.awt.event.MouseListener;
24 |
25 | public class ArscTableView extends JTreeTable implements MouseListener, ResourceEditDialog.ValueChangedListener {
26 |
27 | protected final MainWindow mainWindow;
28 | protected TreeTableModel treeTableModel;
29 | private TableChangedListener tableChangedListener;
30 | private JTableTreeNode selectedNode;
31 |
32 | public ArscTableView(MainWindow window) {
33 | super();
34 | this.mainWindow = window;
35 | setDefaultRenderer(Object.class, new ArscStringRenderer());
36 | addMouseListener(this);
37 | getTree().setCellRenderer(new ArscTreeCellRenderer());
38 | }
39 |
40 | public void setTableChangedListener(TableChangedListener tableChangedListener) {
41 | this.tableChangedListener = tableChangedListener;
42 | }
43 |
44 | public void setTreeTableModel(TreeTableModel treeTableModel) {
45 | this.treeTableModel = treeTableModel;
46 | }
47 |
48 | public void updateTable() {
49 | updateTable(treeTableModel);
50 | }
51 |
52 | @Override
53 | public void mouseClicked(MouseEvent e) {
54 | Point p = e.getPoint();
55 | int col = this.columnAtPoint(p);
56 | int row = this.rowAtPoint(p);
57 | if (row >= 0) {
58 | /* if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2) {
59 | DefaultMutableTreeNode selectedNode = getSelectedNode();
60 | if (selectedNode != null) {
61 | if (selectedNode instanceof ResourceModel.ResourceDirectory) {
62 | ResourceModel.ResourceDirectory resourceDirectory = (ResourceModel.ResourceDirectory) selectedNode;
63 | System.out.println(resourceDirectory.getName());
64 | } else if (selectedNode instanceof ResourceModel.ResourceEntry) {
65 | ResourceModel.ResourceEntry resourceEntry = (ResourceModel.ResourceEntry) selectedNode;
66 | System.out.println(resourceEntry.getName());
67 | }
68 | }
69 |
70 | } else*/
71 | if (SwingUtilities.isRightMouseButton(e)) {
72 | getTree().setSelectionRow(row);
73 | selectedNode = getSelectedNode();
74 | if (selectedNode != null && !selectedNode.isRoot()) {
75 | ResourceEditDialog resourceEditDialog = new ResourceEditDialog(mainWindow, selectedNode);
76 | resourceEditDialog.setValueChangedListener(this);
77 | resourceEditDialog.showDialog();
78 | }
79 | // popupMenu.show(tree, e.getX(), e.getY());
80 | }}
81 | }
82 |
83 | @Override
84 | public void mousePressed(MouseEvent e) {
85 |
86 | }
87 |
88 | @Override
89 | public void mouseReleased(MouseEvent e) {
90 |
91 | }
92 |
93 | @Override
94 | public void mouseEntered(MouseEvent e) {
95 |
96 | }
97 |
98 | @Override
99 | public void mouseExited(MouseEvent e) {
100 |
101 | }
102 |
103 | @Nullable
104 | public JTableTreeNode getSelectedNode() {
105 | TreePath path = getTree().getSelectionPath();
106 | if (path != null) {
107 | Object component = path.getLastPathComponent();
108 | return component instanceof JTableTreeNode ? (JTableTreeNode) component : null;
109 | }
110 | return null;
111 | }
112 |
113 | @Override
114 | public void onResourceEdited(String name, String id, String value, ValueType resourceType, boolean isChildren) {
115 | try {
116 | selectedNode.setId(id);
117 | selectedNode.setName(name);
118 | selectedNode.setValue(value);
119 | selectedNode.setValueType(resourceType);
120 |
121 | StringPoolChunk poolChunk = ValueHelper.getStringPoolChunk();
122 |
123 | int index = selectedNode.getEntryIndex();
124 |
125 | TypeChunk.Entry entry = ValueHelper.getTypeChunk().getEntries().get(index);
126 |
127 | // editing children node on complex value
128 | if (isChildren) {
129 | if (resourceType == ValueType.TYPE_STRING) {
130 | if(value.equals("null")){
131 | BinaryResourceValue resourceValue = new BinaryResourceValue(8, BinaryResourceValue.Type.NULL, 1);
132 | entry.updateValue(selectedNode.getEntryChildrenIndex(), resourceValue);
133 | }else {
134 | // update the string value
135 | poolChunk.updateString(selectedNode.getValueIndex(), value);
136 |
137 | // update the name
138 | //entry.setKey(selectedNode.getNameIndex(), name);
139 | }
140 | } else {
141 | BinaryResourceValue resourceValue = Converter.convertValue(value, resourceType);
142 | entry.updateValue(selectedNode.getEntryChildrenIndex(), resourceValue);
143 | }
144 | } else {
145 | if (resourceType == ValueType.TYPE_STRING) {
146 | // update the string value
147 | poolChunk.updateString(selectedNode.getValueIndex(), value);
148 |
149 | // update the name
150 | entry.updateKey(selectedNode.getNameIndex(), name);
151 | } else if (selectedNode.isComplex()) {
152 | // update the name in complex values
153 | entry.updateKey(selectedNode.getNameIndex(), name);
154 | } else {
155 | // not complex
156 | BinaryResourceValue resourceValue = Converter.convertValue(value, resourceType);
157 | entry.updateValue(resourceValue);
158 | }
159 | }
160 |
161 | // update the entry
162 | ValueHelper.getTypeChunk().overrideEntry(index, entry);
163 | if (tableChangedListener != null)
164 | tableChangedListener.tableChanged();
165 | }catch (Exception ex){
166 | ex.printStackTrace();
167 | new ErrorDialog(mainWindow, ex);
168 | }
169 | }
170 |
171 | static class ArscStringRenderer extends DefaultTableCellRenderer {
172 | private static final long serialVersionUID = 1L;
173 |
174 | public ArscStringRenderer() {
175 | super();
176 | }
177 |
178 | @Override
179 | public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
180 | super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
181 | if (value != null) {
182 | setText(value.toString());
183 | } else {
184 | setText(null);
185 | }
186 | return this;
187 | }
188 |
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/mrikso/arsceditor/gui/MainWindow.java:
--------------------------------------------------------------------------------
1 | package com.mrikso.arsceditor.gui;
2 |
3 | import com.google.common.io.Files;
4 | import com.mrikso.arsceditor.gui.dialogs.AboutDialog;
5 | import com.mrikso.arsceditor.gui.dialogs.ErrorDialog;
6 | import com.mrikso.arsceditor.gui.tree.ArscTableView;
7 | import com.mrikso.arsceditor.gui.tree.ArscTreeView;
8 | import com.mrikso.arsceditor.intrefaces.TableChangedListener;
9 | import com.mrikso.arsceditor.valueeditor.ArscWriter;
10 | import com.mrikso.arsceditor.valueeditor.ValueHelper;
11 | import com.google.devrel.gmscore.tools.apk.arsc.BinaryResourceFile;
12 | import com.google.devrel.gmscore.tools.apk.arsc.Chunk;
13 |
14 | import javax.swing.*;
15 | import javax.swing.filechooser.FileNameExtensionFilter;
16 | import java.awt.*;
17 | import java.awt.event.InputEvent;
18 | import java.awt.event.KeyEvent;
19 | import java.io.File;
20 | import java.net.URI;
21 | import java.nio.file.Paths;
22 | import java.util.List;
23 |
24 | public class MainWindow extends JFrame implements TableChangedListener {
25 |
26 | private ArscTreeView treeView;
27 | private JSplitPane splitPane;
28 | private JMenuItem saveAs;
29 | private String openedFilePath;
30 |
31 | public MainWindow() {
32 | loadComponent();
33 | }
34 |
35 | protected void loadComponent() {
36 | ArscTableView arscTableView = new ArscTableView(this);
37 | arscTableView.setTableChangedListener(this);
38 |
39 | treeView = new ArscTreeView(this, null, arscTableView);
40 | treeView.setTableChangedListener(this);
41 |
42 | BorderLayout layout = new BorderLayout();
43 | JPanel panel = new JPanel();
44 | panel.setLayout(layout);
45 |
46 | splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(treeView), new JScrollPane(arscTableView));
47 |
48 | initSplitPane();
49 |
50 | panel.add(splitPane, BorderLayout.CENTER);
51 |
52 | this.setJMenuBar(createMenuBar());
53 | this.getContentPane().add(panel);
54 |
55 | this.setPreferredSize(new Dimension(900, 600));
56 | this.setMinimumSize(new Dimension(600, 600));
57 | this.setLocationRelativeTo(null);
58 | this.setTitle("Arsc Editor");
59 | this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
60 | this.pack();
61 | this.setVisible(true);
62 | }
63 |
64 | /**
65 | * Creates a menu bar.
66 | */
67 | protected JMenuBar createMenuBar() {
68 | JMenu fileMenu = new JMenu("File");
69 | JMenuItem menuItem;
70 |
71 | menuItem = new JMenuItem("Open");
72 | menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
73 | InputEvent.CTRL_MASK));
74 | menuItem.addActionListener(ae -> {
75 | JFileChooser fileChooser = new JFileChooser();
76 | fileChooser.setAcceptAllFileFilterUsed(false);
77 | FileNameExtensionFilter filter = new FileNameExtensionFilter("Android resource files", "arsc");
78 | fileChooser.addChoosableFileFilter(filter);
79 | int result = fileChooser.showOpenDialog(getRootPane());
80 |
81 | if (result == JFileChooser.APPROVE_OPTION) {
82 | openedFilePath = fileChooser.getSelectedFile().getPath();
83 | openFile(openedFilePath);
84 | }
85 | });
86 |
87 | saveAs = new JMenuItem("Save");
88 | saveAs.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
89 | InputEvent.CTRL_MASK));
90 | saveAs.setEnabled(false);
91 | saveAs.addActionListener(l -> {
92 | selectPathToSave();
93 | });
94 |
95 | fileMenu.add(menuItem);
96 | fileMenu.addSeparator();
97 | fileMenu.add(saveAs);
98 |
99 | menuItem = new JMenuItem("Exit");
100 | menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X,
101 | InputEvent.CTRL_MASK));
102 | menuItem.addActionListener(ae -> System.exit(0));
103 | fileMenu.add(menuItem);
104 |
105 | JMenu aboutMenu = new JMenu("About");
106 | menuItem = new JMenuItem("Open source code");
107 | menuItem.addActionListener(l -> gitHomepage());
108 | aboutMenu.add(menuItem);
109 | aboutMenu.addSeparator();
110 | menuItem = new JMenuItem("Info");
111 | menuItem.addActionListener(l -> new AboutDialog(MainWindow.this).showDialog());
112 | aboutMenu.add(menuItem);
113 |
114 | // Create a menu bar
115 | JMenuBar menuBar = new JMenuBar();
116 |
117 | menuBar.add(fileMenu);
118 | menuBar.add(aboutMenu);
119 |
120 | return menuBar;
121 | }
122 |
123 | private void gitHomepage() {
124 | try {
125 | Desktop.getDesktop().browse(new URI("https://github.com/MrIkso/ArscEditor"));
126 | } catch (Exception ex) {
127 | JOptionPane.showMessageDialog(
128 | this,
129 | ex.getMessage(), this.getTitle(),
130 | JOptionPane.WARNING_MESSAGE);
131 | }
132 | }
133 |
134 | private void initSplitPane() {
135 | splitPane.setDividerLocation(200);
136 | splitPane.setResizeWeight(0);
137 | Dimension minimumSize = new Dimension(200, 600);
138 | splitPane.getLeftComponent().setMinimumSize(minimumSize);
139 | splitPane.getRightComponent().setMinimumSize(minimumSize);
140 | }
141 |
142 | @Override
143 | public void tableChanged() {
144 | saveAs.setEnabled(true);
145 | }
146 |
147 | private void openFile(String path) {
148 | new SwingWorker, Chunk>() {
149 | @Override
150 | protected List
TableModel interface and is not used by the
199 | * JTable.
200 | *
201 | * @param columnName
202 | * string containing name of column to be located
203 | * @return the column with columnName, or -1 if not found
204 | */
205 | public int findColumn(String columnName) {
206 | for (int i = 0; i < getColumnCount(); i++) {
207 | if (columnName.equals(getColumnName(i))) {
208 | return i;
209 | }
210 | }
211 | return -1;
212 | }
213 |
214 | /**
215 | * Returns a default name for the column using spreadsheet conventions: A,
216 | * B, C, ... Z, AA, AB, etc. If column cannot be found, returns
217 | * an empty string.
218 | *
219 | * @param column
220 | * the column being queried
221 | * @return a string containing the default name of column
222 | */
223 | @Override
224 | public String getColumnName(int column) {
225 | String result = "";
226 | for (; column >= 0; column = column / 26 - 1) {
227 | result = (char) ((char) (column % 26) + 'A') + result;
228 | }
229 | return result;
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/mrikso/arsceditor/gui/treetable/DynamicTreeTableModel.java:
--------------------------------------------------------------------------------
1 | package com.mrikso.arsceditor.gui.treetable;
2 |
3 | import java.lang.reflect.Constructor;
4 | import java.lang.reflect.Method;
5 |
6 | import javax.swing.event.TableModelListener;
7 | import javax.swing.tree.TreeNode;
8 |
9 | /**
10 | * 动态生成表的项
11 | */
12 | public class DynamicTreeTableModel extends AbstractTreeTableModel {
13 |
14 | /** Names of the columns, used for the TableModel getColumnName method. */
15 | private final String[] columnNames;
16 | /**
17 | * Method names used to determine a particular value. Used for the
18 | * TableModel method getValueAt.
19 | */
20 | private final String[] methodNames;
21 | /**
22 | * Setter method names, used to set a particular value. Used for the
23 | * TableModel method setValueAt. A null entry, or array, indicates the
24 | * column is not editable.
25 | */
26 | private final String[] setterMethodNames;
27 | /** Column classes, used for the TableModel method getColumnClass. */
28 | private final Class>[] cTypes;
29 |
30 | // ////////////////////////////////////////////////////////////////////////
31 | // TODO
32 | // 考虑到易用性,最好是动态生成类的set和get方法,以后补充
33 | // TODO
34 | // ////////////////////////////////////////////////////////////////////////
35 |
36 | /**
37 | * Constructor for creating a DynamicTreeTableModel.
38 | */
39 | public DynamicTreeTableModel(TreeNode root, String[] columnNames,
40 | String[] getterMethodNames, String[] setterMethodNames,
41 | Class>[] cTypes) {
42 | super(root);
43 | this.columnNames = columnNames;
44 | this.methodNames = getterMethodNames;
45 | this.setterMethodNames = setterMethodNames;
46 | this.cTypes = cTypes;
47 | }
48 |
49 | //
50 | // TreeModel interface
51 | //
52 |
53 | /**
54 | * TreeModel method to return the number of children of a particular node.
55 | * Since node is a TreeNode, this can be answered via the
56 | * TreeNode method getChildCount.
57 | */
58 | @Override
59 | public int getChildCount(Object node) {
60 | return ((TreeNode) node).getChildCount();
61 | }
62 |
63 | /**
64 | * TreeModel method to locate a particular child of the specified node.
65 | * Since node is a TreeNode, this can be answered via the
66 | * TreeNode method getChild.
67 | */
68 | @Override
69 | public Object getChild(Object node, int i) {
70 | return ((TreeNode) node).getChildAt(i);
71 | }
72 |
73 | /**
74 | * TreeModel method to determine if a node is a leaf. Since
75 | * node is a TreeNode, this can be answered via the TreeNode
76 | * method isLeaf.
77 | */
78 | @Override
79 | public boolean isLeaf(Object node) {
80 | return ((TreeNode) node).isLeaf();
81 | }
82 |
83 | //
84 | // The TreeTable interface.
85 | //
86 |
87 | /**
88 | * Returns the number of column names passed into the constructor.
89 | */
90 | @Override
91 | public int getColumnCount() {
92 | return columnNames.length;
93 | }
94 |
95 | /**
96 | * Returns the column name passed into the constructor.
97 | */
98 | @Override
99 | public String getColumnName(int column) {
100 | if (columnNames == null || column < 0 || column >= columnNames.length) {
101 | return null;
102 | }
103 | return columnNames[column];
104 | }
105 |
106 | /**
107 | * Returns the column class for column column. This is set in
108 | * the constructor.
109 | */
110 | @Override
111 | public Class> getColumnClass(int column) {
112 | if (cTypes == null || column < 0 || column >= cTypes.length) {
113 | return null;
114 | }
115 | return cTypes[column];
116 | }
117 |
118 | /**
119 | * Returns the value for the column column and object
120 | * node. The return value is determined by invoking the method
121 | * specified in constructor for the passed in column.
122 | */
123 | @Override
124 | public Object getValueAt(Object node, int column) {
125 | try {
126 | Method method = node.getClass().getMethod(methodNames[column]);
127 | return method.invoke(node);
128 | } catch (Throwable th) {
129 | }
130 |
131 | return null;
132 | }
133 |
134 | /**
135 | * Returns true if there is a setter method name for column
136 | * column. This is set in the constructor.
137 | */
138 | @Override
139 | public boolean isCellEditable(Object node, int column) {
140 | return false;
141 | }
142 |
143 | /**
144 | * Sets the value to aValue for the object node in
145 | * column column. This is done by using the setter method name,
146 | * and coercing the passed in value to the specified type.
147 | */
148 | // Note: This looks up the methods each time! This is rather inefficient;
149 | // it should really be changed to cache matching methods/constructors
150 | // based on node's class, and aValue's class.
151 | @Override
152 | public void setValueAt(Object aValue, Object node, int column) {
153 | boolean found = false;
154 | try {
155 | // We have to search through all the methods since the
156 | // types may not match up.
157 | Method[] methods = node.getClass().getMethods();
158 |
159 | for (int counter = methods.length - 1; counter >= 0; counter--) {
160 | if (methods[counter].getName()
161 | .equals(setterMethodNames[column])
162 | && methods[counter].getParameterTypes() != null
163 | && methods[counter].getParameterTypes().length == 1) {
164 | // We found a matching method
165 | Class> param = methods[counter].getParameterTypes()[0];
166 | if (!param.isInstance(aValue)) {
167 | // Yes, we can use the value passed in directly,
168 | // no coercision is necessary!
169 | if (aValue instanceof String
170 | && ((String) aValue).length() == 0) {
171 | // Assume an empty string is null, this is
172 | // probably bogus for here.
173 | aValue = null;
174 | } else {
175 | // Have to attempt some sort of coercision.
176 | // See if the expected parameter type has
177 | // a constructor that takes a String.
178 | Constructor> cs = param
179 | .getConstructor(String.class);
180 | if (cs != null) {
181 | aValue = cs
182 | .newInstance(aValue);
183 | } else {
184 | aValue = null;
185 | }
186 | }
187 | }
188 | // null either means it was an empty string, or there
189 | // was no translation. Could potentially deal with these
190 | // differently.
191 | methods[counter].invoke(node, aValue);
192 | found = true;
193 | break;
194 | }
195 | }
196 | } catch (Throwable th) {
197 | System.out.println("exception: " + th);
198 | }
199 | if (found) {
200 | // The value changed, fire an event to notify listeners.
201 | TreeNode parent = ((TreeNode) node).getParent();
202 | fireTreeNodesChanged(this, getPathToRoot(parent),
203 | new int[] { getIndexOfChild(parent, node) },
204 | new Object[] { node });
205 | }
206 | }
207 |
208 | /**
209 | * Builds the parents of the node up to and including the root node, where
210 | * the original node is the last element in the returned array. The length
211 | * of the returned array gives the node's depth in the tree.
212 | *
213 | * @param aNode
214 | * the TreeNode to get the path for
215 | * @param an
216 | * array of TreeNodes giving the path from the root to the
217 | * specified node.
218 | */
219 | public TreeNode[] getPathToRoot(TreeNode aNode) {
220 | return getPathToRoot(aNode, 0);
221 | }
222 |
223 | /**
224 | * Builds the parents of the node up to and including the root node, where
225 | * the original node is the last element in the returned array. The length
226 | * of the returned array gives the node's depth in the tree.
227 | *
228 | * @param aNode
229 | * the TreeNode to get the path for
230 | * @param depth
231 | * an int giving the number of steps already taken towards the
232 | * root (on recursive calls), used to size the returned array
233 | * @return an array of TreeNodes giving the path from the root to the
234 | * specified node
235 | */
236 | private TreeNode[] getPathToRoot(TreeNode aNode, int depth) {
237 | TreeNode[] retNodes;
238 | // This method recurses, traversing towards the root in order
239 | // size the array. On the way back, it fills in the nodes,
240 | // starting from the root and working back to the original node.
241 |
242 | /*
243 | * Check for null, in case someone passed in a null node, or they passed
244 | * in an element that isn't rooted at root.
245 | */
246 | if (aNode == null) {
247 | if (depth == 0)
248 | return null;
249 | else
250 | retNodes = new TreeNode[depth];
251 | } else {
252 | depth++;
253 | if (aNode == root)
254 | retNodes = new TreeNode[depth];
255 | else
256 | retNodes = getPathToRoot(aNode.getParent(), depth);
257 | retNodes[retNodes.length - depth] = aNode;
258 | }
259 | return retNodes;
260 | }
261 |
262 | @Override
263 | public void addTableModelListener(TableModelListener l) {
264 | }
265 |
266 | @Override
267 | public void removeTableModelListener(TableModelListener l) {
268 | }
269 | }
270 |
--------------------------------------------------------------------------------
/arsceditor/src/main/java/com/mrikso/arsceditor/util/ResourceHelper2.java:
--------------------------------------------------------------------------------
1 | package com.mrikso.arsceditor.util;
2 |
3 | /*
4 | * Copyright (C) 2008 The Android Open Source Project
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 |
20 | import java.util.regex.Matcher;
21 | import java.util.regex.Pattern;
22 |
23 | /**
24 | * Helper class to provide various conversion method used in handling android resources.
25 | */
26 | public final class ResourceHelper2 {
27 |
28 | private final static Pattern sFloatPattern = Pattern.compile("(-?[0-9]+(?:\\.[0-9]+)?)(.*)");
29 | private final static float[] sFloatOut = new float[1];
30 |
31 | private final static TypedValue mValue = new TypedValue();
32 |
33 | // ------- TypedValue stuff
34 | // This is taken from //device/libs/utils/ResourceTypes.cpp
35 |
36 | private static final class UnitEntry {
37 | String name;
38 | int type;
39 | int unit;
40 | float scale;
41 |
42 | UnitEntry(String name, int type, int unit, float scale) {
43 | this.name = name;
44 | this.type = type;
45 | this.unit = unit;
46 | this.scale = scale;
47 | }
48 | }
49 |
50 | private final static UnitEntry[] sUnitNames = new UnitEntry[] {
51 | new UnitEntry("px", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PX, 1.0f),
52 | new UnitEntry("dip", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f),
53 | new UnitEntry("dp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f),
54 | new UnitEntry("sp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_SP, 1.0f),
55 | new UnitEntry("pt", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PT, 1.0f),
56 | new UnitEntry("in", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_IN, 1.0f),
57 | new UnitEntry("mm", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_MM, 1.0f),
58 | new UnitEntry("%", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION, 1.0f/100),
59 | new UnitEntry("%p", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION_PARENT, 1.0f/100),
60 | };
61 |
62 | /**
63 | * Returns the raw value from the given attribute float-type value string.
64 | * This object is only valid until the next call on to {@link ResourceHelper2}.
65 | *
66 | * @param attribute Attribute name.
67 | * @param value Attribute value.
68 | * @param requireUnit whether the value is expected to contain a unit.
69 | * @return The typed value.
70 | */
71 | public static TypedValue getValue(String attribute, String value, boolean requireUnit) {
72 | if (parseFloatAttribute(attribute, value, mValue, requireUnit)) {
73 | return mValue;
74 | }
75 |
76 | return null;
77 | }
78 |
79 | /**
80 | * Parse a float attribute and return the parsed value into a given TypedValue.
81 | * @param attribute the name of the attribute. Can be null if requireUnit is false.
82 | * @param value the string value of the attribute
83 | * @param outValue the TypedValue to receive the parsed value
84 | * @param requireUnit whether the value is expected to contain a unit.
85 | * @return true if success.
86 | */
87 | public static boolean parseFloatAttribute(String attribute, String value,
88 | TypedValue outValue, boolean requireUnit) {
89 | // assert requireUnit == false || attribute != null;
90 |
91 | // remove the space before and after
92 | value = value.trim();
93 | int len = value.length();
94 |
95 | if (len <= 0) {
96 | return false;
97 | }
98 |
99 | // check that there's no non ascii characters.
100 | char[] buf = value.toCharArray();
101 | for (int i = 0 ; i < len ; i++) {
102 | if (buf[i] > 255) {
103 | return false;
104 | }
105 | }
106 |
107 | // check the first character
108 | if (buf[0] < '0' && buf[0] > '9' && buf[0] != '.' && buf[0] != '-') {
109 | return false;
110 | }
111 |
112 | // now look for the string that is after the float...
113 | Matcher m = sFloatPattern.matcher(value);
114 | if (m.matches()) {
115 | String f_str = m.group(1);
116 | String end = m.group(2);
117 |
118 | float f;
119 | try {
120 | f = Float.parseFloat(f_str);
121 | } catch (NumberFormatException e) {
122 | // this shouldn't happen with the regexp above.
123 | return false;
124 | }
125 |
126 | if (end.length() > 0 && end.charAt(0) != ' ') {
127 | // Might be a unit...
128 | if (parseUnit(end, outValue, sFloatOut)) {
129 | computeTypedValue(outValue, f, sFloatOut[0], end);
130 | return true;
131 | }
132 | return false;
133 | }
134 |
135 | // make sure it's only spaces at the end.
136 | end = end.trim();
137 |
138 | if (end.length() == 0) {
139 | if (outValue != null) {
140 | outValue.assetCookie = 0;
141 | outValue.string = null;
142 |
143 | if (requireUnit == false) {
144 | outValue.type = TypedValue.TYPE_FLOAT;
145 | outValue.data = Float.floatToIntBits(f);
146 | } else {
147 | // no unit when required? Use dp and out an error.
148 | applyUnit(sUnitNames[1], outValue, sFloatOut);
149 | computeTypedValue(outValue, f, sFloatOut[0], "dp");
150 |
151 | System.out.println(String.format(
152 | "Dimension \"%1$s\" in attribute \"%2$s\" is missing unit!",
153 | value, attribute == null ? "(unknown)" : attribute));
154 | }
155 | return true;
156 | }
157 | }
158 | }
159 |
160 | return false;
161 | }
162 |
163 | private static void computeTypedValue(TypedValue outValue, float value, float scale, String unit) {
164 | value *= scale;
165 | boolean neg = value < 0;
166 | if (neg) {
167 | value = -value;
168 | }
169 | long bits = (long)(value*(1<<23)+.5f);
170 | int radix;
171 | int shift;
172 | if ((bits&0x7fffff) == 0) {
173 | // Always use 23p0 if there is no fraction, just to make
174 | // things easier to read.
175 | radix = TypedValue.COMPLEX_RADIX_23p0;
176 | shift = 23;
177 | } else if ((bits&0xffffffffff800000L) == 0) {
178 | // Magnitude is zero -- can fit in 0 bits of precision.
179 | radix = TypedValue.COMPLEX_RADIX_0p23;
180 | shift = 0;
181 | } else if ((bits&0xffffffff80000000L) == 0) {
182 | // Magnitude can fit in 8 bits of precision.
183 | radix = TypedValue.COMPLEX_RADIX_8p15;
184 | shift = 8;
185 | } else if ((bits&0xffffff8000000000L) == 0) {
186 | // Magnitude can fit in 16 bits of precision.
187 | radix = TypedValue.COMPLEX_RADIX_16p7;
188 | shift = 16;
189 | } else {
190 | // Magnitude needs entire range, so no fractional part.
191 | radix = TypedValue.COMPLEX_RADIX_23p0;
192 | shift = 23;
193 | }
194 | int mantissa = (int)(
195 | (bits>>shift) & TypedValue.COMPLEX_MANTISSA_MASK);
196 | if (neg) {
197 | mantissa = (-mantissa) & TypedValue.COMPLEX_MANTISSA_MASK;
198 | }
199 | outValue.data |= (radix<