Crossfire JXClient, Trunk
Parser.java
Go to the documentation of this file.
1 /*
2  * This file is part of JXClient, the Fullscreen Java Crossfire Client.
3  *
4  * JXClient is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * JXClient is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with JXClient; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17  *
18  * Copyright (C) 2005-2008 Yann Chachkoff
19  * Copyright (C) 2006-2017,2019-2023 Andreas Kirschbaum
20  * Copyright (C) 2010-2012,2014-2018,2020-2023 Nicolas Weeger
21  */
22 
23 package com.realtime.crossfire.jxclient.gui.log;
24 
25 import java.awt.Color;
26 import java.time.LocalDateTime;
27 import java.time.format.DateTimeFormatter;
28 import java.util.HashMap;
29 import java.util.Locale;
30 import java.util.Map;
31 import java.util.Map.Entry;
32 import java.util.regex.Pattern;
33 import org.jetbrains.annotations.NotNull;
34 
40 public class Parser {
41 
45  @NotNull
46  private static final DateTimeFormatter FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss ", Locale.ENGLISH);
47 
51  @NotNull
52  @SuppressWarnings("StaticCollection")
53  private static final Map<String, FontID> FONTS = new HashMap<>();
54 
55  static {
56  FONTS.put("print", FontID.PRINT);
57  FONTS.put("fixed", FontID.FIXED);
58  FONTS.put("arcane", FontID.ARCANE);
59  FONTS.put("hand", FontID.HAND);
60  FONTS.put("strange", FontID.STRANGE);
61  }
62 
66  @NotNull
67  @SuppressWarnings("StaticCollection")
68  private static final Map<String, Color> COLORS = new HashMap<>();
69 
70  static {
71  COLORS.put("black", Color.BLACK);
72  COLORS.put("blue", Color.BLUE);
73  COLORS.put("green", new Color(0, 128, 0));
74  COLORS.put("red", Color.RED);
75  COLORS.put("white", Color.WHITE);
76  }
77 
81  @NotNull
82  private static final Pattern WORD_SEPARATOR_PATTERN = Pattern.compile(" ");
83 
87  @NotNull
88  private static final Pattern END_OF_LINE_PATTERN = Pattern.compile(" *\n");
89 
93  private boolean bold;
94 
98  private boolean italic;
99 
103  private boolean underline;
104 
109 
113  @NotNull
114  private Color color = Color.BLACK;
115 
119  @NotNull
120  private final Color selectionColor;
121 
126  public Parser(@NotNull final Color selectionColor) {
127  this.selectionColor = selectionColor;
128  }
129 
138  public void parse(@NotNull final CharSequence text, final int type, final int subtype, @NotNull final Color defaultColor, @NotNull final Buffer buffer) {
139  resetAttributes(defaultColor);
140  for (String line : END_OF_LINE_PATTERN.split(text, -1)) {
141  parseLine(line, type, subtype, defaultColor, buffer);
142  }
143  buffer.prune();
144  }
145 
154  public void parseWithoutMediaTags(@NotNull final CharSequence text, final int type, final int subtype, @NotNull final Color color, @NotNull final Buffer buffer) {
155  if (text.length() == 0) {
156  return;
157  }
158 
160  for (String line : END_OF_LINE_PATTERN.split(text, -1)) {
161  parseLineWithoutMediaTags(line, type, subtype, buffer);
162  }
163  buffer.prune();
164  }
165 
174  private void parseLine(@NotNull final String text, final int type, final int subtype, @NotNull final Color defaultColor, @NotNull final Buffer buffer) {
175  if (buffer.mergeLines(text, type, subtype, defaultColor)) {
176  buffer.replaceLine(parseLine2(text+" [["+buffer.getLastCount()+" times]", type, subtype, defaultColor, buffer));
177  } else {
178  buffer.addLine(parseLine2(text, type, subtype, defaultColor, buffer));
179  }
180  }
181 
191  @NotNull
192  private Line parseLine2(@NotNull final String text, final int type, final int subtype, @NotNull final Color defaultColor, @NotNull final Buffer buffer) {
193  final Line line = new Line(type, subtype, buffer.newTextSegment(getCurrentTimestamp(), bold, italic, underline, fontID, color, selectionColor));
194 
195  int begin = 0;
196  boolean active = false;
197  final int iMax = text.length();
198  for (int i = 0; i < iMax; i++) {
199  final char ch = text.charAt(i);
200  if (active) {
201  if (ch == ']') {
202  processTag(text.substring(begin, i), defaultColor);
203  begin = i+1;
204  active = false;
205  } else if (ch == '[' && i == begin) {
206  processText("[", buffer, line);
207  begin = i+1;
208  active = false;
209  }
210  } else {
211  if (ch == '[') {
212  processText(text.substring(begin, i), buffer, line);
213  begin = i+1;
214  active = true;
215  }
216  }
217  }
218  if (!active) {
219  processText(text.substring(begin, iMax), buffer, line);
220  }
221 
222  return line;
223  }
224 
232  private void parseLineWithoutMediaTags(@NotNull final String text, final int type, final int subtype, @NotNull final Buffer buffer) {
233  final Line line = new Line(type, subtype, buffer.newTextSegment(getCurrentTimestamp(), bold, italic, underline, fontID, color, selectionColor));
234  if (buffer.mergeLines(text, type, subtype, null)) {
235  processText(text+" ["+buffer.getLastCount()+" times]", buffer, line);
236  buffer.replaceLine(line);
237  } else {
238  processText(text, buffer, line);
239  buffer.addLine(line);
240  }
241  }
242 
247  private void resetAttributes(@NotNull final Color defaultColor) {
248  bold = false;
249  italic = false;
250  underline = false;
251  fontID = FontID.PRINT;
252  color = defaultColor;
253  }
254 
261  private void processTag(@NotNull final String tag, @NotNull final Color defaultColor) {
262  if (tag.isEmpty()) {
263  return;
264  }
265 
266  if (tag.equals("b")) {
267  bold = true;
268  } else if (tag.equals("/b")) {
269  bold = false;
270  } else if (tag.equals("i")) {
271  italic = true;
272  } else if (tag.equals("/i")) {
273  italic = false;
274  } else if (tag.equals("ul")) {
275  underline = true;
276  } else if (tag.equals("/ul")) {
277  underline = false;
278  } else if (FONTS.containsKey(tag)) {
279  fontID = FONTS.get(tag);
280  assert fontID != null;
281  } else if (tag.startsWith("color=")) {
282  final String colorName = tag.substring(6).toLowerCase(Locale.ENGLISH);
283  color = COLORS.get(colorName);
284  if (color == null) {
285  if (colorName.startsWith("#") && colorName.length() == 7) {
286  try {
287  color = Color.decode(colorName);
288  } catch (final NumberFormatException ignored) {
289  color = defaultColor;
290  }
291  } else {
292  color = defaultColor;
293  }
294  }
295  } else if (tag.equals("/color")) {
296  color = defaultColor;
297  //} else {
298  // ignore unknown tag
299  }
300  }
301 
308  private void processText(@NotNull final String text, @NotNull final Buffer buffer, @NotNull final Line line) {
309  if (text.isEmpty()) {
310  return;
311  }
312 
313  final CharSequence newText;
314  final TextSegment prevSegment = line.getLastTextSegment();
315  if (prevSegment == null) {
316  newText = text;
317  } else {
318  if (prevSegment.matches(bold, italic, underline, fontID, color, selectionColor)) {
319  newText = prevSegment.getText()+text;
320  line.removeLastTextSegment();
321  } else {
322  newText = text;
323  }
324  }
325 
326  final String[] words = WORD_SEPARATOR_PATTERN.split(newText, -1);
327  for (int i = 0; i < words.length-1; i++) {
328  line.addTextSegment(buffer.newTextSegment(words[i]+" ", bold, italic, underline, fontID, color, selectionColor));
329  }
330  if (!words[words.length-1].isEmpty()) {
331  line.addTextSegment(buffer.newTextSegment(words[words.length-1], bold, italic, underline, fontID, color, selectionColor));
332  }
333  }
334 
340  @NotNull
341  public static String toString(@NotNull final Color color) {
342  // function need not be efficient since it is used for regression tests
343  // only
344  for (Entry<String, Color> e : COLORS.entrySet()) {
345  if (e.getValue() == color) {
346  return e.getKey();
347  }
348  }
349 
350  return color.toString();
351  }
352 
357  @NotNull
358  private static String getCurrentTimestamp() {
359  return FORMAT.format(LocalDateTime.now());
360  }
361 
362 }
com.realtime.crossfire.jxclient.gui.log.Parser.WORD_SEPARATOR_PATTERN
static final Pattern WORD_SEPARATOR_PATTERN
Definition: Parser.java:82
com.realtime.crossfire.jxclient.gui.log.Parser.parseLineWithoutMediaTags
void parseLineWithoutMediaTags(@NotNull final String text, final int type, final int subtype, @NotNull final Buffer buffer)
Definition: Parser.java:232
com.realtime.crossfire.jxclient.gui.log.Parser.FORMAT
static final DateTimeFormatter FORMAT
Definition: Parser.java:46
com.realtime.crossfire.jxclient.gui.log.Parser.parse
void parse(@NotNull final CharSequence text, final int type, final int subtype, @NotNull final Color defaultColor, @NotNull final Buffer buffer)
Definition: Parser.java:138
com.realtime.crossfire.jxclient.gui.log.FontID.ARCANE
ARCANE
Definition: FontID.java:44
com.realtime.crossfire.jxclient.gui.log.Line
Definition: Line.java:39
com.realtime.crossfire.jxclient.gui.log.Parser.parseLine2
Line parseLine2(@NotNull final String text, final int type, final int subtype, @NotNull final Color defaultColor, @NotNull final Buffer buffer)
Definition: Parser.java:192
com.realtime.crossfire.jxclient.gui.log.Buffer
Definition: Buffer.java:41
com.realtime.crossfire.jxclient.gui.log.Parser.parseLine
void parseLine(@NotNull final String text, final int type, final int subtype, @NotNull final Color defaultColor, @NotNull final Buffer buffer)
Definition: Parser.java:174
com.realtime.crossfire.jxclient.gui.log.TextSegment
Definition: TextSegment.java:40
com.realtime.crossfire.jxclient.gui.log.TextSegment.getText
String getText()
Definition: TextSegment.java:154
com.realtime.crossfire.jxclient.gui.log.Parser.FONTS
static final Map< String, FontID > FONTS
Definition: Parser.java:53
com.realtime.crossfire.jxclient.gui.log.Parser.underline
boolean underline
Definition: Parser.java:103
com.realtime.crossfire.jxclient.gui.log.Parser.Parser
Parser(@NotNull final Color selectionColor)
Definition: Parser.java:126
com.realtime.crossfire.jxclient.gui.log.Parser.italic
boolean italic
Definition: Parser.java:98
com.realtime.crossfire.jxclient.gui.log.Parser.color
Color color
Definition: Parser.java:114
com.realtime.crossfire.jxclient.gui.log.FontID.STRANGE
STRANGE
Definition: FontID.java:54
com.realtime.crossfire.jxclient.gui.log.Parser.COLORS
static final Map< String, Color > COLORS
Definition: Parser.java:68
com.realtime.crossfire.jxclient.gui.log.Parser.fontID
FontID fontID
Definition: Parser.java:108
com.realtime.crossfire.jxclient.gui.log.Parser.END_OF_LINE_PATTERN
static final Pattern END_OF_LINE_PATTERN
Definition: Parser.java:88
com.realtime.crossfire.jxclient.gui.log.FontID
Definition: FontID.java:29
com.realtime.crossfire.jxclient.gui.log.Parser.resetAttributes
void resetAttributes(@NotNull final Color defaultColor)
Definition: Parser.java:247
com.realtime.crossfire.jxclient.gui.log.Parser.processText
void processText(@NotNull final String text, @NotNull final Buffer buffer, @NotNull final Line line)
Definition: Parser.java:308
com.realtime.crossfire.jxclient.gui.log.Parser.bold
boolean bold
Definition: Parser.java:93
com.realtime.crossfire.jxclient.gui.log.Parser.processTag
void processTag(@NotNull final String tag, @NotNull final Color defaultColor)
Definition: Parser.java:261
com.realtime.crossfire.jxclient.gui.log.FontID.PRINT
PRINT
Definition: FontID.java:34
com.realtime.crossfire.jxclient.gui.log.Parser.parseWithoutMediaTags
void parseWithoutMediaTags(@NotNull final CharSequence text, final int type, final int subtype, @NotNull final Color color, @NotNull final Buffer buffer)
Definition: Parser.java:154
com.realtime.crossfire.jxclient.gui.log.FontID.HAND
HAND
Definition: FontID.java:49
com.realtime.crossfire.jxclient.gui.log.FontID.FIXED
FIXED
Definition: FontID.java:39
com.realtime.crossfire.jxclient.gui.log.Parser.selectionColor
final Color selectionColor
Definition: Parser.java:120
com.realtime.crossfire.jxclient.gui.log.Parser.toString
static String toString(@NotNull final Color color)
Definition: Parser.java:341
com.realtime.crossfire.jxclient.gui.log.Parser
Definition: Parser.java:40
com.realtime.crossfire.jxclient.gui.log.TextSegment.matches
boolean matches(final boolean bold, final boolean italic, final boolean underline, @NotNull final FontID fontID, @NotNull final Color color, @NotNull final Color selectionColor)
Definition: TextSegment.java:275
com.realtime.crossfire.jxclient.gui.log.Parser.getCurrentTimestamp
static String getCurrentTimestamp()
Definition: Parser.java:358