diff --git a/api/src/org/labkey/api/data/Container.java b/api/src/org/labkey/api/data/Container.java index c55c45cd561..b2ca3dd25f2 100644 --- a/api/src/org/labkey/api/data/Container.java +++ b/api/src/org/labkey/api/data/Container.java @@ -61,6 +61,7 @@ import org.labkey.api.util.NetworkDrive; import org.labkey.api.util.PageFlowUtil; import org.labkey.api.util.Path; +import org.labkey.api.util.StringUtilsLabKey; import org.labkey.api.util.logging.LogHelper; import org.labkey.api.view.ActionURL; import org.labkey.api.view.FolderTab; @@ -1594,7 +1595,7 @@ public String getContainerNoun(boolean titleCase) String noun = _containerType.getContainerNoun(this); if (titleCase) { - return noun.substring(0, 1).toUpperCase() + noun.substring(1); + return StringUtilsLabKey.leftSurrogatePairFriendly(noun, 1).toUpperCase() + StringUtilsLabKey.rightSurrogatePairFriendly(noun, noun.length() - 1); } return noun; diff --git a/api/src/org/labkey/api/data/ExcelCellUtils.java b/api/src/org/labkey/api/data/ExcelCellUtils.java index 3e57a26c2df..bfd0d8a98ab 100644 --- a/api/src/org/labkey/api/data/ExcelCellUtils.java +++ b/api/src/org/labkey/api/data/ExcelCellUtils.java @@ -8,6 +8,7 @@ import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.Workbook; import org.labkey.api.util.DateUtil; +import org.labkey.api.util.StringUtilsLabKey; import java.io.File; import java.math.BigDecimal; @@ -188,7 +189,7 @@ public static void writeCell(Cell cell, CellStyle style, int simpleType, String // Check if the string is too long if (s.length() > 32767) { - s = s.substring(0, 32762) + "..."; + s = StringUtilsLabKey.leftSurrogatePairFriendly(s, 32762) + "..."; } // Ensure the row is tall enough to show the full values when there are newlines int newlines = StringUtils.countMatches(s, '\n'); diff --git a/api/src/org/labkey/api/data/ExcelWriter.java b/api/src/org/labkey/api/data/ExcelWriter.java index 34f2a681b70..e2c66bbfe89 100644 --- a/api/src/org/labkey/api/data/ExcelWriter.java +++ b/api/src/org/labkey/api/data/ExcelWriter.java @@ -36,6 +36,7 @@ import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.WorkbookUtil; +import org.labkey.api.util.StringUtilsLabKey; import org.apache.poi.xssf.streaming.SXSSFSheet; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.jetbrains.annotations.NotNull; @@ -375,7 +376,7 @@ public void setSheetName(String sheetName) sheetName = "Sheet"; if (sheetName.length() > 31) - return cleanSheetName(sheetName.substring(0, 31)); + return cleanSheetName(StringUtilsLabKey.leftSurrogatePairFriendly(sheetName, 31)); return WorkbookUtil.createSafeSheetName(sheetName, '_'); } diff --git a/api/src/org/labkey/api/data/SQLFragment.java b/api/src/org/labkey/api/data/SQLFragment.java index acde96b77c6..87de41aff1b 100644 --- a/api/src/org/labkey/api/data/SQLFragment.java +++ b/api/src/org/labkey/api/data/SQLFragment.java @@ -30,6 +30,7 @@ import org.labkey.api.util.GUID; import org.labkey.api.util.JdbcUtil; import org.labkey.api.util.Pair; +import org.labkey.api.util.StringUtilsLabKey; import java.math.BigDecimal; import java.math.BigInteger; @@ -754,7 +755,7 @@ public boolean appendComment(String comment, SqlDialect dialect) sb.append("\n-- "); boolean truncated = comment.length() > 1000; if (truncated) - comment = comment.substring(0,1000); + comment = StringUtilsLabKey.leftSurrogatePairFriendly(comment, 1000); sb.append(comment); if (StringUtils.countMatches(comment, "'")%2==1) sb.append("'"); diff --git a/api/src/org/labkey/api/data/TSVWriter.java b/api/src/org/labkey/api/data/TSVWriter.java index 396cebd9326..a25235e8891 100644 --- a/api/src/org/labkey/api/data/TSVWriter.java +++ b/api/src/org/labkey/api/data/TSVWriter.java @@ -20,6 +20,7 @@ import org.junit.Assert; import org.junit.Test; import org.labkey.api.util.FileUtil; +import org.labkey.api.util.StringUtilsLabKey; import java.io.IOException; import java.io.PrintWriter; @@ -109,7 +110,7 @@ public void setFilenamePrefix(String filenamePrefix) _filenamePrefix = badChars.matcher(filenamePrefix).replaceAll("_"); if (_filenamePrefix.length() > 30) - _filenamePrefix = _filenamePrefix.substring(0, 30); + _filenamePrefix = StringUtilsLabKey.leftSurrogatePairFriendly(_filenamePrefix, 30); } public void setDelimiterCharacter(char delimiter) diff --git a/api/src/org/labkey/api/data/dialect/StatementWrapper.java b/api/src/org/labkey/api/data/dialect/StatementWrapper.java index 09cee28c623..25c8f6ca9d1 100644 --- a/api/src/org/labkey/api/data/dialect/StatementWrapper.java +++ b/api/src/org/labkey/api/data/dialect/StatementWrapper.java @@ -34,6 +34,7 @@ import org.labkey.api.util.DebugInfoDumper; import org.labkey.api.util.ExceptionUtil; import org.labkey.api.util.MemTracker; +import org.labkey.api.util.StringUtilsLabKey; import org.labkey.api.view.ViewServlet; import java.io.InputStream; @@ -2876,7 +2877,7 @@ else if (o instanceof String) else value = String.valueOf(o); if (value.length() > 100) - value = value.substring(0, 100) + ". . ."; + value = StringUtilsLabKey.leftSurrogatePairFriendly(value, 100) + ". . ."; logEntry.append("\n --[").append(i).append("] "); logEntry.append(value); Class c = null==o ? null : o.getClass(); diff --git a/api/src/org/labkey/api/exp/OntologyManager.java b/api/src/org/labkey/api/exp/OntologyManager.java index 26d1b6d43ec..b30147543c8 100644 --- a/api/src/org/labkey/api/exp/OntologyManager.java +++ b/api/src/org/labkey/api/exp/OntologyManager.java @@ -65,6 +65,7 @@ import org.labkey.api.util.HtmlString; import org.labkey.api.util.HtmlStringBuilder; import org.labkey.api.util.Pair; +import org.labkey.api.util.StringUtilsLabKey; import org.labkey.api.util.ResultSetUtil; import org.labkey.api.util.TestContext; import org.labkey.api.view.HttpView; @@ -767,7 +768,7 @@ public static boolean validateProperty(List valida int stringLength = value == null ? 0 : value.toString().length(); if (value != null && prop.isStringType() && stringLength > stringLengthLimit) { - String s = stringLength < 100 ? value.toString() : value.toString().substring(0, 100); + String s = stringLength <= 100 ? value.toString() : StringUtilsLabKey.leftSurrogatePairFriendly(value.toString(), 100); errors.add(new PropertyValidationError("Field '" + prop.getName() + "' is limited to " + stringLengthLimit + " characters, but the value is " + stringLength + " characters. (The value starts with '" + s + "...')", prop.getName())); ret = false; } diff --git a/api/src/org/labkey/api/jsp/LabKeyJspWriter.java b/api/src/org/labkey/api/jsp/LabKeyJspWriter.java index 5f106580dcc..d3a061dde0e 100644 --- a/api/src/org/labkey/api/jsp/LabKeyJspWriter.java +++ b/api/src/org/labkey/api/jsp/LabKeyJspWriter.java @@ -20,6 +20,7 @@ import org.labkey.api.util.DOM; import org.labkey.api.util.HelpTopic; import org.labkey.api.util.SafeToRender; +import org.labkey.api.util.StringUtilsLabKey; import jakarta.servlet.jsp.JspWriter; import java.io.IOException; @@ -44,7 +45,7 @@ private String truncateAndQuote(String s) { return null; } - return "'" + (s.length() < 50 ? s : (s.substring(0, 50) + "...")) + "'"; + return "'" + (s.length() < 50 ? s : (StringUtilsLabKey.leftSurrogatePairFriendly(s, 50) + "...")) + "'"; } @Override diff --git a/api/src/org/labkey/api/util/MemTracker.java b/api/src/org/labkey/api/util/MemTracker.java index 448a537b058..6527bc68f12 100644 --- a/api/src/org/labkey/api/util/MemTracker.java +++ b/api/src/org/labkey/api/util/MemTracker.java @@ -138,7 +138,7 @@ public String getClassName() public String getObjectSummary() { String desc = getObjectDescription(); - return desc.length() > 50 ? desc.substring(0, 50) + "..." : desc; + return desc.length() > 50 ? StringUtilsLabKey.leftSurrogatePairFriendly(desc, 50) + "..." : desc; } public boolean hasShortSummary() diff --git a/core/src/org/labkey/core/admin/AdminController.java b/core/src/org/labkey/core/admin/AdminController.java index 91469d21dbe..c62f95ec042 100644 --- a/core/src/org/labkey/core/admin/AdminController.java +++ b/core/src/org/labkey/core/admin/AdminController.java @@ -9796,7 +9796,7 @@ public void validateCommand(TabActionForm form, Errors errors) } if (name.length() > 50) - name = name.substring(0, 50).trim(); + name = StringUtilsLabKey.leftSurrogatePairFriendly(name, 50).trim(); CaseInsensitiveHashMap pages = new CaseInsensitiveHashMap<>(Portal.getPages(tabContainer, true)); CaseInsensitiveHashMap folderTabMap = new CaseInsensitiveHashMap<>(); @@ -9850,7 +9850,7 @@ public ApiResponse execute(TabActionForm form, BindException errors) // The name, which shows up on the url, is trimmed to 50 characters. The caption, which is derived from the // name, and is editable, is allowed to be 64 characters. if (name.length() > 50) - name = name.substring(0, 50).trim(); + name = StringUtilsLabKey.leftSurrogatePairFriendly(name, 50).trim(); Portal.saveParts(container, name); Portal.addProperty(container, name, Portal.PROP_CUSTOMTAB); diff --git a/experiment/src/org/labkey/experiment/api/AbstractRunInput.java b/experiment/src/org/labkey/experiment/api/AbstractRunInput.java index 11a1cda4b3c..7982c76963e 100644 --- a/experiment/src/org/labkey/experiment/api/AbstractRunInput.java +++ b/experiment/src/org/labkey/experiment/api/AbstractRunInput.java @@ -18,6 +18,7 @@ import org.jetbrains.annotations.Nullable; import org.labkey.api.exp.IdentifiableBase; import org.labkey.api.exp.Lsid; +import org.labkey.api.util.StringUtilsLabKey; import java.util.Objects; @@ -65,7 +66,7 @@ public void setRole(@Nullable String role) // so truncate here if needed to prevent a SQLException later if (role.length() > 50) { - role = role.substring(0, 49); + role = StringUtilsLabKey.leftSurrogatePairFriendly(role, 49); } _role = role; } diff --git a/experiment/src/org/labkey/experiment/api/ExpDataImpl.java b/experiment/src/org/labkey/experiment/api/ExpDataImpl.java index 79de048c200..b76ee431b56 100644 --- a/experiment/src/org/labkey/experiment/api/ExpDataImpl.java +++ b/experiment/src/org/labkey/experiment/api/ExpDataImpl.java @@ -69,6 +69,7 @@ import org.labkey.api.util.NetworkDrive; import org.labkey.api.util.Pair; import org.labkey.api.util.Path; +import org.labkey.api.util.StringUtilsLabKey; import org.labkey.api.util.URLHelper; import org.labkey.api.util.InputBuilder; import org.labkey.api.view.ActionURL; @@ -796,7 +797,7 @@ private static void appendTokens(StringBuilder sb, Collection toks) if (toks.isEmpty()) return; - sb.append(toks.stream().map(s -> s.length() > 30 ? s.substring(0, 30) + "\u2026" : s).collect(Collectors.joining(", "))).append("\n"); + sb.append(toks.stream().map(s -> s.length() > 30 ? StringUtilsLabKey.leftSurrogatePairFriendly(s, 30) + "\u2026" : s).collect(Collectors.joining(", "))).append("\n"); } private static class ExpDataResource extends SimpleDocumentResource diff --git a/mothership/src/org/labkey/mothership/MothershipManager.java b/mothership/src/org/labkey/mothership/MothershipManager.java index f5a9c22f104..efc3cf368ee 100644 --- a/mothership/src/org/labkey/mothership/MothershipManager.java +++ b/mothership/src/org/labkey/mothership/MothershipManager.java @@ -41,6 +41,7 @@ import org.labkey.api.util.JsonUtil; import org.labkey.api.util.MothershipReport; import org.labkey.api.util.ReentrantLockWithName; +import org.labkey.api.util.StringUtilsLabKey; import org.labkey.api.util.logging.LogHelper; import java.io.IOException; @@ -109,11 +110,11 @@ public void insertException(ExceptionStackTrace stackTrace, ExceptionReport repo String url = report.getUrl(); if (null != url && url.length() > 512) - report.setURL(url.substring(0, 506) + "..."); + report.setURL(StringUtilsLabKey.leftSurrogatePairFriendly(url, 506) + "..."); String referrerURL = report.getReferrerURL(); if (null != referrerURL && referrerURL.length() > 512) - report.setReferrerURL(referrerURL.substring(0, 506) + "..."); + report.setReferrerURL(StringUtilsLabKey.leftSurrogatePairFriendly(referrerURL, 506) + "..."); String browser = report.getBrowser(); if (null != browser && browser.length() > 100) @@ -121,7 +122,7 @@ public void insertException(ExceptionStackTrace stackTrace, ExceptionReport repo String exceptionMessage = report.getExceptionMessage(); if (null != exceptionMessage && exceptionMessage.length() > 1000) - report.setExceptionMessage(exceptionMessage.substring(0,990) + "..."); + report.setExceptionMessage(StringUtilsLabKey.leftSurrogatePairFriendly(exceptionMessage, 990) + "..."); String actionName = report.getPageflowAction(); if (null != actionName && actionName.length() > 40) diff --git a/pipeline/src/org/labkey/pipeline/api/WorkDirectoryRemote.java b/pipeline/src/org/labkey/pipeline/api/WorkDirectoryRemote.java index a8b49ecaa20..f6ab4a307d9 100644 --- a/pipeline/src/org/labkey/pipeline/api/WorkDirectoryRemote.java +++ b/pipeline/src/org/labkey/pipeline/api/WorkDirectoryRemote.java @@ -150,7 +150,7 @@ public WorkDirectory createWorkDirectory(String jobId, FileAnalysisJobSupport su // Don't let the total path get too long - Windows doesn't like paths longer than 255 characters // so if there's a ridiculously long file name, we don't want to duplicate its name in the // directory too - name = name.substring(0, 9); + name = StringUtilsLabKey.leftSurrogatePairFriendly(name, 9); } else if (name.length() < 3) { diff --git a/query/src/org/labkey/query/controllers/QueryController.java b/query/src/org/labkey/query/controllers/QueryController.java index 35ba9a5d411..da1f6703863 100644 --- a/query/src/org/labkey/query/controllers/QueryController.java +++ b/query/src/org/labkey/query/controllers/QueryController.java @@ -235,6 +235,7 @@ import org.labkey.api.util.ResponseHelper; import org.labkey.api.util.ReturnURLString; import org.labkey.api.util.StringExpression; +import org.labkey.api.util.StringUtilsLabKey; import org.labkey.api.util.TestContext; import org.labkey.api.util.URLHelper; import org.labkey.api.util.UnexpectedException; @@ -2134,7 +2135,7 @@ public Object execute(ExportQueriesForm form, BindException errors) throws Excep throw new IllegalArgumentException("Cannot create sheet names from overlapping query names."); for (int i = 0; i < queryForms.size(); i++) { - sheetNames.put(entry.getValue().get(i), name.substring(0, name.length() - countLength) + "(" + i + ")"); + sheetNames.put(entry.getValue().get(i), StringUtilsLabKey.leftSurrogatePairFriendly(name, name.length() - countLength) + "(" + i + ")"); } } else diff --git a/search/src/org/labkey/search/SearchController.java b/search/src/org/labkey/search/SearchController.java index 20036ac9877..c43f5c5d9b9 100644 --- a/search/src/org/labkey/search/SearchController.java +++ b/search/src/org/labkey/search/SearchController.java @@ -58,6 +58,7 @@ import org.labkey.api.util.HtmlStringBuilder; import org.labkey.api.util.Path; import org.labkey.api.util.ResponseHelper; +import org.labkey.api.util.StringUtilsLabKey; import org.labkey.api.util.URLHelper; import org.labkey.api.util.logging.LogHelper; import org.labkey.api.view.ActionURL; @@ -1126,7 +1127,7 @@ public static void audit(@Nullable User user, @Nullable Container c, String quer c = ContainerManager.getRoot(); if (query.length() > 200) - query = query.substring(0, 197) + "..."; + query = StringUtilsLabKey.leftSurrogatePairFriendly(query, 197) + "..."; SearchAuditProvider.SearchAuditEvent event = new SearchAuditProvider.SearchAuditEvent(c, comment); event.setQuery(query);