AI2 Component  (Version nb184)
ExternalComponentGenerator.java
Go to the documentation of this file.
1 // -*- mode: java; c-basic-offset: 2; -*-
2 // Copyright 2015-2018 MIT, All rights reserved
3 // Released under the Apache License, Version 2.0
4 // http://www.apache.org/licenses/LICENSE-2.0
5 
6 package com.google.appinventor.components.scripts;
7 
8 import java.io.FileInputStream;
9 import java.io.FileOutputStream;
10 import java.io.FileWriter;
11 import java.io.IOException;
12 import java.nio.charset.Charset;
13 import java.nio.file.Files;
14 import java.nio.file.Paths;
15 import java.util.*;
16 import java.io.File;
17 import org.json.JSONException;
18 import org.json.JSONArray;
19 import org.json.JSONObject;
20 
28 
29 
30  private static String externalComponentsDirPath;
31  private static String androidRuntimeClassDirPath;
32  private static String buildServerClassDirPath;
33  private static String externalComponentsTempDirPath;
34  private static boolean useFQCN = false;
35 
36  private static Map<String, List<ExternalComponentInfo>> externalComponentsByPackage =
37  new TreeMap<>();
38 
49  public static void main(String[] args) throws IOException, JSONException {
50  String simple_component_json = readFile(args[0], Charset.defaultCharset());
51  String simple_component_build_info_json = readFile(args[1], Charset.defaultCharset());
52  externalComponentsDirPath = args[2];
53  androidRuntimeClassDirPath = args[3];
54  buildServerClassDirPath = args[4];
55  externalComponentsTempDirPath = args[5];
56  useFQCN = Boolean.parseBoolean(args[6]);
57  JSONArray simpleComponentDescriptors = new JSONArray(simple_component_json);
58  JSONArray simpleComponentBuildInfos = new JSONArray(simple_component_build_info_json);
59  Map<String, JSONObject> buildInfos = buildInfoAsMap(simpleComponentBuildInfos);
60  for (int i = 0; i < simpleComponentDescriptors.length(); i++) {
61  JSONObject componentDescriptor = (JSONObject) simpleComponentDescriptors.get(i);
62  if(componentDescriptor.get("external").toString().equals("true")) {
63  ExternalComponentInfo info = new ExternalComponentInfo(componentDescriptor, buildInfos.get(componentDescriptor.getString("type")));
64  if (!externalComponentsByPackage.containsKey(info.packageName)) {
65  externalComponentsByPackage.put(info.packageName, new ArrayList<ExternalComponentInfo>());
66  }
67  externalComponentsByPackage.get(info.packageName).add(info);
68  }
69  }
70 
71  generateAllExtensions();
72  }
73 
77  private static class ExternalComponentInfo {
78  private String type;
79  private String packageName;
80  private JSONObject descriptor;
81  private JSONObject buildInfo;
82 
83  ExternalComponentInfo(JSONObject descriptor, JSONObject buildInfo) {
84  this.descriptor = descriptor;
85  this.buildInfo = buildInfo;
86  this.type = descriptor.optString("type");
87  this.packageName = type.substring(0, type.lastIndexOf('.'));
88  }
89  }
90 
91  private static Map<String, JSONObject> buildInfoAsMap(JSONArray buildInfos) throws JSONException {
92  Map<String, JSONObject> result = new HashMap<>();
93  for (int i = 0; i < buildInfos.length(); i++) {
94  JSONObject componentBuildInfo = buildInfos.getJSONObject(i);
95  result.put(componentBuildInfo.getString("type"), componentBuildInfo);
96  }
97  return result;
98  }
99 
100  private static void generateAllExtensions() throws IOException, JSONException {
101  System.out.println("\nExtensions : Generating extensions");
102  for (Map.Entry<String, List<ExternalComponentInfo>> entry : externalComponentsByPackage.entrySet()) {
103  String name = useFQCN && entry.getValue().size() == 1 ? entry.getValue().get(0).type : entry.getKey();
104  String logComponentType = "[" + name + "]";
105  System.out.println("\nExtensions : Generating files " + logComponentType);
106  generateExternalComponentDescriptors(name, entry.getValue());
107  for (ExternalComponentInfo info : entry.getValue()) {
108  copyIcon(name, info.descriptor);
109  copyAssets(name, info.descriptor);
110  }
111  generateExternalComponentBuildFiles(name, entry.getValue());
112  generateExternalComponentOtherFiles(name);
113  }
114  }
115 
116  private static void generateExternalComponentDescriptors(String packageName, List<ExternalComponentInfo> infos)
117  throws IOException, JSONException {
118  StringBuilder sb = new StringBuilder("[");
119  boolean first = true;
120  for (ExternalComponentInfo info : infos) {
121  if (!first) {
122  sb.append(',');
123  } else {
124  first = false;
125  }
126  sb.append(info.descriptor.toString(1));
127  }
128  sb.append(']');
129  String components = sb.toString();
130  String extensionDirPath = externalComponentsDirPath + File.separator + packageName;
131  ensureDirectory(extensionDirPath, "Unable to create extension directory");
132  FileWriter jsonWriter = null;
133  try {
134  jsonWriter = new FileWriter(extensionDirPath + File.separator + "components.json");
135  jsonWriter.write(components);
136  } catch(IOException e) {
137  e.printStackTrace();
138  } finally {
139  if (jsonWriter != null) {
140  jsonWriter.close();
141  }
142  }
143  // Write legacy format to transition developers
144  try {
145  jsonWriter = new FileWriter(extensionDirPath + File.separator + "component.json");
146  jsonWriter.write(infos.get(0).descriptor.toString(1));
147  } catch(IOException e) {
148  e.printStackTrace();
149  } finally {
150  if (jsonWriter != null) {
151  jsonWriter.close();
152  }
153  }
154  }
155 
156 
157  private static void generateExternalComponentBuildFiles(String packageName, List<ExternalComponentInfo> extensions) throws IOException {
158  String extensionDirPath = externalComponentsDirPath + File.separator + packageName;
159  String extensionTempDirPath = externalComponentsTempDirPath + File.separator + packageName;
160  String extensionFileDirPath = extensionDirPath + File.separator + "files";
161  copyRelatedExternalClasses(androidRuntimeClassDirPath, packageName, extensionTempDirPath);
162 
163  JSONArray buildInfos = new JSONArray();
164  for (ExternalComponentInfo info : extensions) {
165  JSONObject componentBuildInfo = info.buildInfo;
166  try {
167  JSONArray librariesNeeded = componentBuildInfo.getJSONArray("libraries");
168  for (int j = 0; j < librariesNeeded.length(); ++j) {
169  // Copy Library files for Unjar and Jaring
170  String library = librariesNeeded.getString(j);
171  copyFile(buildServerClassDirPath + File.separator + library,
172  extensionTempDirPath + File.separator + library);
173  }
174  //empty the libraries meta-data to avoid redundancy
175  componentBuildInfo.put("libraries", new JSONArray());
176  } catch(JSONException e) {
177  // bad
178  throw new IllegalStateException("Unexpected JSON exception parsing simple_components.json",
179  e);
180  }
181  buildInfos.put(componentBuildInfo);
182  }
183 
184  // Create component_build_info.json
185  ensureDirectory(extensionFileDirPath, "Unable to create path for component_build_info.json");
186  FileWriter extensionBuildInfoFile = null;
187  try {
188  extensionBuildInfoFile = new FileWriter(extensionFileDirPath + File.separator + "component_build_infos.json");
189  extensionBuildInfoFile.write(buildInfos.toString());
190  System.out.println("Extensions : Successfully created " + packageName + " build info file");
191 
192  } catch (IOException e) {
193  e.printStackTrace();
194  } finally {
195  if (extensionBuildInfoFile != null) {
196  extensionBuildInfoFile.flush();
197  extensionBuildInfoFile.close();
198  }
199  }
200  // Write out legacy component_build_info.json to transition developers
201  try {
202  extensionBuildInfoFile = new FileWriter(extensionFileDirPath + File.separator + "component_build_info.json");
203  extensionBuildInfoFile.write(buildInfos.get(0).toString());
204  } catch (IOException|JSONException e) {
205  e.printStackTrace();
206  } finally {
207  if (extensionBuildInfoFile != null) {
208  extensionBuildInfoFile.close();
209  }
210  }
211  }
212 
213  private static void copyIcon(String packageName, JSONObject componentDescriptor)
214  throws IOException, JSONException {
215  String icon = componentDescriptor.getString("iconName");
216  if (icon.equals("") || icon.startsWith("http:") || icon.startsWith("https:")) {
217  // Icon will be loaded from the web
218  return;
219  }
220  String packagePath = packageName.replace('.', File.separatorChar);
221  File sourceDir = new File(externalComponentsDirPath + File.separator + ".." + File.separator + ".." + File.separator + "src" + File.separator + packagePath);
222  File image = new File(sourceDir, icon);
223  if (image.exists()) {
224  File dstIcon = new File(externalComponentsDirPath + File.separator + packageName + File.separator + icon);
225  ensureDirectory(dstIcon.getParent(), "Unable to create directory " + dstIcon.getParent());
226  System.out.println("Extensions : " + "Copying file " + image.getAbsolutePath());
227  copyFile(image.getAbsolutePath(), dstIcon.getAbsolutePath());
228  } else {
229  System.out.println("Extensions : Skipping missing icon " + icon);
230  }
231  }
232 
233  private static void copyAssets(String packageName, JSONObject componentDescriptor)
234  throws IOException, JSONException {
235  JSONArray assets = componentDescriptor.optJSONArray("assets");
236  if (assets == null) {
237  return;
238  }
239 
240  // Get asset source directory
241  String packagePath = packageName.replace('.', File.separatorChar);
242  File sourceDir = new File(externalComponentsDirPath + File.separator + ".." + File.separator + ".." + File.separator + "src" + File.separator + packagePath);
243  File assetSrcDir = new File(sourceDir, "assets");
244  if (!assetSrcDir.exists() || !assetSrcDir.isDirectory()) {
245  return;
246  }
247 
248  // Get asset dest directory
249  File destDir = new File(externalComponentsDirPath + File.separator + packageName + File.separator);
250  File assetDestDir = new File(destDir, "assets");
251  ensureFreshDirectory(assetDestDir.getPath(),
252  "Unable to delete the assets directory for the extension.");
253 
254  // Copy assets
255  for (int i = 0; i < assets.length(); i++) {
256  String asset = assets.getString(i);
257  if (!asset.isEmpty()) {
258  if (!copyFile(assetSrcDir.getAbsolutePath() + File.separator + asset,
259  assetDestDir.getAbsolutePath() + File.separator + asset)) {
260  throw new IllegalStateException("Unable to copy asset to destination.");
261  }
262  }
263  }
264  }
265 
266  private static void generateExternalComponentOtherFiles(String packageName) throws IOException {
267  String extensionDirPath = externalComponentsDirPath + File.separator + packageName;
268 
269  // Create extension.properties
270  StringBuilder extensionPropertiesString = new StringBuilder();
271  extensionPropertiesString.append("type=external\n");
272  FileWriter extensionPropertiesFile = new FileWriter(extensionDirPath + File.separator + "extension.properties");
273  try {
274  extensionPropertiesFile.write(extensionPropertiesString.toString());
275  System.out.println("Extensions : Successfully created " + packageName + " extension properties file");
276  } catch (IOException e) {
277  e.printStackTrace();
278  } finally {
279  extensionPropertiesFile.flush();
280  extensionPropertiesFile.close();
281  }
282  }
283 
290  private static String readFile(String path, Charset encoding) throws IOException{
291  byte[] encoded = Files.readAllBytes(Paths.get(path));
292  return new String(encoded, encoding);
293  }
294 
302  private static Boolean copyFile(String srcPath, String dstPath) {
303  try {
304  FileInputStream in = new FileInputStream(srcPath);
305  FileOutputStream out = new FileOutputStream(dstPath);
306  byte[] buf = new byte[1024];
307  int len;
308  while ((len = in.read(buf)) > 0 ) {
309  out.write(buf, 0, len);
310  }
311  in.close();
312  out.close();
313  }
314  catch (IOException e) {
315  e.printStackTrace();
316  return false;
317  }
318  return true;
319  }
320 
328  private static void copyRelatedExternalClasses(final String srcPath, String extensionPackage,
329  final String destPath) throws IOException {
330  File srcFolder = new File(srcPath);
331  File[] files = srcFolder.listFiles();
332  if (files == null) {
333  return;
334  }
335  for (File fileEntry : files){
336  if (fileEntry.isFile()) {
337  if (isRelatedExternalClass(fileEntry.getAbsolutePath(), extensionPackage)) {
338  System.out.println("Extensions : " + "Copying file " +
339  getClassPackage(fileEntry.getAbsolutePath()).replace(".", File.separator)
340  + File.separator + fileEntry.getName());
341  copyFile(fileEntry.getAbsolutePath(), destPath + File.separator + fileEntry.getName());
342  }
343  } else if (fileEntry.isDirectory()) {
344  String newDestPath=destPath + fileEntry.getAbsolutePath().substring(srcFolder.getAbsolutePath().length());
345  ensureDirectory(newDestPath, "Unable to create temporary path for extension build");
346  copyRelatedExternalClasses(fileEntry.getAbsolutePath(), extensionPackage, newDestPath);
347  }
348  }
349  }
350 
360  private static boolean isRelatedExternalClass(final String testClassAbsolutePath, final String extensionPackage ) {
361  if (!testClassAbsolutePath.endsWith(".class")) { // Ignore things that aren't class files...
362  return false;
363  }
364  String componentPackagePath = extensionPackage.replace(".", File.separator);
365 
366  String testClassPath = getClassPackage(testClassAbsolutePath);
367  testClassPath = testClassPath.replace(".", File.separator);
368  return testClassPath.startsWith(componentPackagePath);
369  }
370 
371  private static String getClassPackage(String classAbsolutePath) {
372  String parentPath = androidRuntimeClassDirPath;
373  if (!parentPath.endsWith("/")) {
374  parentPath += "/";
375  }
376  parentPath = parentPath.replace("/", File.separator);
377  String componentPackage = classAbsolutePath.substring(classAbsolutePath.indexOf(parentPath) + parentPath.length());
378  componentPackage = componentPackage.substring(0, componentPackage.lastIndexOf(File.separator));
379  componentPackage = componentPackage.replace(File.separator, ".");
380  return componentPackage;
381  }
382 
383  private static boolean deleteRecursively(File dirOrFile) {
384  if (dirOrFile.isFile()) {
385  return dirOrFile.delete();
386  } else {
387  boolean result = true;
388  File[] children = dirOrFile.listFiles();
389  if (children != null) {
390  for (File child : children) {
391  result = result && deleteRecursively(child);
392  }
393  }
394  return result && dirOrFile.delete();
395  }
396  }
397 
398  private static void ensureFreshDirectory(String path, String errorMessage) throws IOException {
399  File file = new File(path);
400  if (file.exists() && !deleteRecursively(file)) {
401  throw new IOException(errorMessage);
402  }
403  if (!file.mkdirs()) {
404  throw new IOException(errorMessage);
405  }
406  }
407 
408  private static void ensureDirectory(String path, String errorMessage) throws IOException {
409  File file = new File(path);
410  if (!file.exists() && !file.mkdirs()) {
411  throw new IOException(errorMessage);
412  }
413  }
414 }
com.google.appinventor.components.scripts.ExternalComponentGenerator
Definition: ExternalComponentGenerator.java:27
com.google.appinventor.components.scripts.ExternalComponentGenerator.main
static void main(String[] args)
Definition: ExternalComponentGenerator.java:49