6 package com.google.appinventor.components.scripts;
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;
17 import org.json.JSONException;
18 import org.json.JSONArray;
19 import org.json.JSONObject;
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;
36 private static Map<String, List<ExternalComponentInfo>> externalComponentsByPackage =
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>());
67 externalComponentsByPackage.get(info.packageName).add(info);
71 generateAllExtensions();
77 private static class ExternalComponentInfo {
79 private String packageName;
80 private JSONObject descriptor;
81 private JSONObject buildInfo;
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(
'.'));
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);
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);
111 generateExternalComponentBuildFiles(name, entry.getValue());
112 generateExternalComponentOtherFiles(name);
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) {
126 sb.append(info.descriptor.toString(1));
129 String components = sb.toString();
130 String extensionDirPath = externalComponentsDirPath + File.separator + packageName;
131 ensureDirectory(extensionDirPath,
"Unable to create extension directory");
132 FileWriter jsonWriter =
null;
134 jsonWriter =
new FileWriter(extensionDirPath + File.separator +
"components.json");
135 jsonWriter.write(components);
136 }
catch(IOException e) {
139 if (jsonWriter !=
null) {
145 jsonWriter =
new FileWriter(extensionDirPath + File.separator +
"component.json");
146 jsonWriter.write(infos.get(0).descriptor.toString(1));
147 }
catch(IOException e) {
150 if (jsonWriter !=
null) {
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);
163 JSONArray buildInfos =
new JSONArray();
164 for (ExternalComponentInfo info : extensions) {
165 JSONObject componentBuildInfo = info.buildInfo;
167 JSONArray librariesNeeded = componentBuildInfo.getJSONArray(
"libraries");
168 for (
int j = 0; j < librariesNeeded.length(); ++j) {
170 String library = librariesNeeded.getString(j);
171 copyFile(buildServerClassDirPath + File.separator + library,
172 extensionTempDirPath + File.separator + library);
175 componentBuildInfo.put(
"libraries",
new JSONArray());
176 }
catch(JSONException e) {
178 throw new IllegalStateException(
"Unexpected JSON exception parsing simple_components.json",
181 buildInfos.put(componentBuildInfo);
185 ensureDirectory(extensionFileDirPath,
"Unable to create path for component_build_info.json");
186 FileWriter extensionBuildInfoFile =
null;
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");
192 }
catch (IOException e) {
195 if (extensionBuildInfoFile !=
null) {
196 extensionBuildInfoFile.flush();
197 extensionBuildInfoFile.close();
202 extensionBuildInfoFile =
new FileWriter(extensionFileDirPath + File.separator +
"component_build_info.json");
203 extensionBuildInfoFile.write(buildInfos.get(0).toString());
204 }
catch (IOException|JSONException e) {
207 if (extensionBuildInfoFile !=
null) {
208 extensionBuildInfoFile.close();
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:")) {
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());
229 System.out.println(
"Extensions : Skipping missing icon " + icon);
233 private static void copyAssets(String packageName, JSONObject componentDescriptor)
234 throws IOException, JSONException {
235 JSONArray assets = componentDescriptor.optJSONArray(
"assets");
236 if (assets ==
null) {
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()) {
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.");
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.");
266 private static void generateExternalComponentOtherFiles(String packageName)
throws IOException {
267 String extensionDirPath = externalComponentsDirPath + File.separator + packageName;
270 StringBuilder extensionPropertiesString =
new StringBuilder();
271 extensionPropertiesString.append(
"type=external\n");
272 FileWriter extensionPropertiesFile =
new FileWriter(extensionDirPath + File.separator +
"extension.properties");
274 extensionPropertiesFile.write(extensionPropertiesString.toString());
275 System.out.println(
"Extensions : Successfully created " + packageName +
" extension properties file");
276 }
catch (IOException e) {
279 extensionPropertiesFile.flush();
280 extensionPropertiesFile.close();
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);
302 private static Boolean copyFile(String srcPath, String dstPath) {
304 FileInputStream in =
new FileInputStream(srcPath);
305 FileOutputStream out =
new FileOutputStream(dstPath);
306 byte[] buf =
new byte[1024];
308 while ((len = in.read(buf)) > 0 ) {
309 out.write(buf, 0, len);
314 catch (IOException e) {
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();
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());
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);
360 private static boolean isRelatedExternalClass(
final String testClassAbsolutePath,
final String extensionPackage ) {
361 if (!testClassAbsolutePath.endsWith(
".class")) {
364 String componentPackagePath = extensionPackage.replace(
".", File.separator);
366 String testClassPath = getClassPackage(testClassAbsolutePath);
367 testClassPath = testClassPath.replace(
".", File.separator);
368 return testClassPath.startsWith(componentPackagePath);
371 private static String getClassPackage(String classAbsolutePath) {
372 String parentPath = androidRuntimeClassDirPath;
373 if (!parentPath.endsWith(
"/")) {
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;
383 private static boolean deleteRecursively(File dirOrFile) {
384 if (dirOrFile.isFile()) {
385 return dirOrFile.delete();
387 boolean result =
true;
388 File[] children = dirOrFile.listFiles();
389 if (children !=
null) {
390 for (File child : children) {
391 result = result && deleteRecursively(child);
394 return result && dirOrFile.delete();
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);
403 if (!file.mkdirs()) {
404 throw new IOException(errorMessage);
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);