();
47 |
48 | public ArrayDataIn(byte[] data, boolean isLE) {
49 | super(data);
50 | this.isLE = isLE;
51 | }
52 |
53 | public ArrayDataIn(byte[] buf, int offset, int length, boolean isLE) {
54 | super(buf, offset, length);
55 | this.isLE = isLE;
56 | }
57 |
58 | @Override
59 | public int getCurrentPosition() {
60 | return super.pos - super.mark;
61 | }
62 |
63 | @Override
64 | public void move(int absOffset) {
65 | super.pos = absOffset + super.mark;
66 | }
67 |
68 | @Override
69 | public void pop() {
70 | super.pos = stack.pop();
71 | }
72 |
73 | @Override
74 | public void push() {
75 | stack.push(super.pos);
76 | }
77 |
78 | @Override
79 | public void pushMove(int absOffset) {
80 | this.push();
81 | this.move(absOffset);
82 | }
83 |
84 | @Override
85 | public int readByte() {
86 | return (byte) readUByte();
87 | }
88 |
89 | @Override
90 | public byte[] readBytes(int size) {
91 | byte[] data = new byte[size];
92 | try {
93 | super.read(data);
94 | } catch (IOException e) {
95 | throw new RuntimeException(e);
96 | }
97 | return data;
98 | }
99 |
100 | @Override
101 | public int readIntx() {
102 | return readUIntx();
103 | }
104 |
105 | @Override
106 | public long readLeb128() {
107 | int bitpos = 0;
108 | long vln = 0L;
109 | do {
110 | int inp = readUByte();
111 | vln |= ((long) (inp & 0x7F)) << bitpos;
112 | bitpos += 7;
113 | if ((inp & 0x80) == 0) {
114 | break;
115 | }
116 | } while (true);
117 | if (((1L << (bitpos - 1)) & vln) != 0) {
118 | vln -= (1L << bitpos);
119 | }
120 | return vln;
121 | }
122 |
123 | @Override
124 | public int readShortx() {
125 | return (short) readUShortx();
126 | }
127 |
128 | @Override
129 | public int readUByte() {
130 | if (super.pos >= super.count) {
131 | throw new RuntimeException("EOF");
132 | }
133 | return super.read();
134 | }
135 |
136 | @Override
137 | public int readUIntx() {
138 | if (isLE) {
139 | return readUByte() | (readUByte() << 8) | (readUByte() << 16) | (readUByte() << 24);
140 | } else {
141 | return (readUByte() << 24) | (readUByte() << 16) | (readUByte() << 8) | readUByte();
142 | }
143 | }
144 |
145 | @Override
146 | public long readULeb128() {
147 | long value = 0;
148 | int count = 0;
149 | int b = readUByte();
150 | while ((b & 0x80) != 0) {
151 | value |= (b & 0x7f) << count;
152 | count += 7;
153 | b = readUByte();
154 | }
155 | value |= (b & 0x7f) << count;
156 | return value;
157 | }
158 |
159 | @Override
160 | public int readUShortx() {
161 | if (isLE) {
162 | return readUByte() | (readUByte() << 8);
163 | } else {
164 | return (readUByte() << 8) | readUByte();
165 | }
166 | }
167 |
168 | @Override
169 | public void skip(int bytes) {
170 | super.skip(bytes);
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/src/main/java/com/googlecode/dex2jar/reader/io/DataIn.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2012 Panxiaobo
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 | package com.googlecode.dex2jar.reader.io;
17 |
18 | /**
19 | * 输入流
20 | *
21 | * @author Panxiaobo
22 | * @version $Rev: 3b20152caede $
23 | */
24 | public interface DataIn {
25 |
26 | /**
27 | * 获取当前位置
28 | *
29 | * @return
30 | */
31 | int getCurrentPosition();
32 |
33 | void move(int absOffset);
34 |
35 | void pop();
36 |
37 | void push();
38 |
39 | /**
40 | * equals to
41 | *
42 | *
43 | * push();
44 | * move(absOffset);
45 | *
46 | *
47 | * @see #push()
48 | * @see #move(int)
49 | * @param absOffset
50 | */
51 | void pushMove(int absOffset);
52 |
53 | /**
54 | *
55 | */
56 | int readByte();
57 |
58 | byte[] readBytes(int size);
59 |
60 | int readIntx();
61 |
62 | int readUIntx();
63 |
64 | int readShortx();
65 |
66 | int readUShortx();
67 |
68 | long readLeb128();
69 |
70 | /**
71 | * @return
72 | */
73 | int readUByte();
74 |
75 | long readULeb128();
76 |
77 | /**
78 | * @param i
79 | */
80 | void skip(int bytes);
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/com/googlecode/dex2jar/reader/io/DataOut.java:
--------------------------------------------------------------------------------
1 | package com.googlecode.dex2jar.reader.io;
2 |
3 | import java.io.IOException;
4 |
5 | public interface DataOut {
6 | void writeByte(int b) throws IOException;
7 |
8 | void writeBytes(byte[] bs) throws IOException;
9 |
10 | void writeInt(int i) throws IOException;
11 |
12 | void writeShort(int i) throws IOException;
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/googlecode/dex2jar/reader/io/LeDataOut.java:
--------------------------------------------------------------------------------
1 | package com.googlecode.dex2jar.reader.io;
2 |
3 | import java.io.IOException;
4 | import java.io.OutputStream;
5 |
6 | public class LeDataOut implements DataOut {
7 |
8 | private OutputStream os;
9 |
10 | public LeDataOut(OutputStream os) {
11 | super();
12 | this.os = os;
13 | }
14 |
15 | @Override
16 | public void writeByte(int v) throws IOException {
17 | os.write(v);
18 | }
19 |
20 | @Override
21 | public void writeBytes(byte[] bs) throws IOException {
22 | os.write(bs);
23 | }
24 |
25 | @Override
26 | public void writeInt(int v) throws IOException {
27 | os.write(v);
28 | os.write(v >> 8);
29 | os.write(v >> 16);
30 | os.write(v >>> 24);
31 | }
32 |
33 | @Override
34 | public void writeShort(int v) throws IOException {
35 | os.write(v);
36 | os.write(v >> 8);
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/pxb/android/axml/Axml.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2012 Panxiaobo
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 | package pxb.android.axml;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 | public class Axml extends AxmlVisitor {
22 |
23 | public static class Node extends NodeVisitor {
24 | public static class Attr {
25 | public String ns, name;
26 | public int resourceId, type;
27 | public Object value;
28 |
29 | public void accept(NodeVisitor nodeVisitor) {
30 | nodeVisitor.visitContentAttr(ns, name, resourceId, type, value);
31 | }
32 | }
33 |
34 | public static class Text {
35 | public int ln;
36 | public String text;
37 |
38 | public void accept(NodeVisitor nodeVisitor) {
39 | nodeVisitor.visitContentText(ln, text);
40 | }
41 | }
42 |
43 | public List attrs = new ArrayList();
44 | public List children = new ArrayList();
45 | public Integer ln;
46 | public String ns, name;
47 | public Text text;
48 |
49 | public void accept(NodeVisitor nodeVisitor) {
50 | NodeVisitor nodeVisitor2 = nodeVisitor.visitChild(ns, name);
51 | nodeVisitor2.visitBegin();
52 | acceptB(nodeVisitor2);
53 | nodeVisitor2.visitEnd();
54 | }
55 |
56 | public void acceptB(NodeVisitor nodeVisitor) {
57 | if (ln != null) {
58 | nodeVisitor.visitLineNumber(ln);
59 | }
60 | for (Attr a : attrs) {
61 | a.accept(nodeVisitor);
62 | }
63 | if (text != null) {
64 | text.accept(nodeVisitor);
65 | }
66 | nodeVisitor.visitContentEnd();
67 | for (Node c : children) {
68 | c.accept(nodeVisitor);
69 | }
70 | }
71 |
72 | @Override
73 | public void visitContentAttr(String ns, String name, int resourceId, int type, Object obj) {
74 | Attr attr = new Attr();
75 | attr.name = name;
76 | attr.ns = ns;
77 | attr.resourceId = resourceId;
78 | attr.type = type;
79 | attr.value = obj;
80 | attrs.add(attr);
81 | }
82 |
83 | @Override
84 | public NodeVisitor visitChild(String ns, String name) {
85 | Node node = new Node();
86 | node.name = name;
87 | node.ns = ns;
88 | children.add(node);
89 | return node;
90 | }
91 |
92 | @Override
93 | public void visitLineNumber(int ln) {
94 | this.ln = ln;
95 | }
96 |
97 | @Override
98 | public void visitContentText(int lineNumber, String value) {
99 | Text text = new Text();
100 | text.ln = lineNumber;
101 | text.text = value;
102 | this.text = text;
103 | }
104 | }
105 |
106 | public static class Ns {
107 | public int ln;
108 | public String prefix, uri;
109 |
110 | public void accept(AxmlVisitor visitor) {
111 | visitor.visitNamespace(prefix, uri, ln);
112 | }
113 | }
114 |
115 | public List firsts = new ArrayList();
116 | public List nses = new ArrayList();
117 |
118 | public void accept(final AxmlVisitor visitor) {
119 | visitor.visitBegin();
120 |
121 | for (Ns ns : nses) {
122 | ns.accept(visitor);
123 | }
124 | for (Node first : firsts) {
125 | first.accept(new NodeVisitor(null) {
126 |
127 | @Override
128 | public NodeVisitor visitChild(String ns, String name) {
129 | return visitor.visitFirst(ns, name);
130 | }
131 | });
132 | }
133 | visitor.visitEnd();
134 | }
135 |
136 | @Override
137 | public NodeVisitor visitFirst(String ns, String name) {
138 | Node node = new Node();
139 | node.name = name;
140 | node.ns = ns;
141 | firsts.add(node);
142 | return node;
143 | }
144 |
145 | @Override
146 | public void visitNamespace(String prefix, String uri, int ln) {
147 | Ns ns = new Ns();
148 | ns.prefix = prefix;
149 | ns.uri = uri;
150 | ns.ln = ln;
151 | nses.add(ns);
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/main/java/pxb/android/axml/AxmlReader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2012 Panxiaobo
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 | package pxb.android.axml;
17 |
18 | import com.googlecode.dex2jar.reader.io.ArrayDataIn;
19 | import com.googlecode.dex2jar.reader.io.DataIn;
20 |
21 | import java.io.IOException;
22 | import java.io.InputStream;
23 | import java.util.ArrayList;
24 | import java.util.List;
25 | import java.util.Stack;
26 |
27 | import static pxb.android.axml.AxmlVisitor.TYPE_INT_BOOLEAN;
28 | import static pxb.android.axml.AxmlVisitor.TYPE_STRING;
29 |
30 | /**
31 | * a class to read android axml
32 | *
33 | * @author Panxiaobo
34 | *
35 | */
36 | public class AxmlReader {
37 | static final int CHUNK_AXML_FILE = 0x00080003;
38 | static final int CHUNK_RESOURCEIDS = 0x00080180;
39 | static final int CHUNK_STRINGS = 0x001C0001;
40 | static final int CHUNK_XML_END_NAMESPACE = 0x00100101;
41 | static final int CHUNK_XML_END_TAG = 0x00100103;
42 | static final int CHUNK_XML_START_NAMESPACE = 0x00100100;
43 | static final int CHUNK_XML_START_TAG = 0x00100102;
44 | static final int CHUNK_XML_TEXT = 0x00100104;
45 | public static final NodeVisitor EMPTY_VISITOR = new NodeVisitor() {
46 |
47 | @Override
48 | public NodeVisitor visitChild(String namespace, String name) {
49 | return EMPTY_VISITOR;
50 | }
51 | };
52 | static final int UTF8_FLAG = 0x00000100;
53 | private DataIn input;
54 | private List resourceIds = new ArrayList();
55 | private StringItems stringItems = new StringItems();
56 |
57 | public AxmlReader(byte[] data) {
58 | this(ArrayDataIn.le(data));
59 | }
60 |
61 | public AxmlReader(DataIn input) {
62 | super();
63 | this.input = input;
64 | }
65 |
66 | public static AxmlReader create(InputStream is){
67 | try{
68 | int available = is.available();
69 | byte[] buffer = new byte[available];
70 |
71 | int len = is.read(buffer);
72 | if(len == available){
73 | return new AxmlReader(buffer);
74 | }
75 | }
76 | catch(Exception e){}
77 | return null;
78 | }
79 |
80 | public void accept(final AxmlVisitor documentVisitor) throws IOException {
81 | DataIn input = this.input;
82 | int fileSize;
83 | {
84 | int type = input.readIntx();
85 | if (type != CHUNK_AXML_FILE) {
86 | throw new RuntimeException();
87 | }
88 | fileSize = input.readIntx();
89 | }
90 | NodeVisitor rootVisitor = documentVisitor == null ? EMPTY_VISITOR : new NodeVisitor() {
91 | @Override
92 | public NodeVisitor visitChild(String namespace, String name) {
93 | return documentVisitor.visitFirst(namespace, name);
94 | }
95 | };
96 |
97 | NodeVisitor stackTop;
98 | Stack nodeVisitorStack = new Stack();
99 | Stack nodeVisitorContentsHandled = new Stack();
100 |
101 | String name, namespace;
102 | int nameIdx, namespaceIdx;
103 | int lineNumber;
104 |
105 | stackTop = rootVisitor;
106 | nodeVisitorStack.push(rootVisitor);
107 | nodeVisitorContentsHandled.push(false);
108 |
109 | documentVisitor.visitBegin();
110 |
111 | for (int position = input.getCurrentPosition(); position < fileSize; position = input.getCurrentPosition()) {
112 | int type = input.readIntx();
113 | int size = input.readIntx();
114 | switch (type) {
115 | case CHUNK_XML_START_TAG: {
116 | {
117 | lineNumber = input.readIntx();
118 | input.skip(4);/* 0xFFFFFFFF */
119 | namespaceIdx = input.readIntx();
120 | nameIdx = input.readIntx();
121 | int flag = input.readIntx();// 0x00140014 ?
122 | if (flag != 0x00140014) {
123 | throw new RuntimeException();
124 | }
125 | name = stringItems.get(nameIdx).data;
126 | namespace = namespaceIdx >= 0 ? stringItems.get(namespaceIdx).data : null;
127 |
128 | if(!nodeVisitorContentsHandled.peek()){
129 | nodeVisitorContentsHandled.pop();
130 | nodeVisitorContentsHandled.push(true);
131 | stackTop.visitContentEnd();
132 | }
133 |
134 | stackTop = stackTop.visitChild(namespace, name);
135 | if (stackTop == null) {
136 | stackTop = EMPTY_VISITOR;
137 | }
138 | nodeVisitorStack.push(stackTop);
139 | nodeVisitorContentsHandled.push(false);
140 | stackTop.visitBegin();
141 | stackTop.visitLineNumber(lineNumber);
142 | }
143 |
144 | int attributeCount = input.readUShortx();
145 | // int idAttribute = input.readUShortx();
146 | // int classAttribute = input.readUShortx();
147 | // int styleAttribute = input.readUShortx();
148 | input.skip(6);
149 | if (stackTop != EMPTY_VISITOR) {
150 | for (int i = 0; i < attributeCount; i++) {
151 | namespaceIdx = input.readIntx();
152 | nameIdx = input.readIntx();
153 | input.skip(4);// skip valueString
154 | int aValueType = input.readIntx() >>> 24;
155 | int aValue = input.readIntx();
156 | name = stringItems.get(nameIdx).data;
157 | namespace = namespaceIdx >= 0 ? stringItems.get(namespaceIdx).data : null;
158 | Object value = null;
159 | switch (aValueType) {
160 | case TYPE_STRING:
161 | value = stringItems.get(aValue).data;
162 | break;
163 | case TYPE_INT_BOOLEAN:
164 | value = aValue != 0;
165 | break;
166 | default:
167 | value = aValue;
168 | }
169 | int resourceId = nameIdx < resourceIds.size() ? resourceIds.get(nameIdx) : -1;
170 | stackTop.visitContentAttr(namespace, name, resourceId, aValueType, value);
171 | }
172 | } else {
173 | input.skip(5 * 4);
174 | }
175 | }
176 | break;
177 | case CHUNK_XML_END_TAG: {
178 | input.skip(size - 8);
179 |
180 | if(!nodeVisitorContentsHandled.peek()){
181 | stackTop.visitContentEnd();
182 | nodeVisitorContentsHandled.pop();
183 | nodeVisitorContentsHandled.push(true);
184 | }
185 | stackTop.visitEnd();
186 |
187 | nodeVisitorStack.pop();
188 | stackTop = nodeVisitorStack.peek();
189 | }
190 | break;
191 | case CHUNK_XML_START_NAMESPACE:
192 | if (documentVisitor == null) {
193 | input.skip(4 * 4);
194 | } else {
195 | lineNumber = input.readIntx();
196 | input.skip(4);/* 0xFFFFFFFF */
197 | int prefixIdx = input.readIntx();
198 | namespaceIdx = input.readIntx();
199 | documentVisitor.visitNamespace(stringItems.get(prefixIdx).data, stringItems.get(namespaceIdx).data, lineNumber);
200 | }
201 | break;
202 | case CHUNK_XML_END_NAMESPACE:
203 | input.skip(size - 8);
204 | break;
205 | case CHUNK_STRINGS:
206 | stringItems.read(input, size);
207 | break;
208 | case CHUNK_RESOURCEIDS:
209 | int count = size / 4 - 2;
210 | for (int i = 0; i < count; i++) {
211 | resourceIds.add(input.readIntx());
212 | }
213 | break;
214 | case CHUNK_XML_TEXT:
215 | if (stackTop == EMPTY_VISITOR) {
216 | input.skip(20);
217 | } else {
218 | lineNumber = input.readIntx();
219 | input.skip(4);/* 0xFFFFFFFF */
220 | nameIdx = input.readIntx();
221 | input.skip(8); /* 00000008 00000000 */
222 | name = stringItems.get(nameIdx).data;
223 | stackTop.visitContentText(lineNumber, name);
224 | }
225 | break;
226 | default:
227 | throw new RuntimeException();
228 | }
229 | input.move(position + size);
230 | }
231 |
232 | documentVisitor.visitEnd();
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/src/main/java/pxb/android/axml/AxmlVisitor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2012 Panxiaobo
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 | package pxb.android.axml;
17 |
18 | /*
19 | AXMLVisitor visiting sequence : visitBegin [visitNamespace]* visitFirstNode visitEnd
20 | NodeVisitor visiting sequence : visitBegin [visitContentAttr | visitContentText]* visitContentEnd visitChildNoe visitEnd
21 | */
22 |
23 | /**
24 | * visitor to visit an axml
25 | *
26 | * @author Panxiaobo
27 | *
28 | */
29 | public class AxmlVisitor {
30 |
31 | public static final int TYPE_FIRST_INT = 0x10;
32 | public static final int TYPE_INT_BOOLEAN = 0x12;
33 | public static final int TYPE_INT_HEX = 0x11;
34 | public static final int TYPE_REFERENCE = 0x01;
35 |
36 | public static final int TYPE_STRING = 0x03;
37 |
38 | protected AxmlVisitor av;
39 |
40 | public AxmlVisitor() {
41 | super();
42 |
43 | }
44 |
45 | public AxmlVisitor(AxmlVisitor av) {
46 | super();
47 | this.av = av;
48 | };
49 |
50 | public void visitBegin(){
51 | if (av != null){
52 | av.visitBegin();
53 | }
54 | }
55 |
56 | /**
57 | * end the visit
58 | */
59 | public void visitEnd() {
60 | if (av != null) {
61 | av.visitEnd();
62 | }
63 | };
64 |
65 | /**
66 | * create the first node
67 | *
68 | * @param namespace
69 | * @param name
70 | * @return
71 | */
72 | public NodeVisitor visitFirst(String namespace, String name) {
73 | if (av != null) {
74 | return av.visitFirst(namespace, name);
75 | }
76 | return null;
77 | }
78 |
79 | /**
80 | * create a namespace
81 | *
82 | * @param prefix
83 | * @param uri
84 | * @param ln
85 | */
86 | public void visitNamespace(String prefix, String uri, int ln) {
87 | if (av != null) {
88 | av.visitNamespace(prefix, uri, ln);
89 | }
90 | }
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/src/main/java/pxb/android/axml/AxmlWriter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2012 Panxiaobo
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 | package pxb.android.axml;
17 |
18 | import com.googlecode.dex2jar.reader.io.DataOut;
19 | import com.googlecode.dex2jar.reader.io.LeDataOut;
20 |
21 | import java.io.ByteArrayOutputStream;
22 | import java.io.IOException;
23 | import java.io.OutputStream;
24 | import java.util.*;
25 |
26 | /**
27 | * a class to write android axml
28 | *
29 | * @author Panxiaobo
30 | *
31 | */
32 | public class AxmlWriter extends AxmlVisitor {
33 | static class Attr {
34 | public StringItem name;
35 | public StringItem ns;
36 | public int resourceId;
37 | public int type;
38 | public Object value;
39 |
40 | public Attr(StringItem ns, StringItem name, int resourceId, int type, Object value) {
41 | super();
42 | this.ns = ns;
43 | this.name = name;
44 | this.resourceId = resourceId;
45 | this.type = type;
46 | this.value = value;
47 | }
48 |
49 | public void prepare(AxmlWriter axmlWriter) {
50 | ns = axmlWriter.updateNs(ns);
51 | if (this.name != null) {
52 | if (resourceId != -1) {
53 | this.name = axmlWriter.updateWithResourceId(this.name, this.resourceId);
54 | } else {
55 | this.name = axmlWriter.update(this.name);
56 | }
57 | }
58 | if (value instanceof StringItem) {
59 | value = axmlWriter.update((StringItem) value);
60 | }
61 | }
62 | }
63 |
64 | static class NodeImpl extends NodeVisitor {
65 | private Map attrs = new HashMap();
66 | private List children = new ArrayList();
67 | private int line;
68 | private StringItem name;
69 | private StringItem ns;
70 | private StringItem text;
71 | private int textLineNumber;
72 |
73 | public NodeImpl(String ns, String name) {
74 | super(null);
75 | this.ns = ns == null ? null : new StringItem(ns);
76 | this.name = name == null ? null : new StringItem(name);
77 | }
78 |
79 | @Override
80 | public void visitContentAttr(String ns, String name, int resourceId, int type, Object value) {
81 | if (name == null) {
82 | throw new RuntimeException("name can't be null");
83 | }
84 | attrs.put((ns == null ? "zzz" : ns) + "." + name, new Attr(ns == null ? null : new StringItem(ns),
85 | new StringItem(name), resourceId, type, type == TYPE_STRING ? new StringItem((String) value)
86 | : value));
87 | }
88 |
89 | @Override
90 | public NodeVisitor visitChild(String ns, String name) {
91 | NodeImpl child = new NodeImpl(ns, name);
92 | this.children.add(child);
93 | return child;
94 | }
95 |
96 | @Override
97 | public void visitEnd() {
98 | }
99 |
100 | @Override
101 | public void visitLineNumber(int ln) {
102 | this.line = ln;
103 | }
104 |
105 | public int prepare(AxmlWriter axmlWriter) {
106 | ns = axmlWriter.updateNs(ns);
107 | name = axmlWriter.update(name);
108 |
109 | for (Attr attr : this.sortedAttrs()) {
110 | attr.prepare(axmlWriter);
111 | }
112 | text = axmlWriter.update(text);
113 | int size = 24 + 36 + attrs.size() * 20;// 24 for end tag,36+x*20 for
114 | // start tag
115 | for (NodeImpl child : children) {
116 | size += child.prepare(axmlWriter);
117 | }
118 | if (text != null) {
119 | size += 28;
120 | }
121 | return size;
122 | }
123 |
124 | List sortedAttrs() {
125 | List lAttrs = new ArrayList(attrs.values());
126 | Collections.sort(lAttrs, new Comparator() {
127 |
128 | @Override
129 | public int compare(Attr a, Attr b) {
130 | if (a.ns == null) {
131 | if (b.ns == null) {
132 | return b.name.data.compareTo(a.name.data);
133 | } else {
134 | return 1;
135 | }
136 | } else if (b.ns == null) {
137 | return -1;
138 | } else {
139 | int x = a.ns.data.compareTo(b.ns.data);
140 | if (x == 0) {
141 | x = a.resourceId - b.resourceId;
142 | if (x == 0) {
143 | return a.name.data.compareTo(b.name.data);
144 | }
145 | }
146 | return x;
147 | }
148 | }
149 | });
150 | return lAttrs;
151 | }
152 |
153 | @Override
154 | public void visitContentText(int ln, String value) {
155 | this.text = new StringItem(value);
156 | this.textLineNumber = ln;
157 | }
158 |
159 | void write(DataOut out) throws IOException {
160 | // start tag
161 | out.writeInt(AxmlReader.CHUNK_XML_START_TAG);
162 | out.writeInt(36 + attrs.size() * 20);
163 | out.writeInt(line);
164 | out.writeInt(0xFFFFFFFF);
165 | out.writeInt(ns != null ? this.ns.index : -1);
166 | out.writeInt(name.index);
167 | out.writeInt(0x00140014);// TODO
168 | out.writeShort(this.attrs.size());
169 | out.writeShort(0);
170 | out.writeShort(0);
171 | out.writeShort(0);
172 | for (Attr attr : this.sortedAttrs()) {
173 | out.writeInt(attr.ns == null ? -1 : attr.ns.index);
174 | out.writeInt(attr.name.index);
175 | out.writeInt(attr.value instanceof StringItem ? ((StringItem) attr.value).index : -1);
176 | out.writeInt((attr.type << 24) | 0x000008);
177 | Object v = attr.value;
178 | if (v instanceof StringItem) {
179 | out.writeInt(((StringItem) attr.value).index);
180 | } else if (v instanceof Boolean) {
181 | out.writeInt(Boolean.TRUE.equals(v) ? -1 : 0);
182 | } else {
183 | out.writeInt((Integer) attr.value);
184 | }
185 | }
186 |
187 | if (this.text != null) {
188 | out.writeInt(AxmlReader.CHUNK_XML_TEXT);
189 | out.writeInt(28);
190 | out.writeInt(textLineNumber);
191 | out.writeInt(0xFFFFFFFF);
192 | out.writeInt(text.index);
193 | out.writeInt(0x00000008);
194 | out.writeInt(0x00000000);
195 | }
196 |
197 | // children
198 | for (NodeImpl child : children) {
199 | child.write(out);
200 | }
201 |
202 | // end tag
203 | out.writeInt(AxmlReader.CHUNK_XML_END_TAG);
204 | out.writeInt(24);
205 | out.writeInt(-1);
206 | out.writeInt(0xFFFFFFFF);
207 | out.writeInt(ns != null ? this.ns.index : -1);
208 | out.writeInt(name.index);
209 | }
210 | }
211 |
212 | static class Ns {
213 | int ln;
214 | StringItem prefix;
215 | StringItem uri;
216 |
217 | public Ns(StringItem prefix, StringItem uri, int ln) {
218 | super();
219 | this.prefix = prefix;
220 | this.uri = uri;
221 | this.ln = ln;
222 | }
223 | }
224 |
225 | private List firsts = new ArrayList(3);
226 |
227 | private Map nses = new HashMap();
228 |
229 | private List otherString = new ArrayList();
230 |
231 | private Map resourceId2Str = new HashMap();
232 |
233 | private List resourceIds = new ArrayList();
234 |
235 | private List resourceString = new ArrayList();
236 |
237 | private StringItems stringItems = new StringItems();
238 |
239 | // TODO add style support
240 | // private List styleItems = new ArrayList();
241 |
242 | @Override
243 | public void visitEnd() {
244 | }
245 |
246 | @Override
247 | public NodeVisitor visitFirst(String ns, String name) {
248 | NodeImpl first = new NodeImpl(ns, name);
249 | this.firsts.add(first);
250 | return first;
251 | }
252 |
253 | @Override
254 | public void visitNamespace(String prefix, String uri, int ln) {
255 | nses.put(uri, new Ns(new StringItem(prefix), new StringItem(uri), ln));
256 | }
257 |
258 | private int prepare() throws IOException {
259 |
260 | int size = nses.size() * 24 * 2;
261 | for (NodeImpl first : firsts) {
262 | size += first.prepare(this);
263 | }
264 | {
265 | int a = 0;
266 | for (Map.Entry e : nses.entrySet()) {
267 | Ns ns = e.getValue();
268 | if (ns == null) {
269 | ns = new Ns(new StringItem(String.format("axml_auto_%02d", a++)), new StringItem(e.getKey()), 0);
270 | e.setValue(ns);
271 | }
272 | ns.prefix = update(ns.prefix);
273 | ns.uri = update(ns.uri);
274 | }
275 | }
276 | this.stringItems.addAll(resourceString);
277 | resourceString = null;
278 | this.stringItems.addAll(otherString);
279 | otherString = null;
280 | this.stringItems.prepare();
281 | int stringSize = this.stringItems.getSize();
282 | if (stringSize % 4 != 0) {
283 | stringSize += 4 - stringSize % 4;
284 | }
285 | size += 8 + stringSize;
286 | size += 8 + resourceIds.size() * 4;
287 | return size;
288 | }
289 |
290 | public void writeTo(OutputStream os) throws IOException{
291 | DataOut out = new LeDataOut(os);
292 | int size = prepare();
293 | out.writeInt(AxmlReader.CHUNK_AXML_FILE);
294 | out.writeInt(size + 8);
295 |
296 | int stringSize = this.stringItems.getSize();
297 | int padding = 0;
298 | if (stringSize % 4 != 0) {
299 | padding = 4 - stringSize % 4;
300 | }
301 | out.writeInt(AxmlReader.CHUNK_STRINGS);
302 | out.writeInt(stringSize + padding + 8);
303 | this.stringItems.write(out);
304 | out.writeBytes(new byte[padding]);
305 |
306 | out.writeInt(AxmlReader.CHUNK_RESOURCEIDS);
307 | out.writeInt(8 + this.resourceIds.size() * 4);
308 | for (Integer i : resourceIds) {
309 | out.writeInt(i);
310 | }
311 |
312 | Stack stack = new Stack();
313 | for (Map.Entry e : this.nses.entrySet()) {
314 | Ns ns = e.getValue();
315 | stack.push(ns);
316 | out.writeInt(AxmlReader.CHUNK_XML_START_NAMESPACE);
317 | out.writeInt(24);
318 | out.writeInt(-1);
319 | out.writeInt(0xFFFFFFFF);
320 | out.writeInt(ns.prefix.index);
321 | out.writeInt(ns.uri.index);
322 | }
323 |
324 | for (NodeImpl first : firsts) {
325 | first.write(out);
326 | }
327 |
328 | while (stack.size() > 0) {
329 | Ns ns = stack.pop();
330 | out.writeInt(AxmlReader.CHUNK_XML_END_NAMESPACE);
331 | out.writeInt(24);
332 | out.writeInt(ns.ln);
333 | out.writeInt(0xFFFFFFFF);
334 | out.writeInt(ns.prefix.index);
335 | out.writeInt(ns.uri.index);
336 | }
337 | }
338 |
339 | public byte[] toByteArray() throws IOException {
340 | ByteArrayOutputStream os = new ByteArrayOutputStream();
341 | this.writeTo(os);
342 | return os.toByteArray();
343 | }
344 |
345 | StringItem update(StringItem item) {
346 | if (item == null)
347 | return null;
348 | int i = this.otherString.indexOf(item);
349 | if (i < 0) {
350 | StringItem copy = new StringItem(item.data);
351 | this.otherString.add(copy);
352 | return copy;
353 | } else {
354 | return this.otherString.get(i);
355 | }
356 | }
357 |
358 | StringItem updateNs(StringItem item) {
359 | if (item == null) {
360 | return null;
361 | }
362 | String ns = item.data;
363 | if (!this.nses.containsKey(ns)) {
364 | this.nses.put(ns, null);
365 | }
366 | return update(item);
367 | }
368 |
369 | StringItem updateWithResourceId(StringItem name, int resourceId) {
370 | StringItem item = this.resourceId2Str.get(resourceId);
371 | if (item != null) {
372 | return item;
373 | } else {
374 | StringItem copy = new StringItem(name.data);
375 | resourceIds.add(resourceId);
376 | resourceString.add(copy);
377 | resourceId2Str.put(resourceId, copy);
378 | return copy;
379 | }
380 | }
381 | }
382 |
--------------------------------------------------------------------------------
/src/main/java/pxb/android/axml/DumpAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2012 Panxiaobo
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 | package pxb.android.axml;
17 |
18 | import java.io.PrintWriter;
19 | import java.io.Writer;
20 | import java.util.HashMap;
21 | import java.util.Map;
22 |
23 | /**
24 | * dump axml to stdout
25 | *
26 | * @author Panxiaobo
27 | *
28 | */
29 | public class DumpAdapter extends AxmlVisitor {
30 |
31 | /**
32 | * dump a node to stdout
33 | *
34 | * @author Panxiaobo
35 | *
36 | */
37 | public static class DumpNodeAdapter extends NodeVisitor {
38 | protected final int depth;
39 | protected Map nses;
40 | protected String namespace;
41 | protected String name;
42 | protected PrintWriter writer;
43 |
44 | public DumpNodeAdapter(PrintWriter w, NodeVisitor nv, String namespace, String name) {
45 | super(nv);
46 | this.depth = 0;
47 | this.nses = null;
48 | this.namespace = namespace;
49 | this.name = name;
50 | this.writer = w;
51 | }
52 |
53 | public DumpNodeAdapter(PrintWriter w, NodeVisitor nv, int x, Map nses, String namespace, String name) {
54 | super(nv);
55 | this.depth = x;
56 | this.nses = nses;
57 | this.namespace = namespace;
58 | this.name = name;
59 | this.writer = w;
60 | }
61 |
62 | @Override
63 | public void visitBegin(){
64 | indent();
65 | writer.print("<");
66 | if (namespace != null) {
67 | writer.println(getPrefix(namespace) + ":");
68 | }
69 | writer.print(name);
70 | super.visitBegin();
71 | }
72 |
73 |
74 | @Override
75 | public void visitContentAttr(String ns, String name, int resourceId, int type, Object obj) {
76 | writer.println();
77 |
78 | indentContent();
79 | if (ns != null) {
80 | writer.print(String.format("%s:", getPrefix(ns)));
81 | }
82 | writer.print(name);
83 | if (resourceId != -1) {
84 | writer.print(String.format("(%08x)", resourceId));
85 | }
86 | if (obj instanceof String) {
87 | writer.print(String.format("=[%08x]\"%s\"", type, obj));
88 | } else if (obj instanceof Boolean) {
89 | writer.print(String.format("=[%08x]\"%b\"", type, obj));
90 | } else {
91 | writer.print(String.format("=[%08x]%08x", type, obj));
92 | }
93 |
94 | //writer.println();
95 | super.visitContentAttr(ns, name, resourceId, type, obj);
96 | }
97 |
98 | @Override
99 | public void visitContentEnd(){
100 | writer.println(">");
101 | super.visitContentEnd();
102 | }
103 |
104 | @Override
105 | public NodeVisitor visitChild(String namespace, String name) {
106 | NodeVisitor nv = super.visitChild(namespace, name);
107 | return new DumpNodeAdapter(writer, nv, depth + 1, nses, namespace, name);
108 | }
109 |
110 | protected String getPrefix(String uri) {
111 | if (nses != null) {
112 | String prefix = nses.get(uri);
113 | if (prefix != null) {
114 | return prefix;
115 | }
116 | }
117 | return uri;
118 | }
119 |
120 | @Override
121 | public void visitContentText(int ln, String value) {
122 | writer.println();
123 |
124 | indentContent();
125 | writer.print(value);
126 | super.visitContentText(ln, value);
127 | }
128 |
129 | @Override
130 | public void visitLineNumber(int n){
131 | writer.println();
132 | indentContent();
133 | writer.print("L"+n);
134 | super.visitLineNumber(n);
135 | }
136 |
137 | @Override
138 | public void visitEnd(){
139 | indent();
140 | writer.println(""+name+">");
141 | super.visitEnd();
142 | }
143 |
144 | private void indent(){
145 | for (int i = 0; i < depth; i++) {
146 | writer.print(" ");
147 | }
148 | }
149 |
150 | private void indentContent(){
151 | indent();
152 | writer.print(" ");
153 | }
154 | }
155 |
156 | private Map nses = new HashMap();
157 | private PrintWriter writer = null;
158 |
159 | public DumpAdapter() {
160 | setupWriter(null);
161 | }
162 |
163 | public DumpAdapter(Writer w) {
164 | setupWriter(w);
165 | }
166 |
167 | public DumpAdapter(Writer w, AxmlVisitor av) {
168 | super(av);
169 | setupWriter(w);
170 | }
171 |
172 | private void setupWriter(Writer w){
173 | if(w == null){
174 | writer = new PrintWriter(System.out, true);
175 | }
176 | else{
177 | writer = new PrintWriter(w);
178 | }
179 | }
180 |
181 | @Override
182 | public void visitBegin(){
183 | writer.println("");
184 | super.visitBegin();
185 | }
186 |
187 | @Override
188 | public NodeVisitor visitFirst(String namespace, String name) {
189 | NodeVisitor nv = super.visitFirst(namespace, name);
190 | //if (nv != null) {
191 | DumpNodeAdapter x = new DumpNodeAdapter(writer, nv, 1, nses, namespace, name);
192 | return x;
193 | //}
194 | //else{
195 | // return new DumpNodeAdapter(null, 1, nses);
196 | //}
197 | }
198 |
199 |
200 | @Override
201 | public void visitNamespace(String prefix, String uri, int ln) {
202 | writer.println("xmlns:" + prefix + "=" + uri);
203 | this.nses.put(uri, prefix);
204 | super.visitNamespace(prefix, uri, ln);
205 | }
206 |
207 | @Override
208 | public void visitEnd(){
209 | writer.println("");
210 | super.visitEnd();
211 | }
212 |
213 | }
214 |
--------------------------------------------------------------------------------
/src/main/java/pxb/android/axml/EnableDebugger.java:
--------------------------------------------------------------------------------
1 | package pxb.android.axml;
2 |
3 | import java.io.File;
4 | import java.io.FileInputStream;
5 | import java.io.FileOutputStream;
6 | import java.io.InputStream;
7 |
8 |
9 | public class EnableDebugger {
10 | // @Test
11 | // public void test() throws Exception {
12 | // a(new File("src/test/resources/a.axml"), new File("target/a-debug.axml"));
13 | // }
14 |
15 | public static void main(String... args) throws Exception {
16 | if (args.length < 2) {
17 | System.err.println("test5 in out");
18 | return;
19 | }
20 | new EnableDebugger().a(new File(args[0]), new File(args[1]));
21 | }
22 |
23 | void a(File a, File b) throws Exception {
24 | InputStream is = new FileInputStream(a);
25 | byte[] xml = new byte[is.available()];
26 | is.read(xml);
27 | is.close();
28 |
29 | AxmlReader rd = new AxmlReader(xml);
30 | AxmlWriter wr = new AxmlWriter();
31 | rd.accept(new AxmlVisitor(wr) {
32 |
33 | @Override
34 | public NodeVisitor visitFirst(String ns, String name) {// manifest
35 | return new NodeVisitor(super.visitFirst(ns, name)) {
36 |
37 | @Override
38 | public NodeVisitor visitChild(String ns, String name) {// application
39 | return new NodeVisitor(super.visitChild(ns, name)) {
40 |
41 | @Override
42 | public void visitContentAttr(String ns, String name, int resourceId, int type, Object obj) {
43 | if ("http://schemas.android.com/apk/res/android".equals(ns)
44 | && "debuggable".equals(name)) {
45 | return;
46 | }
47 | super.visitContentAttr(ns, name, resourceId, type, obj);
48 | }
49 |
50 | @Override
51 | public void visitEnd() {
52 | // android:debuggable(0x0101000f)=(type 0x12)0xffffffff
53 | super.visitContentAttr("http://schemas.android.com/apk/res/android", "debuggable", 0x0101000f,
54 | 0x12, 0xffffffff);
55 | super.visitEnd();
56 | }
57 | };
58 | }
59 | };
60 | }
61 |
62 | });
63 | byte[] modified = wr.toByteArray();
64 | FileOutputStream fos = new FileOutputStream(b);
65 | fos.write(modified);
66 | fos.close();
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/pxb/android/axml/NodeVisitor.java:
--------------------------------------------------------------------------------
1 | package pxb.android.axml;
2 |
3 | /**
4 | * Created with IntelliJ IDEA.
5 | * User: wtchoi
6 | * Date: 10/20/12
7 | * Time: 1:30 AM
8 | * To change this template use File | Settings | File Templates.
9 | */
10 | public class NodeVisitor {
11 | protected NodeVisitor nv;
12 |
13 | public NodeVisitor() {
14 | super();
15 | }
16 |
17 | public NodeVisitor(NodeVisitor nv) {
18 | super();
19 | this.nv = nv;
20 | }
21 |
22 | public void visitBegin(){
23 | if( nv != null ){
24 | nv.visitBegin();
25 | }
26 | }
27 |
28 | /**
29 | * add attribute to the node
30 | *
31 | * @param ns
32 | * @param name
33 | * @param resourceId
34 | * @param type
35 | * {@link pxb.android.axml.AxmlVisitor#TYPE_STRING} or others
36 | * @param obj
37 | * a string for {@link pxb.android.axml.AxmlVisitor#TYPE_STRING} ,and Integer for others
38 | */
39 | public void visitContentAttr(String ns, String name, int resourceId, int type, Object obj) {
40 | if (nv != null) {
41 | nv.visitContentAttr(ns, name, resourceId, type, obj);
42 | }
43 | }
44 |
45 | public void visitContentEnd(){
46 | if(nv != null){
47 | nv.visitContentEnd();
48 | }
49 | }
50 |
51 | /**
52 | * create a child node
53 | *
54 | * @param ns
55 | * @param name
56 | * @return
57 | */
58 | public NodeVisitor visitChild(String ns, String name) {
59 | if (nv != null) {
60 | return nv.visitChild(ns, name);
61 | }
62 | return null;
63 | }
64 |
65 | /**
66 | * end the visit
67 | */
68 | public void visitEnd() {
69 | if (nv != null) {
70 | nv.visitEnd();
71 | }
72 | }
73 |
74 | /**
75 | * line number in the .xml
76 | *
77 | * @param ln
78 | */
79 | public void visitLineNumber(int ln) {
80 | if (nv != null) {
81 | nv.visitLineNumber(ln);
82 | }
83 | }
84 |
85 | /**
86 | * the node text
87 | *
88 | * @param value
89 | */
90 | public void visitContentText(int lineNumber, String value) {
91 | if (nv != null) {
92 | nv.visitContentText(lineNumber, value);
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/main/java/pxb/android/axml/StringItem.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2012 Panxiaobo
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 | package pxb.android.axml;
17 |
18 | class StringItem {
19 | public String data;
20 | public int dataOffset;
21 | public int index;
22 |
23 | public StringItem() {
24 | super();
25 | }
26 |
27 | public StringItem(String data) {
28 | super();
29 | this.data = data;
30 | }
31 |
32 | @Override
33 | public boolean equals(Object obj) {
34 | StringItem b = (StringItem) obj;
35 | return b.data.equals(data);
36 | }
37 |
38 | @Override
39 | public int hashCode() {
40 | return data.hashCode();
41 | }
42 |
43 | public String toString() {
44 | return String.format("S%04d %s", index, data);
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/pxb/android/axml/StringItems.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2009-2012 Panxiaobo
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 | package pxb.android.axml;
17 |
18 | import java.io.ByteArrayOutputStream;
19 | import java.io.IOException;
20 | import java.util.ArrayList;
21 | import java.util.HashMap;
22 | import java.util.Map;
23 | import java.util.TreeMap;
24 |
25 | import com.googlecode.dex2jar.reader.io.DataIn;
26 | import com.googlecode.dex2jar.reader.io.DataOut;
27 |
28 | @SuppressWarnings("serial")
29 | class StringItems extends ArrayList {
30 |
31 | byte[] stringData;
32 |
33 | public int getSize() {
34 | return 5 * 4 + this.size() * 4 + stringData.length + 0;// TODO
35 | }
36 |
37 | public void prepare() throws IOException {
38 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
39 | int i = 0;
40 | int offset = 0;
41 | baos.reset();
42 | Map map = new HashMap();
43 | for (StringItem item : this) {
44 | item.index = i++;
45 | String stringData = item.data;
46 | Integer of = map.get(stringData);
47 | if (of != null) {
48 | item.dataOffset = of;
49 | } else {
50 | item.dataOffset = offset;
51 | map.put(stringData, offset);
52 | int length = stringData.length();
53 | byte[] data = stringData.getBytes("UTF-16LE");
54 | baos.write(length);
55 | baos.write(length >> 8);
56 | baos.write(data);
57 | baos.write(0);
58 | baos.write(0);
59 | offset += 4 + data.length;
60 | }
61 | }
62 | // TODO
63 | stringData = baos.toByteArray();
64 | }
65 |
66 | public void read(DataIn in, int size) throws IOException {
67 | int trunkOffset = in.getCurrentPosition() - 4;
68 | int stringCount = in.readIntx();
69 | int styleOffsetCount = in.readIntx();
70 | int flags = in.readIntx();
71 | int stringDataOffset = in.readIntx();
72 | int stylesOffset = in.readIntx();
73 | for (int i = 0; i < stringCount; i++) {
74 | StringItem stringItem = new StringItem();
75 | stringItem.index = i;
76 | stringItem.dataOffset = in.readIntx();
77 | this.add(stringItem);
78 | }
79 | Map stringMap = new TreeMap();
80 | if (styleOffsetCount != 0) {
81 | throw new RuntimeException();
82 | // for (int i = 0; i < styleOffsetCount; i++) {
83 | // StringItem stringItem = new StringItem();
84 | // stringItem.index = i;
85 | // stringItems.add(stringItem);
86 | // }
87 | }
88 | int endOfStringData = stylesOffset == 0 ? size : stylesOffset;
89 | int base = in.getCurrentPosition();
90 | if (0 != (flags & AxmlReader.UTF8_FLAG)) {
91 | for (int p = base; p < endOfStringData; p = in.getCurrentPosition()) {
92 | int length = (int) in.readLeb128();
93 | ByteArrayOutputStream bos = new ByteArrayOutputStream(length + 10);
94 | for (int r = in.readByte(); r != 0; r = in.readByte()) {
95 | bos.write(r);
96 | }
97 | String value = new String(bos.toByteArray(), "UTF-8");
98 | stringMap.put(p - base, value);
99 | }
100 | } else {
101 | for (int p = base; p < endOfStringData; p = in.getCurrentPosition()) {
102 | int length = in.readShortx();
103 | byte[] data = in.readBytes(length * 2);
104 | in.skip(2);
105 | String value = new String(data, "UTF-16LE");
106 | stringMap.put(p - base, value);
107 | // System.out.println(String.format("%08x %s", p - base, value));
108 | }
109 | }
110 | if (stylesOffset != 0) {
111 | throw new RuntimeException();
112 | }
113 | for (StringItem item : this) {
114 | item.data = stringMap.get(item.dataOffset);
115 | // System.out.println(item);
116 | }
117 | }
118 |
119 | public void write(DataOut out) throws IOException {
120 | out.writeInt(this.size());
121 | out.writeInt(0);// TODO
122 | out.writeInt(0);
123 | out.writeInt(7 * 4 + this.size() * 4);
124 | out.writeInt(0);
125 | for (StringItem item : this) {
126 | out.writeInt(item.dataOffset);
127 | }
128 | out.writeBytes(stringData);
129 | // TODO
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/test/java/pxb/android/axml/test/Test2.java:
--------------------------------------------------------------------------------
1 | package pxb.android.axml.test;
2 |
3 | import com.googlecode.dex2jar.reader.io.ArrayDataIn;
4 | import org.junit.Test;
5 | import pxb.android.axml.Axml;
6 | import pxb.android.axml.AxmlReader;
7 | import pxb.android.axml.AxmlWriter;
8 | import pxb.android.axml.DumpAdapter;
9 |
10 | import java.io.File;
11 | import java.io.FileInputStream;
12 | import java.io.InputStream;
13 | import java.io.StringWriter;
14 |
15 | public class Test2 {
16 | @Test
17 | public void test() throws Exception {
18 | for (File file : new File("src/test/resources/").listFiles()) {
19 | if (file.getName().endsWith(".axml")) {
20 | System.out.println("======= test0 " + file);
21 | InputStream is = new FileInputStream(file);
22 | byte[] xml = new byte[is.available()];
23 | is.read(xml);
24 | is.close();
25 | AxmlReader rd = new AxmlReader(ArrayDataIn.le(xml));
26 | AxmlWriter wr = new AxmlWriter();
27 |
28 | //initial parsed
29 | System.out.println("==== A");
30 | StringWriter writerA = new StringWriter();
31 | Axml axml1 = new Axml();
32 | rd.accept(axml1);
33 | axml1.accept(new DumpAdapter(writerA));
34 | System.out.println(writerA);
35 |
36 | //write and re-parsed
37 | System.out.println("==== B");
38 | axml1.accept(wr);
39 | StringWriter writerB = new StringWriter();
40 | Axml axml2 = new Axml();
41 | new AxmlReader(ArrayDataIn.le(wr.toByteArray())).accept(axml2);
42 | axml2.accept(new DumpAdapter(writerB));
43 | System.out.println(writerB);
44 |
45 | // Assert.assertTrue(writerA.toString().compareTo(writerB.toString()) == 0);
46 |
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/test/java/pxb/android/axml/test/Test3.java:
--------------------------------------------------------------------------------
1 | package pxb.android.axml.test;
2 |
3 | import com.googlecode.dex2jar.reader.io.ArrayDataIn;
4 | import org.junit.Test;
5 | import pxb.android.axml.AxmlReader;
6 | import pxb.android.axml.AxmlVisitor;
7 | import pxb.android.axml.DumpAdapter;
8 | import pxb.android.axml.NodeVisitor;
9 |
10 | import java.io.File;
11 | import java.io.FileInputStream;
12 | import java.io.InputStream;
13 |
14 | public class Test3 {
15 | @Test
16 | public void test0() throws Exception {
17 | for (File file : new File("src/test/resources/").listFiles()) {
18 | if (file.getName().endsWith(".axml")) {
19 | System.out.println("======= test " + file);
20 | InputStream is = new FileInputStream(file);
21 | byte[] xml = new byte[is.available()];
22 | is.read(xml);
23 | is.close();
24 | AxmlReader rd = new AxmlReader(ArrayDataIn.le(xml));
25 | rd.accept(null);
26 | }
27 | }
28 | }
29 |
30 | @Test
31 | public void test1() throws Exception {
32 | for (File file : new File("src/test/resources/").listFiles()) {
33 | if (file.getName().endsWith(".axml")) {
34 | System.out.println("======= test " + file);
35 | InputStream is = new FileInputStream(file);
36 | byte[] xml = new byte[is.available()];
37 | is.read(xml);
38 | is.close();
39 | AxmlReader rd = new AxmlReader(ArrayDataIn.le(xml));
40 | rd.accept(new AxmlVisitor(new DumpAdapter()) {
41 |
42 | @Override
43 | public NodeVisitor visitFirst(String ns, String name) {
44 | return null;
45 | }
46 | });
47 | }
48 | }
49 | }
50 |
51 | @Test
52 | public void test2() throws Exception {
53 | for (File file : new File("src/test/resources/").listFiles()) {
54 | if (file.getName().endsWith(".axml")) {
55 | System.out.println("======= test " + file);
56 | InputStream is = new FileInputStream(file);
57 | byte[] xml = new byte[is.available()];
58 | is.read(xml);
59 | is.close();
60 | AxmlReader rd = new AxmlReader(ArrayDataIn.le(xml));
61 | rd.accept(new AxmlVisitor(new DumpAdapter()) {
62 |
63 | @Override
64 | public NodeVisitor visitFirst(String ns, String name) {
65 | return new NodeVisitor(super.visitFirst(ns, name)) {
66 |
67 | @Override
68 | public NodeVisitor visitChild(String ns, String name) {
69 | return null;
70 | }
71 | };
72 | }
73 | });
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/test/java/pxb/android/axml/test/Test4.java:
--------------------------------------------------------------------------------
1 | package pxb.android.axml.test;
2 |
3 | import org.junit.Test;
4 | import pxb.android.axml.AxmlReader;
5 | import pxb.android.axml.AxmlWriter;
6 | import pxb.android.axml.DumpAdapter;
7 | import pxb.android.axml.NodeVisitor;
8 |
9 | import java.io.IOException;
10 |
11 | public class Test4 {
12 | @Test
13 | public void test() throws IOException {
14 | AxmlWriter aw = new AxmlWriter();
15 | NodeVisitor nv = aw.visitFirst("http://abc.com", "abc");
16 | nv.visitEnd();
17 | nv = aw.visitFirst("http://efg.com", "efg");
18 | nv.visitEnd();
19 | aw.visitEnd();
20 | AxmlReader ar = new AxmlReader(aw.toByteArray());
21 | ar.accept(new DumpAdapter());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/test/java/pxb/android/axml/test/Test5.java:
--------------------------------------------------------------------------------
1 | package pxb.android.axml.test;
2 |
3 |
4 | import com.googlecode.dex2jar.reader.io.ArrayDataIn;
5 | import org.junit.Test;
6 | import pxb.android.axml.*;
7 |
8 | import java.io.File;
9 | import java.io.FileInputStream;
10 | import java.io.InputStream;
11 |
12 | /**
13 | * Created with IntelliJ IDEA.
14 | * User: wtchoi
15 | * Date: 10/19/12
16 | * Time: 10:09 PM
17 | * To change this template use File | Settings | File Templates.
18 | */
19 | public class Test5 {
20 | @Test
21 | public void test() throws Exception{
22 | System.out.println("TEST5");
23 | for(File file: new File("src/test/resources/").listFiles()){
24 | if(file.getName().compareTo("manifest1.axml") == 0){
25 | InputStream is = new FileInputStream(file);
26 | byte[] xml = new byte[is.available()];
27 | is.read(xml);
28 | is.close();
29 | AxmlReader rd = new AxmlReader(ArrayDataIn.le(xml));
30 | AxmlWriter wr = new AxmlWriter();
31 | AxmlDecoder av = new AxmlDecoder(new DumpAdapter(null, wr));
32 | rd.accept(av);
33 |
34 | System.out.println(av.getMainActivityName());
35 | }
36 | }
37 | }
38 | }
39 |
40 | class AxmlDecoder extends AxmlVisitor{
41 |
42 | private static String androidNS = "http://schemas.android.com/apk/res/android";
43 |
44 | public AxmlDecoder(AxmlVisitor visitor){
45 | super(visitor);
46 | }
47 |
48 | public static class NodeDecoder extends NodeVisitor{
49 | private AxmlDecoder root;
50 | private NodeDecoder parent;
51 | private String namespace;
52 | private String name;
53 | private Object aux;
54 |
55 | private static boolean permissionHandled = false;
56 | private static boolean screeDensityHandled = false;
57 |
58 | private boolean hasActionMainAttr = false;
59 | private boolean hasCategoryLauncherAttr = false;
60 |
61 | public NodeDecoder(String namespace, String name, NodeDecoder parent, AxmlDecoder root, NodeVisitor nv){
62 | super(nv);
63 | this.namespace = namespace;
64 | this.name = name;
65 | this.parent = parent;
66 | this.root = root;
67 | }
68 |
69 | @Override
70 | public NodeVisitor visitChild(String namespace, String name){
71 | NodeVisitor nv = super.visitChild(namespace, name);
72 | return new NodeDecoder(namespace, name, this, root, nv);
73 | }
74 |
75 | @Override
76 | public void visitContentAttr(String namespace, String name, int resourceId, int type, Object obj){
77 | if(this.name.compareTo("supports-screens") == 0
78 | && namespace.compareTo(androidNS) == 0
79 | && name.compareTo("anyDensity") == 0){
80 | super.visitContentAttr(namespace, name, resourceId, type, true);
81 | screeDensityHandled = true;
82 | }
83 | else{
84 | super.visitContentAttr(namespace, name, resourceId, type, obj);
85 | if(this.name.compareTo("uses-permission") == 0
86 | && namespace.compareTo(androidNS) == 0
87 | && name.compareTo("name") == 0
88 | && ((String)obj).compareTo("android.permission.INTERNET") == 0) {
89 | permissionHandled = true;
90 | }
91 | }
92 |
93 | //check main activity
94 | if(this.parent != null && this.parent.name.compareTo("intent-filter") == 0){
95 | if(this.name.compareTo("action") == 0
96 | && namespace.compareTo(androidNS) == 0
97 | && name.compareTo("name") == 0
98 | && ((String) obj).compareTo("android.intent.action.MAIN") == 0){
99 | parent.parent.hasActionMainAttr = true;
100 | }
101 | if(this.name.compareTo("category") == 0
102 | && namespace.compareTo(androidNS) == 0
103 | && name.compareTo("name") == 0
104 | && ((String) obj).compareTo("android.intent.category.LAUNCHER") == 0){
105 | parent.parent.hasCategoryLauncherAttr = true;
106 | }
107 | }
108 |
109 | if(this.name.compareTo("activity") == 0
110 | && namespace.compareTo(androidNS) == 0
111 | && name.compareTo("name") == 0){
112 | aux = obj;
113 | }
114 |
115 | if(this.name.compareTo("manifest") == 0
116 | && name.compareTo("package") == 0){
117 | root.setPackage((String)obj);
118 | }
119 | }
120 |
121 | @Override
122 | public void visitContentEnd(){
123 | if(this.name.compareTo("supports-screens") == 0 && !screeDensityHandled){
124 | super.visitContentAttr("android", "anyDensity", 0x0101026c, AxmlVisitor.TYPE_INT_BOOLEAN, true);
125 | screeDensityHandled = true;
126 | }
127 | if(this.name.compareTo("uses-permission")== 0 && !permissionHandled){
128 | super.visitContentAttr("android", "name", 0x01010003, AxmlVisitor.TYPE_STRING, "android.permission.INTERNET");
129 | permissionHandled = true;
130 | }
131 | super.visitContentEnd();
132 | }
133 |
134 |
135 | @Override
136 | public void visitEnd(){
137 | if(name.compareTo("manifest") == 0 ){
138 | if(!screeDensityHandled){
139 | screeDensityHandled = true;
140 | NodeVisitor nv = visitChild(null,"supports-screens");
141 | if(nv != null){
142 | nv.visitBegin();
143 | nv.visitContentAttr("android","anyDensity", 0x0101026c, AxmlVisitor.TYPE_INT_BOOLEAN, true);
144 | nv.visitContentEnd();
145 | nv.visitEnd();
146 | }
147 | }
148 | if(!permissionHandled){
149 | permissionHandled = true;
150 | NodeVisitor nv = visitChild(null, "uses-permission");
151 | if(nv != null){
152 | nv.visitBegin();
153 | nv.visitContentAttr("android","name", 0x01010003, AxmlVisitor.TYPE_STRING, "android.permission.INTERNET");
154 | nv.visitContentEnd();
155 | nv.visitEnd();
156 | }
157 | }
158 | }
159 | super.visitEnd();
160 |
161 | //check
162 | if(this.name.compareTo("activity") == 0 && hasActionMainAttr && hasCategoryLauncherAttr){
163 | root.setMainActivity((String)this.aux);
164 | }
165 | }
166 | }
167 |
168 | private String mainActivity;
169 | private String packageName;
170 |
171 | @Override
172 | public NodeVisitor visitFirst(String namespace, String name){
173 | NodeVisitor nv = super.visitFirst(namespace, name);
174 | return new NodeDecoder(namespace, name, null, this, nv);
175 | }
176 |
177 | public void setMainActivity(String name){
178 | mainActivity = name;
179 | }
180 |
181 | public void setPackage(String name){
182 | packageName = name;
183 | }
184 |
185 | public String getMainActivityName(){
186 | return packageName + mainActivity;
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/src/test/resources/a.axml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtchoi/axml/caf94562533d805295d83aa1e8eabed6a9c7e09e/src/test/resources/a.axml
--------------------------------------------------------------------------------
/src/test/resources/b.axml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtchoi/axml/caf94562533d805295d83aa1e8eabed6a9c7e09e/src/test/resources/b.axml
--------------------------------------------------------------------------------
/src/test/resources/c.axml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtchoi/axml/caf94562533d805295d83aa1e8eabed6a9c7e09e/src/test/resources/c.axml
--------------------------------------------------------------------------------
/src/test/resources/d.axml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtchoi/axml/caf94562533d805295d83aa1e8eabed6a9c7e09e/src/test/resources/d.axml
--------------------------------------------------------------------------------
/src/test/resources/manifest1.axml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wtchoi/axml/caf94562533d805295d83aa1e8eabed6a9c7e09e/src/test/resources/manifest1.axml
--------------------------------------------------------------------------------