diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index 85d22ffacd4ce..876a422a26780 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -54,6 +54,7 @@ sources := \ Debug.cpp \ Flags.cpp \ java/AnnotationProcessor.cpp \ + java/ClassDefinition.cpp \ java/JavaClassGenerator.cpp \ java/ManifestClassGenerator.cpp \ java/ProguardRules.cpp \ diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp index 496e92e0bf93d..ba744395ece0b 100644 --- a/tools/aapt2/java/AnnotationProcessor.cpp +++ b/tools/aapt2/java/AnnotationProcessor.cpp @@ -64,7 +64,7 @@ void AnnotationProcessor::appendNewLine() { mComment << "\n *"; } -void AnnotationProcessor::writeToStream(std::ostream* out, const StringPiece& prefix) { +void AnnotationProcessor::writeToStream(std::ostream* out, const StringPiece& prefix) const { if (mHasComments) { std::string result = mComment.str(); for (StringPiece line : util::tokenize(result, '\n')) { diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h index fadf58440991b..0fc5b08e0bf36 100644 --- a/tools/aapt2/java/AnnotationProcessor.h +++ b/tools/aapt2/java/AnnotationProcessor.h @@ -66,7 +66,7 @@ public: /** * Writes the comments and annotations to the stream, with the given prefix before each line. */ - void writeToStream(std::ostream* out, const StringPiece& prefix); + void writeToStream(std::ostream* out, const StringPiece& prefix) const; private: enum : uint32_t { diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp new file mode 100644 index 0000000000000..08f2c8b9805ce --- /dev/null +++ b/tools/aapt2/java/ClassDefinition.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 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/ClassDefinition.h" +#include "util/StringPiece.h" + +#include + +namespace aapt { + +bool ClassDefinition::empty() const { + for (const std::unique_ptr& member : mMembers) { + if (!member->empty()) { + return false; + } + } + return true; +} + +void ClassDefinition::writeToStream(const StringPiece& prefix, bool final, + std::ostream* out) const { + if (mMembers.empty() && !mCreateIfEmpty) { + return; + } + + ClassMember::writeToStream(prefix, final, out); + + *out << prefix << "public "; + if (mQualifier == ClassQualifier::Static) { + *out << "static "; + } + *out << "final class " << mName << " {\n"; + + std::string newPrefix = prefix.toString(); + newPrefix.append(kIndent); + + for (const std::unique_ptr& member : mMembers) { + member->writeToStream(newPrefix, final, out); + *out << "\n"; + } + + *out << prefix << "}"; +} + +constexpr static const char* sWarningHeader = + "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n" + " *\n" + " * This class was automatically generated by the\n" + " * aapt tool from the resource data it found. It\n" + " * should not be modified by hand.\n" + " */\n\n"; + +bool ClassDefinition::writeJavaFile(const ClassDefinition* def, + const StringPiece& package, + bool final, + std::ostream* out) { + *out << sWarningHeader << "package " << package << ";\n\n"; + def->writeToStream("", final, out); + return bool(*out); +} + +} // namespace aapt diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h new file mode 100644 index 0000000000000..53e0f6fe2f1e7 --- /dev/null +++ b/tools/aapt2/java/ClassDefinition.h @@ -0,0 +1,188 @@ +/* + * 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_CLASSDEFINITION_H +#define AAPT_JAVA_CLASSDEFINITION_H + +#include "Resource.h" +#include "java/AnnotationProcessor.h" +#include "util/StringPiece.h" +#include "util/Util.h" + +#include +#include +#include + +namespace aapt { + +// The number of attributes to emit per line in a Styleable array. +constexpr static size_t kAttribsPerLine = 4; +constexpr static const char* kIndent = " "; + +class ClassMember { +public: + virtual ~ClassMember() = default; + + AnnotationProcessor* getCommentBuilder() { + return &mProcessor; + } + + virtual bool empty() const = 0; + + virtual void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const { + mProcessor.writeToStream(out, prefix); + } + +private: + AnnotationProcessor mProcessor; +}; + +template +class PrimitiveMember : public ClassMember { +public: + PrimitiveMember(const StringPiece& name, const T& val) : + mName(name.toString()), mVal(val) { + } + + bool empty() const override { + return false; + } + + void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override { + ClassMember::writeToStream(prefix, final, out); + + *out << prefix << "public static " << (final ? "final " : "") + << "int " << mName << "=" << mVal << ";"; + } + +private: + std::string mName; + T mVal; + + DISALLOW_COPY_AND_ASSIGN(PrimitiveMember); +}; + +/** + * Specialization for strings so they get the right type and are quoted with "". + */ +template <> +class PrimitiveMember : public ClassMember { +public: + PrimitiveMember(const StringPiece& name, const std::string& val) : + mName(name.toString()), mVal(val) { + } + + bool empty() const override { + return false; + } + + void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override { + ClassMember::writeToStream(prefix, final, out); + + *out << prefix << "public static " << (final ? "final " : "") + << "String " << mName << "=\"" << mVal << "\";"; + } + +private: + std::string mName; + std::string mVal; + + DISALLOW_COPY_AND_ASSIGN(PrimitiveMember); +}; + +using IntMember = PrimitiveMember; +using ResourceMember = PrimitiveMember; +using StringMember = PrimitiveMember; + +template +class PrimitiveArrayMember : public ClassMember { +public: + PrimitiveArrayMember(const StringPiece& name) : + mName(name.toString()) { + } + + void addElement(const T& val) { + mElements.push_back(val); + } + + bool empty() const override { + return false; + } + + void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override { + ClassMember::writeToStream(prefix, final, out); + + *out << "public static final int[] " << mName << "={"; + + const auto begin = mElements.begin(); + const auto end = mElements.end(); + for (auto current = begin; current != end; ++current) { + if (std::distance(begin, current) % kAttribsPerLine == 0) { + *out << "\n" << prefix << kIndent << kIndent; + } + + *out << *current; + if (std::distance(current, end) > 1) { + *out << ", "; + } + } + *out << "\n" << prefix << kIndent <<"};"; + } + +private: + std::string mName; + std::vector mElements; + + DISALLOW_COPY_AND_ASSIGN(PrimitiveArrayMember); +}; + +using ResourceArrayMember = PrimitiveArrayMember; + +enum class ClassQualifier { + None, + Static +}; + +class ClassDefinition : public ClassMember { +public: + static bool writeJavaFile(const ClassDefinition* def, + const StringPiece& package, + bool final, + std::ostream* out); + + ClassDefinition(const StringPiece& name, ClassQualifier qualifier, bool createIfEmpty) : + mName(name.toString()), mQualifier(qualifier), mCreateIfEmpty(createIfEmpty) { + } + + void addMember(std::unique_ptr member) { + mMembers.push_back(std::move(member)); + } + + bool empty() const override; + void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override; + +private: + std::string mName; + ClassQualifier mQualifier; + bool mCreateIfEmpty; + std::vector> mMembers; + + DISALLOW_COPY_AND_ASSIGN(ClassDefinition); +}; + +} // namespace aapt + +#endif /* AAPT_JAVA_CLASSDEFINITION_H */ diff --git a/tools/aapt2/java/ClassDefinitionWriter.h b/tools/aapt2/java/ClassDefinitionWriter.h deleted file mode 100644 index cf92c9aa6f89e..0000000000000 --- a/tools/aapt2/java/ClassDefinitionWriter.h +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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_CLASSDEFINITION_H -#define AAPT_JAVA_CLASSDEFINITION_H - -#include "Resource.h" -#include "java/AnnotationProcessor.h" -#include "util/StringPiece.h" -#include "util/Util.h" - -#include -#include - -namespace aapt { - -struct ClassDefinitionWriterOptions { - bool useFinalQualifier = false; - bool forceCreationIfEmpty = false; -}; - -/** - * Writes a class for use in R.java or Manifest.java. - */ -class ClassDefinitionWriter { -public: - ClassDefinitionWriter(const StringPiece& name, const ClassDefinitionWriterOptions& options) : - mName(name.toString()), mOptions(options), mStarted(false) { - } - - ClassDefinitionWriter(const StringPiece16& name, const ClassDefinitionWriterOptions& options) : - mName(util::utf16ToUtf8(name)), mOptions(options), mStarted(false) { - } - - void addIntMember(const StringPiece& name, AnnotationProcessor* processor, - const uint32_t val) { - ensureClassDeclaration(); - if (processor) { - processor->writeToStream(&mOut, kIndent); - } - mOut << kIndent << "public static " << (mOptions.useFinalQualifier ? "final " : "") - << "int " << name << "=" << val << ";\n"; - } - - void addStringMember(const StringPiece16& name, AnnotationProcessor* processor, - const StringPiece16& val) { - ensureClassDeclaration(); - if (processor) { - processor->writeToStream(&mOut, kIndent); - } - mOut << kIndent << "public static " << (mOptions.useFinalQualifier ? "final " : "") - << "String " << name << "=\"" << val << "\";\n"; - } - - void addResourceMember(const StringPiece& name, AnnotationProcessor* processor, - const ResourceId id) { - ensureClassDeclaration(); - if (processor) { - processor->writeToStream(&mOut, kIndent); - } - mOut << kIndent << "public static " << (mOptions.useFinalQualifier ? "final " : "") - << "int " << name << "=" << id <<";\n"; - } - - template - void addArrayMember(const StringPiece& name, AnnotationProcessor* processor, - const Iterator begin, const Iterator end, FieldAccessorFunc f) { - ensureClassDeclaration(); - if (processor) { - processor->writeToStream(&mOut, kIndent); - } - mOut << kIndent << "public static final int[] " << name << "={"; - - for (Iterator current = begin; current != end; ++current) { - if (std::distance(begin, current) % kAttribsPerLine == 0) { - mOut << "\n" << kIndent << kIndent; - } - - mOut << f(*current); - if (std::distance(current, end) > 1) { - mOut << ", "; - } - } - mOut << "\n" << kIndent <<"};\n"; - } - - void writeToStream(std::ostream* out, const StringPiece& prefix, - AnnotationProcessor* processor=nullptr) { - if (mOptions.forceCreationIfEmpty) { - ensureClassDeclaration(); - } - - if (!mStarted) { - return; - } - - if (processor) { - processor->writeToStream(out, prefix); - } - - std::string result = mOut.str(); - for (StringPiece line : util::tokenize(result, '\n')) { - *out << prefix << line << "\n"; - } - *out << prefix << "}\n"; - } - -private: - constexpr static const char* kIndent = " "; - - // The number of attributes to emit per line in a Styleable array. - constexpr static size_t kAttribsPerLine = 4; - - void ensureClassDeclaration() { - if (!mStarted) { - mStarted = true; - mOut << "public static final class " << mName << " {\n"; - } - } - - std::stringstream mOut; - std::string mName; - ClassDefinitionWriterOptions mOptions; - bool mStarted; -}; - -} // namespace aapt - -#endif /* AAPT_JAVA_CLASSDEFINITION_H */ diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index 01330dc6b5a16..2d076c2d5a66e 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -21,7 +21,7 @@ #include "ValueVisitor.h" #include "java/AnnotationProcessor.h" -#include "java/ClassDefinitionWriter.h" +#include "java/ClassDefinition.h" #include "java/JavaClassGenerator.h" #include "process/SymbolTable.h" #include "util/StringPiece.h" @@ -39,16 +39,6 @@ JavaClassGenerator::JavaClassGenerator(IAaptContext* context, ResourceTable* tab mContext(context), mTable(table), mOptions(options) { } -static void generateHeader(const StringPiece16& packageNameToGenerate, std::ostream* out) { - *out << "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n" - " *\n" - " * This class was automatically generated by the\n" - " * aapt tool from the resource data it found. It\n" - " * should not be modified by hand.\n" - " */\n\n" - "package " << packageNameToGenerate << ";\n\n"; -} - static const std::set sJavaIdentifiers = { u"abstract", u"assert", u"boolean", u"break", u"byte", u"case", u"catch", u"char", u"class", u"const", u"continue", @@ -110,15 +100,15 @@ static void addAttributeFormatDoc(AnnotationProcessor* processor, Attribute* att if (typeMask & android::ResTable_map::TYPE_REFERENCE) { processor->appendComment( "

May be a reference to another resource, in the form\n" - "\"@[+][package:]type/name\" or a theme\n" - "attribute in the form\n" - "\"?[package:]type/name\"."); + "\"@[+][package:]type/name\" or a theme\n" + "attribute in the form\n" + "\"?[package:]type/name\"."); } if (typeMask & android::ResTable_map::TYPE_STRING) { processor->appendComment( "

May be a string value, using '\\\\;' to escape characters such as\n" - "'\\\\n' or '\\\\uxxxx' for a unicode character;"); + "'\\\\n' or '\\\\uxxxx' for a unicode character;"); } if (typeMask & android::ResTable_map::TYPE_INTEGER) { @@ -128,14 +118,14 @@ static void addAttributeFormatDoc(AnnotationProcessor* processor, Attribute* att if (typeMask & android::ResTable_map::TYPE_BOOLEAN) { processor->appendComment( "

May be a boolean value, such as \"true\" or\n" - "\"false\"."); + "\"false\"."); } if (typeMask & android::ResTable_map::TYPE_COLOR) { processor->appendComment( "

May be a color value, in the form of \"#rgb\",\n" - "\"#argb\", \"#rrggbb#aarrggbb\"."); + "\"#argb\", \"#rrggbb#aarrggbb\"."); } if (typeMask & android::ResTable_map::TYPE_FLOAT) { @@ -146,33 +136,33 @@ static void addAttributeFormatDoc(AnnotationProcessor* processor, Attribute* att if (typeMask & android::ResTable_map::TYPE_DIMENSION) { processor->appendComment( "

May be a dimension value, which is a floating point number appended with a\n" - "unit such as \"14.5sp\".\n" - "Available units are: px (pixels), dp (density-independent pixels),\n" - "sp (scaled pixels based on preferred font size), in (inches), and\n" - "mm (millimeters)."); + "unit such as \"14.5sp\".\n" + "Available units are: px (pixels), dp (density-independent pixels),\n" + "sp (scaled pixels based on preferred font size), in (inches), and\n" + "mm (millimeters)."); } if (typeMask & android::ResTable_map::TYPE_FRACTION) { processor->appendComment( "

May be a fractional value, which is a floating point number appended with\n" - "either % or %p, such as \"14.5%\".\n" - "The % suffix always means a percentage of the base size;\n" - "the optional %p suffix provides a size relative to some parent container."); + "either % or %p, such as \"14.5%\".\n" + "The % suffix always means a percentage of the base size;\n" + "the optional %p suffix provides a size relative to some parent container."); } if (typeMask & (android::ResTable_map::TYPE_FLAGS | android::ResTable_map::TYPE_ENUM)) { if (typeMask & android::ResTable_map::TYPE_FLAGS) { processor->appendComment( "

Must be one or more (separated by '|') of the following " - "constant values.

"); + "constant values.

"); } else { processor->appendComment("

Must be one of the following constant values.

"); } processor->appendComment("\n\n" - "\n" - "\n" - "\n"); + "\n" + "\n" + "\n"); for (const Attribute::Symbol& symbol : attr->symbols) { std::stringstream line; line << "" @@ -214,13 +204,15 @@ static bool lessStyleableAttr(const StyleableAttr& lhs, const StyleableAttr& rhs } } -void JavaClassGenerator::writeStyleableEntryForClass(ClassDefinitionWriter* outClassDef, - AnnotationProcessor* processor, - const StringPiece16& packageNameToGenerate, - const std::u16string& entryName, - const Styleable* styleable) { +void JavaClassGenerator::addMembersToStyleableClass(const StringPiece16& packageNameToGenerate, + const std::u16string& entryName, + const Styleable* styleable, + ClassDefinition* outStyleableClassDef) { const std::string className = transform(entryName); + std::unique_ptr styleableArrayDef = + util::make_unique(className); + // This must be sorted by resource ID. std::vector sortedAttributes; sortedAttributes.reserve(styleable->entries.size()); @@ -230,6 +222,8 @@ void JavaClassGenerator::writeStyleableEntryForClass(ClassDefinitionWriter* outC assert((!mOptions.useFinal || attr.id) && "no ID set for Styleable entry"); assert(attr.name && "no name set for Styleable entry"); + // We will need the unmangled, transformed name in the comments and the field, + // so create it once and cache it in this StyleableAttr data structure. StyleableAttr styleableAttr = {}; styleableAttr.attrRef = &attr; styleableAttr.fieldName = transformNestedAttr(attr.name.value(), className, @@ -247,6 +241,8 @@ void JavaClassGenerator::writeStyleableEntryForClass(ClassDefinitionWriter* outC mangledReference.name = mangledName; } + // Look up the symbol so that we can write out in the comments what are possible + // legal values for this attribute. const SymbolTable::Symbol* symbol = mContext->getExternalSymbols()->findByReference( mangledReference); if (symbol) { @@ -254,10 +250,11 @@ void JavaClassGenerator::writeStyleableEntryForClass(ClassDefinitionWriter* outC } sortedAttributes.push_back(std::move(styleableAttr)); } + + // Sort the attributes by ID. std::sort(sortedAttributes.begin(), sortedAttributes.end(), lessStyleableAttr); const size_t attrCount = sortedAttributes.size(); - if (attrCount > 0) { // Build the comment string for the Styleable. It includes details about the // child attributes. @@ -267,6 +264,7 @@ void JavaClassGenerator::writeStyleableEntryForClass(ClassDefinitionWriter* outC } else { styleableComment << "Attributes that can be used with a " << className << ".\n"; } + styleableComment << "

Includes the following attributes:

\n" "
ConstantValueDescription
ConstantValueDescription
" << symbol.symbol.name.value().entry << "
\n" @@ -274,7 +272,7 @@ void JavaClassGenerator::writeStyleableEntryForClass(ClassDefinitionWriter* outC "\n" "\n"; - for (const auto& entry : sortedAttributes) { + for (const StyleableAttr& entry : sortedAttributes) { const ResourceName& attrName = entry.attrRef->name.value(); styleableComment << "\n"; } styleableComment << "
AttributeDescription
"; styleableComment << "{@link #" @@ -292,21 +290,22 @@ void JavaClassGenerator::writeStyleableEntryForClass(ClassDefinitionWriter* outC styleableComment << "
\n"; - for (const auto& entry : sortedAttributes) { + + for (const StyleableAttr& entry : sortedAttributes) { styleableComment << "@see #" << entry.fieldName << "\n"; } - processor->appendComment(styleableComment.str()); + + styleableArrayDef->getCommentBuilder()->appendComment(styleableComment.str()); } - auto accessorFunc = [](const StyleableAttr& a) -> ResourceId { - return a.attrRef->id ? a.attrRef->id.value() : ResourceId(0); - }; + // Add the ResourceIds to the array member. + for (const StyleableAttr& styleableAttr : sortedAttributes) { + styleableArrayDef->addElement( + styleableAttr.attrRef->id ? styleableAttr.attrRef->id.value() : ResourceId(0)); + } - // First we emit the array containing the IDs of each attribute. - outClassDef->addArrayMember(className, processor, - sortedAttributes.begin(), - sortedAttributes.end(), - accessorFunc); + // Add the Styleable array to the Styleable class. + outStyleableClassDef->addMember(std::move(styleableArrayDef)); // Now we emit the indices into the array. for (size_t i = 0; i < attrCount; i++) { @@ -318,7 +317,10 @@ void JavaClassGenerator::writeStyleableEntryForClass(ClassDefinitionWriter* outC packageName = mContext->getCompilationPackage(); } - AnnotationProcessor attrProcessor; + std::unique_ptr indexMember = util::make_unique( + sortedAttributes[i].fieldName, i); + + AnnotationProcessor* attrProcessor = indexMember->getCommentBuilder(); StringPiece16 comment = styleableAttr.attrRef->getComment(); if (styleableAttr.attribute && comment.empty()) { @@ -326,8 +328,8 @@ void JavaClassGenerator::writeStyleableEntryForClass(ClassDefinitionWriter* outC } if (!comment.empty()) { - attrProcessor.appendComment("

\n@attr description"); - attrProcessor.appendComment(comment); + attrProcessor->appendComment("

\n@attr description"); + attrProcessor->appendComment(comment); } else { std::stringstream defaultComment; defaultComment @@ -335,27 +337,29 @@ void JavaClassGenerator::writeStyleableEntryForClass(ClassDefinitionWriter* outC << "{@link " << packageName << ".R.attr#" << transform(attrName.entry) << "}\n" << "attribute's value can be found in the " << "{@link #" << className << "} array."; - attrProcessor.appendComment(defaultComment.str()); + attrProcessor->appendComment(defaultComment.str()); } - attrProcessor.appendNewLine(); + attrProcessor->appendNewLine(); if (styleableAttr.attribute) { - addAttributeFormatDoc(&attrProcessor, styleableAttr.attribute.get()); - attrProcessor.appendNewLine(); + addAttributeFormatDoc(attrProcessor, styleableAttr.attribute.get()); + attrProcessor->appendNewLine(); } std::stringstream doclavaName; doclavaName << "@attr name " << packageName << ":" << attrName.entry;; - attrProcessor.appendComment(doclavaName.str()); - outClassDef->addIntMember(sortedAttributes[i].fieldName, &attrProcessor, i); + attrProcessor->appendComment(doclavaName.str()); + + outStyleableClassDef->addMember(std::move(indexMember)); } } -bool JavaClassGenerator::writeEntriesForClass(ClassDefinitionWriter* outClassDef, - const StringPiece16& packageNameToGenerate, - const ResourceTablePackage* package, - const ResourceTableType* type) { +bool JavaClassGenerator::addMembersToTypeClass(const StringPiece16& packageNameToGenerate, + const ResourceTablePackage* package, + const ResourceTableType* type, + ClassDefinition* outTypeClassDef) { + for (const auto& entry : type->entries) { if (skipSymbol(entry->symbolStatus.state)) { continue; @@ -389,33 +393,41 @@ bool JavaClassGenerator::writeEntriesForClass(ClassDefinitionWriter* outClassDef return false; } - // Build the comments and annotations for this entry. - - AnnotationProcessor processor; - if (entry->symbolStatus.state != SymbolState::kUndefined) { - processor.appendComment(entry->symbolStatus.comment); - } - - for (const auto& configValue : entry->values) { - processor.appendComment(configValue->value->getComment()); - } - - // If this is an Attribute, append the format Javadoc. - if (!entry->values.empty()) { - if (Attribute* attr = valueCast(entry->values.front()->value.get())) { - // We list out the available values for the given attribute. - addAttributeFormatDoc(&processor, attr); - } - } - if (type->type == ResourceType::kStyleable) { assert(!entry->values.empty()); + const Styleable* styleable = static_cast( entry->values.front()->value.get()); - writeStyleableEntryForClass(outClassDef, &processor, packageNameToGenerate, - unmangledName, styleable); + + // Comments are handled within this method. + addMembersToStyleableClass(packageNameToGenerate, unmangledName, styleable, + outTypeClassDef); } else { - outClassDef->addResourceMember(transform(unmangledName), &processor, id); + std::unique_ptr resourceMember = + util::make_unique(transform(unmangledName), id); + + // Build the comments and annotations for this entry. + AnnotationProcessor* processor = resourceMember->getCommentBuilder(); + + // Add the comments from any tags. + if (entry->symbolStatus.state != SymbolState::kUndefined) { + processor->appendComment(entry->symbolStatus.comment); + } + + // Add the comments from all configurations of this entry. + for (const auto& configValue : entry->values) { + processor->appendComment(configValue->value->getComment()); + } + + // If this is an Attribute, append the format Javadoc. + if (!entry->values.empty()) { + if (Attribute* attr = valueCast(entry->values.front()->value.get())) { + // We list out the available values for the given attribute. + addAttributeFormatDoc(processor, attr); + } + } + + outTypeClassDef->addMember(std::move(resourceMember)); } } return true; @@ -427,9 +439,8 @@ bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate, st bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate, const StringPiece16& outPackageName, std::ostream* out) { - generateHeader(outPackageName, out); - *out << "public final class R {\n"; + ClassDefinition rClass("R", ClassQualifier::None, true); for (const auto& package : mTable->packages) { for (const auto& type : package->types) { @@ -437,13 +448,15 @@ bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate, continue; } - ClassDefinitionWriterOptions classOptions; - classOptions.useFinalQualifier = mOptions.useFinal; - classOptions.forceCreationIfEmpty = + const bool forceCreationIfEmpty = (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic); - ClassDefinitionWriter classDef(toString(type->type), classOptions); - bool result = writeEntriesForClass(&classDef, packageNameToGenerate, - package.get(), type.get()); + + std::unique_ptr classDef = util::make_unique( + util::utf16ToUtf8(toString(type->type)), ClassQualifier::Static, + forceCreationIfEmpty); + + bool result = addMembersToTypeClass(packageNameToGenerate, package.get(), type.get(), + classDef.get()); if (!result) { return false; } @@ -452,26 +465,31 @@ bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate, // Also include private attributes in this same class. ResourceTableType* privType = package->findType(ResourceType::kAttrPrivate); if (privType) { - result = writeEntriesForClass(&classDef, packageNameToGenerate, - package.get(), privType); + result = addMembersToTypeClass(packageNameToGenerate, package.get(), privType, + classDef.get()); if (!result) { return false; } } } - AnnotationProcessor processor; if (type->type == ResourceType::kStyleable && mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) { // When generating a public R class, we don't want Styleable to be part of the API. // It is only emitted for documentation purposes. - processor.appendComment("@doconly"); + AnnotationProcessor* processor = classDef->getCommentBuilder(); + processor->appendComment("@doconly"); } - classDef.writeToStream(out, " ", &processor); + + rClass.addMember(std::move(classDef)); } } - *out << "}\n"; + if (!ClassDefinition::writeJavaFile(&rClass, util::utf16ToUtf8(outPackageName), + mOptions.useFinal, out)) { + return false; + } + out->flush(); return true; } diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h index 7e46f8c9043c3..b594a88728f43 100644 --- a/tools/aapt2/java/JavaClassGenerator.h +++ b/tools/aapt2/java/JavaClassGenerator.h @@ -28,7 +28,7 @@ namespace aapt { class AnnotationProcessor; -class ClassDefinitionWriter; +class ClassDefinition; struct JavaClassGeneratorOptions { /* @@ -70,16 +70,15 @@ public: const std::string& getError() const; private: - bool writeEntriesForClass(ClassDefinitionWriter* outClassDef, - const StringPiece16& packageNameToGenerate, - const ResourceTablePackage* package, - const ResourceTableType* type); + bool addMembersToTypeClass(const StringPiece16& packageNameToGenerate, + const ResourceTablePackage* package, + const ResourceTableType* type, + ClassDefinition* outTypeClassDef); - void writeStyleableEntryForClass(ClassDefinitionWriter* outClassDef, - AnnotationProcessor* processor, - const StringPiece16& packageNameToGenerate, - const std::u16string& entryName, - const Styleable* styleable); + void addMembersToStyleableClass(const StringPiece16& packageNameToGenerate, + const std::u16string& entryName, + const Styleable* styleable, + ClassDefinition* outStyleableClassDef); bool skipSymbol(SymbolState state); diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp index a9b4c14337fe8..be8955ecdf83b 100644 --- a/tools/aapt2/java/ManifestClassGenerator.cpp +++ b/tools/aapt2/java/ManifestClassGenerator.cpp @@ -16,7 +16,7 @@ #include "Source.h" #include "java/AnnotationProcessor.h" -#include "java/ClassDefinitionWriter.h" +#include "java/ClassDefinition.h" #include "java/ManifestClassGenerator.h" #include "util/Maybe.h" #include "xml/XmlDom.h" @@ -58,8 +58,8 @@ static Maybe extractJavaIdentifier(IDiagnostics* diag, const Sour return result; } -static bool writeSymbol(IDiagnostics* diag, ClassDefinitionWriter* outClassDef, const Source& source, - xml::Element* el) { +static bool writeSymbol(const Source& source, IDiagnostics* diag, xml::Element* el, + ClassDefinition* classDef) { xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, u"name"); if (!attr) { diag->error(DiagMessage(source) << "<" << el->name << "> must define 'android:name'"); @@ -72,54 +72,53 @@ static bool writeSymbol(IDiagnostics* diag, ClassDefinitionWriter* outClassDef, return false; } - AnnotationProcessor processor; - processor.appendComment(el->comment); - outClassDef->addStringMember(result.value(), &processor, attr->value); + std::unique_ptr stringMember = util::make_unique( + util::utf16ToUtf8(result.value()), util::utf16ToUtf8(attr->value)); + stringMember->getCommentBuilder()->appendComment(el->comment); + + classDef->addMember(std::move(stringMember)); return true; } -bool ManifestClassGenerator::generate(IDiagnostics* diag, const StringPiece16& package, - xml::XmlResource* res, std::ostream* out) { +std::unique_ptr generateManifestClass(IDiagnostics* diag, xml::XmlResource* res) { xml::Element* el = xml::findRootElement(res->root.get()); if (!el) { - return false; + diag->error(DiagMessage(res->file.source) << "no root tag defined"); + return {}; } if (el->name != u"manifest" && !el->namespaceUri.empty()) { diag->error(DiagMessage(res->file.source) << "no root tag defined"); - return false; + return {}; } - *out << "package " << package << ";\n\n" - << "public final class Manifest {\n"; + std::unique_ptr permissionClass = + util::make_unique("permission", ClassQualifier::Static, false); + std::unique_ptr permissionGroupClass = + util::make_unique("permission_group", ClassQualifier::Static, false); bool error = false; + std::vector children = el->getChildElements(); - - ClassDefinitionWriterOptions classOptions; - classOptions.useFinalQualifier = true; - classOptions.forceCreationIfEmpty = false; - - // First write out permissions. - ClassDefinitionWriter classDef("permission", classOptions); for (xml::Element* childEl : children) { - if (childEl->namespaceUri.empty() && childEl->name == u"permission") { - error |= !writeSymbol(diag, &classDef, res->file.source, childEl); + if (childEl->namespaceUri.empty()) { + if (childEl->name == u"permission") { + error |= !writeSymbol(res->file.source, diag, childEl, permissionClass.get()); + } else if (childEl->name == u"permission-group") { + error |= !writeSymbol(res->file.source, diag, childEl, permissionGroupClass.get()); + } } } - classDef.writeToStream(out, " "); - // Next write out permission groups. - classDef = ClassDefinitionWriter("permission_group", classOptions); - for (xml::Element* childEl : children) { - if (childEl->namespaceUri.empty() && childEl->name == u"permission-group") { - error |= !writeSymbol(diag, &classDef, res->file.source, childEl); - } + if (error) { + return {}; } - classDef.writeToStream(out, " "); - *out << "}\n"; - return !error; + std::unique_ptr manifestClass = + util::make_unique("Manifest", ClassQualifier::None, false); + manifestClass->addMember(std::move(permissionClass)); + manifestClass->addMember(std::move(permissionGroupClass)); + return manifestClass; } } // namespace aapt diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h index 226ed23b85f88..f565289393fb0 100644 --- a/tools/aapt2/java/ManifestClassGenerator.h +++ b/tools/aapt2/java/ManifestClassGenerator.h @@ -18,6 +18,7 @@ #define AAPT_JAVA_MANIFESTCLASSGENERATOR_H #include "Diagnostics.h" +#include "java/ClassDefinition.h" #include "util/StringPiece.h" #include "xml/XmlDom.h" @@ -25,10 +26,7 @@ namespace aapt { -struct ManifestClassGenerator { - bool generate(IDiagnostics* diag, const StringPiece16& package, xml::XmlResource* res, - std::ostream* out); -}; +std::unique_ptr generateManifestClass(IDiagnostics* diag, xml::XmlResource* res); } // namespace aapt diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp index fc57ae6fd8ffc..a9ec3189ec4f9 100644 --- a/tools/aapt2/java/ManifestClassGenerator_test.cpp +++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp @@ -22,6 +22,23 @@ namespace aapt { +static ::testing::AssertionResult getManifestClassText(IAaptContext* context, xml::XmlResource* res, + std::string* outStr) { + std::unique_ptr manifestClass = generateManifestClass( + context->getDiagnostics(), res); + if (!manifestClass) { + return ::testing::AssertionFailure() << "manifestClass == nullptr"; + } + + std::stringstream out; + if (!manifestClass->writeJavaFile(manifestClass.get(), "android", true, &out)) { + return ::testing::AssertionFailure() << "failed to write java file"; + } + + *outStr = out.str(); + return ::testing::AssertionSuccess(); +} + TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) { std::unique_ptr context = test::ContextBuilder().build(); std::unique_ptr manifest = test::buildXmlDom(R"EOF( @@ -32,11 +49,8 @@ TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) { )EOF"); - std::stringstream out; - ManifestClassGenerator generator; - ASSERT_TRUE(generator.generate(context->getDiagnostics(), u"android", manifest.get(), &out)); - - std::string actual = out.str(); + std::string actual; + ASSERT_TRUE(getManifestClassText(context.get(), manifest.get(), &actual)); const size_t permissionClassPos = actual.find("public static final class permission {"); const size_t permissionGroupClassPos = @@ -87,11 +101,8 @@ TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) { )EOF"); - std::stringstream out; - ManifestClassGenerator generator; - ASSERT_TRUE(generator.generate(context->getDiagnostics(), u"android", manifest.get(), &out)); - - std::string actual = out.str(); + std::string actual; + ASSERT_TRUE(getManifestClassText(context.get(), manifest.get(), &actual)); EXPECT_NE(std::string::npos, actual.find( R"EOF( /** diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp index b84074d1cb58f..8c10fbb6e38af 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -762,9 +762,24 @@ public: return true; } + std::unique_ptr manifestClass = generateManifestClass( + mContext->getDiagnostics(), manifestXml); + + if (!manifestClass) { + // Something bad happened, but we already logged it, so exit. + return false; + } + + if (manifestClass->empty()) { + // Empty Manifest class, no need to generate it. + return true; + } + + const std::string packageUtf8 = util::utf16ToUtf8(mContext->getCompilationPackage()); + std::string outPath = mOptions.generateJavaClassPath.value(); - file::appendPath(&outPath, - file::packageToPath(util::utf16ToUtf8(mContext->getCompilationPackage()))); + file::appendPath(&outPath, file::packageToPath(packageUtf8)); + if (!file::mkdirs(outPath)) { mContext->getDiagnostics()->error( DiagMessage() << "failed to create directory '" << outPath << "'"); @@ -780,13 +795,7 @@ public: return false; } - ManifestClassGenerator generator; - if (!generator.generate(mContext->getDiagnostics(), mContext->getCompilationPackage(), - manifestXml, &fout)) { - return false; - } - - if (!fout) { + if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, &fout)) { mContext->getDiagnostics()->error( DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); return false;