Gridarta Editor
DefaultFaceObjects.java
Go to the documentation of this file.
1 /*
2  * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
3  * Copyright (C) 2000-2023 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.face;
21 
22 import java.io.BufferedReader;
23 import java.io.ByteArrayOutputStream;
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.FileNotFoundException;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.InputStreamReader;
30 import java.io.Reader;
31 import java.net.URL;
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
39 import net.sf.gridarta.utils.IOUtils;
40 import net.sf.japi.swing.action.ActionBuilder;
41 import net.sf.japi.swing.action.ActionBuilderFactory;
42 import org.apache.log4j.Category;
43 import org.apache.log4j.Logger;
44 import org.jetbrains.annotations.NotNull;
45 
51 
55  private static final long serialVersionUID = 1L;
56 
60  private static final int CHUNK_SIZE = 4096;
61 
65  @NotNull
66  private static final Category LOG = Logger.getLogger(DefaultFaceObjects.class);
67 
71  @NotNull
72  private static final ActionBuilder ACTION_BUILDER = ActionBuilderFactory.getInstance().getActionBuilder("net.sf.gridarta");
73 
77  private final boolean includeFaceNumbers;
78 
83  public DefaultFaceObjects(final boolean includeFaceNumbers) {
84  super(ActionBuilderUtils.getString(ACTION_BUILDER, "nameOfFaceObject"));
85  this.includeFaceNumbers = includeFaceNumbers;
86  }
87 
88  @NotNull
89  @Override
90  public FaceProvider loadFacesCollection(@NotNull final ErrorView errorView, @NotNull final File collectedDirectory) {
91  final String faceFile = ActionBuilderUtils.getString(ACTION_BUILDER, "configSource.image.name");
92  final String treeFile = ActionBuilderUtils.getString(ACTION_BUILDER, "configSource.facetree.name");
93  final File tmpFaceFile;
94  try {
95  tmpFaceFile = IOUtils.getFile(collectedDirectory, faceFile);
96  } catch (final IOException ex) {
97  errorView.addWarning(ErrorViewCategory.FACES_FILE_INVALID, new File(collectedDirectory, faceFile) + ": " + ex.getMessage());
98  return new EmptyFaceProvider();
99  }
100  final CollectedFaceProvider collectedFaceProvider;
101  int faces = 0;
102  final ErrorViewCollector faceFileErrorViewCollector = new ErrorViewCollector(errorView, tmpFaceFile);
103  try {
104  collectedFaceProvider = new CollectedFaceProvider(tmpFaceFile);
105  final byte[] data = getFileContents(tmpFaceFile);
106 
107  // Note: treeIn might stay null, this is optional data.
108  final ErrorViewCollector treeFileErrorViewCollector = new ErrorViewCollector(errorView, new File(collectedDirectory, treeFile));
109  BufferedReader treeIn = null;
110  try {
111  final URL url = IOUtils.getResource(collectedDirectory, treeFile);
112  final InputStream inputStream2 = url.openStream();
113  final Reader reader = new InputStreamReader(inputStream2, IOUtils.MAP_ENCODING);
114  //cher: it is safely closed but optional. InspectionGadgets doesn't detect where it's closed.
115  treeIn = new BufferedReader(reader);
116  } catch (final FileNotFoundException ignored) {
117  treeFileErrorViewCollector.addWarning(ErrorViewCategory.FACES_FILE_INVALID);
118  }
119  final byte[] tag = "IMAGE ".getBytes(); // this is the starting string for a new png
120  try {
121  final Pattern pattern = Pattern.compile(ActionBuilderUtils.getString(ACTION_BUILDER, "configSource.facetree.input"));
122  int offset = 0;
123  // face name of png
124  final StringBuilder faceB = new StringBuilder();
125  while (offset < data.length) {
126  // File: Structure*
127  // Structure: "IMAGE " seqNr ' ' size ' ' facename '\n' PNGBinary (if includeFaceNumbers)
128  // Structure: "IMAGE " size ' ' facename '\n' PNGBinary (if !includeFaceNumbers)
129  if (!ArrayUtils.contains(data, offset, tag)) { // check for IMAGE
130  throw new IOException("expecting 'IMAGE' at position " + offset);
131  }
132  offset += 6; // skip "IMAGE ";
133  if (includeFaceNumbers) {
134  while (data[offset++] != 0x20) {
135  // skip seqNr ' '
136  }
137  }
138  int size = 0;
139  // read size ' '
140  while (true) {
141  final char c = (char) data[offset++]; // XXX: do not cast from byte to char
142  if (c == ' ') {
143  break;
144  }
145  if (c < '0' || c > '9') {
146  throw new IOException("syntax error at position " + offset + ": not a digit");
147  }
148  size *= 10;
149  size += c - '0';
150  }
151  faceB.setLength(0);
152  // read facename '\n'
153  while (true) {
154  final char c = (char) data[offset++]; // XXX: do not cast from byte to char
155  if (c == '\n') {
156  break;
157  }
158  if (c == '/') {
159  faceB.setLength(0);
160  } else {
161  faceB.append(c);
162  }
163  }
164  final String faceName = faceB.toString().intern();
165 
166  if (offset + size > data.length) {
167  throw new IOException("truncated at position " + offset);
168  }
169 
170  if (treeIn != null) {
171  final String originalFilename = treeIn.readLine();
172  if (originalFilename == null) {
173  LOG.warn(ACTION_BUILDER.format("logFaceObjectWithoutOriginalName", faceName));
174  } else {
175  final Matcher matcher = pattern.matcher(originalFilename);
176  if (matcher.matches()) {
177  try {
178  addFaceObject(faceName, matcher.group(1), offset, size);
179  } catch (final DuplicateFaceException ex) {
180  faceFileErrorViewCollector.addWarning(ErrorViewCategory.FACES_ENTRY_INVALID, "duplicate face: " + ex.getDuplicate().getFaceName());
181  } catch (final IllegalFaceException ex) {
182  faceFileErrorViewCollector.addWarning(ErrorViewCategory.FACES_ENTRY_INVALID, "invalid face: " + ex.getFaceObject().getFaceName());
183  }
184  } else {
185  treeFileErrorViewCollector.addWarning(ErrorViewCategory.FACES_ENTRY_INVALID, "syntax error: " + originalFilename);
186  }
187  }
188  }
189  collectedFaceProvider.addInfo(faceName, offset, size); // TODO Remove me
190  faces++;
191  offset += size;
192  }
193  } finally {
194  if (treeIn != null) {
195  treeIn.close();
196  }
197  }
198  } catch (final IOException ex) {
199  faceFileErrorViewCollector.addWarning(ErrorViewCategory.FACES_FILE_INVALID, ex.getMessage());
200  return new EmptyFaceProvider();
201  }
202  if (LOG.isInfoEnabled()) {
203  LOG.info("Loaded " + faces + " faces from '" + tmpFaceFile + "'.");
204  }
205  return collectedFaceProvider;
206  }
207 
214  private static byte @NotNull [] getFileContents(@NotNull final File file) throws IOException {
215  final ByteArrayOutputStream bufOut = new ByteArrayOutputStream((int) file.length());
216  try (InputStream inputStream = new FileInputStream(file)) {
217  final byte[] buf = new byte[CHUNK_SIZE];
218  while (true) {
219  final int len = inputStream.read(buf);
220  if (len == -1) {
221  break;
222  }
223  bufOut.write(buf, 0, len);
224  }
225  }
226  return bufOut.toByteArray();
227  }
228 
233  @Override
234  public boolean isIncludeFaceNumbers() {
235  return includeFaceNumbers;
236  }
237 
238 }
net.sf.gridarta.utils.IOUtils.getResource
static URL getResource(@NotNull final File dir, @NotNull final String fileName)
Get the URL of a resource.
Definition: IOUtils.java:69
net.sf.gridarta.model.face.FaceProvider
This interface represents a lazy loader that provides images on demand.
Definition: FaceProvider.java:30
net.sf.gridarta.model.errorview.ErrorViewCollector.addWarning
void addWarning(@NotNull final ErrorViewCategory category)
Adds a warning message.
Definition: ErrorViewCollector.java:68
net.sf.gridarta.utils.IOUtils.getFile
static File getFile(@NotNull final File dir, @NotNull final String fileName)
Returns a File instance for a resource that is a regular file on the file system.
Definition: IOUtils.java:125
net.sf.gridarta.model.face.DefaultFaceObjects.ACTION_BUILDER
static final ActionBuilder ACTION_BUILDER
Action Builder.
Definition: DefaultFaceObjects.java:72
net.sf.gridarta
Base package of all Gridarta classes.
net.sf.gridarta.model.face.DuplicateFaceException.getDuplicate
FaceObject getDuplicate()
Returns the duplicate that caused this exception.
Definition: DuplicateFaceException.java:60
net.sf
net.sf.gridarta.model.face.AbstractFaceObjects.addFaceObject
void addFaceObject(@NotNull final String faceName, @NotNull final String originalFilename, final int offset, final int size)
Adds a new face object.
Definition: AbstractFaceObjects.java:47
net.sf.gridarta.model.face.DefaultFaceObjects
Abstract base implementation of FaceObjects.
Definition: DefaultFaceObjects.java:50
net.sf.gridarta.utils.ArrayUtils.contains
static boolean contains(final byte @NotNull[] src, final int offset, final byte @NotNull[] search)
Helper method that checks whether a region in a byte array at a given offset contains the same as ano...
Definition: ArrayUtils.java:47
net.sf.gridarta.model.face.DefaultFaceObjects.CHUNK_SIZE
static final int CHUNK_SIZE
The chunk size in bytes when reading file contents.
Definition: DefaultFaceObjects.java:60
net.sf.gridarta.model.errorview.ErrorView
Interface for classes displaying error messages.
Definition: ErrorView.java:28
net.sf.gridarta.model.face.DefaultFaceObjects.getFileContents
static byte[] getFileContents(@NotNull final File file)
Returns the contents of a File as a.
Definition: DefaultFaceObjects.java:214
net.sf.gridarta.model.face.CollectedFaceProvider.addInfo
void addInfo(@NotNull final String faceName, final int pos, final int size)
Report position and size of a face for loading it later.
Definition: CollectedFaceProvider.java:80
net.sf.gridarta.model.face.IllegalFaceException
Exception thrown to indicate that a face object is not acceptable.
Definition: IllegalFaceException.java:28
net.sf.gridarta.model.face.DefaultFaceObjects.loadFacesCollection
FaceProvider loadFacesCollection(@NotNull final ErrorView errorView, @NotNull final File collectedDirectory)
Loads all faces from a png collection file.
Definition: DefaultFaceObjects.java:90
net.sf.gridarta.model.face.CollectedFaceProvider
Implementation of FaceProvider which reads images from the collected PNG archive.
Definition: CollectedFaceProvider.java:41
net
net.sf.gridarta.utils.ArrayUtils
Utility class for array related functions.
Definition: ArrayUtils.java:28
net.sf.gridarta.model.errorview
Definition: ErrorView.java:20
net.sf.gridarta.model.errorview.ErrorViewCollector
Convenience class for adding messages to a ErrorView instance using a fixed category name.
Definition: ErrorViewCollector.java:31
net.sf.gridarta.model.face.DefaultFaceObjects.isIncludeFaceNumbers
boolean isIncludeFaceNumbers()
Returns whether the images file contains face numbers.
Definition: DefaultFaceObjects.java:234
net.sf.gridarta.model.errorview.ErrorViewCategory
Defines possible error categories for ErrorView instances.
Definition: ErrorViewCategory.java:28
net.sf.gridarta.model.face.EmptyFaceProvider
A FaceProvider that does not return any faces.
Definition: EmptyFaceProvider.java:30
net.sf.gridarta.model.data.AbstractNamedObjects.size
int size()
Definition: AbstractNamedObjects.java:93
net.sf.gridarta.utils.ActionBuilderUtils.getString
static String getString(@NotNull final ActionBuilder actionBuilder, @NotNull final String key, @NotNull final String defaultValue)
Returns the value of a key.
Definition: ActionBuilderUtils.java:71
net.sf.gridarta.model.errorview.ErrorViewCategory.FACES_ENTRY_INVALID
FACES_ENTRY_INVALID
Definition: ErrorViewCategory.java:64
net.sf.gridarta.model.face.DefaultFaceObjects.LOG
static final Category LOG
The Logger for printing log messages.
Definition: DefaultFaceObjects.java:66
net.sf.gridarta.model.face.DefaultFaceObjects.includeFaceNumbers
final boolean includeFaceNumbers
Whether the face file contains face numbers.
Definition: DefaultFaceObjects.java:77
net.sf.gridarta.model
net.sf.gridarta.model.face.DefaultFaceObjects.DefaultFaceObjects
DefaultFaceObjects(final boolean includeFaceNumbers)
Creates a new instance.
Definition: DefaultFaceObjects.java:83
net.sf.gridarta.model.errorview.ErrorViewCategory.FACES_FILE_INVALID
FACES_FILE_INVALID
Definition: ErrorViewCategory.java:66
net.sf.gridarta.model.face.AbstractFaceObjects
Abstract base class for FaceObjects implementations.
Definition: AbstractFaceObjects.java:31
net.sf.gridarta.utils.IOUtils
Utility-class for Gridarta's I/O.
Definition: IOUtils.java:40
net.sf.gridarta.model.face.FaceObject.getFaceName
String getFaceName()
Get the faceName, which is the name of the face as usable by the "face" attribute.
net.sf.gridarta.model.face.IllegalFaceException.getFaceObject
FaceObject getFaceObject()
Returns the illegal face object.
Definition: IllegalFaceException.java:57
net.sf.gridarta.utils.ActionBuilderUtils
Utility class for ActionBuilder related functions.
Definition: ActionBuilderUtils.java:31
net.sf.gridarta.model.face.DuplicateFaceException
Exception that's thrown in case a face name was not unique.
Definition: DuplicateFaceException.java:26
net.sf.gridarta.utils
Definition: ActionBuilderUtils.java:20
net.sf.gridarta.utils.IOUtils.MAP_ENCODING
static final String MAP_ENCODING
Encoding to use for maps and other data.
Definition: IOUtils.java:52
net.sf.gridarta.model.face.DefaultFaceObjects.serialVersionUID
static final long serialVersionUID
The serial version UID.
Definition: DefaultFaceObjects.java:55