001/* 002 * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games. 003 * Copyright (C) 2000-2010 The Gridarta Developers. 004 * 005 * This program is free software; you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation; either version 2 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License along 016 * with this program; if not, write to the Free Software Foundation, Inc., 017 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 018 */ 019 020package net.sf.gridarta.model.validation.errors; 021 022import java.io.Serializable; 023import java.util.ArrayList; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.List; 027import net.sf.gridarta.model.archetype.Archetype; 028import net.sf.gridarta.model.gameobject.GameObject; 029import net.sf.gridarta.model.maparchobject.MapArchObject; 030import net.sf.gridarta.model.mapmodel.MapModel; 031import net.sf.gridarta.model.mapmodel.MapSquare; 032import net.sf.japi.swing.action.ActionBuilder; 033import net.sf.japi.swing.action.ActionBuilderFactory; 034import org.jetbrains.annotations.NotNull; 035import org.jetbrains.annotations.Nullable; 036 037/** 038 * Super class of all errors that could occur during map validation. The base 039 * methods of this class all return null and might return meaningful values if 040 * overridden in a subclass. 041 * @author <a href="mailto:cher@riedquat.de">Christian Hujer</a> 042 * @author Andreas Kirschbaum 043 */ 044public abstract class ValidationError<G extends GameObject<G, A, R>, A extends MapArchObject<A>, R extends Archetype<G, A, R>> implements Serializable { 045 046 /** 047 * The maximum number of map squares to show in the title. 048 */ 049 private static final int TITLE_MAP_SQUARES = 3; 050 051 /** 052 * The maximum number of game objects to show in the title. 053 */ 054 private static final int TITLE_GAME_OBJECTS = 5; 055 056 /** 057 * The serial version UID. 058 */ 059 private static final long serialVersionUID = 1L; 060 061 /** 062 * Action Builder. 063 */ 064 @NotNull 065 private static final ActionBuilder ACTION_BUILDER = ActionBuilderFactory.getInstance().getActionBuilder("net.sf.gridarta"); 066 067 /** 068 * Key. 069 * @serial 070 */ 071 @NotNull 072 private final String key; 073 074 /** 075 * The {@link MapModel} that caused the error. 076 * @serial 077 */ 078 @NotNull 079 private final MapModel<G, A, R> mapModel; 080 081 /** 082 * The {@link MapSquare MapSquares} that caused the error. 083 * @serial 084 */ 085 @NotNull 086 private final List<MapSquare<G, A, R>> mapSquares = new ArrayList<MapSquare<G, A, R>>(0); 087 088 /** 089 * The {@link GameObject GameObjects} that caused the error. 090 * @serial 091 */ 092 @NotNull 093 private final List<G> gameObjects = new ArrayList<G>(0); 094 095 /** 096 * The {@link MapSquareFormatter} for formatting {@link 097 * MapSquare} instances. 098 * @serial 099 */ 100 @NotNull 101 private final Formatter<G, A, R, MapSquare<G, A, R>> mapSquareFormatter = new MapSquareFormatter<G, A, R>(); 102 103 /** 104 * The {@link MapSquareFormatter} for formatting {@link 105 * GameObject} instances. 106 * @serial 107 */ 108 @NotNull 109 private final Formatter<G, A, R, G> gameObjectFormatter = new GameObjectFormatter<G, A, R>(); 110 111 /** 112 * Creates a new instance. The key is generated from the validator's name, 113 * which must end on "Checker". 114 * @param mapModel the map model that caused the error 115 * @throws IllegalArgumentException in case the validator's name does not 116 * end on "Checker" 117 */ 118 protected ValidationError(@NotNull final MapModel<G, A, R> mapModel) throws IllegalArgumentException { 119 final String name = getClass().getSimpleName(); 120 if (!name.endsWith("Error")) { 121 throw new IllegalArgumentException("Class name must end with \"Error\""); 122 } 123 key = "Validator." + name.substring(0, name.indexOf("Error")); 124 this.mapModel = mapModel; 125 } 126 127 /** 128 * Creates a new instance. 129 * @param key Key 130 * @param mapModel the map model that caused the error 131 */ 132 protected ValidationError(@NotNull final String key, @NotNull final MapModel<G, A, R> mapModel) { 133 this.key = key; 134 this.mapModel = mapModel; 135 } 136 137 /** 138 * Return the map on which the error occurred. 139 * @return the erroneous map 140 */ 141 @NotNull 142 public MapModel<G, A, R> getMapModel() { 143 return mapModel; 144 } 145 146 /** 147 * Returns the {@link MapSquare MapSquares} that caused the error. 148 * @return the map squares 149 */ 150 @NotNull 151 public List<MapSquare<G, A, R>> getMapSquares() { 152 return Collections.unmodifiableList(mapSquares); 153 } 154 155 /** 156 * Adds an erroneous {@link MapSquare}. 157 * @param mapSquare the erroneous map square 158 */ 159 protected void addMapSquare(@NotNull final MapSquare<G, A, R> mapSquare) { 160 if (!mapSquares.contains(mapSquare)) { 161 if (mapSquare.getMapModel() != mapModel) { 162 throw new IllegalArgumentException(); 163 } 164 mapSquares.add(mapSquare); 165 } 166 } 167 168 /** 169 * Returns the {@link GameObject GameObjects} that caused the error. 170 * @return the game objects 171 */ 172 @NotNull 173 @SuppressWarnings("TypeMayBeWeakened") 174 public List<G> getGameObjects() { 175 return Collections.unmodifiableList(gameObjects); 176 } 177 178 /** 179 * Adds an erroneous {@link GameObject}. 180 * @param gameObject the erroneous game object 181 */ 182 protected void addGameObject(@NotNull final G gameObject) { 183 if (!gameObjects.contains(gameObject)) { 184 gameObjects.add(gameObject); 185 final MapSquare<G, A, R> mapSquare = gameObject.getMapSquare(); 186 if (mapSquare != null) { 187 addMapSquare(mapSquare); 188 } 189 } 190 } 191 192 /** 193 * Returns a parameter string to be used in the error message. It can be 194 * accessed with {4+<code>id</code>}. 195 * @param id the error id 196 * @return the error string 197 */ 198 @Nullable 199 public abstract String getParameter(final int id); 200 201 /** 202 * Returns the error message for this validation error. 203 * @return the error message for this validation error 204 * @todo validation errors should be able to provide their own arguments for 205 * message formatting 206 */ 207 @NotNull 208 public String getMessage() { 209 final int x; 210 final int y; 211 if (mapSquares.isEmpty()) { 212 x = 0; 213 y = 0; 214 } else { 215 final MapSquare<G, A, R> mapSquare = mapSquares.get(0); 216 x = mapSquare != null ? mapSquare.getMapX() : -1; 217 y = mapSquare != null ? mapSquare.getMapY() : -1; 218 } 219 final String archName = gameObjects.isEmpty() ? null : gameObjects.get(0).getBestName(); 220 final String parameter0 = getParameter(0); 221 final String parameter1 = getParameter(1); 222 final String parameter2 = getParameter(2); 223 final String parameter3 = getParameter(3); 224 final StringBuilder sb = new StringBuilder(); 225 if (mapSquares.size() > 1) { 226 appendMapSquares(sb); 227 } 228 appendTitle(sb); 229 appendGameObjects(sb); 230 return ACTION_BUILDER.format(key + ".msg", sb.toString(), x, y, archName, parameter0, parameter1, parameter2, parameter3); 231 } 232 233 /** 234 * {@inheritDoc} The String representation of an error is its title. 235 */ 236 @Nullable 237 @Override 238 public String toString() { 239 final StringBuilder sb = new StringBuilder(); 240 appendMapSquares(sb); 241 appendTitle(sb); 242 appendGameObjects(sb); 243 return sb.toString(); 244 } 245 246 /** 247 * Appends the map squares of this error to a {@link StringBuilder}. 248 * @param sb the string builder 249 */ 250 private void appendMapSquares(@NotNull final StringBuilder sb) { 251 appendObjects(sb, mapSquares, mapSquareFormatter, "[", "] ", TITLE_MAP_SQUARES); 252 } 253 254 /** 255 * Appends the title of this error to a {@link StringBuilder}. 256 * @param sb the string builder 257 */ 258 private void appendTitle(@NotNull final StringBuilder sb) { 259 final String title = ACTION_BUILDER.getString(key + ".title"); 260 if (title == null) { 261 sb.append(key); 262 } else { 263 sb.append(title); 264 } 265 } 266 267 /** 268 * Appends the game objects of this error to a {@link StringBuilder}. 269 * @param sb the string builder 270 */ 271 private void appendGameObjects(@NotNull final StringBuilder sb) { 272 appendObjects(sb, gameObjects, gameObjectFormatter, " [", "]", TITLE_GAME_OBJECTS); 273 } 274 275 /** 276 * Appends the given objects to a {@link StringBuilder}. 277 * @param sb the string builder 278 * @param objects the objects to format 279 * @param formatter the formatter for formatting the objects 280 * @param prefix the prefix to prepend to the objects 281 * @param postfix the postfix to append to the objects 282 * @param maxObjects the maximum number of objects to show 283 */ 284 private <O> void appendObjects(@NotNull final StringBuilder sb, @NotNull final Collection<O> objects, @NotNull final Formatter<G, A, R, O> formatter, @NotNull final String prefix, @NotNull final String postfix, final int maxObjects) { 285 if (objects.isEmpty()) { 286 return; 287 } 288 289 final List<O> sortedObjects = new ArrayList<O>(objects); 290 Collections.sort(sortedObjects, formatter); 291 292 sb.append(prefix); 293 int count = 0; 294 for (final O object : sortedObjects) { 295 if (count > 0) { 296 sb.append(' '); 297 } 298 sb.append(formatter.toString(object)); 299 count++; 300 if (count >= maxObjects && sortedObjects.size() > maxObjects + 1) { 301 sb.append("..."); 302 break; 303 } 304 } 305 sb.append(postfix); 306 } 307 308}