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-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.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  //noinspection IOResourceOpenedButNotSafelyClosed
116  treeIn = new BufferedReader(reader);
117  } catch (final FileNotFoundException ignored) {
118  treeFileErrorViewCollector.addWarning(ErrorViewCategory.FACES_FILE_INVALID);
119  }
120  final byte[] tag = "IMAGE ".getBytes(); // this is the starting string for a new png
121  final StringBuilder faceB = new StringBuilder(); // face name of png
122  try {
123  final Pattern pattern = Pattern.compile(ActionBuilderUtils.getString(ACTION_BUILDER, "configSource.facetree.input"));
124  int offset = 0;
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 += (int) c - (int) '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[] 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 }
static final ActionBuilder ACTION_BUILDER
Action Builder.
Convenience class for adding messages to a ErrorView instance using a fixed category name...
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.
final boolean includeFaceNumbers
Whether the face file contains face numbers.
This interface represents a lazy loader that provides images on demand.
void addInfo(@NotNull final String faceName, final int pos, final int size)
Report position and size of a face for loading it later.
Implementation of FaceProvider which reads images from the collected PNG archive. ...
Defines possible error categories for ErrorView instances.
static String getString(@NotNull final ActionBuilder actionBuilder, @NotNull final String key, @NotNull final String defaultValue)
Returns the value of a key.
Interface for classes displaying error messages.
Definition: ErrorView.java:28
Base package of all Gridarta classes.
FaceObject getFaceObject()
Returns the illegal face object.
Exception that&#39;s thrown in case a face name was not unique.
Utility-class for Gridarta&#39;s I/O.
Definition: IOUtils.java:40
Exception thrown to indicate that a face object is not acceptable.
static final Category LOG
The Logger for printing log messages.
static File getFile(@Nullable 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:124
static URL getResource(@Nullable final File dir, @NotNull final String fileName)
Get the URL of a resource.
Definition: IOUtils.java:68
DefaultFaceObjects(final boolean includeFaceNumbers)
Creates a new instance.
FaceProvider loadFacesCollection(@NotNull final ErrorView errorView, @NotNull final File collectedDirectory)
Loads all faces from a png collection file.
Utility class for ActionBuilder related functions.
static final int CHUNK_SIZE
The chunk size in bytes when reading file contents.
static final long serialVersionUID
The serial version UID.
static boolean contains(final byte[] src, final int offset, final byte[] search)
Helper method that checks whether a region in a byte array at a given offset contains the same as ano...
Definition: ArrayUtils.java:45
String getFaceName()
Get the faceName, which is the name of the face as usable by the "face" attribute.
FaceObject getDuplicate()
Returns the duplicate that caused this exception.
Abstract base implementation of FaceObjects.
Abstract base class for FaceObjects implementations.
A FaceProvider that does not return any faces.
boolean isIncludeFaceNumbers()
Returns whether the images file contains face numbers.
Utility class for array related functions.
Definition: ArrayUtils.java:26
void addFaceObject(@NotNull final String faceName, @NotNull final String originalFilename, final int offset, final int size)
static byte [] getFileContents(@NotNull final File file)
Returns the contents of a File as a.