Gridarta Editor
AbstractArchetypeParser.java
Go to the documentation of this file.
1 /*
2  * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
3  * Copyright (C) 2000-2015 The Gridarta Developers.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 package net.sf.gridarta.model.io;
21 
22 import java.io.BufferedReader;
23 import java.io.IOException;
24 import java.util.List;
37 import net.sf.japi.swing.action.ActionBuilder;
38 import net.sf.japi.swing.action.ActionBuilderFactory;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
41 
47 public abstract class AbstractArchetypeParser<G extends GameObject<G, A, R>, A extends MapArchObject<A>, R extends Archetype<G, A, R>, B extends AbstractArchetypeBuilder<G, A, R>> implements ArchetypeParser<G, A, R> {
48 
52  @NotNull
53  private static final ActionBuilder ACTION_BUILDER = ActionBuilderFactory.getInstance().getActionBuilder("net.sf.gridarta");
54 
58  @NotNull
59  private final B archetypeBuilder;
60 
64  @NotNull
66 
70  @NotNull
72 
79  protected AbstractArchetypeParser(@NotNull final B archetypeBuilder, @NotNull final AnimationObjects animationObjects, @NotNull final ArchetypeSet<G, A, R> archetypeSet) {
80  this.archetypeBuilder = archetypeBuilder;
81  this.animationObjects = animationObjects;
82  this.archetypeSet = archetypeSet;
83  }
84 
85  @Override
86  public void parseArchetypeFromStream(@NotNull final BufferedReader in, @Nullable final R prototype, @Nullable final String line, @Nullable final String archName, @NotNull final String panelName, @NotNull final String folderName, @NotNull final String archPath, @NotNull final List<G> invObjects, @NotNull final ErrorViewCollector errorViewCollector) throws IOException {
87  archetypeBuilder.init(prototype, errorViewCollector);
88  final boolean isInternPath;
89  // path is needed when we don't read from collection because there is no editor_folder in arc files
90  @Nullable final String path;
91  if (!archetypeSet.isLoadedFromArchive() && archName == null) {
92  final String tmpPath = archPath + "/";
93  isInternPath = tmpPath.startsWith("/intern/");
94  path = tmpPath;
95  } else {
96  isInternPath = false;
97  path = "";
98  }
99 
100  String thisLine2;
101  if (line == null) {
102  thisLine2 = in.readLine();
103  } else {
104  thisLine2 = line; // pre read "Object" from artifacts file loader
105  }
106 
108  @Nullable R firstArch = null;
109  boolean archMore = false;
110  R lastArch = null;
111  boolean reportError = true;
112 
113 LOOP:
114  while (true) {
115  while (true) {
116  if (thisLine2 == null) {
117  break LOOP;
118  }
119 
120  final String thisLine = thisLine2.trim();
121  if (thisLine.startsWith("#") || thisLine.isEmpty()) {
122  thisLine2 = in.readLine();
123  continue;
124  }
125 
126  if (thisLine.equals("More")) {
127  if (firstArch == null) {
128  firstArch = lastArch;
129  }
130  archMore = true;
131  } else if (isStartLine(thisLine)) {
132  break;
133  } else if (!thisLine.isEmpty() && thisLine.charAt(0) != '#') {
134  if (reportError) {
135  reportError = false;
136  errorViewCollector.addWarning(ErrorViewCategory.ARCHETYPE_INVALID, "unexpected line: " + thisLine);
137  }
138  }
139 
140  thisLine2 = in.readLine();
141  }
142 
143  final String archetypeName = archName != null ? archName : thisLine2.trim().substring(7);
144  archetypeBuilder.reInit(archetypeName);
145  if (!archMore) {
146  if (firstArch != null) {
147  finishParseArchetype(firstArch);
148  }
149  firstArch = null;
150  }
151 
153 
154  @NotNull String editorFolder = archPath.isEmpty() ? "default" : archPath;
155  while (true) {
156  thisLine2 = in.readLine();
157  if (thisLine2 == null) {
158  errorViewCollector.addError(ErrorViewCategory.ARCHETYPE_INVALID, "missing 'end' line");
159  break LOOP;
160  }
161 
162  final String thisLine = thisLine2.trim();
163  if (thisLine.startsWith("#") || thisLine.isEmpty()) {
164  continue;
165  }
166 
167  if (processLine(in, thisLine, thisLine2, archetypeBuilder, errorViewCollector, invObjects)) {
168  // ignore
169  } else if (isStartLine(thisLine)) {
170  errorViewCollector.addWarning(ErrorViewCategory.ARCHETYPE_INVALID, ACTION_BUILDER.format("logInventoryInDefArch", thisLine));
171  } else if (thisLine.equals("end")) {
172  break;
173  } else if (thisLine.equals("msg")) {
174  parseMsg(in, errorViewCollector);
175  } else if (thisLine.equals("anim")) {
176  parseAnim(in, errorViewCollector, path);
177  } else if (thisLine.startsWith("visibility ")) {
178  errorViewCollector.addWarning(ErrorViewCategory.ARCHETYPE_INVALID, "Ignoring obsolete 'visibility' attribute: " + archetypeBuilder.getArchetypeName());
179  } else if (thisLine.startsWith("magicmap ")) {
180  errorViewCollector.addWarning(ErrorViewCategory.ARCHETYPE_INVALID, "Ignoring obsolete 'magicmap' attribute: " + archetypeBuilder.getArchetypeName());
181  } else if (thisLine.startsWith("x ")) {
182  if (!archMore && !archetypeBuilder.getArchetypeName().equals(START_ARCH_NAME)) {
183  errorViewCollector.addWarning(ErrorViewCategory.ARCHETYPE_INVALID, ACTION_BUILDER.format("logFoundCoordInDefArchSingleSquareOrHead", "x", archetypeBuilder.getArchetypeName()));
184  archetypeBuilder.addObjectText(thisLine);
185  } else {
186  archetypeBuilder.setMultiX(Integer.parseInt(thisLine.substring(2)));
187  }
188  } else if (thisLine.startsWith("y ")) {
189  if (!archMore && !archetypeBuilder.getArchetypeName().equals(START_ARCH_NAME)) {
190  errorViewCollector.addWarning(ErrorViewCategory.ARCHETYPE_INVALID, ACTION_BUILDER.format("logFoundCoordInDefArchSingleSquareOrHead", "y", archetypeBuilder.getArchetypeName()));
191  archetypeBuilder.addObjectText(thisLine);
192  } else {
193  archetypeBuilder.setMultiY(Integer.parseInt(thisLine.substring(2)));
194  }
195  } else if (thisLine.startsWith("editor_folder ")) {
196  editorFolder = thisLine.substring(14).trim();
197  } else {
198  archetypeBuilder.addObjectText(thisLine);
199  }
200  }
201 
202  final R archetype = archetypeBuilder.finish();
203 
204  if (firstArch != null) {
205  firstArch.addTailPart(archetype);
206  } else if (addToPanel(isInternPath, editorFolder, archetype)) {
207  final String panel;
208  final String folder;
209  if (!archetypeSet.isLoadedFromArchive() || archName != null) {
210  panel = panelName;
211  folder = folderName;
212  } else {
213  final String[] names = StringUtils.PATTERN_SLASH.split(editorFolder, 3);
214  panel = names[0];
215  folder = names.length >= 2 ? names[1] : names[0];
216  }
217  archetype.setEditorFolder(panel + "/" + folder);
218  } else {
219  archetype.setEditorFolder(GameObject.EDITOR_FOLDER_INTERN);
220  }
221  finishParseArchetypePart(firstArch, archetype, errorViewCollector);
222  try {
223  archetypeSet.addArchetype(archetype);
224  } catch (final DuplicateArchetypeException ex) {
225  errorViewCollector.addWarning(ErrorViewCategory.ARCHETYPE_INVALID, ex.getMessage());
226  }
227 
228  archMore = false;
229 
230  if (archName != null) {
231  archetype.setArtifact();
232  if (prototype != null) {
233  archetype.addObjectText(AttributeListUtils.diffArchTextKeys(archetype, prototype));
234  }
235  break;
236  }
237  lastArch = archetype;
238  reportError = true;
239 
240  thisLine2 = in.readLine();
241  }
242 
243  if (firstArch != null) {
244  finishParseArchetype(firstArch);
245  }
246  }
247 
254  private void parseMsg(@NotNull final BufferedReader in, @NotNull final ErrorViewCollector errorViewCollector) throws IOException {
255  final StringBuilder msgText = new StringBuilder();
256 
257  while (true) {
258  final String thisLine2 = in.readLine();
259  if (thisLine2 == null) {
260  errorViewCollector.addError(ErrorViewCategory.ARCHETYPE_INVALID, "Truncated archetype: msg not terminated by endmsg");
261  return;
262  }
263 
264  final String thisLine3 = thisLine2.trim();
265  if (thisLine3.equals("endmsg")) {
266  break;
267  }
268 
269  // keep leading whitespace
270  msgText.append(thisLine2).append("\n");
271  }
272 
273  if (msgText.length() > 0) {
274  archetypeBuilder.setMsgText(msgText.toString());
275  }
276  }
277 
285  private void parseAnim(@NotNull final BufferedReader in, @NotNull final ErrorViewCollector errorViewCollector, @NotNull final String path) throws IOException {
286  final StringBuilder animText = new StringBuilder();
287 
288  while (true) {
289  final String thisLine2 = in.readLine();
290  if (thisLine2 == null) {
291  errorViewCollector.addError(ErrorViewCategory.ARCHETYPE_INVALID, "Truncated animation: anim not terminated by mina");
292  return;
293  }
294 
295  final String thisLine3 = thisLine2.trim();
296  if (thisLine3.startsWith("#") || thisLine3.isEmpty()) {
297  continue;
298  }
299 
300  if (thisLine3.equals("mina")) {
301  break;
302  }
303 
304  animText.append(thisLine3).append("\n");
305  }
306 
307  final String animationName = "_" + archetypeBuilder.getArchetypeName();
308  try {
309  animationObjects.addAnimationObject(animationName, animText.toString(), path + animationName);
310  } catch (final DuplicateAnimationException e) {
311  errorViewCollector.addWarning(ErrorViewCategory.ARCHETYPE_INVALID, ACTION_BUILDER.format("logDuplicateAnimation", e.getDuplicate().getAnimName()));
312  } catch (final IllegalAnimationException ex) {
313  errorViewCollector.addWarning(ErrorViewCategory.ARCHETYPE_INVALID, "illegal animation: " + ex.getAnimationObject().getPath());
314  }
315  }
316 
320  protected abstract void initParseArchetype();
321 
327  protected abstract boolean isStartLine(@NotNull String line);
328 
340  protected abstract boolean processLine(@NotNull BufferedReader in, @NotNull String line, @NotNull String line2, @NotNull B archetypeBuilder, @NotNull ErrorViewCollector errorViewCollector, @NotNull List<G> invObjects) throws IOException;
341 
349  protected abstract void finishParseArchetypePart(@Nullable R firstArch, @NotNull R archetype, @NotNull ErrorViewCollector errorViewCollector);
350 
355  protected abstract void finishParseArchetype(@NotNull R archetype);
356 
364  protected abstract boolean addToPanel(boolean isInternPath, @NotNull String editorFolder, @NotNull R archetype);
365 
366 }
Utility class for string manipulation.
abstract void initParseArchetype()
Called when a new archetype starts.
abstract boolean addToPanel(boolean isInternPath, @NotNull String editorFolder, @NotNull R archetype)
Returns whether an archetype should be added to the archetype chooser.
Convenience class for adding messages to a ErrorView instance using a fixed category name...
static final ActionBuilder ACTION_BUILDER
Action Builder.
Gridarta can handle frame information of animations and allow the selection of an animation using a t...
abstract boolean isStartLine(@NotNull String line)
Returns whether a give input line denotes the start of a new archetype.
final B archetypeBuilder
The AbstractArchetypeBuilder to use.
Exception thrown to indicate that an animation object is not acceptable.
String START_ARCH_NAME
Name of the system-archetype containing path of starting map.
Defines possible error categories for ErrorView instances.
Base package of all Gridarta classes.
Reflects a game object (object on a map).
Definition: GameObject.java:36
final AnimationObjects animationObjects
The animation objects instance.
An Exception indicating that an Archetype name is not unique.
AnimationObjects is a container for AnimationObjects.
GameObjects are the objects based on Archetypes found on maps.
void parseArchetypeFromStream(@NotNull final BufferedReader in, @Nullable final R prototype, @Nullable final String line, @Nullable final String archName, @NotNull final String panelName, @NotNull final String folderName, @NotNull final String archPath, @NotNull final List< G > invObjects, @NotNull final ErrorViewCollector errorViewCollector)
AbstractArchetypeParser(@NotNull final B archetypeBuilder, @NotNull final AnimationObjects animationObjects, @NotNull final ArchetypeSet< G, A, R > archetypeSet)
Creates an ArchetypeParser.
void parseAnim(@NotNull final BufferedReader in, @NotNull final ErrorViewCollector errorViewCollector, @NotNull final String path)
Parses an "anim..mina" block.
Utility class for archetype attribute related functions.
abstract boolean processLine(@NotNull BufferedReader in, @NotNull String line, @NotNull String line2, @NotNull B archetypeBuilder, @NotNull ErrorViewCollector errorViewCollector, @NotNull List< G > invObjects)
Called for each processed line.
void addArchetype(@NotNull R archetype)
Adds an Archetype to this Set.
String getPath()
Get the path of this AbstractNamedObject.
NamedObject getAnimationObject()
Returns the illegal animation object.
AnimationObject getDuplicate()
Get the duplicate that caused this exception.
static String diffArchTextKeys(@NotNull final BaseObject<?, ?, ?, ?> gameObject, @NotNull final BaseObject<?, ?, ?, ?> archetype)
Returns all attributes from the given game object that don&#39;t exist in an archetype.
Common interface for ArchetypeParsers.
abstract void finishParseArchetype(@NotNull R archetype)
Called after all parts of an archetype have been processed.
void parseMsg(@NotNull final BufferedReader in, @NotNull final ErrorViewCollector errorViewCollector)
Parses a "msg..endmsg" block.
Interface that captures similarities between different ArchetypeSet implementations.
boolean isLoadedFromArchive()
Returns whether the Archetypes in this ArchetypeSet were loaded from an archive.
Exception that&#39;s thrown in case an animation name was not unique.
Abstract base implementation of ArchetypeParser.
String EDITOR_FOLDER_INTERN
The editor folder name for server-internal archetypes.
Definition: GameObject.java:42
void addAnimationObject(@NotNull String animName, @NotNull String list, @NotNull String path)
Add an animation object.
final ArchetypeSet< G, A, R > archetypeSet
The ArchetypeSet to use.
String getAnimName()
Get the animName, which is the name of the animation as usable by the "animations" attribute...
abstract void finishParseArchetypePart(@Nullable R firstArch, @NotNull R archetype, @NotNull ErrorViewCollector errorViewCollector)
Called after the "end" line of a part has been read.
static final Pattern PATTERN_SLASH
The pattern that matches a single slash ("/").