Home | History | Annotate | Download | only in java
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include "NameMangler.h"
     18 #include "Resource.h"
     19 #include "ResourceTable.h"
     20 #include "ResourceValues.h"
     21 #include "ValueVisitor.h"
     22 
     23 #include "java/AnnotationProcessor.h"
     24 #include "java/ClassDefinition.h"
     25 #include "java/JavaClassGenerator.h"
     26 #include "process/SymbolTable.h"
     27 #include "util/StringPiece.h"
     28 
     29 #include <algorithm>
     30 #include <ostream>
     31 #include <set>
     32 #include <sstream>
     33 #include <tuple>
     34 
     35 namespace aapt {
     36 
     37 JavaClassGenerator::JavaClassGenerator(IAaptContext* context, ResourceTable* table,
     38                                        const JavaClassGeneratorOptions& options) :
     39         mContext(context), mTable(table), mOptions(options) {
     40 }
     41 
     42 static const std::set<StringPiece16> sJavaIdentifiers = {
     43     u"abstract", u"assert", u"boolean", u"break", u"byte",
     44     u"case", u"catch", u"char", u"class", u"const", u"continue",
     45     u"default", u"do", u"double", u"else", u"enum", u"extends",
     46     u"final", u"finally", u"float", u"for", u"goto", u"if",
     47     u"implements", u"import", u"instanceof", u"int", u"interface",
     48     u"long", u"native", u"new", u"package", u"private", u"protected",
     49     u"public", u"return", u"short", u"static", u"strictfp", u"super",
     50     u"switch", u"synchronized", u"this", u"throw", u"throws",
     51     u"transient", u"try", u"void", u"volatile", u"while", u"true",
     52     u"false", u"null"
     53 };
     54 
     55 static bool isValidSymbol(const StringPiece16& symbol) {
     56     return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end();
     57 }
     58 
     59 /*
     60  * Java symbols can not contain . or -, but those are valid in a resource name.
     61  * Replace those with '_'.
     62  */
     63 static std::string transform(const StringPiece16& symbol) {
     64     std::string output = util::utf16ToUtf8(symbol);
     65     for (char& c : output) {
     66         if (c == '.' || c == '-') {
     67             c = '_';
     68         }
     69     }
     70     return output;
     71 }
     72 
     73 /**
     74  * Transforms an attribute in a styleable to the Java field name:
     75  *
     76  * <declare-styleable name="Foo">
     77  *   <attr name="android:bar" />
     78  *   <attr name="bar" />
     79  * </declare-styleable>
     80  *
     81  * Foo_android_bar
     82  * Foo_bar
     83  */
     84 static std::string transformNestedAttr(const ResourceNameRef& attrName,
     85                                        const std::string& styleableClassName,
     86                                        const StringPiece16& packageNameToGenerate) {
     87     std::string output = styleableClassName;
     88 
     89     // We may reference IDs from other packages, so prefix the entry name with
     90     // the package.
     91     if (!attrName.package.empty() && packageNameToGenerate != attrName.package) {
     92         output += "_" + transform(attrName.package);
     93     }
     94     output += "_" + transform(attrName.entry);
     95     return output;
     96 }
     97 
     98 static void addAttributeFormatDoc(AnnotationProcessor* processor, Attribute* attr) {
     99     const uint32_t typeMask = attr->typeMask;
    100     if (typeMask & android::ResTable_map::TYPE_REFERENCE) {
    101         processor->appendComment(
    102                 "<p>May be a reference to another resource, in the form\n"
    103                 "\"<code>@[+][<i>package</i>:]<i>type</i>/<i>name</i></code>\" or a theme\n"
    104                 "attribute in the form\n"
    105                 "\"<code>?[<i>package</i>:]<i>type</i>/<i>name</i></code>\".");
    106     }
    107 
    108     if (typeMask & android::ResTable_map::TYPE_STRING) {
    109         processor->appendComment(
    110                 "<p>May be a string value, using '\\\\;' to escape characters such as\n"
    111                 "'\\\\n' or '\\\\uxxxx' for a unicode character;");
    112     }
    113 
    114     if (typeMask & android::ResTable_map::TYPE_INTEGER) {
    115         processor->appendComment("<p>May be an integer value, such as \"<code>100</code>\".");
    116     }
    117 
    118     if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
    119         processor->appendComment(
    120                 "<p>May be a boolean value, such as \"<code>true</code>\" or\n"
    121                 "\"<code>false</code>\".");
    122     }
    123 
    124     if (typeMask & android::ResTable_map::TYPE_COLOR) {
    125         processor->appendComment(
    126                 "<p>May be a color value, in the form of \"<code>#<i>rgb</i></code>\",\n"
    127                 "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code\", or \n"
    128                 "\"<code>#<i>aarrggbb</i></code>\".");
    129     }
    130 
    131     if (typeMask & android::ResTable_map::TYPE_FLOAT) {
    132         processor->appendComment(
    133                 "<p>May be a floating point value, such as \"<code>1.2</code>\".");
    134     }
    135 
    136     if (typeMask & android::ResTable_map::TYPE_DIMENSION) {
    137         processor->appendComment(
    138                 "<p>May be a dimension value, which is a floating point number appended with a\n"
    139                 "unit such as \"<code>14.5sp</code>\".\n"
    140                 "Available units are: px (pixels), dp (density-independent pixels),\n"
    141                 "sp (scaled pixels based on preferred font size), in (inches), and\n"
    142                 "mm (millimeters).");
    143     }
    144 
    145     if (typeMask & android::ResTable_map::TYPE_FRACTION) {
    146         processor->appendComment(
    147                 "<p>May be a fractional value, which is a floating point number appended with\n"
    148                 "either % or %p, such as \"<code>14.5%</code>\".\n"
    149                 "The % suffix always means a percentage of the base size;\n"
    150                 "the optional %p suffix provides a size relative to some parent container.");
    151     }
    152 
    153     if (typeMask & (android::ResTable_map::TYPE_FLAGS | android::ResTable_map::TYPE_ENUM)) {
    154         if (typeMask & android::ResTable_map::TYPE_FLAGS) {
    155             processor->appendComment(
    156                     "<p>Must be one or more (separated by '|') of the following "
    157                     "constant values.</p>");
    158         } else {
    159             processor->appendComment("<p>Must be one of the following constant values.</p>");
    160         }
    161 
    162         processor->appendComment("<table>\n<colgroup align=\"left\" />\n"
    163                                  "<colgroup align=\"left\" />\n"
    164                                  "<colgroup align=\"left\" />\n"
    165                                  "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>\n");
    166         for (const Attribute::Symbol& symbol : attr->symbols) {
    167             std::stringstream line;
    168             line << "<tr><td>" << symbol.symbol.name.value().entry << "</td>"
    169             << "<td>" << std::hex << symbol.value << std::dec << "</td>"
    170             << "<td>" << util::trimWhitespace(symbol.symbol.getComment()) << "</td></tr>";
    171             processor->appendComment(line.str());
    172         }
    173         processor->appendComment("</table>");
    174     }
    175 }
    176 
    177 bool JavaClassGenerator::skipSymbol(SymbolState state) {
    178     switch (mOptions.types) {
    179     case JavaClassGeneratorOptions::SymbolTypes::kAll:
    180         return false;
    181     case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate:
    182         return state == SymbolState::kUndefined;
    183     case JavaClassGeneratorOptions::SymbolTypes::kPublic:
    184         return state != SymbolState::kPublic;
    185     }
    186     return true;
    187 }
    188 
    189 struct StyleableAttr {
    190     const Reference* attrRef;
    191     std::string fieldName;
    192     std::unique_ptr<SymbolTable::Symbol> symbol;
    193 };
    194 
    195 static bool lessStyleableAttr(const StyleableAttr& lhs, const StyleableAttr& rhs) {
    196     const ResourceId lhsId = lhs.attrRef->id ? lhs.attrRef->id.value() : ResourceId(0);
    197     const ResourceId rhsId = rhs.attrRef->id ? rhs.attrRef->id.value() : ResourceId(0);
    198     if (lhsId < rhsId) {
    199         return true;
    200     } else if (lhsId > rhsId) {
    201         return false;
    202     } else {
    203         return lhs.attrRef->name.value() < rhs.attrRef->name.value();
    204     }
    205 }
    206 
    207 void JavaClassGenerator::addMembersToStyleableClass(const StringPiece16& packageNameToGenerate,
    208                                                     const std::u16string& entryName,
    209                                                     const Styleable* styleable,
    210                                                     ClassDefinition* outStyleableClassDef) {
    211     const std::string className = transform(entryName);
    212 
    213     std::unique_ptr<ResourceArrayMember> styleableArrayDef =
    214             util::make_unique<ResourceArrayMember>(className);
    215 
    216     // This must be sorted by resource ID.
    217     std::vector<StyleableAttr> sortedAttributes;
    218     sortedAttributes.reserve(styleable->entries.size());
    219     for (const auto& attr : styleable->entries) {
    220         // If we are not encoding final attributes, the styleable entry may have no ID
    221         // if we are building a static library.
    222         assert((!mOptions.useFinal || attr.id) && "no ID set for Styleable entry");
    223         assert(attr.name && "no name set for Styleable entry");
    224 
    225         // We will need the unmangled, transformed name in the comments and the field,
    226         // so create it once and cache it in this StyleableAttr data structure.
    227         StyleableAttr styleableAttr = {};
    228         styleableAttr.attrRef = &attr;
    229         styleableAttr.fieldName = transformNestedAttr(attr.name.value(), className,
    230                                                       packageNameToGenerate);
    231 
    232         Reference mangledReference;
    233         mangledReference.id = attr.id;
    234         mangledReference.name = attr.name;
    235         if (mangledReference.name.value().package.empty()) {
    236             mangledReference.name.value().package = mContext->getCompilationPackage();
    237         }
    238 
    239         if (Maybe<ResourceName> mangledName =
    240                 mContext->getNameMangler()->mangleName(mangledReference.name.value())) {
    241             mangledReference.name = mangledName;
    242         }
    243 
    244         // Look up the symbol so that we can write out in the comments what are possible
    245         // legal values for this attribute.
    246         const SymbolTable::Symbol* symbol = mContext->getExternalSymbols()->findByReference(
    247                 mangledReference);
    248         if (symbol && symbol->attribute) {
    249             // Copy the symbol data structure because the returned instance can be destroyed.
    250             styleableAttr.symbol = util::make_unique<SymbolTable::Symbol>(*symbol);
    251         }
    252         sortedAttributes.push_back(std::move(styleableAttr));
    253     }
    254 
    255     // Sort the attributes by ID.
    256     std::sort(sortedAttributes.begin(), sortedAttributes.end(), lessStyleableAttr);
    257 
    258     const size_t attrCount = sortedAttributes.size();
    259     if (attrCount > 0) {
    260         // Build the comment string for the Styleable. It includes details about the
    261         // child attributes.
    262         std::stringstream styleableComment;
    263         if (!styleable->getComment().empty()) {
    264             styleableComment << styleable->getComment() << "\n";
    265         } else {
    266             styleableComment << "Attributes that can be used with a " << className << ".\n";
    267         }
    268 
    269         styleableComment <<
    270                 "<p>Includes the following attributes:</p>\n"
    271                 "<table>\n"
    272                 "<colgroup align=\"left\" />\n"
    273                 "<colgroup align=\"left\" />\n"
    274                 "<tr><th>Attribute</th><th>Description</th></tr>\n";
    275 
    276         for (const StyleableAttr& entry : sortedAttributes) {
    277             if (!entry.symbol) {
    278                 continue;
    279             }
    280 
    281             if (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
    282                     !entry.symbol->isPublic) {
    283                 // Don't write entries for non-public attributes.
    284                 continue;
    285             }
    286 
    287             StringPiece16 attrCommentLine = entry.symbol->attribute->getComment();
    288             if (attrCommentLine.contains(StringPiece16(u"@removed"))) {
    289                 // Removed attributes are public but hidden from the documentation, so don't emit
    290                 // them as part of the class documentation.
    291                 continue;
    292             }
    293 
    294             const ResourceName& attrName = entry.attrRef->name.value();
    295             styleableComment << "<tr><td>";
    296             styleableComment << "<code>{@link #"
    297                              << entry.fieldName << " "
    298                              << (!attrName.package.empty()
    299                                     ? attrName.package : mContext->getCompilationPackage())
    300                              << ":" << attrName.entry
    301                              << "}</code>";
    302             styleableComment << "</td>";
    303 
    304             styleableComment << "<td>";
    305 
    306             // Only use the comment up until the first '.'. This is to stay compatible with
    307             // the way old AAPT did it (presumably to keep it short and to avoid including
    308             // annotations like @hide which would affect this Styleable).
    309             auto iter = std::find(attrCommentLine.begin(), attrCommentLine.end(), u'.');
    310             if (iter != attrCommentLine.end()) {
    311                 attrCommentLine = attrCommentLine.substr(
    312                         0, (iter - attrCommentLine.begin()) + 1);
    313             }
    314             styleableComment << attrCommentLine << "</td></tr>\n";
    315         }
    316         styleableComment << "</table>\n";
    317 
    318         for (const StyleableAttr& entry : sortedAttributes) {
    319             if (!entry.symbol) {
    320                 continue;
    321             }
    322 
    323             if (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
    324                     !entry.symbol->isPublic) {
    325                 // Don't write entries for non-public attributes.
    326                 continue;
    327             }
    328             styleableComment << "@see #" << entry.fieldName << "\n";
    329         }
    330 
    331         styleableArrayDef->getCommentBuilder()->appendComment(styleableComment.str());
    332     }
    333 
    334     // Add the ResourceIds to the array member.
    335     for (const StyleableAttr& styleableAttr : sortedAttributes) {
    336         styleableArrayDef->addElement(
    337                 styleableAttr.attrRef->id ? styleableAttr.attrRef->id.value() : ResourceId(0));
    338     }
    339 
    340     // Add the Styleable array to the Styleable class.
    341     outStyleableClassDef->addMember(std::move(styleableArrayDef));
    342 
    343     // Now we emit the indices into the array.
    344     for (size_t i = 0; i < attrCount; i++) {
    345         const StyleableAttr& styleableAttr = sortedAttributes[i];
    346 
    347         if (!styleableAttr.symbol) {
    348             continue;
    349         }
    350 
    351         if (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic &&
    352                 !styleableAttr.symbol->isPublic) {
    353             // Don't write entries for non-public attributes.
    354             continue;
    355         }
    356 
    357         StringPiece16 comment = styleableAttr.attrRef->getComment();
    358         if (styleableAttr.symbol->attribute && comment.empty()) {
    359             comment = styleableAttr.symbol->attribute->getComment();
    360         }
    361 
    362         if (comment.contains(StringPiece16(u"@removed"))) {
    363             // Removed attributes are public but hidden from the documentation, so don't emit them
    364             // as part of the class documentation.
    365             continue;
    366         }
    367 
    368         const ResourceName& attrName = styleableAttr.attrRef->name.value();
    369 
    370         StringPiece16 packageName = attrName.package;
    371         if (packageName.empty()) {
    372             packageName = mContext->getCompilationPackage();
    373         }
    374 
    375         std::unique_ptr<IntMember> indexMember = util::make_unique<IntMember>(
    376                 sortedAttributes[i].fieldName, static_cast<uint32_t>(i));
    377 
    378         AnnotationProcessor* attrProcessor = indexMember->getCommentBuilder();
    379 
    380         if (!comment.empty()) {
    381             attrProcessor->appendComment("<p>\n@attr description");
    382             attrProcessor->appendComment(comment);
    383         } else {
    384             std::stringstream defaultComment;
    385             defaultComment
    386                     << "<p>This symbol is the offset where the "
    387                     << "{@link " << packageName << ".R.attr#" << transform(attrName.entry) << "}\n"
    388                     << "attribute's value can be found in the "
    389                     << "{@link #" << className << "} array.";
    390             attrProcessor->appendComment(defaultComment.str());
    391         }
    392 
    393         attrProcessor->appendNewLine();
    394 
    395         addAttributeFormatDoc(attrProcessor, styleableAttr.symbol->attribute.get());
    396         attrProcessor->appendNewLine();
    397 
    398         std::stringstream doclavaName;
    399         doclavaName << "@attr name " << packageName << ":" << attrName.entry;;
    400         attrProcessor->appendComment(doclavaName.str());
    401 
    402         outStyleableClassDef->addMember(std::move(indexMember));
    403     }
    404 }
    405 
    406 bool JavaClassGenerator::addMembersToTypeClass(const StringPiece16& packageNameToGenerate,
    407                                                const ResourceTablePackage* package,
    408                                                const ResourceTableType* type,
    409                                                ClassDefinition* outTypeClassDef) {
    410 
    411     for (const auto& entry : type->entries) {
    412         if (skipSymbol(entry->symbolStatus.state)) {
    413             continue;
    414         }
    415 
    416         ResourceId id;
    417         if (package->id && type->id && entry->id) {
    418             id = ResourceId(package->id.value(), type->id.value(), entry->id.value());
    419         }
    420 
    421         std::u16string unmangledPackage;
    422         std::u16string unmangledName = entry->name;
    423         if (NameMangler::unmangle(&unmangledName, &unmangledPackage)) {
    424             // The entry name was mangled, and we successfully unmangled it.
    425             // Check that we want to emit this symbol.
    426             if (package->name != unmangledPackage) {
    427                 // Skip the entry if it doesn't belong to the package we're writing.
    428                 continue;
    429             }
    430         } else if (packageNameToGenerate != package->name) {
    431             // We are processing a mangled package name,
    432             // but this is a non-mangled resource.
    433             continue;
    434         }
    435 
    436         if (!isValidSymbol(unmangledName)) {
    437             ResourceNameRef resourceName(packageNameToGenerate, type->type, unmangledName);
    438             std::stringstream err;
    439             err << "invalid symbol name '" << resourceName << "'";
    440             mError = err.str();
    441             return false;
    442         }
    443 
    444         if (type->type == ResourceType::kStyleable) {
    445             assert(!entry->values.empty());
    446 
    447             const Styleable* styleable = static_cast<const Styleable*>(
    448                     entry->values.front()->value.get());
    449 
    450             // Comments are handled within this method.
    451             addMembersToStyleableClass(packageNameToGenerate, unmangledName, styleable,
    452                                        outTypeClassDef);
    453         } else {
    454             std::unique_ptr<ResourceMember> resourceMember =
    455                     util::make_unique<ResourceMember>(transform(unmangledName), id);
    456 
    457             // Build the comments and annotations for this entry.
    458             AnnotationProcessor* processor = resourceMember->getCommentBuilder();
    459 
    460             // Add the comments from any <public> tags.
    461             if (entry->symbolStatus.state != SymbolState::kUndefined) {
    462                 processor->appendComment(entry->symbolStatus.comment);
    463             }
    464 
    465             // Add the comments from all configurations of this entry.
    466             for (const auto& configValue : entry->values) {
    467                 processor->appendComment(configValue->value->getComment());
    468             }
    469 
    470             // If this is an Attribute, append the format Javadoc.
    471             if (!entry->values.empty()) {
    472                 if (Attribute* attr = valueCast<Attribute>(entry->values.front()->value.get())) {
    473                     // We list out the available values for the given attribute.
    474                     addAttributeFormatDoc(processor, attr);
    475                 }
    476             }
    477 
    478             outTypeClassDef->addMember(std::move(resourceMember));
    479         }
    480     }
    481     return true;
    482 }
    483 
    484 bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate, std::ostream* out) {
    485     return generate(packageNameToGenerate, packageNameToGenerate, out);
    486 }
    487 
    488 static void appendJavaDocAnnotations(const std::vector<std::string>& annotations,
    489                                      AnnotationProcessor* processor) {
    490     for (const std::string& annotation : annotations) {
    491         std::string properAnnotation = "@";
    492         properAnnotation += annotation;
    493         processor->appendComment(properAnnotation);
    494     }
    495 }
    496 
    497 bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate,
    498                                   const StringPiece16& outPackageName, std::ostream* out) {
    499 
    500     ClassDefinition rClass("R", ClassQualifier::None, true);
    501 
    502     for (const auto& package : mTable->packages) {
    503         for (const auto& type : package->types) {
    504             if (type->type == ResourceType::kAttrPrivate) {
    505                 continue;
    506             }
    507 
    508             const bool forceCreationIfEmpty =
    509                     (mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic);
    510 
    511             std::unique_ptr<ClassDefinition> classDef = util::make_unique<ClassDefinition>(
    512                     util::utf16ToUtf8(toString(type->type)), ClassQualifier::Static,
    513                     forceCreationIfEmpty);
    514 
    515             bool result = addMembersToTypeClass(packageNameToGenerate, package.get(), type.get(),
    516                                                 classDef.get());
    517             if (!result) {
    518                 return false;
    519             }
    520 
    521             if (type->type == ResourceType::kAttr) {
    522                 // Also include private attributes in this same class.
    523                 ResourceTableType* privType = package->findType(ResourceType::kAttrPrivate);
    524                 if (privType) {
    525                     result = addMembersToTypeClass(packageNameToGenerate, package.get(), privType,
    526                                                    classDef.get());
    527                     if (!result) {
    528                         return false;
    529                     }
    530                 }
    531             }
    532 
    533             if (type->type == ResourceType::kStyleable &&
    534                     mOptions.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) {
    535                 // When generating a public R class, we don't want Styleable to be part of the API.
    536                 // It is only emitted for documentation purposes.
    537                 classDef->getCommentBuilder()->appendComment("@doconly");
    538             }
    539 
    540             appendJavaDocAnnotations(mOptions.javadocAnnotations, classDef->getCommentBuilder());
    541 
    542             rClass.addMember(std::move(classDef));
    543         }
    544     }
    545 
    546     appendJavaDocAnnotations(mOptions.javadocAnnotations, rClass.getCommentBuilder());
    547 
    548     if (!ClassDefinition::writeJavaFile(&rClass, util::utf16ToUtf8(outPackageName),
    549                                         mOptions.useFinal, out)) {
    550         return false;
    551     }
    552 
    553     out->flush();
    554     return true;
    555 }
    556 
    557 } // namespace aapt
    558