Merge "AAPT2: Support generating Manifest.java"
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 275476c..a41d2d7 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -45,9 +45,11 @@
ConfigDescription.cpp \
Debug.cpp \
Flags.cpp \
- JavaClassGenerator.cpp \
+ java/AnnotationProcessor.cpp \
+ java/JavaClassGenerator.cpp \
+ java/ManifestClassGenerator.cpp \
+ java/ProguardRules.cpp \
Locale.cpp \
- ProguardRules.cpp \
Resource.cpp \
ResourceParser.cpp \
ResourceTable.cpp \
@@ -76,7 +78,8 @@
util/StringPiece_test.cpp \
util/Util_test.cpp \
ConfigDescription_test.cpp \
- JavaClassGenerator_test.cpp \
+ java/JavaClassGenerator_test.cpp \
+ java/ManifestClassGenerator_test.cpp \
Locale_test.cpp \
Resource_test.cpp \
ResourceParser_test.cpp \
diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp
index 1962f58..34dc1d5 100644
--- a/tools/aapt2/Resource.cpp
+++ b/tools/aapt2/Resource.cpp
@@ -36,7 +36,6 @@
case ResourceType::kFraction: return u"fraction";
case ResourceType::kId: return u"id";
case ResourceType::kInteger: return u"integer";
- case ResourceType::kIntegerArray: return u"integer-array";
case ResourceType::kInterpolator: return u"interpolator";
case ResourceType::kLayout: return u"layout";
case ResourceType::kMenu: return u"menu";
@@ -65,7 +64,6 @@
{ u"fraction", ResourceType::kFraction },
{ u"id", ResourceType::kId },
{ u"integer", ResourceType::kInteger },
- { u"integer-array", ResourceType::kIntegerArray },
{ u"interpolator", ResourceType::kInterpolator },
{ u"layout", ResourceType::kLayout },
{ u"menu", ResourceType::kMenu },
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 7ef1897..a7afbb5 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -47,7 +47,6 @@
kFraction,
kId,
kInteger,
- kIntegerArray,
kInterpolator,
kLayout,
kMenu,
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 44710eb..0c7a4d5 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -308,6 +308,9 @@
} else if (elementName == u"dimen") {
parsedResource.name.type = ResourceType::kDimen;
result = parsePrimitive(parser, &parsedResource);
+ } else if (elementName == u"fraction") {
+ parsedResource.name.type = ResourceType::kFraction;
+ result = parsePrimitive(parser, &parsedResource);
} else if (elementName == u"style") {
parsedResource.name.type = ResourceType::kStyle;
result = parseStyle(parser, &parsedResource);
@@ -321,7 +324,7 @@
parsedResource.name.type = ResourceType::kArray;
result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_STRING);
} else if (elementName == u"integer-array") {
- parsedResource.name.type = ResourceType::kIntegerArray;
+ parsedResource.name.type = ResourceType::kArray;
result = parseArray(parser, &parsedResource, android::ResTable_map::TYPE_INTEGER);
} else if (elementName == u"declare-styleable") {
parsedResource.name.type = ResourceType::kStyleable;
@@ -464,6 +467,8 @@
typeMask |= android::ResTable_map::TYPE_INTEGER;
break;
+ case ResourceType::kFraction:
+ // fallthrough
case ResourceType::kDimen:
typeMask |= android::ResTable_map::TYPE_DIMENSION
| android::ResTable_map::TYPE_FLOAT
@@ -576,6 +581,12 @@
return mask;
}
+/**
+ * Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
+ */
+static bool shouldIgnoreElement(const StringPiece16& ns, const StringPiece16& name) {
+ return ns.empty() && (name == u"skip" || name == u"eat-comment");
+}
bool ResourceParser::parseAttr(XmlPullParser* parser, ParsedResource* outResource) {
outResource->source = mSource.withLine(parser->getLineNumber());
@@ -613,25 +624,30 @@
bool error = false;
const size_t depth = parser->getDepth();
while (XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
- // Skip comments and text.
+ if (parser->getEvent() == XmlPullParser::Event::kComment) {
+ comment = util::trimWhitespace(parser->getComment()).toString();
+ continue;
+ } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ // Skip text.
continue;
}
+ const Source itemSource = mSource.withLine(parser->getLineNumber());
const std::u16string& elementNamespace = parser->getElementNamespace();
const std::u16string& elementName = parser->getElementName();
- if (elementNamespace == u"" && (elementName == u"flag" || elementName == u"enum")) {
+ if (elementNamespace.empty() && (elementName == u"flag" || elementName == u"enum")) {
if (elementName == u"enum") {
if (typeMask & android::ResTable_map::TYPE_FLAGS) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ mDiag->error(DiagMessage(itemSource)
<< "can not define an <enum>; already defined a <flag>");
error = true;
continue;
}
typeMask |= android::ResTable_map::TYPE_ENUM;
+
} else if (elementName == u"flag") {
if (typeMask & android::ResTable_map::TYPE_ENUM) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ mDiag->error(DiagMessage(itemSource)
<< "can not define a <flag>; already defined an <enum>");
error = true;
continue;
@@ -642,21 +658,22 @@
if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) {
ParsedResource childResource;
childResource.name = s.value().symbol.name.value();
- childResource.source = mSource.withLine(parser->getLineNumber());
+ childResource.source = itemSource;
childResource.value = util::make_unique<Id>();
outResource->childResources.push_back(std::move(childResource));
+
+ s.value().symbol.setComment(std::move(comment));
+ s.value().symbol.setSource(itemSource);
items.push_back(std::move(s.value()));
} else {
error = true;
}
- } else if (elementName == u"skip" || elementName == u"eat-comment") {
- comment = u"";
-
- } else {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
- << ":" << elementName << ">");
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+ mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">");
error = true;
}
+
+ comment = {};
}
if (error) {
@@ -716,11 +733,10 @@
p++;
}
- return ResourceName{ package.toString(), ResourceType::kAttr,
- name.empty() ? str.toString() : name.toString() };
+ return ResourceName(package.toString(), ResourceType::kAttr,
+ name.empty() ? str.toString() : name.toString());
}
-
bool ResourceParser::parseStyleItem(XmlPullParser* parser, Style* style) {
const Source source = mSource.withLine(parser->getLineNumber());
@@ -783,7 +799,6 @@
}
bool error = false;
- std::u16string comment;
const size_t depth = parser->getDepth();
while (XmlPullParser::nextChildNode(parser, depth)) {
if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
@@ -796,11 +811,7 @@
if (elementNamespace == u"" && elementName == u"item") {
error |= !parseStyleItem(parser, style.get());
- } else if (elementNamespace.empty() &&
- (elementName == u"skip" || elementName == u"eat-comment")) {
- comment = u"";
-
- } else {
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
<< ":" << elementName << ">");
error = true;
@@ -820,7 +831,6 @@
const Source source = mSource.withLine(parser->getLineNumber());
std::unique_ptr<Array> array = util::make_unique<Array>();
- std::u16string comment;
bool error = false;
const size_t depth = parser->getDepth();
while (XmlPullParser::nextChildNode(parser, depth)) {
@@ -839,13 +849,10 @@
error = true;
continue;
}
+ item->setSource(itemSource);
array->items.emplace_back(std::move(item));
- } else if (elementNamespace.empty() &&
- (elementName == u"skip" || elementName == u"eat-comment")) {
- comment = u"";
-
- } else {
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
<< "unknown tag <" << elementNamespace << ":" << elementName << ">");
error = true;
@@ -864,7 +871,6 @@
const Source source = mSource.withLine(parser->getLineNumber());
std::unique_ptr<Plural> plural = util::make_unique<Plural>();
- std::u16string comment;
bool error = false;
const size_t depth = parser->getDepth();
while (XmlPullParser::nextChildNode(parser, depth)) {
@@ -873,13 +879,14 @@
continue;
}
+ const Source itemSource = mSource.withLine(parser->getLineNumber());
const std::u16string& elementNamespace = parser->getElementNamespace();
const std::u16string& elementName = parser->getElementName();
if (elementNamespace.empty() && elementName == u"item") {
const auto endAttrIter = parser->endAttributes();
auto attrIter = parser->findAttribute(u"", u"quantity");
if (attrIter == endAttrIter || attrIter->value.empty()) {
- mDiag->error(DiagMessage(source) << "<item> in <plurals> requires attribute "
+ mDiag->error(DiagMessage(itemSource) << "<item> in <plurals> requires attribute "
<< "'quantity'");
error = true;
continue;
@@ -900,7 +907,7 @@
} else if (trimmedQuantity == u"other") {
index = Plural::Other;
} else {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ mDiag->error(DiagMessage(itemSource)
<< "<item> in <plural> has invalid value '" << trimmedQuantity
<< "' for attribute 'quantity'");
error = true;
@@ -908,7 +915,7 @@
}
if (plural->values[index]) {
- mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ mDiag->error(DiagMessage(itemSource)
<< "duplicate quantity '" << trimmedQuantity << "'");
error = true;
continue;
@@ -918,11 +925,10 @@
kNoRawString))) {
error = true;
}
- } else if (elementNamespace.empty() &&
- (elementName == u"skip" || elementName == u"eat-comment")) {
- comment = u"";
- } else {
- mDiag->error(DiagMessage(source) << "unknown tag <" << elementNamespace << ":"
+ plural->values[index]->setSource(itemSource);
+
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+ mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":"
<< elementName << ">");
error = true;
}
@@ -944,43 +950,52 @@
bool error = false;
const size_t depth = parser->getDepth();
while (XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
- // Ignore text and comments.
+ if (parser->getEvent() == XmlPullParser::Event::kComment) {
+ comment = util::trimWhitespace(parser->getComment()).toString();
+ continue;
+ } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ // Ignore text.
continue;
}
+ const Source itemSource = mSource.withLine(parser->getLineNumber());
const std::u16string& elementNamespace = parser->getElementNamespace();
const std::u16string& elementName = parser->getElementName();
if (elementNamespace.empty() && elementName == u"attr") {
const auto endAttrIter = parser->endAttributes();
auto attrIter = parser->findAttribute(u"", u"name");
if (attrIter == endAttrIter || attrIter->value.empty()) {
- mDiag->error(DiagMessage(source) << "<attr> tag must have a 'name' attribute");
+ mDiag->error(DiagMessage(itemSource) << "<attr> tag must have a 'name' attribute");
error = true;
continue;
}
+ // Create the ParsedResource that will add the attribute to the table.
ParsedResource childResource;
childResource.name = ResourceName({}, ResourceType::kAttr, attrIter->value);
- childResource.source = mSource.withLine(parser->getLineNumber());
+ childResource.source = itemSource;
+ childResource.comment = std::move(comment);
if (!parseAttrImpl(parser, &childResource, true)) {
error = true;
continue;
}
- styleable->entries.push_back(Reference(childResource.name));
+ // Create the reference to this attribute.
+ Reference childRef(childResource.name);
+ childRef.setComment(childResource.comment);
+ childRef.setSource(itemSource);
+ styleable->entries.push_back(std::move(childRef));
+
outResource->childResources.push_back(std::move(childResource));
- } else if (elementNamespace.empty() &&
- (elementName == u"skip" || elementName == u"eat-comment")) {
- comment = u"";
-
- } else {
- mDiag->error(DiagMessage(source) << "unknown tag <" << elementNamespace << ":"
+ } else if (!shouldIgnoreElement(elementNamespace, elementName)) {
+ mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":"
<< elementName << ">");
error = true;
}
+
+ comment = {};
}
if (error) {
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index af6bf67..2f5daae 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -414,6 +414,34 @@
EXPECT_EQ(value->getComment(), u"One");
}
+TEST_F(ResourceParserTest, ParseNestedComments) {
+ // We only care about declare-styleable and enum/flag attributes because comments
+ // from those end up in R.java
+ std::string input = R"EOF(
+ <declare-styleable name="foo">
+ <!-- The name of the bar -->
+ <attr name="barName" format="string|reference" />
+ </declare-styleable>
+
+ <attr name="foo">
+ <!-- The very first -->
+ <enum name="one" value="1" />
+ </attr>)EOF";
+ ASSERT_TRUE(testParse(input));
+
+ Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo");
+ ASSERT_NE(nullptr, styleable);
+ ASSERT_EQ(1u, styleable->entries.size());
+
+ EXPECT_EQ(StringPiece16(u"The name of the bar"), styleable->entries.front().getComment());
+
+ Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
+ ASSERT_NE(nullptr, attr);
+ ASSERT_EQ(1u, attr->symbols.size());
+
+ EXPECT_EQ(StringPiece16(u"The very first"), attr->symbols.front().symbol.getComment());
+}
+
/*
* Declaring an ID as public should not require a separate definition
* (as an ID has no value).
diff --git a/tools/aapt2/Resource_test.cpp b/tools/aapt2/Resource_test.cpp
index d957999..48dc521 100644
--- a/tools/aapt2/Resource_test.cpp
+++ b/tools/aapt2/Resource_test.cpp
@@ -69,10 +69,6 @@
ASSERT_NE(type, nullptr);
EXPECT_EQ(*type, ResourceType::kInteger);
- type = parseResourceType(u"integer-array");
- ASSERT_NE(type, nullptr);
- EXPECT_EQ(*type, ResourceType::kIntegerArray);
-
type = parseResourceType(u"interpolator");
ASSERT_NE(type, nullptr);
EXPECT_EQ(*type, ResourceType::kInterpolator);
diff --git a/tools/aapt2/XmlDom.cpp b/tools/aapt2/XmlDom.cpp
index d948775..b769c76 100644
--- a/tools/aapt2/XmlDom.cpp
+++ b/tools/aapt2/XmlDom.cpp
@@ -125,7 +125,7 @@
Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
assert(!stack->nodeStack.empty());
- stack->nodeStack.top()->comment = std::move(stack->pendingComment);
+ //stack->nodeStack.top()->comment = std::move(stack->pendingComment);
stack->nodeStack.pop();
}
@@ -194,7 +194,7 @@
XML_ParserFree(parser);
if (stack.root) {
- return util::make_unique<XmlResource>(ResourceFile{}, std::move(stack.root));
+ return util::make_unique<XmlResource>(ResourceFile{ {}, {}, source }, std::move(stack.root));
}
return {};
}
@@ -317,6 +317,22 @@
return util::make_unique<XmlResource>(ResourceFile{}, std::move(root));
}
+Element* findRootElement(Node* node) {
+ if (!node) {
+ return nullptr;
+ }
+
+ Element* el = nullptr;
+ while ((el = nodeCast<Element>(node)) == nullptr) {
+ if (node->children.empty()) {
+ return nullptr;
+ }
+ // We are looking for the first element, and namespaces can only have one child.
+ node = node->children.front().get();
+ }
+ return el;
+}
+
void Node::addChild(std::unique_ptr<Node> child) {
child->parent = this;
children.push_back(std::move(child));
diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/XmlDom.h
index c095f08..b1987f1 100644
--- a/tools/aapt2/XmlDom.h
+++ b/tools/aapt2/XmlDom.h
@@ -132,6 +132,8 @@
std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag,
const Source& source);
+Element* findRootElement(Node* node);
+
/**
* A visitor interface for the different XML Node subtypes. This will not traverse into
* children. Use Visitor for that.
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
new file mode 100644
index 0000000..16440bc
--- /dev/null
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "java/AnnotationProcessor.h"
+#include "util/Util.h"
+
+#include <algorithm>
+
+namespace aapt {
+
+void AnnotationProcessor::appendCommentLine(const StringPiece16& line) {
+ static const std::string sDeprecated = "@deprecated";
+ static const std::string sSystemApi = "@SystemApi";
+
+ if (line.empty()) {
+ return;
+ }
+
+ std::string comment = util::utf16ToUtf8(line);
+
+ if (comment.find(sDeprecated) != std::string::npos && !mDeprecated) {
+ mDeprecated = true;
+ if (!mAnnotations.empty()) {
+ mAnnotations += "\n";
+ }
+ mAnnotations += mPrefix;
+ mAnnotations += "@Deprecated";
+ }
+
+ if (comment.find(sSystemApi) != std::string::npos && !mSystemApi) {
+ mSystemApi = true;
+ if (!mAnnotations.empty()) {
+ mAnnotations += "\n";
+ }
+ mAnnotations += mPrefix;
+ mAnnotations += "@android.annotations.SystemApi";
+ }
+
+ if (mComment.empty()) {
+ mComment += mPrefix;
+ mComment += "/**";
+ }
+
+ mComment += "\n";
+ mComment += mPrefix;
+ mComment += " * ";
+ mComment += std::move(comment);
+}
+
+void AnnotationProcessor::appendComment(const StringPiece16& comment) {
+ // We need to process line by line to clean-up whitespace and append prefixes.
+ for (StringPiece16 line : util::tokenize(comment, u'\n')) {
+ appendCommentLine(util::trimWhitespace(line));
+ }
+}
+
+std::string AnnotationProcessor::buildComment() {
+ mComment += "\n";
+ mComment += mPrefix;
+ mComment += " */";
+ return std::move(mComment);
+}
+
+std::string AnnotationProcessor::buildAnnotations() {
+ return std::move(mAnnotations);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
new file mode 100644
index 0000000..b472109
--- /dev/null
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef AAPT_JAVA_ANNOTATIONPROCESSOR_H
+#define AAPT_JAVA_ANNOTATIONPROCESSOR_H
+
+#include "util/StringPiece.h"
+
+#include <string>
+
+namespace aapt {
+
+/**
+ * Builds a JavaDoc comment from a set of XML comments.
+ * This will also look for instances of @SystemApi and convert them to
+ * actual Java annotations.
+ *
+ * Example:
+ *
+ * Input XML:
+ *
+ * <!-- This is meant to be hidden because
+ * It is system api. Also it is @deprecated
+ * @SystemApi
+ * -->
+ *
+ * Output JavaDoc:
+ *
+ * /\*
+ * * This is meant to be hidden because
+ * * It is system api. Also it is @deprecated
+ * * @SystemApi
+ * *\/
+ *
+ * Output Annotations:
+ *
+ * @Deprecated
+ * @android.annotation.SystemApi
+ *
+ */
+class AnnotationProcessor {
+public:
+ /**
+ * Creates an AnnotationProcessor with a given prefix for each line generated.
+ * This is usually a set of spaces for indentation.
+ */
+ AnnotationProcessor(const StringPiece& prefix) : mPrefix(prefix.toString()) {
+ }
+
+ /**
+ * Adds more comments. Since resources can have various values with different configurations,
+ * we need to collect all the comments.
+ */
+ void appendComment(const StringPiece16& comment);
+
+ /**
+ * Finishes the comment and moves it to the caller. Subsequent calls to buildComment() have
+ * undefined results.
+ */
+ std::string buildComment();
+
+ /**
+ * Finishes the annotation and moves it to the caller. Subsequent calls to buildAnnotations()
+ * have undefined results.
+ */
+ std::string buildAnnotations();
+
+private:
+ std::string mPrefix;
+ std::string mComment;
+ std::string mAnnotations;
+ bool mDeprecated = false;
+ bool mSystemApi = false;
+
+ void appendCommentLine(const StringPiece16& line);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_JAVA_ANNOTATIONPROCESSOR_H */
diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
similarity index 98%
rename from tools/aapt2/JavaClassGenerator.cpp
rename to tools/aapt2/java/JavaClassGenerator.cpp
index cdf1b6a..0175489 100644
--- a/tools/aapt2/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-#include "JavaClassGenerator.h"
#include "NameMangler.h"
#include "Resource.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
+#include "java/AnnotationProcessor.h"
+#include "java/JavaClassGenerator.h"
#include "util/StringPiece.h"
#include <algorithm>
diff --git a/tools/aapt2/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
similarity index 100%
rename from tools/aapt2/JavaClassGenerator.h
rename to tools/aapt2/java/JavaClassGenerator.h
diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
similarity index 99%
rename from tools/aapt2/JavaClassGenerator_test.cpp
rename to tools/aapt2/java/JavaClassGenerator_test.cpp
index cc5e981..3625f9c 100644
--- a/tools/aapt2/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "JavaClassGenerator.h"
+#include "java/JavaClassGenerator.h"
#include "util/Util.h"
#include "test/Builders.h"
diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp
new file mode 100644
index 0000000..c12da64
--- /dev/null
+++ b/tools/aapt2/java/ManifestClassGenerator.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "Source.h"
+#include "XmlDom.h"
+
+#include "java/AnnotationProcessor.h"
+#include "java/ManifestClassGenerator.h"
+#include "util/Maybe.h"
+
+#include <algorithm>
+
+namespace aapt {
+
+constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+
+static Maybe<StringPiece16> extractJavaIdentifier(IDiagnostics* diag, const Source& source,
+ const StringPiece16& value) {
+ const StringPiece16 sep = u".";
+ auto iter = std::find_end(value.begin(), value.end(), sep.begin(), sep.end());
+
+ StringPiece16 result;
+ if (iter != value.end()) {
+ result.assign(iter + sep.size(), value.end() - (iter + sep.size()));
+ } else {
+ result = value;
+ }
+
+ if (result.empty()) {
+ diag->error(DiagMessage(source) << "empty symbol");
+ return {};
+ }
+
+ iter = util::findNonAlphaNumericAndNotInSet(result, u"_");
+ if (iter != result.end()) {
+ diag->error(DiagMessage(source)
+ << "invalid character '" << StringPiece16(iter, 1)
+ << "' in '" << result << "'");
+ return {};
+ }
+
+ if (*result.begin() >= u'0' && *result.begin() <= u'9') {
+ diag->error(DiagMessage(source) << "symbol can not start with a digit");
+ return {};
+ }
+
+ return result;
+}
+
+static bool writeSymbol(IDiagnostics* diag, const Source& source, xml::Element* el,
+ std::ostream* out) {
+ xml::Attribute* attr = el->findAttribute(kSchemaAndroid, u"name");
+ if (!attr) {
+ diag->error(DiagMessage(source) << "<" << el->name << "> must define 'android:name'");
+ return false;
+ }
+
+ Maybe<StringPiece16> result = extractJavaIdentifier(diag, source.withLine(el->lineNumber),
+ attr->value);
+ if (!result) {
+ return false;
+ }
+
+ *out << "\n";
+
+ if (!util::trimWhitespace(el->comment).empty()) {
+ AnnotationProcessor processor(" ");
+ processor.appendComment(el->comment);
+ *out << processor.buildComment() << "\n";
+ std::string annotations = processor.buildAnnotations();
+ if (!annotations.empty()) {
+ *out << annotations << "\n";
+ }
+ }
+ *out << " public static final String " << result.value() << "=\"" << attr->value << "\";\n";
+ return true;
+}
+
+bool ManifestClassGenerator::generate(IDiagnostics* diag, const StringPiece16& package,
+ XmlResource* res, std::ostream* out) {
+ xml::Element* el = xml::findRootElement(res->root.get());
+ if (!el) {
+ return false;
+ }
+
+ if (el->name != u"manifest" && !el->namespaceUri.empty()) {
+ diag->error(DiagMessage(res->file.source) << "no <manifest> root tag defined");
+ return false;
+ }
+
+ *out << "package " << package << ";\n\n"
+ << "public class Manifest {\n";
+
+ bool error = false;
+ std::vector<xml::Element*> children = el->getChildElements();
+
+
+ // First write out permissions.
+ *out << " public static class permission {\n";
+ for (xml::Element* childEl : children) {
+ if (childEl->namespaceUri.empty() && childEl->name == u"permission") {
+ error |= !writeSymbol(diag, res->file.source, childEl, out);
+ }
+ }
+ *out << " }\n";
+
+ // Next write out permission groups.
+ *out << " public static class permission_group {\n";
+ for (xml::Element* childEl : children) {
+ if (childEl->namespaceUri.empty() && childEl->name == u"permission-group") {
+ error |= !writeSymbol(diag, res->file.source, childEl, out);
+ }
+ }
+ *out << " }\n";
+
+ *out << "}\n";
+ return !error;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h
new file mode 100644
index 0000000..0f0998f
--- /dev/null
+++ b/tools/aapt2/java/ManifestClassGenerator.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef AAPT_JAVA_MANIFESTCLASSGENERATOR_H
+#define AAPT_JAVA_MANIFESTCLASSGENERATOR_H
+
+#include "Diagnostics.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/StringPiece.h"
+
+#include <iostream>
+
+namespace aapt {
+
+struct ManifestClassGenerator {
+ bool generate(IDiagnostics* diag, const StringPiece16& package, XmlResource* res,
+ std::ostream* out);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_JAVA_MANIFESTCLASSGENERATOR_H */
diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp
new file mode 100644
index 0000000..1b5bc05
--- /dev/null
+++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "java/ManifestClassGenerator.h"
+
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <permission android:name="android.permission.ACCESS_INTERNET" />
+ <permission android:name="android.DO_DANGEROUS_THINGS" />
+ <permission android:name="com.test.sample.permission.HUH" />
+ <permission-group android:name="foo.bar.PERMISSION" />
+ </manifest>)EOF");
+
+ std::stringstream out;
+ ManifestClassGenerator generator;
+ ASSERT_TRUE(generator.generate(context->getDiagnostics(), u"android", manifest.get(), &out));
+
+ std::string actual = out.str();
+
+ const size_t permissionClassPos = actual.find("public static class permission {");
+ const size_t permissionGroupClassPos = actual.find("public static class permission_group {");
+ ASSERT_NE(std::string::npos, permissionClassPos);
+ ASSERT_NE(std::string::npos, permissionGroupClassPos);
+
+ //
+ // Make sure these permissions are in the permission class.
+ //
+
+ size_t pos = actual.find("public static final String ACCESS_INTERNET="
+ "\"android.permission.ACCESS_INTERNET\";");
+ EXPECT_GT(pos, permissionClassPos);
+ EXPECT_LT(pos, permissionGroupClassPos);
+
+ pos = actual.find("public static final String DO_DANGEROUS_THINGS="
+ "\"android.DO_DANGEROUS_THINGS\";");
+ EXPECT_GT(pos, permissionClassPos);
+ EXPECT_LT(pos, permissionGroupClassPos);
+
+ pos = actual.find("public static final String HUH=\"com.test.sample.permission.HUH\";");
+ EXPECT_GT(pos, permissionClassPos);
+ EXPECT_LT(pos, permissionGroupClassPos);
+
+ //
+ // Make sure these permissions are in the permission_group class
+ //
+
+ pos = actual.find("public static final String PERMISSION="
+ "\"foo.bar.PERMISSION\";");
+ EXPECT_GT(pos, permissionGroupClassPos);
+ EXPECT_LT(pos, std::string::npos);
+}
+
+TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+ std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Required to access the internet.
+ Added in API 1. -->
+ <permission android:name="android.permission.ACCESS_INTERNET" />
+ <!-- @deprecated This permission is for playing outside. -->
+ <permission android:name="android.permission.PLAY_OUTSIDE" />
+ <!-- This is a private permission for system only!
+ @hide
+ @SystemApi -->
+ <permission android:name="android.permission.SECRET" />
+ </manifest>)EOF");
+
+ std::stringstream out;
+ ManifestClassGenerator generator;
+ ASSERT_TRUE(generator.generate(context->getDiagnostics(), u"android", manifest.get(), &out));
+
+ std::string actual = out.str();
+
+ EXPECT_NE(std::string::npos, actual.find(
+R"EOF( /**
+ * Required to access the internet.
+ * Added in API 1.
+ */
+ public static final String ACCESS_INTERNET="android.permission.ACCESS_INTERNET";)EOF"));
+
+ EXPECT_NE(std::string::npos, actual.find(
+R"EOF( /**
+ * @deprecated This permission is for playing outside.
+ */
+ @Deprecated
+ public static final String PLAY_OUTSIDE="android.permission.PLAY_OUTSIDE";)EOF"));
+
+ EXPECT_NE(std::string::npos, actual.find(
+R"EOF( /**
+ * This is a private permission for system only!
+ * @hide
+ * @SystemApi
+ */
+ @android.annotations.SystemApi
+ public static final String SECRET="android.permission.SECRET";)EOF"));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
similarity index 99%
rename from tools/aapt2/ProguardRules.cpp
rename to tools/aapt2/java/ProguardRules.cpp
index 7f4dc91..7683f27 100644
--- a/tools/aapt2/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-#include "ProguardRules.h"
#include "XmlDom.h"
+#include "java/ProguardRules.h"
#include "util/Util.h"
#include <memory>
diff --git a/tools/aapt2/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
similarity index 100%
rename from tools/aapt2/ProguardRules.h
rename to tools/aapt2/java/ProguardRules.h
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index ad701de..77918ac 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -17,15 +17,16 @@
#include "AppInfo.h"
#include "Debug.h"
#include "Flags.h"
-#include "JavaClassGenerator.h"
#include "NameMangler.h"
-#include "ProguardRules.h"
#include "XmlDom.h"
#include "compile/IdAssigner.h"
#include "flatten/Archive.h"
#include "flatten/TableFlattener.h"
#include "flatten/XmlFlattener.h"
+#include "java/JavaClassGenerator.h"
+#include "java/ManifestClassGenerator.h"
+#include "java/ProguardRules.h"
#include "link/Linkers.h"
#include "link/TableMerger.h"
#include "process/IResourceTableConsumer.h"
@@ -354,6 +355,36 @@
return true;
}
+ bool writeManifestJavaFile(XmlResource* manifestXml) {
+ if (!mOptions.generateJavaClassPath) {
+ return true;
+ }
+
+ std::string outPath = mOptions.generateJavaClassPath.value();
+ file::appendPath(&outPath,
+ file::packageToPath(util::utf16ToUtf8(mContext.getCompilationPackage())));
+ file::mkdirs(outPath);
+ file::appendPath(&outPath, "Manifest.java");
+
+ std::ofstream fout(outPath, std::ofstream::binary);
+ if (!fout) {
+ mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+ return false;
+ }
+
+ ManifestClassGenerator generator;
+ if (!generator.generate(mContext.getDiagnostics(), mContext.getCompilationPackage(),
+ manifestXml, &fout)) {
+ return false;
+ }
+
+ if (!fout) {
+ mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+ return false;
+ }
+ return true;
+ }
+
bool writeProguardFile(const proguard::KeepSet& keepSet) {
if (!mOptions.generateProguardRulesPath) {
return true;
@@ -548,6 +579,12 @@
error = true;
}
+ if (mOptions.generateJavaClassPath) {
+ if (!writeManifestJavaFile(manifestXml.get())) {
+ error = true;
+ }
+ }
+
if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
archiveWriter.get())) {
error = true;
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 1b510e7..89cd972 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -19,8 +19,8 @@
#include "ResourceTable.h"
#include "ResourceValues.h"
-#include "util/Util.h"
#include "XmlDom.h"
+#include "util/Util.h"
#include "test/Common.h"
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 314c1e8..30c6091 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -678,8 +678,6 @@
// fallthrough
case ResourceType::kAttr:
return parseAttr(name, config, map);
- case ResourceType::kIntegerArray:
- // fallthrough
case ResourceType::kArray:
return parseArray(name, config, map);
case ResourceType::kStyleable:
diff --git a/tools/aapt2/util/StringPiece.h b/tools/aapt2/util/StringPiece.h
index 8cbdeae..31deb45 100644
--- a/tools/aapt2/util/StringPiece.h
+++ b/tools/aapt2/util/StringPiece.h
@@ -36,6 +36,7 @@
class BasicStringPiece {
public:
using const_iterator = const TChar*;
+ using difference_type = size_t;
BasicStringPiece();
BasicStringPiece(const BasicStringPiece<TChar>& str);