private static void unquotePropertyValues( Map<String, JSONValue> componentProperties, String componentType) { // From the component database, get the map of property names and types for the component type. Map<String, String> propertyTypesByName = COMPONENT_DATABASE.getPropertyTypesByName(componentType); // Iterate through the component properties. for (String propertyName : componentProperties.keySet()) { // Get the property type. String propertyType = propertyTypesByName.get(propertyName); // In theory the check for propertyType == null shouldn't be necessary // but I have sometimes had a problem with it being null when running // with GWT debugging. Maybe it changes the timing somehow. Anyway, // this test for null should not hurt anything. -Sharon if (propertyType == null) { OdeLog.wlog( "Couldn't find propertyType for property " + propertyName + " in component type " + componentType); continue; } // If the property type is one that was previously quoted, unquote the value. if (propertyType.equals("asset") || propertyType.equals("BluetoothClient") || propertyType.equals("component") || propertyType.equals("lego_nxt_sensor_port") || propertyType.equals("string")) { // Unquote the property value. JSONValue propertyValue = componentProperties.get(propertyName); String propertyValueString = propertyValue.asString().getString(); propertyValueString = StringUtils.unquote(propertyValueString); componentProperties.put(propertyName, new ClientJsonString(propertyValueString)); } } }
/* * Format a description string as a json string. Note that the returned value * include surrounding double quotes. */ private static String formatDescription(String description) { return StringUtils.toJson(description); }
@VisibleForTesting static String processCompilerOutput(String output, String srcPath) { // First, remove references to the temp source directory from the messages. String messages = output.replace(srcPath, ""); // Then, format warnings and errors nicely. try { // Split the messages by \n and process each line separately. String[] lines = messages.split("\n"); Pattern pattern = Pattern.compile("(.*?):(\\d+):\\d+: (error|warning)?:? ?(.*?)"); StringBuilder sb = new StringBuilder(); boolean skippedErrorOrWarning = false; for (String line : lines) { Matcher matcher = pattern.matcher(line); if (matcher.matches()) { // Determine whether it is an error or warning. String kind; String spanClass; // Scanner messages do not contain either 'error' or 'warning'. // I treat them as errors because they prevent compilation. if ("warning".equals(matcher.group(3))) { kind = "WARNING"; spanClass = "compiler-WarningMarker"; } else { kind = "ERROR"; spanClass = "compiler-ErrorMarker"; } // Extract the filename, lineNumber, and message. String filename = matcher.group(1); String lineNumber = matcher.group(2); String text = matcher.group(4); // If the error/warning is in a yail file, generate a div and append it to the // StringBuilder. if (filename.endsWith(YoungAndroidConstants.YAIL_EXTENSION)) { skippedErrorOrWarning = false; sb.append( "<div><span class='" + spanClass + "'>" + kind + "</span>: " + StringUtils.escape(filename) + " line " + lineNumber + ": " + StringUtils.escape(text) + "</div>"); } else { // The error/warning is in runtime.scm. Don't append it to the StringBuilder. skippedErrorOrWarning = true; } // Log the message, first truncating it if it is too long. if (text.length() > MAX_COMPILER_MESSAGE_LENGTH) { text = text.substring(0, MAX_COMPILER_MESSAGE_LENGTH); } } else { // The line isn't an error or a warning. This is expected. // If the line begins with two spaces, it is a continuation of the previous // error/warning. if (line.startsWith(" ")) { // If we didn't skip the most recent error/warning, append the line to our // StringBuilder. if (!skippedErrorOrWarning) { sb.append(StringUtils.escape(line)).append("<br>"); } } else { skippedErrorOrWarning = false; // We just append the line to our StringBuilder. sb.append(StringUtils.escape(line)).append("<br>"); } } } messages = sb.toString(); } catch (Exception e) { // Report exceptions that happen during the processing of output, but don't make the // whole build fail. e.printStackTrace(); // We were not able to process the output, so we just escape for HTML. messages = StringUtils.escape(messages); } return messages; }
@Override public UserProject importProject( String userId, String projectName, InputStream uploadedFileStream, @Nullable String projectHistory) throws FileImporterException, IOException { // The projectName parameter has already been validated, including checking for an // existing project with the same name. (See TextValidators.checkNewProjectName). // Begin creating the project. Project project = new Project(projectName); project.setProjectType(YoungAndroidProjectNode.YOUNG_ANDROID_PROJECT_TYPE); // As we process the ZipEntry for each file, we'll adjust the directory structure so that it is // appropriate for this user. // Here we get the information (such as the qualified form name) that we'll need to do that. String qualifiedFormName = StringUtils.getQualifiedFormName(storageIo.getUser(userId).getUserEmail(), projectName); String srcDirectory = YoungAndroidProjectService.getSourceDirectory(qualifiedFormName); ZipInputStream zin = new ZipInputStream(uploadedFileStream); boolean isProjectArchive = false; // have we found at least one project properties file? try { // Extract files while (true) { ZipEntry entry; try { entry = zin.getNextEntry(); if (entry == null) { break; } } catch (ZipException e) { // The uploaded file is not a valid zip file throw new FileImporterException(UploadResponse.Status.NOT_PROJECT_ARCHIVE); } if (!entry.isDirectory()) { String fileName = entry.getName(); if (fileName.equals(YoungAndroidProjectService.PROJECT_PROPERTIES_FILE_NAME)) { // The content for the youngandroidproject/project.properties file must be regenerated // so that it contains the correct entries for "main" and "name", which are dependent on // the projectName and qualifiedFormName. String content = YoungAndroidProjectService.getProjectPropertiesFileContents( projectName, qualifiedFormName, null, null, null, null); project.addTextFile(new TextFile(fileName, content)); isProjectArchive = true; } else if (fileName.equals(FileExporter.REMIX_INFORMATION_FILE_PATH) || fileName.equals(StorageUtil.ANDROID_KEYSTORE_FILENAME)) { // If the remix information file is present, we ignore it. In the past, a remix // information file was saved in the zip when project source was downloaded and // retrieved from the zip when it was uploaded. However, we no longer do that because // we don't have a way to verify that the contents of the remix information file is // accurate during the upload. // If a keystore file is present we ignore that too for now, since // we don't have per-project keystores. The only way to get such a // source zip at the moment is using the admin functionality to // download another user's project source. continue; } else { if (fileName.startsWith(YoungAndroidProjectService.SRC_FOLDER)) { // For files within the src folder, we need to update the directory that we put files // in. Adjust the fileName so that it corresponds to this project's package. fileName = srcDirectory + '/' + StorageUtil.basename(fileName); } // Get the file content from the ZipEntry. ByteArrayOutputStream contentStream = new ByteArrayOutputStream(); ByteStreams.copy(zin, contentStream); project.addRawFile(new RawFile(fileName, contentStream.toByteArray())); } } } } finally { zin.close(); } if (!isProjectArchive) { // The uploaded file seems to be a valid zip file, but it doesn't contain the project // properties file. throw new FileImporterException(UploadResponse.Status.NOT_PROJECT_ARCHIVE); } // Set project history if provided if (projectHistory != null) { project.setProjectHistory(projectHistory); } String settings = YoungAndroidProjectService.getProjectSettings(null, null, null, null); long projectId = storageIo.createProject(userId, project, settings); return storageIo.getUserProject(userId, projectId); }