/*
* Copyright 2010-2014, 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 "slang_rs_reflect_utils.h"
#include <cstdio>
#include <cstring>
#include <string>
#include <iomanip>
#include "llvm/ADT/StringRef.h"
#include "os_sep.h"
#include "slang_assert.h"
#include "slang_utils.h"
namespace slang {
using std::string;
string RSSlangReflectUtils::GetFileNameStem(const char *fileName) {
const char *dot = fileName + strlen(fileName);
const char *slash = dot - 1;
while (slash >= fileName) {
if (*slash == OS_PATH_SEPARATOR) {
break;
}
if ((*slash == '.') && (*dot == 0)) {
dot = slash;
}
--slash;
}
++slash;
return string(slash, dot - slash);
}
string RSSlangReflectUtils::ComputePackagedPath(const char *prefixPath,
const char *packageName) {
string packaged_path(prefixPath);
if (!packaged_path.empty() &&
(packaged_path[packaged_path.length() - 1] != OS_PATH_SEPARATOR)) {
packaged_path += OS_PATH_SEPARATOR_STR;
}
size_t s = packaged_path.length();
packaged_path += packageName;
while (s < packaged_path.length()) {
if (packaged_path[s] == '.') {
packaged_path[s] = OS_PATH_SEPARATOR;
}
++s;
}
return packaged_path;
}
static string InternalFileNameConvert(const char *rsFileName, bool toLower) {
const char *dot = rsFileName + strlen(rsFileName);
const char *slash = dot - 1;
while (slash >= rsFileName) {
if (*slash == OS_PATH_SEPARATOR) {
break;
}
if ((*slash == '.') && (*dot == 0)) {
dot = slash;
}
--slash;
}
++slash;
char ret[256];
int i = 0;
for (; (i < 255) && (slash < dot); ++slash) {
if (isalnum(*slash) || *slash == '_') {
if (toLower) {
ret[i] = tolower(*slash);
} else {
ret[i] = *slash;
}
++i;
}
}
ret[i] = 0;
return string(ret);
}
std::string
RSSlangReflectUtils::JavaClassNameFromRSFileName(const char *rsFileName) {
return InternalFileNameConvert(rsFileName, false);
}
std::string RootNameFromRSFileName(const std::string &rsFileName) {
return InternalFileNameConvert(rsFileName.c_str(), false);
}
std::string
RSSlangReflectUtils::BCFileNameFromRSFileName(const char *rsFileName) {
return InternalFileNameConvert(rsFileName, true);
}
std::string RSSlangReflectUtils::JavaBitcodeClassNameFromRSFileName(
const char *rsFileName) {
std::string tmp(InternalFileNameConvert(rsFileName, false));
return tmp.append("BitCode");
}
static bool GenerateAccessorMethod(
const RSSlangReflectUtils::BitCodeAccessorContext &context,
int bitwidth, GeneratedFile &out) {
// the prototype of the accessor method
out.indent() << "// return byte array representation of the " << bitwidth
<< "-bit bitcode.\n";
out.indent() << "public static byte[] getBitCode" << bitwidth << "()";
out.startBlock();
out.indent() << "return getBitCode" << bitwidth << "Internal();\n";
out.endBlock(true);
return true;
}
// Java method size must not exceed 64k,
// so we have to split the bitcode into multiple segments.
static bool GenerateSegmentMethod(const char *buff, int blen, int bitwidth,
int seg_num, GeneratedFile &out) {
out.indent() << "private static byte[] getSegment" << bitwidth << "_"
<< seg_num << "()";
out.startBlock();
out.indent() << "byte[] data = {";
out.increaseIndent();
const int kEntriesPerLine = 16;
int position = kEntriesPerLine; // We start with a new line and indent.
for (int written = 0; written < blen; written++) {
if (++position >= kEntriesPerLine) {
out << "\n";
out.indent();
position = 0;
} else {
out << " ";
}
out << std::setw(4) << static_cast<int>(buff[written]) << ",";
}
out << "\n";
out.decreaseIndent();
out.indent() << "};\n";
out.indent() << "return data;\n";
out.endBlock();
return true;
}
static bool GenerateJavaCodeAccessorMethodForBitwidth(
const RSSlangReflectUtils::BitCodeAccessorContext &context,
int bitwidth, GeneratedFile &out) {
std::string filename(context.bc32FileName);
if (bitwidth == 64) {
filename = context.bc64FileName;
}
FILE *pfin = fopen(filename.c_str(), "rb");
if (pfin == NULL) {
fprintf(stderr, "Error: could not read file %s\n", filename.c_str());
return false;
}
// start the accessor method
GenerateAccessorMethod(context, bitwidth, out);
// output the data
// make sure the generated function for a segment won't break the Javac
// size limitation (64K).
static const int SEG_SIZE = 0x2000;
char *buff = new char[SEG_SIZE];
int read_length;
int seg_num = 0;
int total_length = 0;
while ((read_length = fread(buff, 1, SEG_SIZE, pfin)) > 0) {
GenerateSegmentMethod(buff, read_length, bitwidth, seg_num, out);
++seg_num;
total_length += read_length;
}
delete[] buff;
fclose(pfin);
// output the internal accessor method
out.indent() << "private static int bitCode" << bitwidth << "Length = "
<< total_length << ";\n\n";
out.indent() << "private static byte[] getBitCode" << bitwidth
<< "Internal()";
out.startBlock();
out.indent() << "byte[] bc = new byte[bitCode" << bitwidth << "Length];\n";
out.indent() << "int offset = 0;\n";
out.indent() << "byte[] seg;\n";
for (int i = 0; i < seg_num; ++i) {
out.indent() << "seg = getSegment" << bitwidth << "_" << i << "();\n";
out.indent() << "System.arraycopy(seg, 0, bc, offset, seg.length);\n";
out.indent() << "offset += seg.length;\n";
}
out.indent() << "return bc;\n";
out.endBlock();
return true;
}
static bool GenerateJavaCodeAccessorMethod(
const RSSlangReflectUtils::BitCodeAccessorContext &context,
GeneratedFile &out) {
if (!GenerateJavaCodeAccessorMethodForBitwidth(context, 32, out)) {
slangAssert(false && "Couldn't generate 32-bit embedded bitcode!");
return false;
}
if (!GenerateJavaCodeAccessorMethodForBitwidth(context, 64, out)) {
slangAssert(false && "Couldn't generate 64-bit embedded bitcode!");
return false;
}
return true;
}
static bool GenerateAccessorClass(
const RSSlangReflectUtils::BitCodeAccessorContext &context,
const char *clazz_name, GeneratedFile &out) {
// begin the class.
out << "/**\n";
out << " * @hide\n";
out << " */\n";
out << "public class " << clazz_name;
out.startBlock();
bool ret = true;
switch (context.bcStorage) {
case BCST_APK_RESOURCE:
slangAssert(false &&
"Invalid generation of bitcode accessor with resource");
break;
case BCST_JAVA_CODE:
ret = GenerateJavaCodeAccessorMethod(context, out);
break;
default:
ret = false;
}
// end the class.
out.endBlock();
return ret;
}
bool RSSlangReflectUtils::GenerateJavaBitCodeAccessor(
const BitCodeAccessorContext &context) {
string output_path =
ComputePackagedPath(context.reflectPath, context.packageName);
if (!SlangUtils::CreateDirectoryWithParents(llvm::StringRef(output_path),
NULL)) {
fprintf(stderr, "Error: could not create dir %s\n", output_path.c_str());
return false;
}
string clazz_name(JavaBitcodeClassNameFromRSFileName(context.rsFileName));
string filename(clazz_name);
filename += ".java";
GeneratedFile out;
if (!out.startFile(output_path, filename, context.rsFileName,
context.licenseNote, true, context.verbose)) {
return false;
}
out << "package " << context.packageName << ";\n\n";
bool ret = GenerateAccessorClass(context, clazz_name.c_str(), out);
out.closeFile();
return ret;
}
std::string JoinPath(const std::string &path1, const std::string &path2) {
if (path1.empty()) {
return path2;
}
if (path2.empty()) {
return path1;
}
std::string fullPath = path1;
if (fullPath[fullPath.length() - 1] != OS_PATH_SEPARATOR) {
fullPath += OS_PATH_SEPARATOR;
}
if (path2[0] == OS_PATH_SEPARATOR) {
fullPath += path2.substr(1, string::npos);
} else {
fullPath += path2;
}
return fullPath;
}
// Replace all instances of "\" with "\\" in a single string to prevent
// formatting errors. In Java, this can happen even within comments, as
// Java processes \u before the comments are stripped. E.g. if the generated
// file in Windows contains the note:
// /* Do not modify! Generated from \Users\MyName\MyDir\foo.cs */
// Java will think that \U tells of a Unicode character.
static void SanitizeString(std::string *s) {
size_t p = 0;
while ((p = s->find('\\', p)) != std::string::npos) {
s->replace(p, 1, "\\\\");
p += 2;
}
}
static const char *const gApacheLicenseNote =
"/*\n"
" * Copyright (C) 2011-2014 The Android Open Source Project\n"
" *\n"
" * Licensed under the Apache License, Version 2.0 (the \"License\");\n"
" * you may not use this file except in compliance with the License.\n"
" * You may obtain a copy of the License at\n"
" *\n"
" * http://www.apache.org/licenses/LICENSE-2.0\n"
" *\n"
" * Unless required by applicable law or agreed to in writing, software\n"
" * distributed under the License is distributed on an \"AS IS\" BASIS,\n"
" * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or "
"implied.\n"
" * See the License for the specific language governing permissions and\n"
" * limitations under the License.\n"
" */\n"
"\n";
bool GeneratedFile::startFile(const string &outDirectory,
const string &outFileName,
const string &sourceFileName,
const string *optionalLicense, bool isJava,
bool verbose) {
if (verbose) {
printf("Generating %s\n", outFileName.c_str());
}
// Create the parent directories.
if (!outDirectory.empty()) {
std::string errorMsg;
if (!SlangUtils::CreateDirectoryWithParents(outDirectory, &errorMsg)) {
fprintf(stderr, "Error: %s\n", errorMsg.c_str());
return false;
}
}
std::string FilePath = JoinPath(outDirectory, outFileName);
// Open the file.
open(FilePath.c_str());
if (!good()) {
fprintf(stderr, "Error: could not write file %s\n", outFileName.c_str());
return false;
}
// Write the license.
if (optionalLicense != NULL) {
*this << *optionalLicense;
} else {
*this << gApacheLicenseNote;
}
// Write a notice that this is a generated file.
std::string source(sourceFileName);
if (isJava) {
SanitizeString(&source);
}
*this << "/*\n"
<< " * This file is auto-generated. DO NOT MODIFY!\n"
<< " * The source Renderscript file: " << source << "\n"
<< " */\n\n";
return true;
}
void GeneratedFile::closeFile() { close(); }
void GeneratedFile::increaseIndent() { mIndent.append(" "); }
void GeneratedFile::decreaseIndent() {
slangAssert(!mIndent.empty() && "No indent");
mIndent.erase(0, 4);
}
void GeneratedFile::comment(const std::string &s) {
indent() << "/* ";
// +3 for the " * " starting each line.
std::size_t indentLength = mIndent.length() + 3;
std::size_t lengthOfCommentOnLine = 0;
const std::size_t maxPerLine = 80;
for (std::size_t start = 0, length = s.length(), nextStart = 0;
start < length; start = nextStart) {
std::size_t p = s.find_first_of(" \n", start);
std::size_t toCopy = 1;
bool forceBreak = false;
if (p == std::string::npos) {
toCopy = length - start;
nextStart = length;
} else {
toCopy = p - start;
nextStart = p + 1;
forceBreak = s[p] == '\n';
}
if (lengthOfCommentOnLine > 0) {
if (indentLength + lengthOfCommentOnLine + toCopy >= maxPerLine) {
*this << "\n";
indent() << " * ";
lengthOfCommentOnLine = 0;
} else {
*this << " ";
}
}
*this << s.substr(start, toCopy);
if (forceBreak) {
lengthOfCommentOnLine = maxPerLine;
} else {
lengthOfCommentOnLine += toCopy;
}
}
*this << "\n";
indent() << " */\n";
}
} // namespace slang