From 7aaa3f9a29c280f01c677c918932620be45cdbd7 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Thu, 8 Nov 2018 21:38:27 +0300 Subject: Merge everything into single Spring Boot application --- src/main/java/com/juick/util/DateFormatter.java | 57 ++++ .../java/com/juick/util/DateFormattersHolder.java | 75 +++++ src/main/java/com/juick/util/MessageUtils.java | 324 +++++++++++++++++++++ .../java/com/juick/util/PrettyTimeFormatter.java | 53 ++++ src/main/java/com/juick/util/StreamUtils.java | 38 +++ 5 files changed, 547 insertions(+) create mode 100644 src/main/java/com/juick/util/DateFormatter.java create mode 100644 src/main/java/com/juick/util/DateFormattersHolder.java create mode 100644 src/main/java/com/juick/util/MessageUtils.java create mode 100644 src/main/java/com/juick/util/PrettyTimeFormatter.java create mode 100644 src/main/java/com/juick/util/StreamUtils.java (limited to 'src/main/java/com/juick/util') diff --git a/src/main/java/com/juick/util/DateFormatter.java b/src/main/java/com/juick/util/DateFormatter.java new file mode 100644 index 00000000..8f569562 --- /dev/null +++ b/src/main/java/com/juick/util/DateFormatter.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2008-2017, Juick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.juick.util; + +import org.apache.commons.lang3.StringUtils; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +/** + * Created by aalexeev on 12/7/16. + */ +public class DateFormatter { + private final DateTimeFormatter formatter; + + public DateFormatter(String pattern) { + formatter = DateTimeFormatter.ofPattern(pattern, Locale.ENGLISH); + } + + public String format(final Instant ts) { + if (ts == null) + return null; + + ZonedDateTime ldt = ZonedDateTime.ofInstant(ts, ZoneOffset.UTC); + + return ldt.format(formatter); + } + + + public Instant parse(final String v) { + if (StringUtils.isBlank(v)) + return null; + + ZonedDateTime ldt = ZonedDateTime.parse(v, formatter); + + return ldt.toInstant(); + } +} diff --git a/src/main/java/com/juick/util/DateFormattersHolder.java b/src/main/java/com/juick/util/DateFormattersHolder.java new file mode 100644 index 00000000..8292e68e --- /dev/null +++ b/src/main/java/com/juick/util/DateFormattersHolder.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2008-2017, Juick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.juick.util; + +/** + * Created by aalexeev on 12/7/16. + */ +public class DateFormattersHolder { + + private DateFormattersHolder() { + throw new IllegalStateException(); + } + + private static volatile DateFormatter messageFormatter; + + public static DateFormatter getMessageFormatterInstance() { + DateFormatter localInstance = messageFormatter; + + if (localInstance == null) { + synchronized (DateFormatter.class) { + localInstance = messageFormatter; + + if (localInstance == null) + messageFormatter = localInstance = new DateFormatter("yyyy-MM-dd HH:mm:ss"); + } + } + return localInstance; + } + + private static volatile DateFormatter rssFormatter; + + public static DateFormatter getRssFormatterInstance() { + DateFormatter localInstance = rssFormatter; + + if (localInstance == null) { + synchronized (DateFormatter.class) { + localInstance = rssFormatter; + + if (localInstance == null) + rssFormatter = localInstance = new DateFormatter("EEE, d MMM yyyy HH:mm:ss"); + } + } + return localInstance; + } + + private static volatile DateFormatter httpDateFormatter; + + public static DateFormatter getHttpDateFormatter() { + DateFormatter localInstance = httpDateFormatter; + if (localInstance == null) { + synchronized (DateFormatter.class) { + localInstance = httpDateFormatter; + if (localInstance == null) { + httpDateFormatter = localInstance = new DateFormatter("EEE, dd MMM yyyy HH:mm:ss O"); + } + } + } + return localInstance; + } +} diff --git a/src/main/java/com/juick/util/MessageUtils.java b/src/main/java/com/juick/util/MessageUtils.java new file mode 100644 index 00000000..fd357c32 --- /dev/null +++ b/src/main/java/com/juick/util/MessageUtils.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2008-2017, Juick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.juick.util; + +import com.juick.Message; +import com.juick.Tag; +import com.juick.User; +import org.apache.commons.codec.CharEncoding; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.util.UriComponentsBuilder; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * Created by aalexeev on 11/13/16. + */ +public class MessageUtils { + private MessageUtils() { + throw new IllegalStateException(); + } + + public static String formatQuote(final String quote) { + String result = quote; + + if (quote != null) { + if (quote.length() > 50) { + result = ">" + quote.substring(0, 47).replace('\n', ' ') + "...\n"; + } else if (!quote.isEmpty()) { + result = ">" + quote.replace('\n', ' ') + "\n"; + } + } + + return result; + } + + private final static String urlWhiteSpacePrefix = "((?<=\\s)|(?<=\\A))"; + + private final static String urlRegex = "((?:ht|f)tps?://(?:www\\.)?([^\\/\\s\\n\\\"]+)/?[^\\]\\s\\n\\\"\\>]*)"; + + private final static String urlWithWhitespacesRegex = + urlWhiteSpacePrefix + urlRegex; + + private final static Pattern regexLinks2 = Pattern.compile("((?<=\\s)|(?<=\\A))([\\[\\{]|<)((?:ht|f)tps?://(?:www\\.)?([^\\/\\s\\\"\\)\\!]+)/?(?:[^\\]\\}](?", ">"); + + // http://juick.com/last?page=2 + // http://juick.com/last?page=2 + msg = msg.replaceAll(urlWithWhitespacesRegex, "$1$2"); + + // (http://juick.com/last?page=2) + // (http://juick.com/last?page=2) + Matcher m = regexLinks2.matcher(msg); + StringBuffer sb = new StringBuffer(); + while (m.find()) { + String url = m.group(3).replace(" ", "%20").replaceAll("\\s+", StringUtils.EMPTY); + m.appendReplacement(sb, "$1$2" + url + "$5"); + } + m.appendTail(sb); + msg = sb.toString(); + + return "
" + msg + "
"; + } + + public static String formatMessage(String msg) { + msg = msg.replaceAll("&", "&"); + msg = msg.replaceAll("<", "<"); + msg = msg.replaceAll(">", ">"); + + // -- + // — + msg = msg.replaceAll("((?<=\\s)|(?<=\\A))\\-\\-?((?=\\s)|(?=\\Z))", "$1—$2"); + + // http://juick.com/last?page=2 + // juick.com + msg = msg.replaceAll(urlWithWhitespacesRegex, "$1$3"); + + // [link text][http://juick.com/last?page=2] + // link text + msg = msg.replaceAll("\\[([^\\]]+)\\]\\[((?:ht|f)tps?://[^\\]]+)\\]", "$1"); + msg = msg.replaceAll("\\[([^\\]]+)\\]\\(((?:ht|f)tps?://[^\\)]+)\\)", "$1"); + + // #12345 + // #12345 + msg = msg.replaceAll("((?<=\\s)|(?<=\\A)|(?<=\\p{Punct}))#(\\d+)((?=\\s)|(?=\\Z)|(?=\\))|(?=\\.)|(?=\\,))", "$1#$2$3"); + + // #12345/65 + // #12345/65 + msg = msg.replaceAll("((?<=\\s)|(?<=\\A)|(?<=\\p{Punct}))#(\\d+)/(\\d+)((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1#$2/$3$4"); + + // *bold* + // bold + msg = msg.replaceAll("((?<=\\s)|(?<=\\A)|(?<=\\p{Punct}))\\*([^\\*\\n<>]+)\\*((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1$2$3"); + + // /italic/ + // italic + msg = msg.replaceAll("((?<=\\s)|(?<=\\A))/([^\\/\\n<>]+)/((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1$2$3"); + + // _underline_ + // underline + msg = msg.replaceAll("((?<=\\s)|(?<=\\A))_([^\\_\\n<>]+)_((?=\\s)|(?=\\Z)|(?=\\p{Punct}))", "$1$2$3"); + + // /12 + // /12 + msg = msg.replaceAll(replyNumberRegex, "$1/$2$3"); + + // @username@jabber.org + // @username@jabber.org + msg = msg.replaceAll(jidRegex, "$1@$2$3"); + + // @username + // @username + msg = msg.replaceAll(usernameRegex, "$1@$2$3"); + + // (http://juick.com/last?page=2) + // (juick.com) + Matcher m = regexLinks2.matcher(msg); + StringBuffer sb = new StringBuffer(); + while (m.find()) { + String url = m.group(3).replace(" ", "%20").replaceAll("\\s+", StringUtils.EMPTY); + m.appendReplacement(sb, "$1$2$4$5"); + } + m.appendTail(sb); + msg = sb.toString(); + + // > citate + msg = msg.replaceAll("(?:(?<=\\n)|(?<=\\A))> *(.*)?(\\n|(?=\\Z))", "$1"); + msg = msg.replaceAll("", "\n"); + + msg = msg.replaceAll("\n", "
\n"); + return msg; + } + + public static String formatHtml(Message jmsg) { + StringBuilder sb = new StringBuilder(); + boolean isReply = jmsg.getRid() > 0; + String title = isReply ? "Reply by @" : "@"; + String subtitle = isReply ? "
" + jmsg.getReplyQuote() + "
" : "" + getTagsString(jmsg) + ""; + boolean isCode = jmsg.getTags().stream().anyMatch(t -> t.getName().equals("code")); + + sb.append(title).append(jmsg.getUser().getName()).append(":

") + .append(subtitle).append("
") + .append(isCode ? formatMessageCode(StringUtils.defaultString(jmsg.getText())) + : formatMessage(StringUtils.defaultString(jmsg.getText()))).append("
"); + if (StringUtils.isNotEmpty(jmsg.getAttachmentType())) { + // FIXME: attachment does not serialized to xml + if (jmsg.getAttachment() == null) { + if (jmsg.getRid() > 0) { + sb.append(String.format("", jmsg.getMid(), + jmsg.getRid(), jmsg.getAttachmentType())); + } else { + sb.append(String.format("", jmsg.getMid(), + jmsg.getAttachmentType())); + } + } else { + sb.append(""); + } + } + return sb.toString(); + } + + public static String getMessageHashTags(final Message jmsg) { + StringBuilder hashtags = new StringBuilder(); + for (Tag tag : jmsg.getTags()) { + hashtags.append("#").append(tag).append(" "); + } + return hashtags.toString(); + } + public static String getMarkdownTags(final Message jmsg) { + return jmsg.getTags().stream().map(t -> String.format("[%s](http://juick.com/tag/%s)", t.getName(), percentEncode(t.getName()))) + .collect(Collectors.joining(", ")); + } + + public static String getMarkdownUser(final User user) { + return String.format("[%s](https://juick.com/%s/)", user.getName(), user.getName()); + } + + // TODO: check if it is really needed + public static String percentEncode(final String s) { + String ret = StringUtils.EMPTY; + try { + ret = URLEncoder.encode(s, CharEncoding.UTF_8).replace("+", "%20").replace("*", "%2A").replace("%7E", "~"); + } catch (UnsupportedEncodingException e) { + } + return ret; + } + public static String formatMarkdownText(final Message msg) { + String s = StringUtils.defaultString(msg.getText()).replaceAll(replyNumberRegex, String.format("$1[/$2](https://juick.com/m/%d#$2)$3", msg.getMid())); + return escapeMarkdown(s); + } + public static String escapeMarkdown(final String s) { + return s.replace("_", "\\_").replace("*", "\\*") + .replace("`", "\\`"); + } + public static String attachmentUrl(final Message jmsg) { + if (StringUtils.isEmpty(jmsg.getAttachmentType())) { + return StringUtils.EMPTY; + } + // FIXME: attachment does not serialized to xml + if (jmsg.getAttachment() == null) { + if (jmsg.getRid() > 0) { + return String.format("http://i.juick.com/photos-1024/%d-%d.%s", jmsg.getMid(), + jmsg.getRid(), jmsg.getAttachmentType()); + } else { + return String.format("http://i.juick.com/photos-1024/%d.%s", jmsg.getMid(), + jmsg.getAttachmentType()); + } + } else { + return jmsg.getAttachment().getMedium().getUrl(); + } + } + public static boolean replyStartsWithQuote(Message msg) { + return msg.getRid() > 0 && StringUtils.defaultString(msg.getText()).startsWith(">"); + } + public static List parseTags(String strTags) { + List tags = new ArrayList<>(); + if (StringUtils.isNotEmpty(strTags)) { + Set tagSet = new TreeSet<>(tags); + for (String str : strTags.split(" ")) { + Tag tag = new Tag(str); + if (!tagSet.contains(tag)) { + tags.add(tag); + tagSet.add(tag); + } + } + } + return tags; + } + public static String getTagsString(Message msg) { + StringBuilder builder = new StringBuilder(); + List tags = msg.getTags(); + if (!tags.isEmpty()) { + for (Tag Tag : tags) + builder.append(" *").append(Tag.getName()); + + if (msg.FriendsOnly) + builder.append(" *friends"); + + if (msg.getPrivacy() == -2) + builder.append(" *private"); + else if (msg.getPrivacy() == -1) + builder.append(" *friends"); + else if (msg.getPrivacy() == 2) + builder.append(" *public"); + + if (msg.ReadOnly) + builder.append(" *readonly"); + } + return builder.toString(); + } + public static boolean isPM(Message message) { + return message.getMid() == 0; + } + public static boolean isReply(Message message) { + return message.getRid() > 0; + } + + public static String stripNonSafeUrls(String input) { + // strip login urls + try { + Matcher urlMatcher = Pattern.compile(MessageUtils.urlRegex).matcher(input); + while (urlMatcher.find()) { + URI uri = URI.create(urlMatcher.group(0)); + if (uri.getHost().equals("juick.com")) { + UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(uri); + uriComponentsBuilder.replaceQueryParam("hash"); + input = input.replace(urlMatcher.group(0), uriComponentsBuilder.build().toUriString()); + } + } + } catch (IllegalArgumentException | NullPointerException e) { + return input; + } + return input; + } + private static List collectMatches(Pattern pattern, String input) { + Matcher matcher = pattern.matcher(input); + List result = new ArrayList<>(); + while (matcher.find()) { + result.add(matcher.group()); + } + return result; + } + public static List getMentions(Message msg) { + return collectMatches(usernamePattern, msg.getText()); + } + public static List getGlobalMentions(Message msg) { + return collectMatches(jidPattern, msg.getText()); + } +} diff --git a/src/main/java/com/juick/util/PrettyTimeFormatter.java b/src/main/java/com/juick/util/PrettyTimeFormatter.java new file mode 100644 index 00000000..383f4d9a --- /dev/null +++ b/src/main/java/com/juick/util/PrettyTimeFormatter.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2008-2017, Juick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.juick.util; + +import org.ocpsoft.prettytime.PrettyTime; + +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; + +/** + * Created by vitalyster on 04.05.2017. + */ +public class PrettyTimeFormatter { + private static final int MAX_CACHE_SIZE = 20; + + // Cache PrettyTime per locale. LRU cache to prevent memory leak. + private static final Map PRETTY_TIME_LOCALE_MAP = + new LinkedHashMap(MAX_CACHE_SIZE + 1, 1.1F, true) + { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) + { + return size() > MAX_CACHE_SIZE; + } + }; + + public String format(final Locale locale, final Date value) + { + PrettyTime prettyTime; + + synchronized (PRETTY_TIME_LOCALE_MAP) { + prettyTime = PRETTY_TIME_LOCALE_MAP.computeIfAbsent(locale, PrettyTime::new); + } + return prettyTime.format(value); + } +} diff --git a/src/main/java/com/juick/util/StreamUtils.java b/src/main/java/com/juick/util/StreamUtils.java new file mode 100644 index 00000000..576107af --- /dev/null +++ b/src/main/java/com/juick/util/StreamUtils.java @@ -0,0 +1,38 @@ +package com.juick.util; + +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * @deprecated switch to JDK9+ Stream API when possible + */ +@Deprecated +public class StreamUtils { + private static Spliterator takeWhile( + Spliterator splitr, Predicate predicate) { + return new Spliterators.AbstractSpliterator(splitr.estimateSize(), 0) { + boolean stillGoing = true; + @Override public boolean tryAdvance(Consumer consumer) { + if (stillGoing) { + boolean hadNext = splitr.tryAdvance(elem -> { + if (predicate.test(elem)) { + consumer.accept(elem); + } else { + stillGoing = false; + } + }); + return hadNext && stillGoing; + } + return false; + } + }; + } + + public static Stream takeWhile(Stream stream, Predicate predicate) { + return StreamSupport.stream(takeWhile(stream.spliterator(), predicate), false); + } +} -- cgit v1.2.3