Gridarta Editor
TreasureLoader.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.treasurelist;
21 
22 import java.io.BufferedReader;
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.FilenameFilter;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.InputStreamReader;
29 import java.io.Reader;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Collection;
33 import java.util.Comparator;
34 import java.util.List;
35 import java.util.Map;
36 import javax.swing.tree.DefaultMutableTreeNode;
37 import javax.swing.tree.MutableTreeNode;
38 import javax.swing.tree.TreeNode;
44 import net.sf.gridarta.utils.IOUtils;
45 import net.sf.japi.util.EnumerationIterator;
46 import org.apache.log4j.Category;
47 import org.apache.log4j.Logger;
48 import org.jetbrains.annotations.NotNull;
49 import org.jetbrains.annotations.Nullable;
50 
56 public class TreasureLoader {
57 
61  @NotNull
62  private static final Category LOG = Logger.getLogger(TreasureLoader.class);
63 
68  @NotNull
69  private static final Comparator<TreasureTreeNode> TREASURE_NODE_COMPARATOR = new Comparator<TreasureTreeNode>() {
70 
71  @Override
72  public int compare(@NotNull final TreasureTreeNode o1, @NotNull final TreasureTreeNode o2) {
73  return String.CASE_INSENSITIVE_ORDER.compare(o1.getTreasureObj().getName(), o2.getTreasureObj().getName());
74  }
75 
76  };
77 
81  @NotNull
82  private static final FilenameFilter TREASURE_LIST_FILTER = new FilenameFilter() {
83 
84  @Override
85  public boolean accept(@NotNull final File dir, @NotNull final String name) {
86  final File fullPath = new File(dir, name);
87  if (name.startsWith(".")) {
88  return false;
89  }
90  if (fullPath.isDirectory()) {
91  return true;
92  }
93  final String lowerCaseName = name.toLowerCase();
94  return lowerCaseName.endsWith(".tl") || lowerCaseName.endsWith(".trs");
95  }
96 
97  };
98 
102  private TreasureLoader() {
103  }
104 
113  @NotNull
114  public static TreasureTree parseTreasures(@NotNull final ErrorView errorView, @NotNull final Map<String, TreasureTreeNode> specialTreasureLists, @NotNull final ConfigSource configSource, @NotNull final ProjectSettings projectSettings) {
115  final List<TreasureTreeNode> tmpList = new ArrayList<>(); // tmp. container for all treasurelists
116  final List<TreasureTreeNode> needLink = new ArrayList<>(); // all sub-treasurelist nodes that need linking
117 
118  // first step: parsing data file, adding all treasurelists to the tmpList vector
119  int index = 0;
120  while (true) {
121  final File treasureLocation;
122  try {
123  treasureLocation = configSource.getFile(projectSettings, "treasures", index);
124  } catch (final IOException ex) {
125  errorView.addWarning(ErrorViewCategory.TREASURES_FILE_INVALID, ex.getMessage());
126  index++;
127  continue;
128  }
129  if (treasureLocation == null) {
130  break;
131  }
132 
133  final int tmpListSize = tmpList.size();
134  if (treasureLocation.isDirectory()) {
135  loadTreasureDir(errorView, treasureLocation, tmpList, needLink);
136  } else if (treasureLocation.isFile()) {
137  loadTreasureList(errorView, treasureLocation, tmpList, needLink);
138  } else {
139  new ErrorViewCollector(errorView, treasureLocation).addWarning(ErrorViewCategory.TREASURES_FILE_INVALID, "location is neither file nor directory");
140  }
141  if (LOG.isInfoEnabled()) {
142  LOG.info("Loaded " + (tmpList.size() - tmpListSize) + " treasurelists from '" + treasureLocation + "'.");
143  }
144  index++;
145  }
146 
147  return createTreasureTree(tmpList, needLink, specialTreasureLists);
148  }
149 
158  @NotNull
159  private static TreasureTree createTreasureTree(@NotNull final List<TreasureTreeNode> tmpList, @NotNull final Iterable<TreasureTreeNode> needLink, @NotNull final Map<String, TreasureTreeNode> specialTreasureLists) {
160  final DefaultMutableTreeNode root = new DefaultMutableTreeNode("Treasurelists:");
161  final TreasureTree treasures = new TreasureTree(root);
162  treasures.putAll(tmpList);
163  addTopLevelEntries(tmpList, specialTreasureLists, root);
164  linkSubLists(needLink, treasures);
165  addSpecialEntries(specialTreasureLists, root);
166  return treasures;
167  }
168 
177  private static void addTopLevelEntries(@NotNull final List<TreasureTreeNode> tmpList, @NotNull final Map<String, TreasureTreeNode> specialTreasureLists, @NotNull final DefaultMutableTreeNode root) {
178  tmpList.sort(TREASURE_NODE_COMPARATOR);
179 
180  // Calculate the real ratio of chances (summed up to be 100%). Also
181  // attach lists to tree model.
182  for (final TreasureTreeNode realNode : tmpList) {
183  realNode.recalculateChances();
184 
185  // check for special treasurelists, which are put in sub-folders
186  final DefaultMutableTreeNode specialTreasureList = specialTreasureLists.get(realNode.getTreasureObj().getName());
187  if (specialTreasureList != null) {
188  specialTreasureList.add(realNode);
189  } else {
190  root.add(realNode); // normal treasurelist - attach to root node
191  }
192  }
193  }
194 
200  private static void linkSubLists(@NotNull final Iterable<TreasureTreeNode> needLink, @NotNull final TreasureTree treasures) {
201  final List<TreasureTreeNode> needSecondLink = new ArrayList<>();
202  linkSubList2(needLink, treasures, false, needSecondLink);
203  linkSubList2(needSecondLink, treasures, true, null); // do second linking to link all what is left
204  }
205 
206  private static void linkSubList2(@NotNull final Iterable<TreasureTreeNode> needLink, @NotNull final TreasureTree treasures, final boolean processSecondLinking, @Nullable final List<TreasureTreeNode> needSecondLink) {
207  for (final TreasureTreeNode node : needLink) {
208  final TreasureTreeNode realNode = getRealNode(treasures, node);
209  if (realNode != null) {
210  node.getTreasureObj().copyListType(realNode.getTreasureObj());
211 
212  for (final TreeNode ttn : new EnumerationIterator<TreeNode>(realNode.children())) {
213  node.add(((TreasureTreeNode) ttn).getClone(processSecondLinking, needSecondLink));
214  }
215  }
216  }
217  }
218 
225  @Nullable
226  private static TreasureTreeNode getRealNode(@NotNull final TreasureTree treasures, @NotNull final TreasureTreeNode node) {
227  return treasures.get(node.getTreasureObj().getName());
228  }
229 
236  private static void addSpecialEntries(@NotNull final Map<String, TreasureTreeNode> specialTreasureLists, @NotNull final DefaultMutableTreeNode root) {
237  for (final MutableTreeNode folder : specialTreasureLists.values()) {
238  root.add(folder);
239  }
240  }
241 
251  private static void loadTreasureList(@NotNull final ErrorView errorView, @NotNull final File file, final Collection<TreasureTreeNode> tmpList, final List<TreasureTreeNode> needLink) {
252  final ErrorViewCollector errorViewCollector = new ErrorViewCollector(errorView, file);
253  try {
254  try (InputStream inputStream = new FileInputStream(file)) {
255  try (Reader reader = new InputStreamReader(inputStream, IOUtils.MAP_ENCODING)) {
256  try (BufferedReader bufferedReader = new BufferedReader(reader)) {
257  while (true) {
258  final String rawLine = bufferedReader.readLine();
259  if (rawLine == null) {
260  break;
261  }
262  final String line = rawLine.trim();
263  if (!line.isEmpty() && !line.startsWith("#")) {
264  if (line.startsWith("treasure")) {
265  final int i = line.indexOf(' ');
266  if (i == -1) {
267  errorViewCollector.addWarning(ErrorViewCategory.TREASURES_ENTRY_INVALID, "unexpected line: \"" + line + "\"");
268  } else {
269  final String name = line.substring(i).trim();
270  final TreasureTreeNode node = new TreasureTreeNode(new TreasureListTreasureObj(name, line.startsWith("treasureone") ? TreasureListTreasureObjType.ONE : TreasureListTreasureObjType.MULTI));
271  tmpList.add(node);
272 
273  readInsideList(errorViewCollector, node, bufferedReader, needLink);
274  }
275  } else {
276  errorViewCollector.addWarning(ErrorViewCategory.TREASURES_ENTRY_INVALID, "unexpected line: \"" + line + "\"");
277  }
278  }
279  }
280  }
281  }
282  }
283  } catch (final IOException ex) {
284  errorViewCollector.addWarning(ErrorViewCategory.TREASURES_FILE_INVALID, ex.getMessage());
285  }
286  }
287 
297  private static void loadTreasureDir(@NotNull final ErrorView errorView, @NotNull final File dir, @NotNull final List<TreasureTreeNode> tmpList, @NotNull final List<TreasureTreeNode> needLink) {
298  final String[] traverse = dir.list(TREASURE_LIST_FILTER);
299  if (traverse == null) {
300  return;
301  }
302 
303  Arrays.sort(traverse);
304  for (final String entry : traverse) {
305  final File file = new File(dir, entry);
306  if (file.isFile()) {
307  loadTreasureList(errorView, file, tmpList, needLink);
308  } else if (file.isDirectory()) {
309  loadTreasureDir(errorView, file, tmpList, needLink);
310  }
311  }
312  }
313 
324  private static void readInsideList(@NotNull final ErrorViewCollector errorViewCollector, @NotNull final TreasureTreeNode parentNode, @NotNull final BufferedReader reader, @NotNull final List<TreasureTreeNode> needLink) throws IOException {
325  TreasureTreeNode node = null;
326 
327  boolean insideArch = false;
328 
329  while (true) {
330  final String rawLine = reader.readLine();
331  if (rawLine == null) {
332  break;
333  }
334  final String line = rawLine.trim();
335  if (line.equals("end")) {
336  break;
337  }
338  if (!line.isEmpty() && !line.startsWith("#")) {
339  if (insideArch) {
340  if (line.equals("more")) {
341  insideArch = false;
342  } else if (line.startsWith("chance")) {
343  try {
344  node.getTreasureObj().setChance(Integer.parseInt(line.substring(line.indexOf(' ') + 1).trim()));
345  } catch (final NumberFormatException ignored) {
346  errorViewCollector.addWarning(ErrorViewCategory.TREASURES_ENTRY_INVALID, "list " + parentNode.getTreasureObj().getName() + ": arch " + node.getTreasureObj().getName() + " chance is not a number.");
347  }
348  } else if (line.startsWith("nrof")) {
349  try {
350  node.getTreasureObj().setNrof(Integer.parseInt(line.substring(line.indexOf(' ') + 1).trim()));
351  } catch (final NumberFormatException ignored) {
352  errorViewCollector.addWarning(ErrorViewCategory.TREASURES_ENTRY_INVALID, "list " + parentNode.getTreasureObj().getName() + ": arch " + node.getTreasureObj().getName() + " nrof value is not a number.");
353  }
354  } else if (line.startsWith("magic")) {
355  try {
356  node.getTreasureObj().setMagic(Integer.parseInt(line.substring(line.indexOf(' ') + 1).trim()));
357  } catch (final NumberFormatException ignored) {
358  errorViewCollector.addWarning(ErrorViewCategory.TREASURES_ENTRY_INVALID, "list " + parentNode.getTreasureObj().getName() + ": arch " + node.getTreasureObj().getName() + " magic value is not a number.");
359  }
360  } else if (line.startsWith("artifact_chance") || line.startsWith("title") || line.startsWith("difficulty") || line.startsWith("quality_quality") || line.startsWith("quality_range") || line.startsWith("material_quality") || line.startsWith("material_range")) {
361  // ignored for now; prevent error message when loading Daimonin/Atrinik treasure lists
362  } else if (line.startsWith("name") || line.startsWith("t_style") || line.startsWith("affinity")) {
363  // ignored for now; prevent error message when loading Atrinik treasure lists
364  } else if (line.equals("no")) {
365  final int parentChance = node.getTreasureObj().getChance();
366  final int chance;
367  if (parentChance == TreasureObj.UNSET) {
368  errorViewCollector.addWarning(ErrorViewCategory.TREASURES_ENTRY_INVALID, "list " + parentNode.getTreasureObj().getName() + ": arch " + node.getTreasureObj().getName() + " has NO-list but chance is unset!");
369  chance = 0;
370  } else {
371  chance = 100 - parentChance;
372  }
373 
374  final TreasureTreeNode subNode = new TreasureTreeNode(new NoTreasureObj(chance));
375  node.add(subNode);
376 
377  readInsideList(errorViewCollector, subNode, reader, needLink);
378  } else if (line.equals("yes")) {
379  final TreasureTreeNode subNode = new TreasureTreeNode(new YesTreasureObj(node.getTreasureObj().getChance()));
380  node.add(subNode);
381  readInsideList(errorViewCollector, subNode, reader, needLink);
382  } else {
383  errorViewCollector.addWarning(ErrorViewCategory.TREASURES_ENTRY_INVALID, "list " + parentNode.getTreasureObj().getName() + ", arch " + node.getTreasureObj().getName() + ": unexpected line: \"" + line + "\"");
384  }
385  } else {
386  if (line.startsWith("arch ")) {
387  node = new TreasureTreeNode(new ArchTreasureObj(line.substring(line.indexOf(' ')).trim()));
388  parentNode.add(node);
389  insideArch = true;
390  } else if (line.startsWith("list ")) {
391  final String newName = line.substring(line.indexOf(' ')).trim();
393  parentNode.add(node);
394  needLink.add(node); // this node needs to be linked to it's content later
395 
396  // check for potential infinite loops by lists containing itself
397  if (node.getTreasureObj().isTreasureList() && parentNode.getTreasureObj().getName().equals(newName)) {
398  node.getTreasureObj().setHasLoop(true);
399  }
400 
401  insideArch = true;
402  } else {
403  errorViewCollector.addWarning(ErrorViewCategory.TREASURES_ENTRY_INVALID, parentNode + ": unknown line: \"" + line + "\"");
404  }
405  }
406  }
407  }
408  }
409 
410 }
A TreasureObj representing a "no" entry.
TreasureLoader()
Private constructor to prevent instantiation.
Convenience class for adding messages to a ErrorView instance using a fixed category name...
void setNrof(final int nrof)
Sets the maximum number of generated items.
String getName()
Returns the name of this treasure object.
Settings that apply to a project.
static final String MAP_ENCODING
Encoding to use for maps and other data.
Definition: IOUtils.java:51
void addWarning(@NotNull final ErrorViewCategory category)
Adds a warning message.
static void addTopLevelEntries(@NotNull final List< TreasureTreeNode > tmpList, @NotNull final Map< String, TreasureTreeNode > specialTreasureLists, @NotNull final DefaultMutableTreeNode root)
Adds a list of TreasureTreeNodes to a root DefaultMutableTreeNode.
static void loadTreasureList(@NotNull final ErrorView errorView, @NotNull final File file, final Collection< TreasureTreeNode > tmpList, final List< TreasureTreeNode > needLink)
Parses one treasurelist file.
void setHasLoop(final boolean hasLoop)
Sets whether this treasure object contains itself.
Defines possible error categories for ErrorView instances.
Interface for classes displaying error messages.
Definition: ErrorView.java:28
Base package of all Gridarta classes.
Utility-class for Gridarta&#39;s I/O.
Definition: IOUtils.java:40
Possible source locations for configuration files.
static void readInsideList(@NotNull final ErrorViewCollector errorViewCollector, @NotNull final TreasureTreeNode parentNode, @NotNull final BufferedReader reader, @NotNull final List< TreasureTreeNode > needLink)
Reads and parses the text inside a treasurelist definition.
final boolean isTreasureList
Whether this treasure object is a "treasure" or "treasureone" object.
static final FilenameFilter TREASURE_LIST_FILTER
The FilenameFilter for recursively loading treasure list files.
static void addSpecialEntries(@NotNull final Map< String, TreasureTreeNode > specialTreasureLists, @NotNull final DefaultMutableTreeNode root)
Add the special treasure list parents to the root DefaultMutableTreeNode.
static void linkSubList2(@NotNull final Iterable< TreasureTreeNode > needLink, @NotNull final TreasureTree treasures, final boolean processSecondLinking, @Nullable final List< TreasureTreeNode > needSecondLink)
static void loadTreasureDir(@NotNull final ErrorView errorView, @NotNull final File dir, @NotNull final List< TreasureTreeNode > tmpList, @NotNull final List< TreasureTreeNode > needLink)
Recursively traverses a directory and parses all treasurelist files.
A TreasureObj representing an "arch" entry.
Stores all defined treasure lists.
static final int UNSET
Unset values.
static final Comparator< TreasureTreeNode > TREASURE_NODE_COMPARATOR
Comparator for TreasureTreeNodes that compares their object name case insensitive.
A TreasureObj representing a "yes" entry.
static void linkSubLists(@NotNull final Iterable< TreasureTreeNode > needLink, @NotNull final TreasureTree treasures)
Links sub-treasure tree nodes to their parent nodes.
static TreasureTree createTreasureTree(@NotNull final List< TreasureTreeNode > tmpList, @NotNull final Iterable< TreasureTreeNode > needLink, @NotNull final Map< String, TreasureTreeNode > specialTreasureLists)
Creates a TreasureTree instance from a list of TreasureTreeNodes.
Subclass: Nodes in the CFTreasureListTree.
Subclass: UserObject (= content object) for nodes in the CFTreasureListTree These can be either treas...
static TreasureTreeNode getRealNode(@NotNull final TreasureTree treasures, @NotNull final TreasureTreeNode node)
Returns the "real" (top-level) node that corresponds to a given node.
void putAll(final Iterable< TreasureTreeNode > treasureTreeNodes)
Adds all TreasureTreeNode.
void setChance(final int value)
Sets the chance attribute.
int getChance()
Returns the chance attribute.
static TreasureTree parseTreasures(@NotNull final ErrorView errorView, @NotNull final Map< String, TreasureTreeNode > specialTreasureLists, @NotNull final ConfigSource configSource, @NotNull final ProjectSettings projectSettings)
Parses a treasure file into a TreasureTree instance.
static final Category LOG
The Logger for printing log messages.
void setMagic(final int magic)
Sets the magic attribute.