parent
d09d3faf4d
commit
2c7fccaefd
|
|
@ -0,0 +1,203 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
Xpatch - Tool to work with android apk file, it can insert xposed loader into the apk file, and repackage the apk, then
|
||||||
|
the repacked apk can load xposed module when started.
|
||||||
|
|
||||||
|
Copyright (c) 2019 Wind
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
description = 'android manifest content parser'
|
||||||
|
apply plugin: 'java-library'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,932 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2008 Android4ME
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package wind.android.content.res;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Reader;
|
||||||
|
import wind.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
import wind.android.content.res.ChunkUtil;
|
||||||
|
import wind.android.content.res.IntReader;
|
||||||
|
import wind.android.content.res.StringBlock;
|
||||||
|
import wind.android.content.res.XmlResourceParser;
|
||||||
|
import wind.android.util.TypedValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dmitry Skiba
|
||||||
|
*
|
||||||
|
* Binary xml files parser.
|
||||||
|
*
|
||||||
|
* Parser has only two states:
|
||||||
|
* (1) Operational state, which parser obtains after first successful call
|
||||||
|
* to next() and retains until open(), close(), or failed call to next().
|
||||||
|
* (2) Closed state, which parser obtains after open(), close(), or failed
|
||||||
|
* call to next(). In this state methods return invalid values or throw exceptions.
|
||||||
|
*
|
||||||
|
* TODO:
|
||||||
|
* * check all methods in closed state
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class AXmlResourceParser implements XmlResourceParser {
|
||||||
|
|
||||||
|
public AXmlResourceParser() {
|
||||||
|
resetEventInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void open(InputStream stream) {
|
||||||
|
close();
|
||||||
|
if (stream!=null) {
|
||||||
|
m_reader=new wind.android.content.res.IntReader(stream,false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
if (!m_operational) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_operational=false;
|
||||||
|
m_reader.close();
|
||||||
|
m_reader=null;
|
||||||
|
m_strings=null;
|
||||||
|
m_resourceIDs=null;
|
||||||
|
m_namespaces.reset();
|
||||||
|
resetEventInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////// iteration
|
||||||
|
|
||||||
|
public int next() throws XmlPullParserException,IOException {
|
||||||
|
if (m_reader==null) {
|
||||||
|
throw new XmlPullParserException("Parser is not opened.",this,null);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
doNext();
|
||||||
|
return m_event;
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int nextToken() throws XmlPullParserException,IOException {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int nextTag() throws XmlPullParserException,IOException {
|
||||||
|
int eventType=next();
|
||||||
|
if (eventType==TEXT && isWhitespace()) {
|
||||||
|
eventType=next();
|
||||||
|
}
|
||||||
|
if (eventType!=START_TAG && eventType!=END_TAG) {
|
||||||
|
throw new XmlPullParserException("Expected start or end tag.",this,null);
|
||||||
|
}
|
||||||
|
return eventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String nextText() throws XmlPullParserException,IOException {
|
||||||
|
if(getEventType()!=START_TAG) {
|
||||||
|
throw new XmlPullParserException("Parser must be on START_TAG to read next text.",this,null);
|
||||||
|
}
|
||||||
|
int eventType=next();
|
||||||
|
if (eventType==TEXT) {
|
||||||
|
String result=getText();
|
||||||
|
eventType=next();
|
||||||
|
if (eventType!=END_TAG) {
|
||||||
|
throw new XmlPullParserException("Event TEXT must be immediately followed by END_TAG.",this,null);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} else if (eventType==END_TAG) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
throw new XmlPullParserException("Parser must be on START_TAG or TEXT to read text.",this,null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void require(int type,String namespace,String name) throws XmlPullParserException,IOException {
|
||||||
|
if (type!=getEventType() ||
|
||||||
|
(namespace!=null && !namespace.equals(getNamespace())) ||
|
||||||
|
(name!=null && !name.equals(getName())))
|
||||||
|
{
|
||||||
|
throw new XmlPullParserException(TYPES[type]+" is expected.",this,null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDepth() {
|
||||||
|
return m_namespaces.getDepth()-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEventType() throws XmlPullParserException {
|
||||||
|
return m_event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLineNumber() {
|
||||||
|
return m_lineNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
if (m_name==-1 || (m_event!=START_TAG && m_event!=END_TAG)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return m_strings.getString(m_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
if (m_name==-1 || m_event!=TEXT) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return m_strings.getString(m_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public char[] getTextCharacters(int[] holderForStartAndLength) {
|
||||||
|
String text=getText();
|
||||||
|
if (text==null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
holderForStartAndLength[0]=0;
|
||||||
|
holderForStartAndLength[1]=text.length();
|
||||||
|
char[] chars=new char[text.length()];
|
||||||
|
text.getChars(0,text.length(),chars,0);
|
||||||
|
return chars;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNamespace() {
|
||||||
|
return m_strings.getString(m_namespaceUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPrefix() {
|
||||||
|
int prefix=m_namespaces.findPrefix(m_namespaceUri);
|
||||||
|
return m_strings.getString(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPositionDescription() {
|
||||||
|
return "XML line #"+getLineNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNamespaceCount(int depth) throws XmlPullParserException {
|
||||||
|
return m_namespaces.getAccumulatedCount(depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNamespacePrefix(int pos) throws XmlPullParserException {
|
||||||
|
int prefix=m_namespaces.getPrefix(pos);
|
||||||
|
return m_strings.getString(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNamespaceUri(int pos) throws XmlPullParserException {
|
||||||
|
int uri=m_namespaces.getUri(pos);
|
||||||
|
return m_strings.getString(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////// attributes
|
||||||
|
|
||||||
|
public String getClassAttribute() {
|
||||||
|
if (m_classAttribute==-1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int offset=getAttributeOffset(m_classAttribute);
|
||||||
|
int value=m_attributes[offset+ATTRIBUTE_IX_VALUE_STRING];
|
||||||
|
return m_strings.getString(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIdAttribute() {
|
||||||
|
if (m_idAttribute==-1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int offset=getAttributeOffset(m_idAttribute);
|
||||||
|
int value=m_attributes[offset+ATTRIBUTE_IX_VALUE_STRING];
|
||||||
|
return m_strings.getString(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIdAttributeResourceValue(int defaultValue) {
|
||||||
|
if (m_idAttribute==-1) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
int offset=getAttributeOffset(m_idAttribute);
|
||||||
|
int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE];
|
||||||
|
if (valueType!=TypedValue.TYPE_REFERENCE) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStyleAttribute() {
|
||||||
|
if (m_styleAttribute==-1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int offset=getAttributeOffset(m_styleAttribute);
|
||||||
|
return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAttributeCount() {
|
||||||
|
if (m_event!=START_TAG) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return m_attributes.length/ATTRIBUTE_LENGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAttributeNamespace(int index) {
|
||||||
|
int offset=getAttributeOffset(index);
|
||||||
|
int namespace=m_attributes[offset+ATTRIBUTE_IX_NAMESPACE_URI];
|
||||||
|
if (namespace==-1) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return m_strings.getString(namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAttributePrefix(int index) {
|
||||||
|
int offset=getAttributeOffset(index);
|
||||||
|
int uri=m_attributes[offset+ATTRIBUTE_IX_NAMESPACE_URI];
|
||||||
|
int prefix=m_namespaces.findPrefix(uri);
|
||||||
|
if (prefix==-1) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return m_strings.getString(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAttributeName(int index) {
|
||||||
|
int offset=getAttributeOffset(index);
|
||||||
|
int name=m_attributes[offset+ATTRIBUTE_IX_NAME];
|
||||||
|
if (name==-1) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return m_strings.getString(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAttributeNameResource(int index) {
|
||||||
|
int offset=getAttributeOffset(index);
|
||||||
|
int name=m_attributes[offset+ATTRIBUTE_IX_NAME];
|
||||||
|
if (m_resourceIDs==null ||
|
||||||
|
name<0 || name>=m_resourceIDs.length)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return m_resourceIDs[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAttributeValueType(int index) {
|
||||||
|
int offset=getAttributeOffset(index);
|
||||||
|
return m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE];
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAttributeValueData(int index) {
|
||||||
|
int offset=getAttributeOffset(index);
|
||||||
|
return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAttributeValue(int index) {
|
||||||
|
int offset=getAttributeOffset(index);
|
||||||
|
int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE];
|
||||||
|
if (valueType==TypedValue.TYPE_STRING) {
|
||||||
|
int valueString=m_attributes[offset+ATTRIBUTE_IX_VALUE_STRING];
|
||||||
|
return m_strings.getString(valueString);
|
||||||
|
}
|
||||||
|
int valueData=m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
|
||||||
|
return "";//TypedValue.coerceToString(valueType,valueData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getAttributeBooleanValue(int index,boolean defaultValue) {
|
||||||
|
return getAttributeIntValue(index,defaultValue?1:0)!=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getAttributeFloatValue(int index,float defaultValue) {
|
||||||
|
int offset=getAttributeOffset(index);
|
||||||
|
int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE];
|
||||||
|
if (valueType==TypedValue.TYPE_FLOAT) {
|
||||||
|
int valueData=m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
|
||||||
|
return Float.intBitsToFloat(valueData);
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAttributeIntValue(int index,int defaultValue) {
|
||||||
|
int offset=getAttributeOffset(index);
|
||||||
|
int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE];
|
||||||
|
if (valueType>=TypedValue.TYPE_FIRST_INT &&
|
||||||
|
valueType<=TypedValue.TYPE_LAST_INT)
|
||||||
|
{
|
||||||
|
return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAttributeUnsignedIntValue(int index,int defaultValue) {
|
||||||
|
return getAttributeIntValue(index,defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAttributeResourceValue(int index,int defaultValue) {
|
||||||
|
int offset=getAttributeOffset(index);
|
||||||
|
int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE];
|
||||||
|
if (valueType==TypedValue.TYPE_REFERENCE) {
|
||||||
|
return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAttributeValue(String namespace,String attribute) {
|
||||||
|
int index=findAttribute(namespace,attribute);
|
||||||
|
if (index==-1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getAttributeValue(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getAttributeBooleanValue(String namespace,String attribute,boolean defaultValue) {
|
||||||
|
int index=findAttribute(namespace,attribute);
|
||||||
|
if (index==-1) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return getAttributeBooleanValue(index,defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getAttributeFloatValue(String namespace,String attribute,float defaultValue) {
|
||||||
|
int index=findAttribute(namespace,attribute);
|
||||||
|
if (index==-1) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return getAttributeFloatValue(index,defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAttributeIntValue(String namespace,String attribute,int defaultValue) {
|
||||||
|
int index=findAttribute(namespace,attribute);
|
||||||
|
if (index==-1) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return getAttributeIntValue(index,defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAttributeUnsignedIntValue(String namespace,String attribute,int defaultValue) {
|
||||||
|
int index=findAttribute(namespace,attribute);
|
||||||
|
if (index==-1) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return getAttributeUnsignedIntValue(index,defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAttributeResourceValue(String namespace,String attribute,int defaultValue) {
|
||||||
|
int index=findAttribute(namespace,attribute);
|
||||||
|
if (index==-1) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return getAttributeResourceValue(index,defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAttributeListValue(int index,String[] options,int defaultValue) {
|
||||||
|
// TODO implement
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAttributeListValue(String namespace,String attribute,String[] options,int defaultValue) {
|
||||||
|
// TODO implement
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAttributeType(int index) {
|
||||||
|
return "CDATA";
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAttributeDefault(int index) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////// dummies
|
||||||
|
|
||||||
|
public void setInput(InputStream stream,String inputEncoding) throws XmlPullParserException {
|
||||||
|
throw new XmlPullParserException(E_NOT_SUPPORTED);
|
||||||
|
}
|
||||||
|
public void setInput(Reader reader) throws XmlPullParserException {
|
||||||
|
throw new XmlPullParserException(E_NOT_SUPPORTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInputEncoding() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getColumnNumber() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmptyElementTag() throws XmlPullParserException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWhitespace() throws XmlPullParserException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void defineEntityReplacementText(String entityName,String replacementText) throws XmlPullParserException {
|
||||||
|
throw new XmlPullParserException(E_NOT_SUPPORTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNamespace(String prefix) {
|
||||||
|
throw new RuntimeException(E_NOT_SUPPORTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getProperty(String name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public void setProperty(String name,Object value) throws XmlPullParserException {
|
||||||
|
throw new XmlPullParserException(E_NOT_SUPPORTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getFeature(String feature) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public void setFeature(String name,boolean value) throws XmlPullParserException {
|
||||||
|
throw new XmlPullParserException(E_NOT_SUPPORTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////// implementation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Namespace stack, holds prefix+uri pairs, as well as
|
||||||
|
* depth information.
|
||||||
|
* All information is stored in one int[] array.
|
||||||
|
* Array consists of depth frames:
|
||||||
|
* Data=DepthFrame*;
|
||||||
|
* DepthFrame=Count+[Prefix+Uri]*+Count;
|
||||||
|
* Count='count of Prefix+Uri pairs';
|
||||||
|
* Yes, count is stored twice, to enable bottom-up traversal.
|
||||||
|
* increaseDepth adds depth frame, decreaseDepth removes it.
|
||||||
|
* push/pop operations operate only in current depth frame.
|
||||||
|
* decreaseDepth removes any remaining (not pop'ed) namespace pairs.
|
||||||
|
* findXXX methods search all depth frames starting
|
||||||
|
* from the last namespace pair of current depth frame.
|
||||||
|
* All functions that operate with int, use -1 as 'invalid value'.
|
||||||
|
*
|
||||||
|
* !! functions expect 'prefix'+'uri' pairs, not 'uri'+'prefix' !!
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static final class NamespaceStack {
|
||||||
|
public NamespaceStack() {
|
||||||
|
m_data=new int[32];
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void reset() {
|
||||||
|
m_dataLength=0;
|
||||||
|
m_count=0;
|
||||||
|
m_depth=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getTotalCount() {
|
||||||
|
return m_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getCurrentCount() {
|
||||||
|
if (m_dataLength==0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int offset=m_dataLength-1;
|
||||||
|
return m_data[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getAccumulatedCount(int depth) {
|
||||||
|
if (m_dataLength==0 || depth<0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (depth>m_depth) {
|
||||||
|
depth=m_depth;
|
||||||
|
}
|
||||||
|
int accumulatedCount=0;
|
||||||
|
int offset=0;
|
||||||
|
for (;depth!=0;--depth) {
|
||||||
|
int count=m_data[offset];
|
||||||
|
accumulatedCount+=count;
|
||||||
|
offset+=(2+count*2);
|
||||||
|
}
|
||||||
|
return accumulatedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void push(int prefix,int uri) {
|
||||||
|
if (m_depth==0) {
|
||||||
|
increaseDepth();
|
||||||
|
}
|
||||||
|
ensureDataCapacity(2);
|
||||||
|
int offset=m_dataLength-1;
|
||||||
|
int count=m_data[offset];
|
||||||
|
m_data[offset-1-count*2]=count+1;
|
||||||
|
m_data[offset]=prefix;
|
||||||
|
m_data[offset+1]=uri;
|
||||||
|
m_data[offset+2]=count+1;
|
||||||
|
m_dataLength+=2;
|
||||||
|
m_count+=1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean pop(int prefix,int uri) {
|
||||||
|
if (m_dataLength==0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int offset=m_dataLength-1;
|
||||||
|
int count=m_data[offset];
|
||||||
|
for (int i=0,o=offset-2;i!=count;++i,o-=2) {
|
||||||
|
if (m_data[o]!=prefix || m_data[o+1]!=uri) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
count-=1;
|
||||||
|
if (i==0) {
|
||||||
|
m_data[o]=count;
|
||||||
|
o-=(1+count*2);
|
||||||
|
m_data[o]=count;
|
||||||
|
} else {
|
||||||
|
m_data[offset]=count;
|
||||||
|
offset-=(1+2+count*2);
|
||||||
|
m_data[offset]=count;
|
||||||
|
System.arraycopy(
|
||||||
|
m_data,o+2,
|
||||||
|
m_data,o,
|
||||||
|
m_dataLength-o);
|
||||||
|
}
|
||||||
|
m_dataLength-=2;
|
||||||
|
m_count-=1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean pop() {
|
||||||
|
if (m_dataLength==0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int offset=m_dataLength-1;
|
||||||
|
int count=m_data[offset];
|
||||||
|
if (count==0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
count-=1;
|
||||||
|
offset-=2;
|
||||||
|
m_data[offset]=count;
|
||||||
|
offset-=(1+count*2);
|
||||||
|
m_data[offset]=count;
|
||||||
|
m_dataLength-=2;
|
||||||
|
m_count-=1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getPrefix(int index) {
|
||||||
|
return get(index,true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getUri(int index) {
|
||||||
|
return get(index,false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int findPrefix(int uri) {
|
||||||
|
return find(uri,false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int findUri(int prefix) {
|
||||||
|
return find(prefix,true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getDepth() {
|
||||||
|
return m_depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void increaseDepth() {
|
||||||
|
ensureDataCapacity(2);
|
||||||
|
int offset=m_dataLength;
|
||||||
|
m_data[offset]=0;
|
||||||
|
m_data[offset+1]=0;
|
||||||
|
m_dataLength+=2;
|
||||||
|
m_depth+=1;
|
||||||
|
}
|
||||||
|
public final void decreaseDepth() {
|
||||||
|
if (m_dataLength==0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int offset=m_dataLength-1;
|
||||||
|
int count=m_data[offset];
|
||||||
|
if ((offset-1-count*2)==0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_dataLength-=2+count*2;
|
||||||
|
m_count-=count;
|
||||||
|
m_depth-=1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureDataCapacity(int capacity) {
|
||||||
|
int available=(m_data.length-m_dataLength);
|
||||||
|
if (available>capacity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int newLength=(m_data.length+available)*2;
|
||||||
|
int[] newData=new int[newLength];
|
||||||
|
System.arraycopy(m_data,0,newData,0,m_dataLength);
|
||||||
|
m_data=newData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final int find(int prefixOrUri,boolean prefix) {
|
||||||
|
if (m_dataLength==0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int offset=m_dataLength-1;
|
||||||
|
for (int i=m_depth;i!=0;--i) {
|
||||||
|
int count=m_data[offset];
|
||||||
|
offset-=2;
|
||||||
|
for (;count!=0;--count) {
|
||||||
|
if (prefix) {
|
||||||
|
if (m_data[offset]==prefixOrUri) {
|
||||||
|
return m_data[offset+1];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (m_data[offset+1]==prefixOrUri) {
|
||||||
|
return m_data[offset];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset-=2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final int get(int index,boolean prefix) {
|
||||||
|
if (m_dataLength==0 || index<0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int offset=0;
|
||||||
|
for (int i=m_depth;i!=0;--i) {
|
||||||
|
int count=m_data[offset];
|
||||||
|
if (index>=count) {
|
||||||
|
index-=count;
|
||||||
|
offset+=(2+count*2);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
offset+=(1+index*2);
|
||||||
|
if (!prefix) {
|
||||||
|
offset+=1;
|
||||||
|
}
|
||||||
|
return m_data[offset];
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int[] m_data;
|
||||||
|
private int m_dataLength;
|
||||||
|
private int m_count;
|
||||||
|
private int m_depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////// package-visible
|
||||||
|
|
||||||
|
// final void fetchAttributes(int[] styleableIDs,TypedArray result) {
|
||||||
|
// result.resetIndices();
|
||||||
|
// if (m_attributes==null || m_resourceIDs==null) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// boolean needStrings=false;
|
||||||
|
// for (int i=0,e=styleableIDs.length;i!=e;++i) {
|
||||||
|
// int id=styleableIDs[i];
|
||||||
|
// for (int o=0;o!=m_attributes.length;o+=ATTRIBUTE_LENGHT) {
|
||||||
|
// int name=m_attributes[o+ATTRIBUTE_IX_NAME];
|
||||||
|
// if (name>=m_resourceIDs.length ||
|
||||||
|
// m_resourceIDs[name]!=id)
|
||||||
|
// {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// int valueType=m_attributes[o+ATTRIBUTE_IX_VALUE_TYPE];
|
||||||
|
// int valueData;
|
||||||
|
// int assetCookie;
|
||||||
|
// if (valueType==TypedValue.TYPE_STRING) {
|
||||||
|
// valueData=m_attributes[o+ATTRIBUTE_IX_VALUE_STRING];
|
||||||
|
// assetCookie=-1;
|
||||||
|
// needStrings=true;
|
||||||
|
// } else {
|
||||||
|
// valueData=m_attributes[o+ATTRIBUTE_IX_VALUE_DATA];
|
||||||
|
// assetCookie=0;
|
||||||
|
// }
|
||||||
|
// result.addValue(i,valueType,valueData,assetCookie,id,0);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if (needStrings) {
|
||||||
|
// result.setStrings(m_strings);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
final wind.android.content.res.StringBlock getStrings() {
|
||||||
|
return m_strings;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////
|
||||||
|
|
||||||
|
private final int getAttributeOffset(int index) {
|
||||||
|
if (m_event!=START_TAG) {
|
||||||
|
throw new IndexOutOfBoundsException("Current event is not START_TAG.");
|
||||||
|
}
|
||||||
|
int offset=index*5;
|
||||||
|
if (offset>=m_attributes.length) {
|
||||||
|
throw new IndexOutOfBoundsException("Invalid attribute index ("+index+").");
|
||||||
|
}
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final int findAttribute(String namespace,String attribute) {
|
||||||
|
if (m_strings==null || attribute==null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int name=m_strings.find(attribute);
|
||||||
|
if (name==-1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int uri=(namespace!=null)?
|
||||||
|
m_strings.find(namespace):
|
||||||
|
-1;
|
||||||
|
for (int o=0;o!=m_attributes.length;++o) {
|
||||||
|
if (name==m_attributes[o+ATTRIBUTE_IX_NAME] &&
|
||||||
|
(uri==-1 || uri==m_attributes[o+ATTRIBUTE_IX_NAMESPACE_URI]))
|
||||||
|
{
|
||||||
|
return o/ATTRIBUTE_LENGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final void resetEventInfo() {
|
||||||
|
m_event=-1;
|
||||||
|
m_lineNumber=-1;
|
||||||
|
m_name=-1;
|
||||||
|
m_namespaceUri=-1;
|
||||||
|
m_attributes=null;
|
||||||
|
m_idAttribute=-1;
|
||||||
|
m_classAttribute=-1;
|
||||||
|
m_styleAttribute=-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final void doNext() throws IOException {
|
||||||
|
// Delayed initialization.
|
||||||
|
if (m_strings==null) {
|
||||||
|
wind.android.content.res.ChunkUtil.readCheckType(m_reader,CHUNK_AXML_FILE);
|
||||||
|
/*chunkSize*/m_reader.skipInt();
|
||||||
|
m_strings= wind.android.content.res.StringBlock.read(m_reader);
|
||||||
|
m_namespaces.increaseDepth();
|
||||||
|
m_operational=true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_event==END_DOCUMENT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int event=m_event;
|
||||||
|
resetEventInfo();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (m_decreaseDepth) {
|
||||||
|
m_decreaseDepth=false;
|
||||||
|
m_namespaces.decreaseDepth();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fake END_DOCUMENT event.
|
||||||
|
if (event==END_TAG &&
|
||||||
|
m_namespaces.getDepth()==1 &&
|
||||||
|
m_namespaces.getCurrentCount()==0)
|
||||||
|
{
|
||||||
|
m_event=END_DOCUMENT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int chunkType;
|
||||||
|
if (event==START_DOCUMENT) {
|
||||||
|
// Fake event, see CHUNK_XML_START_TAG handler.
|
||||||
|
chunkType=CHUNK_XML_START_TAG;
|
||||||
|
} else {
|
||||||
|
chunkType=m_reader.readInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunkType==CHUNK_RESOURCEIDS) {
|
||||||
|
int chunkSize=m_reader.readInt();
|
||||||
|
if (chunkSize<8 || (chunkSize%4)!=0) {
|
||||||
|
throw new IOException("Invalid resource ids size ("+chunkSize+").");
|
||||||
|
}
|
||||||
|
m_resourceIDs=m_reader.readIntArray(chunkSize/4-2);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunkType<CHUNK_XML_FIRST || chunkType>CHUNK_XML_LAST) {
|
||||||
|
throw new IOException("Invalid chunk type ("+chunkType+").");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fake START_DOCUMENT event.
|
||||||
|
if (chunkType==CHUNK_XML_START_TAG && event==-1) {
|
||||||
|
m_event=START_DOCUMENT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common header.
|
||||||
|
/*chunkSize*/m_reader.skipInt();
|
||||||
|
int lineNumber=m_reader.readInt();
|
||||||
|
/*0xFFFFFFFF*/m_reader.skipInt();
|
||||||
|
|
||||||
|
if (chunkType==CHUNK_XML_START_NAMESPACE ||
|
||||||
|
chunkType==CHUNK_XML_END_NAMESPACE)
|
||||||
|
{
|
||||||
|
if (chunkType==CHUNK_XML_START_NAMESPACE) {
|
||||||
|
int prefix=m_reader.readInt();
|
||||||
|
int uri=m_reader.readInt();
|
||||||
|
m_namespaces.push(prefix,uri);
|
||||||
|
} else {
|
||||||
|
/*prefix*/m_reader.skipInt();
|
||||||
|
/*uri*/m_reader.skipInt();
|
||||||
|
m_namespaces.pop();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lineNumber=lineNumber;
|
||||||
|
|
||||||
|
if (chunkType==CHUNK_XML_START_TAG) {
|
||||||
|
m_namespaceUri=m_reader.readInt();
|
||||||
|
m_name=m_reader.readInt();
|
||||||
|
/*flags?*/m_reader.skipInt();
|
||||||
|
int attributeCount=m_reader.readInt();
|
||||||
|
m_idAttribute=(attributeCount>>>16)-1;
|
||||||
|
attributeCount&=0xFFFF;
|
||||||
|
m_classAttribute=m_reader.readInt();
|
||||||
|
m_styleAttribute=(m_classAttribute>>>16)-1;
|
||||||
|
m_classAttribute=(m_classAttribute & 0xFFFF)-1;
|
||||||
|
m_attributes=m_reader.readIntArray(attributeCount*ATTRIBUTE_LENGHT);
|
||||||
|
for (int i=ATTRIBUTE_IX_VALUE_TYPE;i<m_attributes.length;) {
|
||||||
|
m_attributes[i]=(m_attributes[i]>>>24);
|
||||||
|
i+=ATTRIBUTE_LENGHT;
|
||||||
|
}
|
||||||
|
m_namespaces.increaseDepth();
|
||||||
|
m_event=START_TAG;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunkType==CHUNK_XML_END_TAG) {
|
||||||
|
m_namespaceUri=m_reader.readInt();
|
||||||
|
m_name=m_reader.readInt();
|
||||||
|
m_event=END_TAG;
|
||||||
|
m_decreaseDepth=true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunkType==CHUNK_XML_TEXT) {
|
||||||
|
m_name=m_reader.readInt();
|
||||||
|
/*?*/m_reader.skipInt();
|
||||||
|
/*?*/m_reader.skipInt();
|
||||||
|
m_event=TEXT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////// data
|
||||||
|
|
||||||
|
/*
|
||||||
|
* All values are essentially indices, e.g. m_name is
|
||||||
|
* an index of name in m_strings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private IntReader m_reader;
|
||||||
|
private boolean m_operational=false;
|
||||||
|
|
||||||
|
private StringBlock m_strings;
|
||||||
|
private int[] m_resourceIDs;
|
||||||
|
private NamespaceStack m_namespaces=new NamespaceStack();
|
||||||
|
|
||||||
|
private boolean m_decreaseDepth;
|
||||||
|
|
||||||
|
private int m_event;
|
||||||
|
private int m_lineNumber;
|
||||||
|
private int m_name;
|
||||||
|
private int m_namespaceUri;
|
||||||
|
private int[] m_attributes;
|
||||||
|
private int m_idAttribute;
|
||||||
|
private int m_classAttribute;
|
||||||
|
private int m_styleAttribute;
|
||||||
|
|
||||||
|
private static final String
|
||||||
|
E_NOT_SUPPORTED ="Method is not supported.";
|
||||||
|
|
||||||
|
private static final int
|
||||||
|
ATTRIBUTE_IX_NAMESPACE_URI =0,
|
||||||
|
ATTRIBUTE_IX_NAME =1,
|
||||||
|
ATTRIBUTE_IX_VALUE_STRING =2,
|
||||||
|
ATTRIBUTE_IX_VALUE_TYPE =3,
|
||||||
|
ATTRIBUTE_IX_VALUE_DATA =4,
|
||||||
|
ATTRIBUTE_LENGHT =5;
|
||||||
|
|
||||||
|
private static final int
|
||||||
|
CHUNK_AXML_FILE =0x00080003,
|
||||||
|
CHUNK_RESOURCEIDS =0x00080180,
|
||||||
|
CHUNK_XML_FIRST =0x00100100,
|
||||||
|
CHUNK_XML_START_NAMESPACE =0x00100100,
|
||||||
|
CHUNK_XML_END_NAMESPACE =0x00100101,
|
||||||
|
CHUNK_XML_START_TAG =0x00100102,
|
||||||
|
CHUNK_XML_END_TAG =0x00100103,
|
||||||
|
CHUNK_XML_TEXT =0x00100104,
|
||||||
|
CHUNK_XML_LAST =0x00100104;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2008 Android4ME
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package wind.android.content.res;
|
||||||
|
|
||||||
|
import wind.android.content.res.IntReader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dmitry Skiba
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class ChunkUtil {
|
||||||
|
|
||||||
|
public static final void readCheckType(IntReader reader, int expectedType) throws IOException {
|
||||||
|
int type=reader.readInt();
|
||||||
|
if (type!=expectedType) {
|
||||||
|
throw new IOException(
|
||||||
|
"Expected chunk of type 0x"+Integer.toHexString(expectedType)+
|
||||||
|
", read 0x"+Integer.toHexString(type)+".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2008 Android4ME
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package wind.android.content.res;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dmitry Skiba
|
||||||
|
*
|
||||||
|
* Simple helper class that allows reading of integers.
|
||||||
|
*
|
||||||
|
* TODO:
|
||||||
|
* * implement buffering
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public final class IntReader {
|
||||||
|
|
||||||
|
public IntReader() {
|
||||||
|
}
|
||||||
|
public IntReader(InputStream stream,boolean bigEndian) {
|
||||||
|
reset(stream,bigEndian);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void reset(InputStream stream,boolean bigEndian) {
|
||||||
|
m_stream=stream;
|
||||||
|
m_bigEndian=bigEndian;
|
||||||
|
m_position=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void close() {
|
||||||
|
if (m_stream==null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
m_stream.close();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
}
|
||||||
|
reset(null,false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final InputStream getStream() {
|
||||||
|
return m_stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isBigEndian() {
|
||||||
|
return m_bigEndian;
|
||||||
|
}
|
||||||
|
public final void setBigEndian(boolean bigEndian) {
|
||||||
|
m_bigEndian=bigEndian;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int readByte() throws IOException {
|
||||||
|
return readInt(1);
|
||||||
|
}
|
||||||
|
public final int readShort() throws IOException {
|
||||||
|
return readInt(2);
|
||||||
|
}
|
||||||
|
public final int readInt() throws IOException {
|
||||||
|
return readInt(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int readInt(int length) throws IOException {
|
||||||
|
if (length<0 || length>4) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
int result=0;
|
||||||
|
if (m_bigEndian) {
|
||||||
|
for (int i=(length-1)*8;i>=0;i-=8) {
|
||||||
|
int b=m_stream.read();
|
||||||
|
if (b==-1) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
m_position+=1;
|
||||||
|
result|=(b<<i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
length*=8;
|
||||||
|
for (int i=0;i!=length;i+=8) {
|
||||||
|
int b=m_stream.read();
|
||||||
|
if (b==-1) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
m_position+=1;
|
||||||
|
result|=(b<<i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int[] readIntArray(int length) throws IOException {
|
||||||
|
int[] array=new int[length];
|
||||||
|
readIntArray(array,0,length);
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void readIntArray(int[] array,int offset,int length) throws IOException {
|
||||||
|
for (;length>0;length-=1) {
|
||||||
|
array[offset++]=readInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final byte[] readByteArray(int length) throws IOException {
|
||||||
|
byte[] array=new byte[length];
|
||||||
|
int read=m_stream.read(array);
|
||||||
|
m_position+=read;
|
||||||
|
if (read!=length) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void skip(int bytes) throws IOException {
|
||||||
|
if (bytes<=0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long skipped=m_stream.skip(bytes);
|
||||||
|
m_position+=skipped;
|
||||||
|
if (skipped!=bytes) {
|
||||||
|
throw new EOFException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void skipInt() throws IOException {
|
||||||
|
skip(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int available() throws IOException {
|
||||||
|
return m_stream.available();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getPosition() {
|
||||||
|
return m_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////// data
|
||||||
|
|
||||||
|
private InputStream m_stream;
|
||||||
|
private boolean m_bigEndian;
|
||||||
|
private int m_position;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,248 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2008 Android4ME
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package wind.android.content.res;
|
||||||
|
|
||||||
|
import wind.android.content.res.ChunkUtil;
|
||||||
|
import wind.android.content.res.IntReader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dmitry Skiba
|
||||||
|
*
|
||||||
|
* Block of strings, used in binary xml and arsc.
|
||||||
|
*
|
||||||
|
* TODO:
|
||||||
|
* - implement get()
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class StringBlock {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads whole (including chunk type) string block from stream.
|
||||||
|
* Stream must be at the chunk type.
|
||||||
|
*/
|
||||||
|
public static StringBlock read(IntReader reader) throws IOException {
|
||||||
|
wind.android.content.res.ChunkUtil.readCheckType(reader,CHUNK_TYPE);
|
||||||
|
int chunkSize=reader.readInt();
|
||||||
|
int stringCount=reader.readInt();
|
||||||
|
int styleOffsetCount=reader.readInt();
|
||||||
|
/*?*/reader.readInt();
|
||||||
|
int stringsOffset=reader.readInt();
|
||||||
|
int stylesOffset=reader.readInt();
|
||||||
|
|
||||||
|
StringBlock block=new StringBlock();
|
||||||
|
block.m_stringOffsets=reader.readIntArray(stringCount);
|
||||||
|
if (styleOffsetCount!=0) {
|
||||||
|
block.m_styleOffsets=reader.readIntArray(styleOffsetCount);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
int size=((stylesOffset==0)?chunkSize:stylesOffset)-stringsOffset;
|
||||||
|
if ((size%4)!=0) {
|
||||||
|
throw new IOException("String data size is not multiple of 4 ("+size+").");
|
||||||
|
}
|
||||||
|
block.m_strings=reader.readIntArray(size/4);
|
||||||
|
}
|
||||||
|
if (stylesOffset!=0) {
|
||||||
|
int size=(chunkSize-stylesOffset);
|
||||||
|
if ((size%4)!=0) {
|
||||||
|
throw new IOException("Style data size is not multiple of 4 ("+size+").");
|
||||||
|
}
|
||||||
|
block.m_styles=reader.readIntArray(size/4);
|
||||||
|
}
|
||||||
|
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns number of strings in block.
|
||||||
|
*/
|
||||||
|
public int getCount() {
|
||||||
|
return m_stringOffsets!=null?
|
||||||
|
m_stringOffsets.length:
|
||||||
|
0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns raw string (without any styling information) at specified index.
|
||||||
|
*/
|
||||||
|
public String getString(int index) {
|
||||||
|
if (index<0 ||
|
||||||
|
m_stringOffsets==null ||
|
||||||
|
index>=m_stringOffsets.length)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int offset=m_stringOffsets[index];
|
||||||
|
int length=getShort(m_strings,offset);
|
||||||
|
StringBuilder result=new StringBuilder(length);
|
||||||
|
for (;length!=0;length-=1) {
|
||||||
|
offset+=2;
|
||||||
|
result.append((char)getShort(m_strings,offset));
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not yet implemented.
|
||||||
|
*
|
||||||
|
* Returns string with style information (if any).
|
||||||
|
*/
|
||||||
|
public CharSequence get(int index) {
|
||||||
|
return getString(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns string with style tags (html-like).
|
||||||
|
*/
|
||||||
|
public String getHTML(int index) {
|
||||||
|
String raw=getString(index);
|
||||||
|
if (raw==null) {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
int[] style=getStyle(index);
|
||||||
|
if (style==null) {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
StringBuilder html=new StringBuilder(raw.length()+32);
|
||||||
|
int offset=0;
|
||||||
|
while (true) {
|
||||||
|
int i=-1;
|
||||||
|
for (int j=0;j!=style.length;j+=3) {
|
||||||
|
if (style[j+1]==-1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (i==-1 || style[i+1]>style[j+1]) {
|
||||||
|
i=j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int start=((i!=-1)?style[i+1]:raw.length());
|
||||||
|
for (int j=0;j!=style.length;j+=3) {
|
||||||
|
int end=style[j+2];
|
||||||
|
if (end==-1 || end>=start) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (offset<=end) {
|
||||||
|
html.append(raw,offset,end+1);
|
||||||
|
offset=end+1;
|
||||||
|
}
|
||||||
|
style[j+2]=-1;
|
||||||
|
html.append('<');
|
||||||
|
html.append('/');
|
||||||
|
html.append(getString(style[j]));
|
||||||
|
html.append('>');
|
||||||
|
}
|
||||||
|
if (offset<start) {
|
||||||
|
html.append(raw,offset,start);
|
||||||
|
offset=start;
|
||||||
|
}
|
||||||
|
if (i==-1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
html.append('<');
|
||||||
|
html.append(getString(style[i]));
|
||||||
|
html.append('>');
|
||||||
|
style[i+1]=-1;
|
||||||
|
}
|
||||||
|
return html.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds index of the string.
|
||||||
|
* Returns -1 if the string was not found.
|
||||||
|
*/
|
||||||
|
public int find(String string) {
|
||||||
|
if (string==null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
for (int i=0;i!=m_stringOffsets.length;++i) {
|
||||||
|
int offset=m_stringOffsets[i];
|
||||||
|
int length=getShort(m_strings,offset);
|
||||||
|
if (length!=string.length()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int j=0;
|
||||||
|
for (;j!=length;++j) {
|
||||||
|
offset+=2;
|
||||||
|
if (string.charAt(j)!=getShort(m_strings,offset)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (j==length) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////// implementation
|
||||||
|
|
||||||
|
private StringBlock() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns style information - array of int triplets,
|
||||||
|
* where in each triplet:
|
||||||
|
* * first int is index of tag name ('b','i', etc.)
|
||||||
|
* * second int is tag start index in string
|
||||||
|
* * third int is tag end index in string
|
||||||
|
*/
|
||||||
|
private int[] getStyle(int index) {
|
||||||
|
if (m_styleOffsets==null || m_styles==null ||
|
||||||
|
index>=m_styleOffsets.length)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int offset=m_styleOffsets[index]/4;
|
||||||
|
int style[];
|
||||||
|
{
|
||||||
|
int count=0;
|
||||||
|
for (int i=offset;i<m_styles.length;++i) {
|
||||||
|
if (m_styles[i]==-1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
count+=1;
|
||||||
|
}
|
||||||
|
if (count==0 || (count%3)!=0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
style=new int[count];
|
||||||
|
}
|
||||||
|
for (int i=offset,j=0;i<m_styles.length;) {
|
||||||
|
if (m_styles[i]==-1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
style[j++]=m_styles[i++];
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int getShort(int[] array,int offset) {
|
||||||
|
int value=array[offset/4];
|
||||||
|
if ((offset%4)/2==0) {
|
||||||
|
return (value & 0xFFFF);
|
||||||
|
} else {
|
||||||
|
return (value >>> 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int[] m_stringOffsets;
|
||||||
|
private int[] m_strings;
|
||||||
|
private int[] m_styleOffsets;
|
||||||
|
private int[] m_styles;
|
||||||
|
|
||||||
|
private static final int CHUNK_TYPE=0x001C0001;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2008 Android4ME
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package wind.android.content.res;
|
||||||
|
|
||||||
|
import wind.v1.XmlPullParser;
|
||||||
|
import wind.android.util.AttributeSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dmitry Skiba
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface XmlResourceParser extends XmlPullParser, AttributeSet {
|
||||||
|
void close();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2008 Android4ME
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package wind.android.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dmitry Skiba
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface AttributeSet {
|
||||||
|
int getAttributeCount();
|
||||||
|
String getAttributeName(int index);
|
||||||
|
String getAttributeValue(int index);
|
||||||
|
String getPositionDescription();
|
||||||
|
int getAttributeNameResource(int index);
|
||||||
|
int getAttributeListValue(int index, String options[], int defaultValue);
|
||||||
|
boolean getAttributeBooleanValue(int index, boolean defaultValue);
|
||||||
|
int getAttributeResourceValue(int index, int defaultValue);
|
||||||
|
int getAttributeIntValue(int index, int defaultValue);
|
||||||
|
int getAttributeUnsignedIntValue(int index, int defaultValue);
|
||||||
|
float getAttributeFloatValue(int index, float defaultValue);
|
||||||
|
String getIdAttribute();
|
||||||
|
String getClassAttribute();
|
||||||
|
int getIdAttributeResourceValue(int index);
|
||||||
|
int getStyleAttribute();
|
||||||
|
String getAttributeValue(String namespace, String attribute);
|
||||||
|
int getAttributeListValue(String namespace, String attribute, String options[], int defaultValue);
|
||||||
|
boolean getAttributeBooleanValue(String namespace, String attribute, boolean defaultValue);
|
||||||
|
int getAttributeResourceValue(String namespace, String attribute, int defaultValue);
|
||||||
|
int getAttributeIntValue(String namespace, String attribute, int defaultValue);
|
||||||
|
int getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue);
|
||||||
|
float getAttributeFloatValue(String namespace, String attribute, float defaultValue);
|
||||||
|
|
||||||
|
//TODO: remove
|
||||||
|
int getAttributeValueType(int index);
|
||||||
|
int getAttributeValueData(int index);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2008 Android4ME
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package wind.android.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dmitry Skiba
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class TypedValue {
|
||||||
|
|
||||||
|
public int type;
|
||||||
|
public CharSequence string;
|
||||||
|
public int data;
|
||||||
|
public int assetCookie;
|
||||||
|
public int resourceId;
|
||||||
|
public int changingConfigurations;
|
||||||
|
|
||||||
|
public static final int
|
||||||
|
TYPE_NULL =0,
|
||||||
|
TYPE_REFERENCE =1,
|
||||||
|
TYPE_ATTRIBUTE =2,
|
||||||
|
TYPE_STRING =3,
|
||||||
|
TYPE_FLOAT =4,
|
||||||
|
TYPE_DIMENSION =5,
|
||||||
|
TYPE_FRACTION =6,
|
||||||
|
TYPE_FIRST_INT =16,
|
||||||
|
TYPE_INT_DEC =16,
|
||||||
|
TYPE_INT_HEX =17,
|
||||||
|
TYPE_INT_BOOLEAN =18,
|
||||||
|
TYPE_FIRST_COLOR_INT =28,
|
||||||
|
TYPE_INT_COLOR_ARGB8 =28,
|
||||||
|
TYPE_INT_COLOR_RGB8 =29,
|
||||||
|
TYPE_INT_COLOR_ARGB4 =30,
|
||||||
|
TYPE_INT_COLOR_RGB4 =31,
|
||||||
|
TYPE_LAST_COLOR_INT =31,
|
||||||
|
TYPE_LAST_INT =31;
|
||||||
|
|
||||||
|
public static final int
|
||||||
|
COMPLEX_UNIT_PX =0,
|
||||||
|
COMPLEX_UNIT_DIP =1,
|
||||||
|
COMPLEX_UNIT_SP =2,
|
||||||
|
COMPLEX_UNIT_PT =3,
|
||||||
|
COMPLEX_UNIT_IN =4,
|
||||||
|
COMPLEX_UNIT_MM =5,
|
||||||
|
COMPLEX_UNIT_SHIFT =0,
|
||||||
|
COMPLEX_UNIT_MASK =15,
|
||||||
|
COMPLEX_UNIT_FRACTION =0,
|
||||||
|
COMPLEX_UNIT_FRACTION_PARENT=1,
|
||||||
|
COMPLEX_RADIX_23p0 =0,
|
||||||
|
COMPLEX_RADIX_16p7 =1,
|
||||||
|
COMPLEX_RADIX_8p15 =2,
|
||||||
|
COMPLEX_RADIX_0p23 =3,
|
||||||
|
COMPLEX_RADIX_SHIFT =4,
|
||||||
|
COMPLEX_RADIX_MASK =3,
|
||||||
|
COMPLEX_MANTISSA_SHIFT =8,
|
||||||
|
COMPLEX_MANTISSA_MASK =0xFFFFFF;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2008 Android4ME
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package wind.test;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import wind.v1.XmlPullParser;
|
||||||
|
import wind.android.content.res.AXmlResourceParser;
|
||||||
|
//import android.util.TypedValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dmitry Skiba
|
||||||
|
*
|
||||||
|
* This is example usage of AXMLParser class.
|
||||||
|
*
|
||||||
|
* Prints xml document from Android's binary xml file.
|
||||||
|
*/
|
||||||
|
public class AXMLPrinter {
|
||||||
|
|
||||||
|
public static void main(String[] arguments) {
|
||||||
|
if (arguments.length<1) {
|
||||||
|
log("Usage: AXMLPrinter <binary xml file>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
AXmlResourceParser parser=new AXmlResourceParser();
|
||||||
|
parser.open(new FileInputStream(arguments[0]));
|
||||||
|
StringBuilder indent=new StringBuilder(10);
|
||||||
|
final String indentStep=" ";
|
||||||
|
while (true) {
|
||||||
|
int type=parser.next();
|
||||||
|
if (type==XmlPullParser.END_DOCUMENT) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (type) {
|
||||||
|
case XmlPullParser.START_DOCUMENT:
|
||||||
|
{
|
||||||
|
log("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case XmlPullParser.START_TAG:
|
||||||
|
{
|
||||||
|
log("%s<%s%s",indent,
|
||||||
|
getNamespacePrefix(parser.getPrefix()),parser.getName());
|
||||||
|
indent.append(indentStep);
|
||||||
|
|
||||||
|
int namespaceCountBefore=parser.getNamespaceCount(parser.getDepth()-1);
|
||||||
|
int namespaceCount=parser.getNamespaceCount(parser.getDepth());
|
||||||
|
for (int i=namespaceCountBefore;i!=namespaceCount;++i) {
|
||||||
|
log("%sxmlns:%s=\"%s\"",
|
||||||
|
indent,
|
||||||
|
parser.getNamespacePrefix(i),
|
||||||
|
parser.getNamespaceUri(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i=0;i!=parser.getAttributeCount();++i) {
|
||||||
|
log("%s%s%s=\"%s\"",indent,
|
||||||
|
getNamespacePrefix(parser.getAttributePrefix(i)),
|
||||||
|
parser.getAttributeName(i),
|
||||||
|
getAttributeValue(parser,i));
|
||||||
|
}
|
||||||
|
log("%s>",indent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case XmlPullParser.END_TAG:
|
||||||
|
{
|
||||||
|
indent.setLength(indent.length()-indentStep.length());
|
||||||
|
log("%s</%s%s>",indent,
|
||||||
|
getNamespacePrefix(parser.getPrefix()),
|
||||||
|
parser.getName());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case XmlPullParser.TEXT:
|
||||||
|
{
|
||||||
|
log("%s%s",indent,parser.getText());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getNamespacePrefix(String prefix) {
|
||||||
|
if (prefix==null || prefix.length()==0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return prefix+":";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getAttributeValue(AXmlResourceParser parser,int index) {
|
||||||
|
int type=parser.getAttributeValueType(index);
|
||||||
|
int data=parser.getAttributeValueData(index);
|
||||||
|
// if (type==TypedValue.TYPE_STRING) {
|
||||||
|
// return parser.getAttributeValue(index);
|
||||||
|
// }
|
||||||
|
// if (type==TypedValue.TYPE_ATTRIBUTE) {
|
||||||
|
// return String.format("?%s%08X",getPackage(data),data);
|
||||||
|
// }
|
||||||
|
// if (type==TypedValue.TYPE_REFERENCE) {
|
||||||
|
// return String.format("@%s%08X",getPackage(data),data);
|
||||||
|
// }
|
||||||
|
// if (type==TypedValue.TYPE_FLOAT) {
|
||||||
|
// return String.valueOf(Float.intBitsToFloat(data));
|
||||||
|
// }
|
||||||
|
// if (type==TypedValue.TYPE_INT_HEX) {
|
||||||
|
// return String.format("0x%08X",data);
|
||||||
|
// }
|
||||||
|
// if (type==TypedValue.TYPE_INT_BOOLEAN) {
|
||||||
|
// return data!=0?"true":"false";
|
||||||
|
// }
|
||||||
|
// if (type==TypedValue.TYPE_DIMENSION) {
|
||||||
|
// return Float.toString(complexToFloat(data))+
|
||||||
|
// DIMENSION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK];
|
||||||
|
// }
|
||||||
|
// if (type==TypedValue.TYPE_FRACTION) {
|
||||||
|
// return Float.toString(complexToFloat(data))+
|
||||||
|
// FRACTION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK];
|
||||||
|
// }
|
||||||
|
// if (type>=TypedValue.TYPE_FIRST_COLOR_INT && type<=TypedValue.TYPE_LAST_COLOR_INT) {
|
||||||
|
// return String.format("#%08X",data);
|
||||||
|
// }
|
||||||
|
// if (type>=TypedValue.TYPE_FIRST_INT && type<=TypedValue.TYPE_LAST_INT) {
|
||||||
|
// return String.valueOf(data);
|
||||||
|
// }
|
||||||
|
return String.format("<0x%X, type 0x%02X>",data,type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getPackage(int id) {
|
||||||
|
if (id>>>24==1) {
|
||||||
|
return "android:";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void log(String format,Object...arguments) {
|
||||||
|
System.out.printf(format,arguments);
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////// ILLEGAL STUFF, DONT LOOK :)
|
||||||
|
|
||||||
|
public static float complexToFloat(int complex) {
|
||||||
|
return (float)(complex & 0xFFFFFF00)*RADIX_MULTS[(complex>>4) & 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final float RADIX_MULTS[]={
|
||||||
|
0.00390625F,3.051758E-005F,1.192093E-007F,4.656613E-010F
|
||||||
|
};
|
||||||
|
private static final String DIMENSION_UNITS[]={
|
||||||
|
"px","dip","sp","pt","in","mm","",""
|
||||||
|
};
|
||||||
|
private static final String FRACTION_UNITS[]={
|
||||||
|
"%","%p","","","","","",""
|
||||||
|
};
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,78 @@
|
||||||
|
/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- //------100-columns-wide------>|*/
|
||||||
|
// for license please see accompanying LICENSE.txt file (available also at http://www.xmlpull.org/)
|
||||||
|
|
||||||
|
package wind.v1;
|
||||||
|
|
||||||
|
import wind.v1.XmlPullParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception is thrown to signal XML Pull Parser related faults.
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.extreme.indiana.edu/~aslom/">Aleksander Slominski</a>
|
||||||
|
*/
|
||||||
|
public class XmlPullParserException extends Exception {
|
||||||
|
protected Throwable detail;
|
||||||
|
protected int row = -1;
|
||||||
|
protected int column = -1;
|
||||||
|
|
||||||
|
/* public XmlPullParserException() {
|
||||||
|
}*/
|
||||||
|
|
||||||
|
public XmlPullParserException(String s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
public XmlPullParserException(String s, Throwable thrwble) {
|
||||||
|
super(s);
|
||||||
|
this.detail = thrwble;
|
||||||
|
}
|
||||||
|
|
||||||
|
public XmlPullParserException(String s, int row, int column) {
|
||||||
|
super(s);
|
||||||
|
this.row = row;
|
||||||
|
this.column = column;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
public XmlPullParserException(String msg, XmlPullParser parser, Throwable chain) {
|
||||||
|
super ((msg == null ? "" : msg+" ")
|
||||||
|
+ (parser == null ? "" : "(position:"+parser.getPositionDescription()+") ")
|
||||||
|
+ (chain == null ? "" : "caused by: "+chain));
|
||||||
|
|
||||||
|
if (parser != null) {
|
||||||
|
this.row = parser.getLineNumber();
|
||||||
|
this.column = parser.getColumnNumber();
|
||||||
|
}
|
||||||
|
this.detail = chain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Throwable getDetail() { return detail; }
|
||||||
|
// public void setDetail(Throwable cause) { this.detail = cause; }
|
||||||
|
public int getLineNumber() { return row; }
|
||||||
|
public int getColumnNumber() { return column; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
public String getMessage() {
|
||||||
|
if(detail == null)
|
||||||
|
return super.getMessage();
|
||||||
|
else
|
||||||
|
return super.getMessage() + "; nested exception is: \n\t"
|
||||||
|
+ detail.getMessage();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
//NOTE: code that prints this and detail is difficult in J2ME
|
||||||
|
public void printStackTrace() {
|
||||||
|
if (detail == null) {
|
||||||
|
super.printStackTrace();
|
||||||
|
} else {
|
||||||
|
synchronized(System.err) {
|
||||||
|
System.err.println(super.getMessage() + "; nested exception is:");
|
||||||
|
detail.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,360 @@
|
||||||
|
/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- //------100-columns-wide------>|*/
|
||||||
|
// for license please see accompanying LICENSE.txt file (available also at http://www.xmlpull.org/)
|
||||||
|
|
||||||
|
package wind.v1;
|
||||||
|
|
||||||
|
import wind.v1.XmlPullParser;
|
||||||
|
import wind.v1.XmlPullParserException;
|
||||||
|
import wind.v1.XmlSerializer;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used to create implementations of XML Pull Parser defined in XMPULL V1 API.
|
||||||
|
* The name of actual factory class will be determined based on several parameters.
|
||||||
|
* It works similar to JAXP but tailored to work in J2ME environments
|
||||||
|
* (no access to system properties or file system) so name of parser class factory to use
|
||||||
|
* and its class used for loading (no class loader - on J2ME no access to context class loaders)
|
||||||
|
* must be passed explicitly. If no name of parser factory was passed (or is null)
|
||||||
|
* it will try to find name by searching in CLASSPATH for
|
||||||
|
* META-INF/services/wind.v1.XmlPullParserFactory resource that should contain
|
||||||
|
* a comma separated list of class names of factories or parsers to try (in order from
|
||||||
|
* left to the right). If none found, it will throw an exception.
|
||||||
|
*
|
||||||
|
* <br /><strong>NOTE:</strong>In J2SE or J2EE environments, you may want to use
|
||||||
|
* <code>newInstance(property, classLoaderCtx)</code>
|
||||||
|
* where first argument is
|
||||||
|
* <code>System.getProperty(XmlPullParserFactory.PROPERTY_NAME)</code>
|
||||||
|
* and second is <code>Thread.getContextClassLoader().getClass()</code> .
|
||||||
|
*
|
||||||
|
* @see wind.v1.XmlPullParser
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.extreme.indiana.edu/~aslom/">Aleksander Slominski</a>
|
||||||
|
* @author Stefan Haustein
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class XmlPullParserFactory {
|
||||||
|
/** used as default class to server as context class in newInstance() */
|
||||||
|
final static Class referenceContextClass;
|
||||||
|
|
||||||
|
static {
|
||||||
|
XmlPullParserFactory f = new XmlPullParserFactory();
|
||||||
|
referenceContextClass = f.getClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Name of the system or midlet property that should be used for
|
||||||
|
a system property containing a comma separated list of factory
|
||||||
|
or parser class names (value:
|
||||||
|
wind.v1.XmlPullParserFactory). */
|
||||||
|
|
||||||
|
|
||||||
|
public static final String PROPERTY_NAME =
|
||||||
|
"org.xmlpull.v1.XmlPullParserFactory";
|
||||||
|
|
||||||
|
private static final String RESOURCE_NAME =
|
||||||
|
"/META-INF/services/" + PROPERTY_NAME;
|
||||||
|
|
||||||
|
|
||||||
|
// public static final String DEFAULT_PROPERTY =
|
||||||
|
// "wind.xpp3.XmlPullParser,org.kxml2.io.KXmlParser";
|
||||||
|
|
||||||
|
|
||||||
|
protected Vector parserClasses;
|
||||||
|
protected String classNamesLocation;
|
||||||
|
|
||||||
|
protected Vector serializerClasses;
|
||||||
|
|
||||||
|
|
||||||
|
// features are kept there
|
||||||
|
protected Hashtable features = new Hashtable();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protected constructor to be called by factory implementations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
protected XmlPullParserFactory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the features to be set when XML Pull Parser is created by this factory.
|
||||||
|
* <p><b>NOTE:</b> factory features are not used for XML Serializer.
|
||||||
|
*
|
||||||
|
* @param name string with URI identifying feature
|
||||||
|
* @param state if true feature will be set; if false will be ignored
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void setFeature(String name,
|
||||||
|
boolean state) throws XmlPullParserException {
|
||||||
|
|
||||||
|
features.put(name, new Boolean(state));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current value of the feature with given name.
|
||||||
|
* <p><b>NOTE:</b> factory features are not used for XML Serializer.
|
||||||
|
*
|
||||||
|
* @param name The name of feature to be retrieved.
|
||||||
|
* @return The value of named feature.
|
||||||
|
* Unknown features are <string>always</strong> returned as false
|
||||||
|
*/
|
||||||
|
|
||||||
|
public boolean getFeature (String name) {
|
||||||
|
Boolean value = (Boolean) features.get(name);
|
||||||
|
return value != null ? value.booleanValue() : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies that the parser produced by this factory will provide
|
||||||
|
* support for XML namespaces.
|
||||||
|
* By default the value of this is set to false.
|
||||||
|
*
|
||||||
|
* @param awareness true if the parser produced by this code
|
||||||
|
* will provide support for XML namespaces; false otherwise.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void setNamespaceAware(boolean awareness) {
|
||||||
|
features.put (wind.v1.XmlPullParser.FEATURE_PROCESS_NAMESPACES, new Boolean (awareness));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not the factory is configured to produce
|
||||||
|
* parsers which are namespace aware
|
||||||
|
* (it simply set feature XmlPullParser.FEATURE_PROCESS_NAMESPACES to true or false).
|
||||||
|
*
|
||||||
|
* @return true if the factory is configured to produce parsers
|
||||||
|
* which are namespace aware; false otherwise.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public boolean isNamespaceAware() {
|
||||||
|
return getFeature (wind.v1.XmlPullParser.FEATURE_PROCESS_NAMESPACES);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies that the parser produced by this factory will be validating
|
||||||
|
* (it simply set feature XmlPullParser.FEATURE_VALIDATION to true or false).
|
||||||
|
*
|
||||||
|
* By default the value of this is set to false.
|
||||||
|
*
|
||||||
|
* @param validating - if true the parsers created by this factory must be validating.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void setValidating(boolean validating) {
|
||||||
|
features.put (wind.v1.XmlPullParser.FEATURE_VALIDATION, new Boolean (validating));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not the factory is configured to produce parsers
|
||||||
|
* which validate the XML content during parse.
|
||||||
|
*
|
||||||
|
* @return true if the factory is configured to produce parsers
|
||||||
|
* which validate the XML content during parse; false otherwise.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public boolean isValidating() {
|
||||||
|
return getFeature (wind.v1.XmlPullParser.FEATURE_VALIDATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of a XML Pull Parser
|
||||||
|
* using the currently configured factory features.
|
||||||
|
*
|
||||||
|
* @return A new instance of a XML Pull Parser.
|
||||||
|
* @throws XmlPullParserException if a parser cannot be created which satisfies the
|
||||||
|
* requested configuration.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public wind.v1.XmlPullParser newPullParser() throws XmlPullParserException {
|
||||||
|
|
||||||
|
if (parserClasses == null) throw new XmlPullParserException
|
||||||
|
("Factory initialization was incomplete - has not tried "+classNamesLocation);
|
||||||
|
|
||||||
|
if (parserClasses.size() == 0) throw new XmlPullParserException
|
||||||
|
("No valid parser classes found in "+classNamesLocation);
|
||||||
|
|
||||||
|
final StringBuffer issues = new StringBuffer ();
|
||||||
|
|
||||||
|
for (int i = 0; i < parserClasses.size (); i++) {
|
||||||
|
final Class ppClass = (Class) parserClasses.elementAt (i);
|
||||||
|
try {
|
||||||
|
final wind.v1.XmlPullParser pp = (wind.v1.XmlPullParser) ppClass.newInstance();
|
||||||
|
// if( ! features.isEmpty() ) {
|
||||||
|
//Enumeration keys = features.keys();
|
||||||
|
// while(keys.hasMoreElements()) {
|
||||||
|
|
||||||
|
for (Enumeration e = features.keys (); e.hasMoreElements ();) {
|
||||||
|
final String key = (String) e.nextElement();
|
||||||
|
final Boolean value = (Boolean) features.get(key);
|
||||||
|
if(value != null && value.booleanValue()) {
|
||||||
|
pp.setFeature(key, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pp;
|
||||||
|
|
||||||
|
} catch(Exception ex) {
|
||||||
|
issues.append (ppClass.getName () + ": "+ ex.toString ()+"; ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new XmlPullParserException ("could not create parser: "+issues);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of a XML Serializer.
|
||||||
|
*
|
||||||
|
* <p><b>NOTE:</b> factory features are not used for XML Serializer.
|
||||||
|
*
|
||||||
|
* @return A new instance of a XML Serializer.
|
||||||
|
* @throws XmlPullParserException if a parser cannot be created which satisfies the
|
||||||
|
* requested configuration.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public wind.v1.XmlSerializer newSerializer() throws XmlPullParserException {
|
||||||
|
|
||||||
|
if (serializerClasses == null) {
|
||||||
|
throw new XmlPullParserException
|
||||||
|
("Factory initialization incomplete - has not tried "+classNamesLocation);
|
||||||
|
}
|
||||||
|
if(serializerClasses.size() == 0) {
|
||||||
|
throw new XmlPullParserException
|
||||||
|
("No valid serializer classes found in "+classNamesLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
final StringBuffer issues = new StringBuffer ();
|
||||||
|
|
||||||
|
for (int i = 0; i < serializerClasses.size (); i++) {
|
||||||
|
final Class ppClass = (Class) serializerClasses.elementAt (i);
|
||||||
|
try {
|
||||||
|
final wind.v1.XmlSerializer ser = (wind.v1.XmlSerializer) ppClass.newInstance();
|
||||||
|
|
||||||
|
// for (Enumeration e = features.keys (); e.hasMoreElements ();) {
|
||||||
|
// String key = (String) e.nextElement();
|
||||||
|
// Boolean value = (Boolean) features.get(key);
|
||||||
|
// if(value != null && value.booleanValue()) {
|
||||||
|
// ser.setFeature(key, true);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
return ser;
|
||||||
|
|
||||||
|
} catch(Exception ex) {
|
||||||
|
issues.append (ppClass.getName () + ": "+ ex.toString ()+"; ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new XmlPullParserException ("could not create serializer: "+issues);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance of a PullParserFactory that can be used
|
||||||
|
* to create XML pull parsers (see class description for more
|
||||||
|
* details).
|
||||||
|
*
|
||||||
|
* @return a new instance of a PullParserFactory, as returned by newInstance (null, null);
|
||||||
|
*/
|
||||||
|
public static XmlPullParserFactory newInstance () throws XmlPullParserException {
|
||||||
|
return newInstance(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XmlPullParserFactory newInstance (String classNames, Class context)
|
||||||
|
throws XmlPullParserException {
|
||||||
|
|
||||||
|
if (context == null) {
|
||||||
|
//NOTE: make sure context uses the same class loader as API classes
|
||||||
|
// this is the best we can do without having access to context classloader in J2ME
|
||||||
|
// if API is in the same classloader as implementation then this will work
|
||||||
|
context = referenceContextClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
String classNamesLocation = null;
|
||||||
|
|
||||||
|
if (classNames == null || classNames.length() == 0 || "DEFAULT".equals(classNames)) {
|
||||||
|
try {
|
||||||
|
InputStream is = context.getResourceAsStream (RESOURCE_NAME);
|
||||||
|
|
||||||
|
if (is == null) throw new XmlPullParserException
|
||||||
|
("resource not found: "+RESOURCE_NAME
|
||||||
|
+" make sure that parser implementing XmlPull API is available");
|
||||||
|
final StringBuffer sb = new StringBuffer();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
final int ch = is.read();
|
||||||
|
if (ch < 0) break;
|
||||||
|
else if (ch > ' ')
|
||||||
|
sb.append((char) ch);
|
||||||
|
}
|
||||||
|
is.close ();
|
||||||
|
|
||||||
|
classNames = sb.toString ();
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new XmlPullParserException (null, null, e);
|
||||||
|
}
|
||||||
|
classNamesLocation = "resource "+RESOURCE_NAME+" that contained '"+classNames+"'";
|
||||||
|
} else {
|
||||||
|
classNamesLocation =
|
||||||
|
"parameter classNames to newInstance() that contained '"+classNames+"'";
|
||||||
|
}
|
||||||
|
|
||||||
|
XmlPullParserFactory factory = null;
|
||||||
|
final Vector parserClasses = new Vector ();
|
||||||
|
final Vector serializerClasses = new Vector ();
|
||||||
|
int pos = 0;
|
||||||
|
|
||||||
|
while (pos < classNames.length ()) {
|
||||||
|
int cut = classNames.indexOf (',', pos);
|
||||||
|
|
||||||
|
if (cut == -1) cut = classNames.length ();
|
||||||
|
final String name = classNames.substring (pos, cut);
|
||||||
|
|
||||||
|
Class candidate = null;
|
||||||
|
Object instance = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
candidate = Class.forName (name);
|
||||||
|
// necessary because of J2ME .class issue
|
||||||
|
instance = candidate.newInstance ();
|
||||||
|
}
|
||||||
|
catch (Exception e) {}
|
||||||
|
|
||||||
|
if (candidate != null) {
|
||||||
|
boolean recognized = false;
|
||||||
|
if (instance instanceof XmlPullParser) {
|
||||||
|
parserClasses.addElement (candidate);
|
||||||
|
recognized = true;
|
||||||
|
}
|
||||||
|
if (instance instanceof XmlSerializer) {
|
||||||
|
serializerClasses.addElement (candidate);
|
||||||
|
recognized = true;
|
||||||
|
}
|
||||||
|
if (instance instanceof XmlPullParserFactory) {
|
||||||
|
if (factory == null) {
|
||||||
|
factory = (XmlPullParserFactory) instance;
|
||||||
|
}
|
||||||
|
recognized = true;
|
||||||
|
}
|
||||||
|
if (!recognized) {
|
||||||
|
throw new XmlPullParserException ("incompatible class: "+name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos = cut + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (factory == null) {
|
||||||
|
factory = new XmlPullParserFactory ();
|
||||||
|
}
|
||||||
|
factory.parserClasses = parserClasses;
|
||||||
|
factory.serializerClasses = serializerClasses;
|
||||||
|
factory.classNamesLocation = classNamesLocation;
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,326 @@
|
||||||
|
package wind.v1;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.Writer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define an interface to serialziation of XML Infoset.
|
||||||
|
* This interface abstracts away if serialized XML is XML 1.0 comaptible text or
|
||||||
|
* other formats of XML 1.0 serializations (such as binary XML for example with WBXML).
|
||||||
|
*
|
||||||
|
* <p><b>PLEASE NOTE:</b> This interface will be part of XmlPull 1.2 API.
|
||||||
|
* It is included as basis for discussion. It may change in any way.
|
||||||
|
*
|
||||||
|
* <p>Exceptions that may be thrown are: IOException or runtime exception
|
||||||
|
* (more runtime exceptions can be thrown but are not declared and as such
|
||||||
|
* have no semantics defined for this interface):
|
||||||
|
* <ul>
|
||||||
|
* <li><em>IllegalArgumentException</em> - for almost all methods to signal that
|
||||||
|
* argument is illegal
|
||||||
|
* <li><em>IllegalStateException</em> - to signal that call has good arguments but
|
||||||
|
* is not expected here (violation of contract) and for features/properties
|
||||||
|
* when requesting setting unimplemented feature/property
|
||||||
|
* (UnsupportedOperationException would be better but it is not in MIDP)
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p><b>NOTE:</b> writing CDSECT, ENTITY_REF, IGNORABLE_WHITESPACE,
|
||||||
|
* PROCESSING_INSTRUCTION, COMMENT, and DOCDECL in some implementations
|
||||||
|
* may not be supported (for example when serializing to WBXML).
|
||||||
|
* In such case IllegalStateException will be thrown and it is recommened
|
||||||
|
* to use an optional feature to signal that implementation is not
|
||||||
|
* supporting this kind of output.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface XmlSerializer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set feature identified by name (recommended to be URI for uniqueness).
|
||||||
|
* Some well known optional features are defined in
|
||||||
|
* <a href="http://www.xmlpull.org/v1/doc/features.html">
|
||||||
|
* http://www.xmlpull.org/v1/doc/features.html</a>.
|
||||||
|
*
|
||||||
|
* If feature is not recocgnized or can not be set
|
||||||
|
* then IllegalStateException MUST be thrown.
|
||||||
|
*
|
||||||
|
* @exception IllegalStateException If the feature is not supported or can not be set
|
||||||
|
*/
|
||||||
|
void setFeature(String name,
|
||||||
|
boolean state)
|
||||||
|
throws IllegalArgumentException, IllegalStateException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current value of the feature with given name.
|
||||||
|
* <p><strong>NOTE:</strong> unknown properties are <strong>always</strong> returned as null
|
||||||
|
*
|
||||||
|
* @param name The name of feature to be retrieved.
|
||||||
|
* @return The value of named feature.
|
||||||
|
* @exception IllegalArgumentException if feature string is null
|
||||||
|
*/
|
||||||
|
boolean getFeature(String name);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the value of a property.
|
||||||
|
* (the property name is recommened to be URI for uniqueness).
|
||||||
|
* Some well known optional properties are defined in
|
||||||
|
* <a href="http://www.xmlpull.org/v1/doc/properties.html">
|
||||||
|
* http://www.xmlpull.org/v1/doc/properties.html</a>.
|
||||||
|
*
|
||||||
|
* If property is not recocgnized or can not be set
|
||||||
|
* then IllegalStateException MUST be thrown.
|
||||||
|
*
|
||||||
|
* @exception IllegalStateException if the property is not supported or can not be set
|
||||||
|
*/
|
||||||
|
void setProperty(String name,
|
||||||
|
Object value)
|
||||||
|
throws IllegalArgumentException, IllegalStateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up the value of a property.
|
||||||
|
*
|
||||||
|
* The property name is any fully-qualified URI. I
|
||||||
|
* <p><strong>NOTE:</strong> unknown properties are <string>always</strong> returned as null
|
||||||
|
*
|
||||||
|
* @param name The name of property to be retrieved.
|
||||||
|
* @return The value of named property.
|
||||||
|
*/
|
||||||
|
Object getProperty(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to use binary output stream with given encoding.
|
||||||
|
*/
|
||||||
|
void setOutput(OutputStream os, String encoding)
|
||||||
|
throws IOException, IllegalArgumentException, IllegalStateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the output to the given writer.
|
||||||
|
* <p><b>WARNING</b> no information about encoding is available!
|
||||||
|
*/
|
||||||
|
void setOutput(Writer writer)
|
||||||
|
throws IOException, IllegalArgumentException, IllegalStateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write <?xml declaration with encoding (if encoding not null)
|
||||||
|
* and standalone flag (if standalone not null)
|
||||||
|
* This method can only be called just after setOutput.
|
||||||
|
*/
|
||||||
|
void startDocument(String encoding, Boolean standalone)
|
||||||
|
throws IOException, IllegalArgumentException, IllegalStateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finish writing. All unclosed start tags will be closed and output
|
||||||
|
* will be flushed. After calling this method no more output can be
|
||||||
|
* serialized until next call to setOutput()
|
||||||
|
*/
|
||||||
|
void endDocument()
|
||||||
|
throws IOException, IllegalArgumentException, IllegalStateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds the given prefix to the given namespace.
|
||||||
|
* This call is valid for the next element including child elements.
|
||||||
|
* The prefix and namespace MUST be always declared even if prefix
|
||||||
|
* is not used in element (startTag() or attribute()) - for XML 1.0
|
||||||
|
* it must result in declaring <code>xmlns:prefix='namespace'</code>
|
||||||
|
* (or <code>xmlns:prefix="namespace"</code> depending what character is used
|
||||||
|
* to quote attribute value).
|
||||||
|
*
|
||||||
|
* <p><b>NOTE:</b> this method MUST be called directly before startTag()
|
||||||
|
* and if anything but startTag() or setPrefix() is called next there will be exception.
|
||||||
|
* <p><b>NOTE:</b> prefixes "xml" and "xmlns" are already bound
|
||||||
|
* and can not be redefined see:
|
||||||
|
* <a href="http://www.w3.org/XML/xml-names-19990114-errata#NE05">Namespaces in XML Errata</a>.
|
||||||
|
* <p><b>NOTE:</b> to set default namespace use as prefix empty string.
|
||||||
|
*
|
||||||
|
* @param prefix must be not null (or IllegalArgumentException is thrown)
|
||||||
|
* @param namespace must be not null
|
||||||
|
*/
|
||||||
|
void setPrefix(String prefix, String namespace)
|
||||||
|
throws IOException, IllegalArgumentException, IllegalStateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return namespace that corresponds to given prefix
|
||||||
|
* If there is no prefix bound to this namespace return null
|
||||||
|
* but if generatePrefix is false then return generated prefix.
|
||||||
|
*
|
||||||
|
* <p><b>NOTE:</b> if the prefix is empty string "" and defualt namespace is bound
|
||||||
|
* to this prefix then empty string ("") is returned.
|
||||||
|
*
|
||||||
|
* <p><b>NOTE:</b> prefixes "xml" and "xmlns" are already bound
|
||||||
|
* will have values as defined
|
||||||
|
* <a href="http://www.w3.org/TR/REC-xml-names/">Namespaces in XML specification</a>
|
||||||
|
*/
|
||||||
|
String getPrefix(String namespace, boolean generatePrefix)
|
||||||
|
throws IllegalArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current depth of the element.
|
||||||
|
* Outside the root element, the depth is 0. The
|
||||||
|
* depth is incremented by 1 when startTag() is called.
|
||||||
|
* The depth is decremented after the call to endTag()
|
||||||
|
* event was observed.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* <!-- outside --> 0
|
||||||
|
* <root> 1
|
||||||
|
* sometext 1
|
||||||
|
* <foobar> 2
|
||||||
|
* </foobar> 2
|
||||||
|
* </root> 1
|
||||||
|
* <!-- outside --> 0
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
int getDepth();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the namespace URI of the current element as set by startTag().
|
||||||
|
*
|
||||||
|
* <p><b>NOTE:</b> that measn in particaulr that: <ul>
|
||||||
|
* <li>if there was startTag("", ...) then getNamespace() returns ""
|
||||||
|
* <li>if there was startTag(null, ...) then getNamespace() returns null
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @return namespace set by startTag() that is currently in scope
|
||||||
|
*/
|
||||||
|
String getNamespace();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the current element as set by startTag().
|
||||||
|
* It can only be null before first call to startTag()
|
||||||
|
* or when last endTag() is called to close first startTag().
|
||||||
|
*
|
||||||
|
* @return namespace set by startTag() that is currently in scope
|
||||||
|
*/
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a start tag with the given namespace and name.
|
||||||
|
* If there is no prefix defined for the given namespace,
|
||||||
|
* a prefix will be defined automatically.
|
||||||
|
* The explicit prefixes for namespaces can be established by calling setPrefix()
|
||||||
|
* immediately before this method.
|
||||||
|
* If namespace is null no namespace prefix is printed but just name.
|
||||||
|
* If namespace is empty string then serialzier will make sure that
|
||||||
|
* default empty namespace is declared (in XML 1.0 xmlns='')
|
||||||
|
* or throw IllegalStateException if default namespace is already bound
|
||||||
|
* to non-empty string.
|
||||||
|
*/
|
||||||
|
XmlSerializer startTag(String namespace, String name)
|
||||||
|
throws IOException, IllegalArgumentException, IllegalStateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write an attribute. Calls to attribute() MUST follow a call to
|
||||||
|
* startTag() immediately. If there is no prefix defined for the
|
||||||
|
* given namespace, a prefix will be defined automatically.
|
||||||
|
* If namespace is null or empty string
|
||||||
|
* no namespace prefix is printed but just name.
|
||||||
|
*/
|
||||||
|
XmlSerializer attribute(String namespace, String name, String value)
|
||||||
|
throws IOException, IllegalArgumentException, IllegalStateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write end tag. Repetition of namespace and name is just for avoiding errors.
|
||||||
|
* <p><b>Background:</b> in kXML endTag had no arguments, and non matching tags were
|
||||||
|
* very difficult to find...
|
||||||
|
* If namespace is null no namespace prefix is printed but just name.
|
||||||
|
* If namespace is empty string then serialzier will make sure that
|
||||||
|
* default empty namespace is declared (in XML 1.0 xmlns='').
|
||||||
|
*/
|
||||||
|
XmlSerializer endTag(String namespace, String name)
|
||||||
|
throws IOException, IllegalArgumentException, IllegalStateException;
|
||||||
|
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Writes a start tag with the given namespace and name.
|
||||||
|
// * <br />If there is no prefix defined (prefix == null) for the given namespace,
|
||||||
|
// * a prefix will be defined automatically.
|
||||||
|
// * <br />If explicit prefixes is passed (prefix != null) then it will be used
|
||||||
|
// *and namespace declared if not already declared or
|
||||||
|
// * throw IllegalStateException the same prefix was already set on this
|
||||||
|
// * element (setPrefix()) and was bound to different namespace.
|
||||||
|
// * <br />If namespace is null then prefix must be null too or IllegalStateException is thrown.
|
||||||
|
// * <br />If namespace is null then no namespace prefix is printed but just name.
|
||||||
|
// * <br />If namespace is empty string then serializer will make sure that
|
||||||
|
// * default empty namespace is declared (in XML 1.0 xmlns='')
|
||||||
|
// * or throw IllegalStateException if default namespace is already bound
|
||||||
|
// * to non-empty string.
|
||||||
|
// */
|
||||||
|
// XmlSerializer startTag (String prefix, String namespace, String name)
|
||||||
|
// throws IOException, IllegalArgumentException, IllegalStateException;
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * Write an attribute. Calls to attribute() MUST follow a call to
|
||||||
|
// * startTag() immediately.
|
||||||
|
// * <br />If there is no prefix defined (prefix == null) for the given namespace,
|
||||||
|
// * a prefix will be defined automatically.
|
||||||
|
// * <br />If explicit prefixes is passed (prefix != null) then it will be used
|
||||||
|
// * and namespace declared if not already declared or
|
||||||
|
// * throw IllegalStateException the same prefix was already set on this
|
||||||
|
// * element (setPrefix()) and was bound to different namespace.
|
||||||
|
// * <br />If namespace is null then prefix must be null too or IllegalStateException is thrown.
|
||||||
|
// * <br />If namespace is null then no namespace prefix is printed but just name.
|
||||||
|
// * <br />If namespace is empty string then serializer will make sure that
|
||||||
|
// * default empty namespace is declared (in XML 1.0 xmlns='')
|
||||||
|
// * or throw IllegalStateException if default namespace is already bound
|
||||||
|
// * to non-empty string.
|
||||||
|
// */
|
||||||
|
// XmlSerializer attribute (String prefix, String namespace, String name, String value)
|
||||||
|
// throws IOException, IllegalArgumentException, IllegalStateException;
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * Write end tag. Repetition of namespace, prefix, and name is just for avoiding errors.
|
||||||
|
// * <br />If namespace or name arguments are different from corresponding startTag call
|
||||||
|
// * then IllegalArgumentException is thrown, if prefix argument is not null and is different
|
||||||
|
// * from corresponding starTag then IllegalArgumentException is thrown.
|
||||||
|
// * <br />If namespace is null then prefix must be null too or IllegalStateException is thrown.
|
||||||
|
// * <br />If namespace is null then no namespace prefix is printed but just name.
|
||||||
|
// * <br />If namespace is empty string then serializer will make sure that
|
||||||
|
// * default empty namespace is declared (in XML 1.0 xmlns='').
|
||||||
|
// * <p><b>Background:</b> in kXML endTag had no arguments, and non matching tags were
|
||||||
|
// * very difficult to find...</p>
|
||||||
|
// */
|
||||||
|
// ALEK: This is really optional as prefix in end tag MUST correspond to start tag but good for error checking
|
||||||
|
// XmlSerializer endTag (String prefix, String namespace, String name)
|
||||||
|
// throws IOException, IllegalArgumentException, IllegalStateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes text, where special XML chars are escaped automatically
|
||||||
|
*/
|
||||||
|
XmlSerializer text(String text)
|
||||||
|
throws IOException, IllegalArgumentException, IllegalStateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes text, where special XML chars are escaped automatically
|
||||||
|
*/
|
||||||
|
XmlSerializer text(char[] buf, int start, int len)
|
||||||
|
throws IOException, IllegalArgumentException, IllegalStateException;
|
||||||
|
|
||||||
|
void cdsect(String text)
|
||||||
|
throws IOException, IllegalArgumentException, IllegalStateException;
|
||||||
|
void entityRef(String text) throws IOException,
|
||||||
|
IllegalArgumentException, IllegalStateException;
|
||||||
|
void processingInstruction(String text)
|
||||||
|
throws IOException, IllegalArgumentException, IllegalStateException;
|
||||||
|
void comment(String text)
|
||||||
|
throws IOException, IllegalArgumentException, IllegalStateException;
|
||||||
|
void docdecl(String text)
|
||||||
|
throws IOException, IllegalArgumentException, IllegalStateException;
|
||||||
|
void ignorableWhitespace(String text)
|
||||||
|
throws IOException, IllegalArgumentException, IllegalStateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write all pending output to the stream.
|
||||||
|
* If method startTag() or attribute() was called then start tag is closed (final >)
|
||||||
|
* before flush() is called on underlying output stream.
|
||||||
|
*
|
||||||
|
* <p><b>NOTE:</b> if there is need to close start tag
|
||||||
|
* (so no more attribute() calls are allowed) but without flushinging output
|
||||||
|
* call method text() with empty string (text("")).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void flush()
|
||||||
|
throws IOException;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
allprojects {
|
||||||
|
apply plugin: 'maven'
|
||||||
|
apply plugin: 'idea'
|
||||||
|
apply plugin: 'eclipse'
|
||||||
|
// version = '1.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultTasks('clean','distZip')
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'maven'
|
||||||
|
sourceCompatibility = 1.7
|
||||||
|
targetCompatibility = 1.7
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// task packageSources(type: Jar) {
|
||||||
|
// classifier = 'sources'
|
||||||
|
// from sourceSets.main.allSource
|
||||||
|
// }
|
||||||
|
// artifacts.archives packageSources
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
[compileJava, compileTestJava]*.options.collect {options ->options.encoding = 'UTF-8'}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile fileTree(dir: 'libs', include: '*.jar')
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
manifest {
|
||||||
|
attributes("Implementation-Title": project.name,
|
||||||
|
"Implementation-Version": project.version,
|
||||||
|
"Build-Time": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
|
||||||
|
"Build-Number": System.env.BUILD_NUMBER?System.env.BUILD_NUMBER:"-1",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
from (project.parent.projectDir) {
|
||||||
|
include 'NOTICE.txt'
|
||||||
|
include 'LICENSE.txt'
|
||||||
|
into('META-INF')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Project-wide Gradle settings.
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
org.gradle.jvmargs=-Xmx1536m
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
|
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||||
|
# Android operating system, and which are packaged with your app's APK
|
||||||
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
|
android.useAndroidX=true
|
||||||
|
# Automatically convert third-party libraries to use AndroidX
|
||||||
|
android.enableJetifier=false
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,6 @@
|
||||||
|
#Wed Mar 06 01:11:44 CST 2019
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-all.zip
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=$(save "$@")
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||||
|
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS=
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
rootProject.name = 'Xpatch'
|
||||||
|
include ':axmlprinter', ':xpatch'
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
apply plugin: 'java-library'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
compile project(':axmlprinter')
|
||||||
|
}
|
||||||
|
|
||||||
|
jar{
|
||||||
|
baseName = "xpatch"
|
||||||
|
// version = '1.0'
|
||||||
|
manifest {
|
||||||
|
attributes 'Main-Class': 'com.storm.wind.xpatch.MainCommand'
|
||||||
|
}
|
||||||
|
//添加将引用的jar的源码打入最终的jar
|
||||||
|
from {
|
||||||
|
(configurations.runtime).collect {
|
||||||
|
it.isDirectory() ? it : zipTree(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
from fileTree(dir:'src/main', includes: ['assets/**'])
|
||||||
|
|
||||||
|
//排除引用的jar中的签名信息
|
||||||
|
exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.MF'
|
||||||
|
}
|
||||||
|
|
||||||
|
//添加源码中引入的非代码文件,例如资源等
|
||||||
|
sourceSets.main.resources {
|
||||||
|
srcDirs = [
|
||||||
|
"src/main/java",
|
||||||
|
];
|
||||||
|
include "**/*.*"
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,188 @@
|
||||||
|
package com.storm.wind.xpatch;
|
||||||
|
|
||||||
|
import com.storm.wind.xpatch.base.BaseCommand;
|
||||||
|
import com.storm.wind.xpatch.task.ApkModifyTask;
|
||||||
|
import com.storm.wind.xpatch.task.BuildAndSignApkTask;
|
||||||
|
import com.storm.wind.xpatch.task.SoAndDexCopyTask;
|
||||||
|
import com.storm.wind.xpatch.util.FileUtils;
|
||||||
|
import com.storm.wind.xpatch.util.ManifestParser;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class MainCommand extends BaseCommand {
|
||||||
|
|
||||||
|
private String apkPath;
|
||||||
|
|
||||||
|
private String unzipApkFilePath;
|
||||||
|
|
||||||
|
@Opt(opt = "o", longOpt = "output", description = "output .apk file, default is " +
|
||||||
|
"$source_apk_dir/[file-name]-xposed-signed.apk", argName = "out-apk-file")
|
||||||
|
private String output; // 输出的apk文件的目录以及名称
|
||||||
|
|
||||||
|
@Opt(opt = "f", longOpt = "force", hasArg = false, description = "force overwrite")
|
||||||
|
private boolean forceOverwrite = false;
|
||||||
|
|
||||||
|
@Opt(opt = "k", longOpt = "keep", hasArg = false, description = "not delete the jar file " +
|
||||||
|
"that is changed by dex2jar and the apk zip files")
|
||||||
|
private boolean keepBuildFiles = false;
|
||||||
|
|
||||||
|
@Opt(opt = "l", longOpt = "log", hasArg = false, description = "show all the debug logs")
|
||||||
|
private boolean showAllLogs = false;
|
||||||
|
|
||||||
|
// 原来apk中dex文件的数量
|
||||||
|
private int dexFileCount = 0;
|
||||||
|
|
||||||
|
private static final String UNZIP_APK_FILE_NAME = "apk-unzip-files";
|
||||||
|
|
||||||
|
private static final String DEFAULT_APPLICATION_NAME = "android.app.Application";
|
||||||
|
|
||||||
|
private List<Runnable> mXpatchTasks = new ArrayList<>();
|
||||||
|
|
||||||
|
public static void main(String... args) {
|
||||||
|
new MainCommand().doMain(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doCommandLine() {
|
||||||
|
if (remainingArgs.length != 1) {
|
||||||
|
if (remainingArgs.length == 0) {
|
||||||
|
System.out.println("Please choose one apk file you want to process. ");
|
||||||
|
}
|
||||||
|
if (remainingArgs.length > 1) {
|
||||||
|
System.out.println("This tool can only used with one apk file.");
|
||||||
|
}
|
||||||
|
usage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
apkPath = remainingArgs[0];
|
||||||
|
|
||||||
|
File srcApkFile = new File(apkPath);
|
||||||
|
|
||||||
|
if (!srcApkFile.exists()) {
|
||||||
|
System.out.println(" The source apk file not exsit, please choose another one. " +
|
||||||
|
"current apk file is = " + apkPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String srcApkFileParentPath = srcApkFile.getParent();
|
||||||
|
if (srcApkFileParentPath == null) {
|
||||||
|
String absPath = srcApkFile.getAbsolutePath();
|
||||||
|
int index = absPath.lastIndexOf(File.separatorChar);
|
||||||
|
srcApkFileParentPath = absPath.substring(0, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
String currentDir = new File(".").getAbsolutePath(); // 当前命令行所在的目录
|
||||||
|
if (showAllLogs) {
|
||||||
|
System.out.println(" currentDir = " + currentDir + " \n apkPath = " + apkPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output == null || output.length() == 0) {
|
||||||
|
output = getBaseName(apkPath) + "-xposed-signed.apk";
|
||||||
|
}
|
||||||
|
|
||||||
|
File outputFile = new File(output);
|
||||||
|
if (outputFile.exists() && !forceOverwrite) {
|
||||||
|
System.err.println(output + " exists, use --force to overwrite");
|
||||||
|
usage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println(" !!!!! output apk path --> " + output);
|
||||||
|
|
||||||
|
String apkFileName = getBaseName(srcApkFile);
|
||||||
|
|
||||||
|
// 中间文件临时存储的位置
|
||||||
|
String tempFilePath = srcApkFileParentPath + File.separator + currentTimeStr() + "-tmp" + File.separator;
|
||||||
|
|
||||||
|
// apk文件解压的目录
|
||||||
|
unzipApkFilePath = tempFilePath + apkFileName + "-" + UNZIP_APK_FILE_NAME + File.separator;
|
||||||
|
|
||||||
|
if (showAllLogs) {
|
||||||
|
System.out.println(" !!!!! srcApkFileParentPath = " + srcApkFileParentPath +
|
||||||
|
"\n unzipApkFilePath = " + unzipApkFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先解压apk到指定目录下
|
||||||
|
FileUtils.decompressZip(apkPath, unzipApkFilePath);
|
||||||
|
|
||||||
|
// Get the dex count in the apk zip file
|
||||||
|
dexFileCount = findDexFileCount(unzipApkFilePath);
|
||||||
|
|
||||||
|
if (showAllLogs) {
|
||||||
|
System.out.println(" --- dexFileCount = " + dexFileCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
String manifestFilePath = unzipApkFilePath + "AndroidManifest.xml";
|
||||||
|
|
||||||
|
// parse the app main application full name from the manifest file
|
||||||
|
ManifestParser.Pair pair = ManifestParser.parseManidestFile(manifestFilePath);
|
||||||
|
String applicationName;
|
||||||
|
if (pair != null && pair.applictionName != null) {
|
||||||
|
applicationName = pair.applictionName;
|
||||||
|
} else {
|
||||||
|
System.out.println(" Application name not found error !!!!!! ");
|
||||||
|
applicationName = DEFAULT_APPLICATION_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showAllLogs) {
|
||||||
|
System.out.println(" Get the application name --> " + applicationName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. modify the apk dex file to make xposed can run in it
|
||||||
|
mXpatchTasks.add(new ApkModifyTask(showAllLogs, keepBuildFiles, unzipApkFilePath, applicationName,
|
||||||
|
dexFileCount));
|
||||||
|
|
||||||
|
// 2. copy xposed so and dex files into the unzipped apk
|
||||||
|
mXpatchTasks.add(new SoAndDexCopyTask(dexFileCount, unzipApkFilePath));
|
||||||
|
|
||||||
|
// 3. compress all files into an apk and then sign it.
|
||||||
|
mXpatchTasks.add(new BuildAndSignApkTask(keepBuildFiles, unzipApkFilePath, output));
|
||||||
|
|
||||||
|
// 4. excute these tasks
|
||||||
|
for (Runnable executor : mXpatchTasks) {
|
||||||
|
executor.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. delete all the build files that is useless now
|
||||||
|
File unzipApkFile = new File(unzipApkFilePath);
|
||||||
|
if (!keepBuildFiles && unzipApkFile.exists()) {
|
||||||
|
FileUtils.deleteDir(unzipApkFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
File tempFile = new File(tempFilePath);
|
||||||
|
if (!keepBuildFiles && tempFile.exists()) {
|
||||||
|
tempFile.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int findDexFileCount(String unzipApkFilePath) {
|
||||||
|
File zipfileRoot = new File(unzipApkFilePath);
|
||||||
|
if (!zipfileRoot.exists()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
File[] childFiles = zipfileRoot.listFiles();
|
||||||
|
if (childFiles == null || childFiles.length == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int count = 0;
|
||||||
|
for (File file : childFiles) {
|
||||||
|
String fileName = file.getName();
|
||||||
|
if (Pattern.matches("classes.*\\.dex", fileName)) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the current timestamp as the name of the build file
|
||||||
|
private String currentTimeStr() {
|
||||||
|
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");//设置日期格式
|
||||||
|
return df.format(new Date());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,460 @@
|
||||||
|
package com.storm.wind.xpatch.base;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Wind
|
||||||
|
*/
|
||||||
|
public abstract class BaseCommand {
|
||||||
|
|
||||||
|
private String onlineHelp;
|
||||||
|
|
||||||
|
protected Map<String, Option> optMap = new HashMap<String, Option>();
|
||||||
|
|
||||||
|
@Opt(opt = "h", longOpt = "help", hasArg = false, description = "Print this help message")
|
||||||
|
private boolean printHelp = false;
|
||||||
|
|
||||||
|
protected String remainingArgs[];
|
||||||
|
protected String orginalArgs[];
|
||||||
|
|
||||||
|
@Retention(value = RetentionPolicy.RUNTIME)
|
||||||
|
@Target(value = { ElementType.FIELD })
|
||||||
|
static public @interface Opt {
|
||||||
|
String argName() default "";
|
||||||
|
|
||||||
|
String description() default "";
|
||||||
|
|
||||||
|
boolean hasArg() default true;
|
||||||
|
|
||||||
|
String longOpt() default "";
|
||||||
|
|
||||||
|
String opt() default "";
|
||||||
|
|
||||||
|
boolean required() default false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static protected class Option implements Comparable<Option> {
|
||||||
|
public String argName = "arg";
|
||||||
|
public String description;
|
||||||
|
public Field field;
|
||||||
|
public boolean hasArg = true;
|
||||||
|
public String longOpt;
|
||||||
|
public String opt;
|
||||||
|
public boolean required = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Option o) {
|
||||||
|
int result = s(this.opt, o.opt);
|
||||||
|
if (result == 0) {
|
||||||
|
result = s(this.longOpt, o.longOpt);
|
||||||
|
if (result == 0) {
|
||||||
|
result = s(this.argName, o.argName);
|
||||||
|
if (result == 0) {
|
||||||
|
result = s(this.description, o.description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int s(String a, String b) {
|
||||||
|
if (a != null && b != null) {
|
||||||
|
return a.compareTo(b);
|
||||||
|
} else if (a != null) {
|
||||||
|
return 1;
|
||||||
|
} else if (b != null) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOptAndLongOpt() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
boolean havePrev = false;
|
||||||
|
if (opt != null && opt.length() > 0) {
|
||||||
|
sb.append("-").append(opt);
|
||||||
|
havePrev = true;
|
||||||
|
}
|
||||||
|
if (longOpt != null && longOpt.length() > 0) {
|
||||||
|
if (havePrev) {
|
||||||
|
sb.append(",");
|
||||||
|
}
|
||||||
|
sb.append("--").append(longOpt);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(value = RetentionPolicy.RUNTIME)
|
||||||
|
@Target(value = { ElementType.TYPE })
|
||||||
|
static public @interface Syntax {
|
||||||
|
|
||||||
|
String cmd();
|
||||||
|
|
||||||
|
String desc() default "";
|
||||||
|
|
||||||
|
String onlineHelp() default "";
|
||||||
|
|
||||||
|
String syntax() default "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doMain(String... args) {
|
||||||
|
try {
|
||||||
|
initOptions();
|
||||||
|
parseSetArgs(args);
|
||||||
|
doCommandLine();
|
||||||
|
} catch (HelpException e) {
|
||||||
|
String msg = e.getMessage();
|
||||||
|
if (msg != null && msg.length() > 0) {
|
||||||
|
System.err.println("ERROR: " + msg);
|
||||||
|
}
|
||||||
|
usage();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace(System.err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void doCommandLine() throws Exception;
|
||||||
|
|
||||||
|
protected String getVersionString() {
|
||||||
|
return getClass().getPackage().getImplementationVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initOptions() {
|
||||||
|
initOptionFromClass(this.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initOptionFromClass(Class<?> clz) {
|
||||||
|
if (clz == null) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
initOptionFromClass(clz.getSuperclass());
|
||||||
|
}
|
||||||
|
|
||||||
|
Syntax syntax = clz.getAnnotation(Syntax.class);
|
||||||
|
if (syntax != null) {
|
||||||
|
this.onlineHelp = syntax.onlineHelp();
|
||||||
|
}
|
||||||
|
|
||||||
|
Field[] fs = clz.getDeclaredFields();
|
||||||
|
for (Field f : fs) {
|
||||||
|
Opt opt = f.getAnnotation(Opt.class);
|
||||||
|
if (opt != null) {
|
||||||
|
f.setAccessible(true);
|
||||||
|
Option option = new Option();
|
||||||
|
option.field = f;
|
||||||
|
option.description = opt.description();
|
||||||
|
option.hasArg = opt.hasArg();
|
||||||
|
option.required = opt.required();
|
||||||
|
if ("".equals(opt.longOpt()) && "".equals(opt.opt())) { // into automode
|
||||||
|
option.longOpt = fromCamel(f.getName());
|
||||||
|
if (f.getType().equals(boolean.class)) {
|
||||||
|
option.hasArg=false;
|
||||||
|
try {
|
||||||
|
if (f.getBoolean(this)) {
|
||||||
|
throw new RuntimeException("the value of " + f +
|
||||||
|
" must be false, as it is declared as no args");
|
||||||
|
}
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkConflict(option, "--" + option.longOpt);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!opt.hasArg()) {
|
||||||
|
if (!f.getType().equals(boolean.class)) {
|
||||||
|
throw new RuntimeException("the type of " + f
|
||||||
|
+ " must be boolean, as it is declared as no args");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (f.getBoolean(this)) {
|
||||||
|
throw new RuntimeException("the value of " + f +
|
||||||
|
" must be false, as it is declared as no args");
|
||||||
|
}
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boolean haveLongOpt = false;
|
||||||
|
if (!"".equals(opt.longOpt())) {
|
||||||
|
option.longOpt = opt.longOpt();
|
||||||
|
checkConflict(option, "--" + option.longOpt);
|
||||||
|
haveLongOpt = true;
|
||||||
|
}
|
||||||
|
if (!"".equals(opt.argName())) {
|
||||||
|
option.argName = opt.argName();
|
||||||
|
}
|
||||||
|
if (!"".equals(opt.opt())) {
|
||||||
|
option.opt = opt.opt();
|
||||||
|
checkConflict(option, "-" + option.opt);
|
||||||
|
} else {
|
||||||
|
if (!haveLongOpt) {
|
||||||
|
throw new RuntimeException("opt or longOpt is not set in @Opt(...) " + f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkConflict(Option option, String key) {
|
||||||
|
if (optMap.containsKey(key)) {
|
||||||
|
Option preOption = optMap.get(key);
|
||||||
|
throw new RuntimeException(String.format("[@Opt(...) %s] conflict with [@Opt(...) %s]",
|
||||||
|
preOption.field.toString(), option.field
|
||||||
|
));
|
||||||
|
}
|
||||||
|
optMap.put(key, option);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String fromCamel(String name) {
|
||||||
|
if (name.length() == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
char[] charArray = name.toCharArray();
|
||||||
|
sb.append(Character.toLowerCase(charArray[0]));
|
||||||
|
for (int i = 1; i < charArray.length; i++) {
|
||||||
|
char c = charArray[i];
|
||||||
|
if (Character.isUpperCase(c)) {
|
||||||
|
sb.append("-").append(Character.toLowerCase(c));
|
||||||
|
} else {
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void parseSetArgs(String... args) throws IllegalArgumentException, IllegalAccessException {
|
||||||
|
this.orginalArgs = args;
|
||||||
|
List<String> remainsOptions = new ArrayList<String>();
|
||||||
|
Set<Option> requiredOpts = collectRequriedOptions(optMap);
|
||||||
|
Option needArgOpt = null;
|
||||||
|
for (String s : args) {
|
||||||
|
if (needArgOpt != null) {
|
||||||
|
needArgOpt.field.set(this, convert(s, needArgOpt.field.getType()));
|
||||||
|
needArgOpt = null;
|
||||||
|
} else if (s.startsWith("-")) {// its a short or long option
|
||||||
|
Option opt = optMap.get(s);
|
||||||
|
requiredOpts.remove(opt);
|
||||||
|
if (opt == null) {
|
||||||
|
System.err.println("ERROR: Unrecognized option: " + s);
|
||||||
|
throw new HelpException();
|
||||||
|
} else {
|
||||||
|
if (opt.hasArg) {
|
||||||
|
needArgOpt = opt;
|
||||||
|
} else {
|
||||||
|
opt.field.set(this, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
remainsOptions.add(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needArgOpt != null) {
|
||||||
|
System.err.println("ERROR: Option " + needArgOpt.getOptAndLongOpt() + " need an argument value");
|
||||||
|
throw new HelpException();
|
||||||
|
}
|
||||||
|
this.remainingArgs = remainsOptions.toArray(new String[remainsOptions.size()]);
|
||||||
|
if (this.printHelp) {
|
||||||
|
throw new HelpException();
|
||||||
|
}
|
||||||
|
if (!requiredOpts.isEmpty()) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("ERROR: Options: ");
|
||||||
|
boolean first = true;
|
||||||
|
for (Option option : requiredOpts) {
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
sb.append(" and ");
|
||||||
|
}
|
||||||
|
sb.append(option.getOptAndLongOpt());
|
||||||
|
}
|
||||||
|
sb.append(" is required");
|
||||||
|
System.err.println(sb.toString());
|
||||||
|
throw new HelpException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
|
protected Object convert(String value, Class type) {
|
||||||
|
if (type.equals(String.class)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (type.equals(int.class) || type.equals(Integer.class)) {
|
||||||
|
return Integer.parseInt(value);
|
||||||
|
}
|
||||||
|
if (type.equals(long.class) || type.equals(Long.class)) {
|
||||||
|
return Long.parseLong(value);
|
||||||
|
}
|
||||||
|
if (type.equals(float.class) || type.equals(Float.class)) {
|
||||||
|
return Float.parseFloat(value);
|
||||||
|
}
|
||||||
|
if (type.equals(double.class) || type.equals(Double.class)) {
|
||||||
|
return Double.parseDouble(value);
|
||||||
|
}
|
||||||
|
if (type.equals(boolean.class) || type.equals(Boolean.class)) {
|
||||||
|
return Boolean.parseBoolean(value);
|
||||||
|
}
|
||||||
|
if (type.equals(File.class)) {
|
||||||
|
return new File(value);
|
||||||
|
}
|
||||||
|
if (type.equals(Path.class)) {
|
||||||
|
return new File(value).toPath();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
type.asSubclass(Enum.class);
|
||||||
|
return Enum.valueOf(type, value);
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException("can't convert [" + value + "] to type " + type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<Option> collectRequriedOptions(Map<String, Option> optMap) {
|
||||||
|
Set<Option> options = new HashSet<Option>();
|
||||||
|
for (Map.Entry<String, Option> e : optMap.entrySet()) {
|
||||||
|
Option option = e.getValue();
|
||||||
|
if (option.required) {
|
||||||
|
options.add(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
protected static class HelpException extends RuntimeException {
|
||||||
|
|
||||||
|
public HelpException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public HelpException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void usage() {
|
||||||
|
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.err, StandardCharsets.UTF_8), true);
|
||||||
|
|
||||||
|
final int maxLength = 80;
|
||||||
|
final int maxPaLength = 40;
|
||||||
|
// out.println(this.cmdName + " -- " + desc);
|
||||||
|
// out.println("usage: " + this.cmdName + " " + cmdLineSyntax);
|
||||||
|
if (this.optMap.size() > 0) {
|
||||||
|
out.println("options:");
|
||||||
|
}
|
||||||
|
// [PART.A.........][Part.B
|
||||||
|
// .-a,--aa.<arg>...desc1
|
||||||
|
// .................desc2
|
||||||
|
// .-b,--bb
|
||||||
|
TreeSet<Option> options = new TreeSet<Option>(this.optMap.values());
|
||||||
|
int palength = -1;
|
||||||
|
for (Option option : options) {
|
||||||
|
int pa = 4 + option.getOptAndLongOpt().length();
|
||||||
|
if (option.hasArg) {
|
||||||
|
pa += 3 + option.argName.length();
|
||||||
|
}
|
||||||
|
if (pa < maxPaLength) {
|
||||||
|
if (pa > palength) {
|
||||||
|
palength = pa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int pblength = maxLength - palength;
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (Option option : options) {
|
||||||
|
sb.setLength(0);
|
||||||
|
sb.append(" ").append(option.getOptAndLongOpt());
|
||||||
|
if (option.hasArg) {
|
||||||
|
sb.append(" <").append(option.argName).append(">");
|
||||||
|
}
|
||||||
|
String desc = option.description;
|
||||||
|
if (desc == null || desc.length() == 0) {// no description
|
||||||
|
out.println(sb);
|
||||||
|
} else {
|
||||||
|
for (int i = palength - sb.length(); i > 0; i--) {
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
if (sb.length() > maxPaLength) {// to huge part A
|
||||||
|
out.println(sb);
|
||||||
|
sb.setLength(0);
|
||||||
|
for (int i = 0; i < palength; i++) {
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int nextStart = 0;
|
||||||
|
while (nextStart < desc.length()) {
|
||||||
|
if (desc.length() - nextStart < pblength) {// can put in one line
|
||||||
|
sb.append(desc.substring(nextStart));
|
||||||
|
out.println(sb);
|
||||||
|
nextStart = desc.length();
|
||||||
|
sb.setLength(0);
|
||||||
|
} else {
|
||||||
|
sb.append(desc.substring(nextStart, nextStart + pblength));
|
||||||
|
out.println(sb);
|
||||||
|
nextStart += pblength;
|
||||||
|
sb.setLength(0);
|
||||||
|
if (nextStart < desc.length()) {
|
||||||
|
for (int i = 0; i < palength; i++) {
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
out.println(sb);
|
||||||
|
sb.setLength(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String ver = getVersionString();
|
||||||
|
if (ver != null && !"".equals(ver)) {
|
||||||
|
out.println("version: " + ver);
|
||||||
|
}
|
||||||
|
if (onlineHelp != null && !"".equals(onlineHelp)) {
|
||||||
|
if (onlineHelp.length() + "online help: ".length() > maxLength) {
|
||||||
|
out.println("online help: ");
|
||||||
|
out.println(onlineHelp);
|
||||||
|
} else {
|
||||||
|
out.println("online help: " + onlineHelp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getBaseName(String fn) {
|
||||||
|
int x = fn.lastIndexOf('.');
|
||||||
|
return x >= 0 ? fn.substring(0, x) : fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件不包含后缀的名称
|
||||||
|
public static String getBaseName(File fn) {
|
||||||
|
return getBaseName(fn.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
package com.storm.wind.xpatch.task;
|
||||||
|
|
||||||
|
import com.googlecode.dex2jar.tools.Dex2jarCmd;
|
||||||
|
import com.googlecode.dex2jar.tools.Jar2Dex;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Wind
|
||||||
|
*/
|
||||||
|
public class ApkModifyTask implements Runnable {
|
||||||
|
|
||||||
|
private static final String JAR_FILE_NAME = "output-jar.jar";
|
||||||
|
|
||||||
|
private String unzipApkFilePath;
|
||||||
|
private boolean keepJarFile;
|
||||||
|
private boolean showAllLogs;
|
||||||
|
private String applicationName;
|
||||||
|
|
||||||
|
private int dexFileCount;
|
||||||
|
|
||||||
|
public ApkModifyTask(boolean showAllLogs, boolean keepJarFile, String unzipApkFilePath, String applicationName, int
|
||||||
|
dexFileCount) {
|
||||||
|
this.showAllLogs = showAllLogs;
|
||||||
|
this.unzipApkFilePath = unzipApkFilePath;
|
||||||
|
this.keepJarFile = keepJarFile;
|
||||||
|
this.applicationName = applicationName;
|
||||||
|
this.dexFileCount = dexFileCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
File unzipApkFile = new File(unzipApkFilePath);
|
||||||
|
|
||||||
|
String jarOutputPath = unzipApkFile.getParent() + File.separator + JAR_FILE_NAME;
|
||||||
|
|
||||||
|
// classes.dex
|
||||||
|
String targetDexFileName = dumpJarFile(dexFileCount, unzipApkFilePath, jarOutputPath, applicationName);
|
||||||
|
|
||||||
|
if (showAllLogs) {
|
||||||
|
System.out.println(" the application class is in this dex file = " + targetDexFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
String dexOutputPath = unzipApkFilePath + targetDexFileName;
|
||||||
|
File dexFile = new File(dexOutputPath);
|
||||||
|
if (dexFile.exists()) {
|
||||||
|
dexFile.delete();
|
||||||
|
}
|
||||||
|
// 将jar转换为dex文件
|
||||||
|
jar2DexCmd(jarOutputPath, dexOutputPath);
|
||||||
|
|
||||||
|
// 删除掉jar文件
|
||||||
|
File jarFile = new File(jarOutputPath);
|
||||||
|
if (!keepJarFile && jarFile.exists()) {
|
||||||
|
jarFile.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private String dumpJarFile(int dexFileCount, String dexFilePath, String jarOutputPath, String applicationName) {
|
||||||
|
ArrayList<String> dexFileList = createClassesDotDexFileList(dexFileCount);
|
||||||
|
// String jarOutputPath = dexFilePath + JAR_FILE_NAME;
|
||||||
|
for (String dexFileName : dexFileList) {
|
||||||
|
String filePath = dexFilePath + dexFileName;
|
||||||
|
// 执行dex2jar命令,修改源代码
|
||||||
|
boolean isApplicationClassFound = dex2JarCmd(filePath, jarOutputPath, applicationName);
|
||||||
|
// 找到了目标应用主application的包名,说明代码注入成功,则返回当前dex文件
|
||||||
|
if (isApplicationClassFound) {
|
||||||
|
return dexFileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean dex2JarCmd(String dexPath, String jarOutputPath, String applicationName) {
|
||||||
|
Dex2jarCmd cmd = new Dex2jarCmd();
|
||||||
|
String[] args = new String[]{
|
||||||
|
dexPath,
|
||||||
|
"-o",
|
||||||
|
jarOutputPath,
|
||||||
|
"-app",
|
||||||
|
applicationName,
|
||||||
|
"--force"
|
||||||
|
};
|
||||||
|
cmd.doMain(args);
|
||||||
|
|
||||||
|
boolean isApplicationClassFounded = cmd.isApplicationClassFounded();
|
||||||
|
if (showAllLogs) {
|
||||||
|
System.out.println("isApplicationClassFounded -> " + isApplicationClassFounded + "the dexPath is " +
|
||||||
|
dexPath);
|
||||||
|
}
|
||||||
|
return isApplicationClassFounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void jar2DexCmd(String jarFilePath, String dexOutPath) {
|
||||||
|
Jar2Dex cmd = new Jar2Dex();
|
||||||
|
String[] args = new String[]{
|
||||||
|
jarFilePath,
|
||||||
|
"-o",
|
||||||
|
dexOutPath
|
||||||
|
};
|
||||||
|
cmd.doMain(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 列出目录下所有dex文件,classes.dex,classes2.dex,classes3.dex .....
|
||||||
|
private ArrayList<String> createClassesDotDexFileList(int dexFileCount) {
|
||||||
|
ArrayList<String> list = new ArrayList<>();
|
||||||
|
for (int i = 0; i < dexFileCount; i++) {
|
||||||
|
if (i == 0) {
|
||||||
|
list.add("classes.dex");
|
||||||
|
} else {
|
||||||
|
list.add("classes" + (i + 1) + ".dex");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
package com.storm.wind.xpatch.task;
|
||||||
|
|
||||||
|
import com.storm.wind.xpatch.util.FileUtils;
|
||||||
|
import com.storm.wind.xpatch.util.ShellCmdUtil;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Wind
|
||||||
|
*/
|
||||||
|
public class BuildAndSignApkTask implements Runnable {
|
||||||
|
|
||||||
|
private boolean keepUnsignedApkFile;
|
||||||
|
|
||||||
|
private String signedApkPath;
|
||||||
|
|
||||||
|
private String unzipApkFilePath;
|
||||||
|
|
||||||
|
public BuildAndSignApkTask(boolean keepUnsignedApkFile, String unzipApkFilePath, String signedApkPath) {
|
||||||
|
this.keepUnsignedApkFile = keepUnsignedApkFile;
|
||||||
|
this.unzipApkFilePath = unzipApkFilePath;
|
||||||
|
this.signedApkPath = signedApkPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
File unzipApkFile = new File(unzipApkFilePath);
|
||||||
|
|
||||||
|
// 将文件压缩到当前apk文件的上一级目录上
|
||||||
|
String unsignedApkPath = unzipApkFile.getParent() + File.separator + "unsigned.apk";
|
||||||
|
FileUtils.compressToZip(unzipApkFilePath, unsignedApkPath);
|
||||||
|
|
||||||
|
// 将签名文件复制从assets目录下复制出来
|
||||||
|
String keyStoreFilePath = unzipApkFile.getParent() + File.separator + "keystore";
|
||||||
|
|
||||||
|
File keyStoreFile = new File(keyStoreFilePath);
|
||||||
|
// assets/keystore分隔符不能使用File.separator,否则在windows上抛出IOException !!!
|
||||||
|
FileUtils.copyFileFromJar("assets/keystore", keyStoreFilePath);
|
||||||
|
|
||||||
|
signApk(unsignedApkPath, keyStoreFilePath, signedApkPath, false);
|
||||||
|
|
||||||
|
File unsignedApkFile = new File(unsignedApkPath);
|
||||||
|
File signedApkFile = new File(signedApkPath);
|
||||||
|
// delete unsigned apk file
|
||||||
|
if (!keepUnsignedApkFile && unsignedApkFile.exists() && signedApkFile.exists()) {
|
||||||
|
unsignedApkFile.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the keystore file
|
||||||
|
if (keyStoreFile.exists()) {
|
||||||
|
keyStoreFile.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean signApk(String apkPath, String keyStorePath, String signedApkPath, boolean useLocalJarsigner) {
|
||||||
|
File localJarsignerFile = null;
|
||||||
|
try {
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
File keystoreFile = new File(keyStorePath);
|
||||||
|
if (keystoreFile.exists()) {
|
||||||
|
StringBuilder signCmd;
|
||||||
|
if (!useLocalJarsigner) {
|
||||||
|
signCmd = new StringBuilder("jarsigner ");
|
||||||
|
} else {
|
||||||
|
String localJarsignerPath = (new File(apkPath)).getParent() + File.separator + "jarsigner-081688";
|
||||||
|
localJarsignerFile = new File(localJarsignerPath);
|
||||||
|
FileUtils.copyFileFromJar("assets/jarsigner", localJarsignerPath);
|
||||||
|
ShellCmdUtil.execCmd("chmod -R 777 " + localJarsignerPath, null);
|
||||||
|
signCmd = new StringBuilder(localJarsignerPath + " ");
|
||||||
|
}
|
||||||
|
signCmd.append(" -keystore ")
|
||||||
|
.append(keyStorePath)
|
||||||
|
.append(" -storepass ")
|
||||||
|
.append("123456")
|
||||||
|
.append(" -signedjar ")
|
||||||
|
.append(" " + signedApkPath + " ")
|
||||||
|
.append(" " + apkPath + " ")
|
||||||
|
.append(" -digestalg SHA1 -sigalg SHA1withRSA ")
|
||||||
|
.append(" key0 ");
|
||||||
|
// System.out.println("\n" + signCmd + "\n");
|
||||||
|
String result = ShellCmdUtil.execCmd(signCmd.toString(), null);
|
||||||
|
System.out.println(" sign apk time is :" + ((System.currentTimeMillis() - time) / 1000) +
|
||||||
|
"s\n\n" + " result=" + result);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
System.out.println(" keystore not exist :" + keystoreFile.getAbsolutePath() +
|
||||||
|
" please sign the apk by hand. \n");
|
||||||
|
return false;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (!useLocalJarsigner) {
|
||||||
|
System.out.println("use default jarsigner to sign apk failed,and try again, fail msg is :" +
|
||||||
|
e.toString());
|
||||||
|
signApk(apkPath, keyStorePath, signedApkPath, true);
|
||||||
|
} else {
|
||||||
|
System.out.println("use inner jarsigner to sign apk failed, sign it yourself fail msg is :" +
|
||||||
|
e.toString());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (localJarsignerFile != null && localJarsignerFile.exists()) {
|
||||||
|
localJarsignerFile.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
package com.storm.wind.xpatch.task;
|
||||||
|
|
||||||
|
import com.storm.wind.xpatch.util.FileUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Wind
|
||||||
|
*/
|
||||||
|
public class SoAndDexCopyTask implements Runnable {
|
||||||
|
|
||||||
|
private static final String SO_FILE_NAME = "libxpatch_wl.so";
|
||||||
|
|
||||||
|
private final String[] APK_LIB_PATH_ARRAY = {
|
||||||
|
"lib/armeabi-v7a/",
|
||||||
|
"lib/armeabi/",
|
||||||
|
"lib/arm64-v8a/"
|
||||||
|
};
|
||||||
|
|
||||||
|
private final HashMap<String, String> SO_FILE_PATH_MAP = new HashMap<String, String>() {
|
||||||
|
{
|
||||||
|
put(APK_LIB_PATH_ARRAY[0], "assets/" + APK_LIB_PATH_ARRAY[0] + SO_FILE_NAME);
|
||||||
|
put(APK_LIB_PATH_ARRAY[1], "assets/" + APK_LIB_PATH_ARRAY[0] + SO_FILE_NAME);
|
||||||
|
put(APK_LIB_PATH_ARRAY[2], "assets/" + APK_LIB_PATH_ARRAY[2] + SO_FILE_NAME);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private int dexFileCount;
|
||||||
|
private String unzipApkFilePath;
|
||||||
|
|
||||||
|
public SoAndDexCopyTask(int dexFileCount, String unzipApkFilePath) {
|
||||||
|
this.dexFileCount = dexFileCount;
|
||||||
|
this.unzipApkFilePath = unzipApkFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// 复制xposed兼容层的dex文件以及so文件到当前目录下
|
||||||
|
copySoFile();
|
||||||
|
copyDexFile(dexFileCount);
|
||||||
|
|
||||||
|
// 删除签名信息
|
||||||
|
deleteMetaInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copySoFile() {
|
||||||
|
// Try to find so file path in the apk, then copy so into it
|
||||||
|
boolean copySuccess = false;
|
||||||
|
for (String libPath : APK_LIB_PATH_ARRAY) {
|
||||||
|
copySuccess = copyLibFile(unzipApkFilePath + libPath.replace("/", File.separator),
|
||||||
|
SO_FILE_PATH_MAP.get(libPath), false);
|
||||||
|
if (copySuccess) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iif apk do not contain so file path, then create lib/armeabi-v7a, and copy libwhale.so into it
|
||||||
|
if (!copySuccess) {
|
||||||
|
String path = APK_LIB_PATH_ARRAY[0];
|
||||||
|
copySuccess = copyLibFile(unzipApkFilePath + path.replace("/", File.separator),
|
||||||
|
SO_FILE_PATH_MAP.get(path), true);
|
||||||
|
}
|
||||||
|
if (!copySuccess) {
|
||||||
|
throw new IllegalArgumentException(" copy so file failed ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyDexFile(int dexFileCount) {
|
||||||
|
// copy dex file to root dir, rename it first
|
||||||
|
String copiedDexFileName = "classes" + (dexFileCount + 1) + ".dex";
|
||||||
|
// assets/classes.dex分隔符不能使用File.seperater,否则在windows上无法读取到文件,IOException
|
||||||
|
FileUtils.copyFileFromJar("assets/classes.dex", unzipApkFilePath + copiedDexFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean copyLibFile(String libFilePath, String srcSoPath, boolean forceCopy) {
|
||||||
|
File apkSoParentFile = new File(libFilePath);
|
||||||
|
if (forceCopy && !apkSoParentFile.exists()) {
|
||||||
|
apkSoParentFile.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
File[] childs = apkSoParentFile.listFiles();
|
||||||
|
if (apkSoParentFile.exists() && (forceCopy || (childs != null && childs.length > 0))) {
|
||||||
|
FileUtils.copyFileFromJar(srcSoPath, new File(apkSoParentFile, SO_FILE_NAME).getAbsolutePath());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteMetaInfo() {
|
||||||
|
String metaInfoFilePath = "META-INF";
|
||||||
|
File metaInfoFileRoot = new File(unzipApkFilePath + metaInfoFilePath);
|
||||||
|
if (!metaInfoFileRoot.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
File[] childFileList = metaInfoFileRoot.listFiles();
|
||||||
|
if (childFileList == null || childFileList.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (File file : childFileList) {
|
||||||
|
String fileName = file.getName().toUpperCase();
|
||||||
|
if (fileName.endsWith(".MF") || fileName.endsWith(".RAS") || fileName.endsWith(".SF")) {
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,218 @@
|
||||||
|
package com.storm.wind.xpatch.util;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
import java.util.zip.CheckedOutputStream;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Wind
|
||||||
|
*/
|
||||||
|
public class FileUtils {
|
||||||
|
|
||||||
|
static final int BUFFER = 8192;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解压文件
|
||||||
|
*
|
||||||
|
* @param zipPath 要解压的目标文件
|
||||||
|
* @param descDir 指定解压目录
|
||||||
|
* @return 解压结果:成功,失败
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public static boolean decompressZip(String zipPath, String descDir) {
|
||||||
|
File zipFile = new File(zipPath);
|
||||||
|
boolean flag = false;
|
||||||
|
if (!descDir.endsWith(File.separator)) {
|
||||||
|
descDir = descDir + File.separator;
|
||||||
|
}
|
||||||
|
File pathFile = new File(descDir);
|
||||||
|
if (!pathFile.exists()) {
|
||||||
|
pathFile.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
ZipFile zip = null;
|
||||||
|
try {
|
||||||
|
zip = new ZipFile(zipFile, Charset.forName("gbk"));//防止中文目录,乱码
|
||||||
|
for (Enumeration entries = zip.entries(); entries.hasMoreElements(); ) {
|
||||||
|
ZipEntry entry = (ZipEntry) entries.nextElement();
|
||||||
|
String zipEntryName = entry.getName();
|
||||||
|
InputStream in = zip.getInputStream(entry);
|
||||||
|
|
||||||
|
//指定解压后的文件夹+当前zip文件的名称
|
||||||
|
String outPath = (descDir + zipEntryName).replace("/", File.separator);
|
||||||
|
//判断路径是否存在,不存在则创建文件路径
|
||||||
|
File file = new File(outPath.substring(0, outPath.lastIndexOf(File.separator)));
|
||||||
|
|
||||||
|
if (!file.exists()) {
|
||||||
|
file.mkdirs();
|
||||||
|
}
|
||||||
|
//判断文件全路径是否为文件夹,如果是上面已经上传,不需要解压
|
||||||
|
if (new File(outPath).isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
//保存文件路径信息(可利用md5.zip名称的唯一性,来判断是否已经解压)
|
||||||
|
// System.err.println("当前zip解压之后的路径为:" + outPath);
|
||||||
|
OutputStream out = new FileOutputStream(outPath);
|
||||||
|
byte[] buf1 = new byte[2048];
|
||||||
|
int len;
|
||||||
|
while ((len = in.read(buf1)) > 0) {
|
||||||
|
out.write(buf1, 0, len);
|
||||||
|
}
|
||||||
|
close(in);
|
||||||
|
close(out);
|
||||||
|
}
|
||||||
|
flag = true;
|
||||||
|
close(zip);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InputStream getInputStreamFromFile(String filePath) {
|
||||||
|
return FileUtils.class.getClassLoader().getResourceAsStream(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy an asset file into a path
|
||||||
|
public static void copyFileFromJar(String inJarPath, String distPath) {
|
||||||
|
|
||||||
|
// System.out.println("start copyFile inJarPath =" + inJarPath + " distPath = " + distPath);
|
||||||
|
InputStream inputStream = getInputStreamFromFile(inJarPath);
|
||||||
|
|
||||||
|
BufferedInputStream in = null;
|
||||||
|
BufferedOutputStream out = null;
|
||||||
|
try {
|
||||||
|
in = new BufferedInputStream(inputStream);
|
||||||
|
out = new BufferedOutputStream(new FileOutputStream(distPath));
|
||||||
|
|
||||||
|
int len = -1;
|
||||||
|
byte[] b = new byte[1024];
|
||||||
|
while ((len = in.read(b)) != -1) {
|
||||||
|
out.write(b, 0, len);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
close(out);
|
||||||
|
close(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void deleteDir(File file) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
File[] files = file.listFiles();
|
||||||
|
if (files != null && files.length > 0) {
|
||||||
|
for (File f : files) {
|
||||||
|
deleteDir(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void compressToZip(String srcPath, String dstPath) {
|
||||||
|
File srcFile = new File(srcPath);
|
||||||
|
File dstFile = new File(dstPath);
|
||||||
|
if (!srcFile.exists()) {
|
||||||
|
System.out.println(srcPath + " does not exist !");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOutputStream out = null;
|
||||||
|
ZipOutputStream zipOut = null;
|
||||||
|
try {
|
||||||
|
out = new FileOutputStream(dstFile);
|
||||||
|
CheckedOutputStream cos = new CheckedOutputStream(out, new CRC32());
|
||||||
|
zipOut = new ZipOutputStream(cos);
|
||||||
|
String baseDir = "";
|
||||||
|
compress(srcFile, zipOut, baseDir, true);
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println(" compress exception = " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (zipOut != null) {
|
||||||
|
zipOut.closeEntry();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
close(zipOut);
|
||||||
|
close(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void compress(File file, ZipOutputStream zipOut, String baseDir, boolean isRootDir) throws IOException {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
compressDirectory(file, zipOut, baseDir, isRootDir);
|
||||||
|
} else {
|
||||||
|
compressFile(file, zipOut, baseDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 压缩一个目录
|
||||||
|
*/
|
||||||
|
private static void compressDirectory(File dir, ZipOutputStream zipOut, String baseDir, boolean isRootDir) throws IOException {
|
||||||
|
File[] files = dir.listFiles();
|
||||||
|
if (files == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < files.length; i++) {
|
||||||
|
String compressBaseDir = "";
|
||||||
|
if (!isRootDir) {
|
||||||
|
compressBaseDir = baseDir + dir.getName() + "/";
|
||||||
|
}
|
||||||
|
compress(files[i], zipOut, compressBaseDir, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 压缩一个文件
|
||||||
|
*/
|
||||||
|
private static void compressFile(File file, ZipOutputStream zipOut, String baseDir) throws IOException {
|
||||||
|
if (!file.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferedInputStream bis = null;
|
||||||
|
try {
|
||||||
|
bis = new BufferedInputStream(new FileInputStream(file));
|
||||||
|
ZipEntry entry = new ZipEntry(baseDir + file.getName());
|
||||||
|
zipOut.putNextEntry(entry);
|
||||||
|
int count;
|
||||||
|
byte data[] = new byte[BUFFER];
|
||||||
|
while ((count = bis.read(data, 0, BUFFER)) != -1) {
|
||||||
|
zipOut.write(data, 0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (null != bis) {
|
||||||
|
bis.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void close(Closeable closeable) {
|
||||||
|
try {
|
||||||
|
if (closeable != null) {
|
||||||
|
closeable.close();
|
||||||
|
}
|
||||||
|
} catch (IOException io) {
|
||||||
|
io.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
package com.storm.wind.xpatch.util;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import wind.android.content.res.AXmlResourceParser;
|
||||||
|
import wind.v1.XmlPullParser;
|
||||||
|
import wind.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Wind
|
||||||
|
*/
|
||||||
|
public class ManifestParser {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the package name and the main application name from the manifest file
|
||||||
|
* */
|
||||||
|
public static Pair parseManidestFile(String filePath) {
|
||||||
|
AXmlResourceParser parser = new AXmlResourceParser();
|
||||||
|
File file = new File(filePath);
|
||||||
|
String packageName = null;
|
||||||
|
String applictionName = null;
|
||||||
|
if (!file.exists()) {
|
||||||
|
System.out.println(" manifest file not exsit!!! filePath -> " + filePath);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
FileInputStream inputStream = new FileInputStream(file);
|
||||||
|
|
||||||
|
parser.open(inputStream);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
int type = parser.next();
|
||||||
|
if (type == XmlPullParser.END_DOCUMENT) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (type == XmlPullParser.START_TAG) {
|
||||||
|
int attrCount = parser.getAttributeCount();
|
||||||
|
for (int i = 0; i < attrCount; i++) {
|
||||||
|
String attrName = parser.getAttributeName(i);
|
||||||
|
|
||||||
|
String name = parser.getName();
|
||||||
|
|
||||||
|
if ("manifest".equals(name)) {
|
||||||
|
if ("package".equals(attrName)) {
|
||||||
|
packageName = parser.getAttributeValue(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("application".equals(name)) {
|
||||||
|
if ("name".equals(attrName)) {
|
||||||
|
applictionName = parser.getAttributeValue(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packageName != null && packageName.length() > 0 && applictionName != null && applictionName.length() > 0) {
|
||||||
|
return new Pair(packageName, applictionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type == XmlPullParser.END_TAG) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (XmlPullParserException | IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.out.println("parseManidestFile failed, reason --> " + e.getMessage());
|
||||||
|
}
|
||||||
|
return new Pair(packageName, applictionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Pair {
|
||||||
|
public String packageName;
|
||||||
|
public String applictionName;
|
||||||
|
|
||||||
|
public Pair(String packageName, String applictionName) {
|
||||||
|
this.packageName = packageName;
|
||||||
|
this.applictionName = applictionName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
package com.storm.wind.xpatch.util;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Wind
|
||||||
|
*/
|
||||||
|
public class ShellCmdUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行系统命令, 返回执行结果
|
||||||
|
*
|
||||||
|
* @param cmd 需要执行的命令
|
||||||
|
* @param dir 执行命令的子进程的工作目录, null 表示和当前主进程工作目录相同
|
||||||
|
*/
|
||||||
|
public static String execCmd(String cmd, File dir) throws Exception {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
|
Process process = null;
|
||||||
|
BufferedReader bufrIn = null;
|
||||||
|
BufferedReader bufrError = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 执行命令, 返回一个子进程对象(命令在子进程中执行)
|
||||||
|
process = Runtime.getRuntime().exec(cmd, null, dir);
|
||||||
|
|
||||||
|
// 方法阻塞, 等待命令执行完成(成功会返回0)
|
||||||
|
process.waitFor();
|
||||||
|
|
||||||
|
// 获取命令执行结果, 有两个结果: 正常的输出 和 错误的输出(PS: 子进程的输出就是主进程的输入)
|
||||||
|
bufrIn = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
|
||||||
|
bufrError = new BufferedReader(new InputStreamReader(process.getErrorStream(), "UTF-8"));
|
||||||
|
|
||||||
|
// 读取输出
|
||||||
|
String line = null;
|
||||||
|
while ((line = bufrIn.readLine()) != null) {
|
||||||
|
result.append(line).append('\n');
|
||||||
|
}
|
||||||
|
while ((line = bufrError.readLine()) != null) {
|
||||||
|
result.append(line).append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
close(bufrIn);
|
||||||
|
close(bufrError);
|
||||||
|
|
||||||
|
// 销毁子进程
|
||||||
|
if (process != null) {
|
||||||
|
process.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回执行结果
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void close(Closeable stream) {
|
||||||
|
if (stream != null) {
|
||||||
|
try {
|
||||||
|
stream.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue