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