1/*
2 * Copyright 2010-2014, 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 "slang_rs_reflect_utils.h"
18
19#include <cstdio>
20#include <cstring>
21#include <string>
22#include <iomanip>
23
24#include "llvm/ADT/StringRef.h"
25
26#include "os_sep.h"
27#include "slang_assert.h"
28#include "slang_utils.h"
29
30namespace slang {
31
32using std::string;
33
34string RSSlangReflectUtils::GetFileNameStem(const char *fileName) {
35  const char *dot = fileName + strlen(fileName);
36  const char *slash = dot - 1;
37  while (slash >= fileName) {
38    if (*slash == OS_PATH_SEPARATOR) {
39      break;
40    }
41    if ((*slash == '.') && (*dot == 0)) {
42      dot = slash;
43    }
44    --slash;
45  }
46  ++slash;
47  return string(slash, dot - slash);
48}
49
50string RSSlangReflectUtils::ComputePackagedPath(const char *prefixPath,
51                                                const char *packageName) {
52  string packaged_path(prefixPath);
53  if (!packaged_path.empty() &&
54      (packaged_path[packaged_path.length() - 1] != OS_PATH_SEPARATOR)) {
55    packaged_path += OS_PATH_SEPARATOR_STR;
56  }
57  size_t s = packaged_path.length();
58  packaged_path += packageName;
59  while (s < packaged_path.length()) {
60    if (packaged_path[s] == '.') {
61      packaged_path[s] = OS_PATH_SEPARATOR;
62    }
63    ++s;
64  }
65  return packaged_path;
66}
67
68static string InternalFileNameConvert(const char *rsFileName, bool toLower) {
69  const char *dot = rsFileName + strlen(rsFileName);
70  const char *slash = dot - 1;
71  while (slash >= rsFileName) {
72    if (*slash == OS_PATH_SEPARATOR) {
73      break;
74    }
75    if ((*slash == '.') && (*dot == 0)) {
76      dot = slash;
77    }
78    --slash;
79  }
80  ++slash;
81  char ret[256];
82  int i = 0;
83  for (; (i < 255) && (slash < dot); ++slash) {
84    if (isalnum(*slash) || *slash == '_') {
85      if (toLower) {
86        ret[i] = tolower(*slash);
87      } else {
88        ret[i] = *slash;
89      }
90      ++i;
91    }
92  }
93  ret[i] = 0;
94  return string(ret);
95}
96
97std::string
98RSSlangReflectUtils::JavaClassNameFromRSFileName(const char *rsFileName) {
99  return InternalFileNameConvert(rsFileName, false);
100}
101
102std::string RootNameFromRSFileName(const std::string &rsFileName) {
103  return InternalFileNameConvert(rsFileName.c_str(), false);
104}
105
106std::string
107RSSlangReflectUtils::BCFileNameFromRSFileName(const char *rsFileName) {
108  return InternalFileNameConvert(rsFileName, true);
109}
110
111std::string RSSlangReflectUtils::JavaBitcodeClassNameFromRSFileName(
112    const char *rsFileName) {
113  std::string tmp(InternalFileNameConvert(rsFileName, false));
114  return tmp.append("BitCode");
115}
116
117static bool GenerateAccessorMethod(
118    const RSSlangReflectUtils::BitCodeAccessorContext &context,
119    int bitwidth, GeneratedFile &out) {
120  // the prototype of the accessor method
121  out.indent() << "// return byte array representation of the " << bitwidth
122               << "-bit bitcode.\n";
123  out.indent() << "public static byte[] getBitCode" << bitwidth << "()";
124  out.startBlock();
125  out.indent() << "return getBitCode" << bitwidth << "Internal();\n";
126  out.endBlock(true);
127  return true;
128}
129
130// Java method size must not exceed 64k,
131// so we have to split the bitcode into multiple segments.
132static bool GenerateSegmentMethod(const char *buff, int blen, int bitwidth,
133                                  int seg_num, GeneratedFile &out) {
134  out.indent() << "private static byte[] getSegment" << bitwidth << "_"
135               << seg_num << "()";
136  out.startBlock();
137  out.indent() << "byte[] data = {";
138  out.increaseIndent();
139
140  const int kEntriesPerLine = 16;
141  int position = kEntriesPerLine;  // We start with a new line and indent.
142  for (int written = 0; written < blen; written++) {
143    if (++position >= kEntriesPerLine) {
144      out << "\n";
145      out.indent();
146      position = 0;
147    } else {
148      out << " ";
149    }
150    out << std::setw(4) << static_cast<int>(buff[written]) << ",";
151  }
152  out << "\n";
153
154  out.decreaseIndent();
155  out.indent() << "};\n";
156  out.indent() << "return data;\n";
157  out.endBlock();
158
159  return true;
160}
161
162static bool GenerateJavaCodeAccessorMethodForBitwidth(
163    const RSSlangReflectUtils::BitCodeAccessorContext &context,
164    int bitwidth, GeneratedFile &out) {
165
166  std::string filename(context.bc32FileName);
167  if (bitwidth == 64) {
168    filename = context.bc64FileName;
169  }
170
171  FILE *pfin = fopen(filename.c_str(), "rb");
172  if (pfin == NULL) {
173    fprintf(stderr, "Error: could not read file %s\n", filename.c_str());
174    return false;
175  }
176
177  // start the accessor method
178  GenerateAccessorMethod(context, bitwidth, out);
179
180  // output the data
181  // make sure the generated function for a segment won't break the Javac
182  // size limitation (64K).
183  static const int SEG_SIZE = 0x2000;
184  char *buff = new char[SEG_SIZE];
185  int read_length;
186  int seg_num = 0;
187  int total_length = 0;
188  while ((read_length = fread(buff, 1, SEG_SIZE, pfin)) > 0) {
189    GenerateSegmentMethod(buff, read_length, bitwidth, seg_num, out);
190    ++seg_num;
191    total_length += read_length;
192  }
193  delete[] buff;
194  fclose(pfin);
195
196  // output the internal accessor method
197  out.indent() << "private static int bitCode" << bitwidth << "Length = "
198               << total_length << ";\n\n";
199  out.indent() << "private static byte[] getBitCode" << bitwidth
200               << "Internal()";
201  out.startBlock();
202  out.indent() << "byte[] bc = new byte[bitCode" << bitwidth << "Length];\n";
203  out.indent() << "int offset = 0;\n";
204  out.indent() << "byte[] seg;\n";
205  for (int i = 0; i < seg_num; ++i) {
206    out.indent() << "seg = getSegment" << bitwidth << "_" << i << "();\n";
207    out.indent() << "System.arraycopy(seg, 0, bc, offset, seg.length);\n";
208    out.indent() << "offset += seg.length;\n";
209  }
210  out.indent() << "return bc;\n";
211  out.endBlock();
212
213  return true;
214}
215
216static bool GenerateJavaCodeAccessorMethod(
217    const RSSlangReflectUtils::BitCodeAccessorContext &context,
218    GeneratedFile &out) {
219  if (!GenerateJavaCodeAccessorMethodForBitwidth(context, 32, out)) {
220    slangAssert(false && "Couldn't generate 32-bit embedded bitcode!");
221    return false;
222  }
223  if (!GenerateJavaCodeAccessorMethodForBitwidth(context, 64, out)) {
224    slangAssert(false && "Couldn't generate 64-bit embedded bitcode!");
225    return false;
226  }
227
228  return true;
229}
230
231static bool GenerateAccessorClass(
232    const RSSlangReflectUtils::BitCodeAccessorContext &context,
233    const char *clazz_name, GeneratedFile &out) {
234  // begin the class.
235  out << "/**\n";
236  out << " * @hide\n";
237  out << " */\n";
238  out << "public class " << clazz_name;
239  out.startBlock();
240
241  bool ret = true;
242  switch (context.bcStorage) {
243  case BCST_APK_RESOURCE:
244    slangAssert(false &&
245                "Invalid generation of bitcode accessor with resource");
246    break;
247  case BCST_JAVA_CODE:
248    ret = GenerateJavaCodeAccessorMethod(context, out);
249    break;
250  default:
251    ret = false;
252  }
253
254  // end the class.
255  out.endBlock();
256
257  return ret;
258}
259
260bool RSSlangReflectUtils::GenerateJavaBitCodeAccessor(
261    const BitCodeAccessorContext &context) {
262  string output_path =
263      ComputePackagedPath(context.reflectPath, context.packageName);
264  if (!SlangUtils::CreateDirectoryWithParents(llvm::StringRef(output_path),
265                                              NULL)) {
266    fprintf(stderr, "Error: could not create dir %s\n", output_path.c_str());
267    return false;
268  }
269
270  string clazz_name(JavaBitcodeClassNameFromRSFileName(context.rsFileName));
271  string filename(clazz_name);
272  filename += ".java";
273
274  GeneratedFile out;
275  if (!out.startFile(output_path, filename, context.rsFileName,
276                     context.licenseNote, true, context.verbose)) {
277    return false;
278  }
279
280  out << "package " << context.packageName << ";\n\n";
281
282  bool ret = GenerateAccessorClass(context, clazz_name.c_str(), out);
283
284  out.closeFile();
285  return ret;
286}
287
288std::string JoinPath(const std::string &path1, const std::string &path2) {
289  if (path1.empty()) {
290    return path2;
291  }
292  if (path2.empty()) {
293    return path1;
294  }
295  std::string fullPath = path1;
296  if (fullPath[fullPath.length() - 1] != OS_PATH_SEPARATOR) {
297    fullPath += OS_PATH_SEPARATOR;
298  }
299  if (path2[0] == OS_PATH_SEPARATOR) {
300    fullPath += path2.substr(1, string::npos);
301  } else {
302    fullPath += path2;
303  }
304  return fullPath;
305}
306
307// Replace all instances of "\" with "\\" in a single string to prevent
308// formatting errors.  In Java, this can happen even within comments, as
309// Java processes \u before the comments are stripped.  E.g. if the generated
310// file in Windows contains the note:
311//     /* Do not modify!  Generated from \Users\MyName\MyDir\foo.cs */
312// Java will think that \U tells of a Unicode character.
313static void SanitizeString(std::string *s) {
314  size_t p = 0;
315  while ((p = s->find('\\', p)) != std::string::npos) {
316    s->replace(p, 1, "\\\\");
317    p += 2;
318  }
319}
320
321static const char *const gApacheLicenseNote =
322    "/*\n"
323    " * Copyright (C) 2011-2014 The Android Open Source Project\n"
324    " *\n"
325    " * Licensed under the Apache License, Version 2.0 (the \"License\");\n"
326    " * you may not use this file except in compliance with the License.\n"
327    " * You may obtain a copy of the License at\n"
328    " *\n"
329    " *      http://www.apache.org/licenses/LICENSE-2.0\n"
330    " *\n"
331    " * Unless required by applicable law or agreed to in writing, software\n"
332    " * distributed under the License is distributed on an \"AS IS\" BASIS,\n"
333    " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or "
334    "implied.\n"
335    " * See the License for the specific language governing permissions and\n"
336    " * limitations under the License.\n"
337    " */\n"
338    "\n";
339
340bool GeneratedFile::startFile(const string &outDirectory,
341                              const string &outFileName,
342                              const string &sourceFileName,
343                              const string *optionalLicense, bool isJava,
344                              bool verbose) {
345  if (verbose) {
346    printf("Generating %s\n", outFileName.c_str());
347  }
348
349  // Create the parent directories.
350  if (!outDirectory.empty()) {
351    std::string errorMsg;
352    if (!SlangUtils::CreateDirectoryWithParents(outDirectory, &errorMsg)) {
353      fprintf(stderr, "Error: %s\n", errorMsg.c_str());
354      return false;
355    }
356  }
357
358  std::string FilePath = JoinPath(outDirectory, outFileName);
359
360  // Open the file.
361  open(FilePath.c_str());
362  if (!good()) {
363    fprintf(stderr, "Error: could not write file %s\n", outFileName.c_str());
364    return false;
365  }
366
367  // Write the license.
368  if (optionalLicense != NULL) {
369    *this << *optionalLicense;
370  } else {
371    *this << gApacheLicenseNote;
372  }
373
374  // Write a notice that this is a generated file.
375  std::string source(sourceFileName);
376  if (isJava) {
377    SanitizeString(&source);
378  }
379
380  *this << "/*\n"
381        << " * This file is auto-generated. DO NOT MODIFY!\n"
382        << " * The source Renderscript file: " << source << "\n"
383        << " */\n\n";
384
385  return true;
386}
387
388void GeneratedFile::closeFile() { close(); }
389
390void GeneratedFile::increaseIndent() { mIndent.append("    "); }
391
392void GeneratedFile::decreaseIndent() {
393  slangAssert(!mIndent.empty() && "No indent");
394  mIndent.erase(0, 4);
395}
396
397void GeneratedFile::comment(const std::string &s) {
398  indent() << "/* ";
399  // +3 for the " * " starting each line.
400  std::size_t indentLength = mIndent.length() + 3;
401  std::size_t lengthOfCommentOnLine = 0;
402  const std::size_t maxPerLine = 80;
403  for (std::size_t start = 0, length = s.length(), nextStart = 0;
404       start < length; start = nextStart) {
405    std::size_t p = s.find_first_of(" \n", start);
406    std::size_t toCopy = 1;
407    bool forceBreak = false;
408    if (p == std::string::npos) {
409      toCopy = length - start;
410      nextStart = length;
411    } else {
412      toCopy = p - start;
413      nextStart = p + 1;
414      forceBreak = s[p] == '\n';
415    }
416    if (lengthOfCommentOnLine > 0) {
417      if (indentLength + lengthOfCommentOnLine + toCopy >= maxPerLine) {
418        *this << "\n";
419        indent() << " * ";
420        lengthOfCommentOnLine = 0;
421      } else {
422        *this << " ";
423      }
424    }
425
426    *this << s.substr(start, toCopy);
427    if (forceBreak) {
428      lengthOfCommentOnLine = maxPerLine;
429    } else {
430      lengthOfCommentOnLine += toCopy;
431    }
432  }
433  *this << "\n";
434  indent() << " */\n";
435}
436
437} // namespace slang
438