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.checks; 021 022import java.util.Collection; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.Map; 026import java.util.Set; 027import net.sf.gridarta.model.archetype.Archetype; 028import net.sf.gridarta.model.connectionview.Connections; 029import net.sf.gridarta.model.gameobject.GameObject; 030import net.sf.gridarta.model.maparchobject.MapArchObject; 031import net.sf.gridarta.model.mapmodel.MapModel; 032import net.sf.gridarta.model.match.GameObjectMatcher; 033import net.sf.gridarta.model.validation.AbstractValidator; 034import net.sf.gridarta.model.validation.ErrorCollector; 035import net.sf.gridarta.model.validation.MapValidator; 036import net.sf.gridarta.model.validation.ValidatorPreferences; 037import net.sf.gridarta.model.validation.errors.ConnectionUnknownError; 038import net.sf.gridarta.model.validation.errors.ConnectionWithoutSinksError; 039import net.sf.gridarta.model.validation.errors.ConnectionWithoutSourcesError; 040import org.jetbrains.annotations.NotNull; 041 042/** 043 * Checks that for each connection value at least one source and at least one 044 * sink exists. 045 * @author Andreas Kirschbaum 046 */ 047public class ConnectionChecker<G extends GameObject<G, A, R>, A extends MapArchObject<A>, R extends Archetype<G, A, R>> extends AbstractValidator<G, A, R> implements MapValidator<G, A, R> { 048 049 /** 050 * The {@link GameObjectMatcher} for matching sources. 051 */ 052 @NotNull 053 private final GameObjectMatcher sourceGameObjectMatcher; 054 055 /** 056 * The {@link GameObjectMatcher} for matching sinks. 057 */ 058 @NotNull 059 private final GameObjectMatcher sinkGameObjectMatcher; 060 061 /** 062 * The {@link GameObjectMatcher} for matching sinks. 063 */ 064 @NotNull 065 private final GameObjectMatcher sink2GameObjectMatcher; 066 067 /** 068 * Create a new instance. 069 * @param validatorPreferences the validator preferences to use 070 * @param sourceGameObjectMatcher the game object matcher for matching 071 * sources 072 * @param sinkGameObjectMatcher the game object matcher for matching sinks 073 * @param sink2GameObjectMatcher the game object matcher for matching sinks 074 */ 075 public ConnectionChecker(@NotNull final ValidatorPreferences validatorPreferences, @NotNull final GameObjectMatcher sourceGameObjectMatcher, @NotNull final GameObjectMatcher sinkGameObjectMatcher, @NotNull final GameObjectMatcher sink2GameObjectMatcher) { 076 super(validatorPreferences); 077 this.sourceGameObjectMatcher = sourceGameObjectMatcher; 078 this.sinkGameObjectMatcher = sinkGameObjectMatcher; 079 this.sink2GameObjectMatcher = sink2GameObjectMatcher; 080 } 081 082 /** 083 * {@inheritDoc} 084 */ 085 @Override 086 public void validateMap(@NotNull final MapModel<G, A, R> mapModel, @NotNull final ErrorCollector<G, A, R> errorCollector) { 087 final Map<Integer, Rec> info = new HashMap<Integer, Rec>(); 088 for (final Iterable<G> mapSquare : mapModel) { 089 for (final G gameObject : mapSquare) { 090 check(gameObject, info); 091 for (final G invObject : gameObject.recursive()) { 092 check(invObject, info); 093 } 094 } 095 } 096 097 for (final Map.Entry<Integer, Rec> e : info.entrySet()) { 098 e.getValue().addErrors(e.getKey(), mapModel, errorCollector); 099 } 100 } 101 102 /** 103 * Processes a game object. 104 * @param gameObject the game object to process 105 * @param info the connection info to update 106 */ 107 private void check(@NotNull final G gameObject, @NotNull final Map<Integer, Rec> info) { 108 final int[] values = Connections.parseConnections(gameObject); 109 if (values == null) { 110 return; 111 } 112 113 for (final int value : values) { 114 getRec(value, info).add(gameObject); 115 } 116 } 117 118 /** 119 * Returns the record that describes a connection value. A new record is 120 * created if none exists yet. 121 * @param value the connection value 122 * @param info the connection info to use 123 * @return the record 124 */ 125 private Rec getRec(final int value, @NotNull final Map<Integer, Rec> info) { 126 final Rec existingRec = info.get(value); 127 if (existingRec != null) { 128 return existingRec; 129 } 130 131 final Rec rec = new Rec(); 132 info.put(value, rec); 133 return rec; 134 } 135 136 /** 137 * Returns whether a given game object is a source if connected. 138 * @param gameObject the game object 139 * @return whether the game object is a source 140 */ 141 private boolean isSource(@NotNull final GameObject<?, ?, ?> gameObject) { 142 return sourceGameObjectMatcher.isMatching(gameObject); 143 } 144 145 /** 146 * Returns whether a given game object is a sink if connected. 147 * @param gameObject the game object 148 * @return whether the game object is a sink 149 */ 150 private boolean isSink(@NotNull final GameObject<G, A, R> gameObject) { 151 return sinkGameObjectMatcher.isMatching(gameObject) || sink2GameObjectMatcher.isMatching(gameObject); 152 } 153 154 /** 155 * Manages information about one connection set. 156 */ 157 private class Rec { 158 159 /** 160 * The source game objects in this connection set. 161 */ 162 @NotNull 163 private final Set<G> sourceGameObjects = new HashSet<G>(); 164 165 /** 166 * The sink game objects in this connection set. 167 */ 168 @NotNull 169 private final Set<G> sinkGameObjects = new HashSet<G>(); 170 171 /** 172 * The game objects in this connection set which are neither sources nor 173 * sinks. 174 */ 175 @NotNull 176 private final Collection<G> unknowns = new HashSet<G>(); 177 178 /** 179 * Adds a game object to this connection set. 180 * @param gameObject the game object to add 181 */ 182 public void add(@NotNull final G gameObject) { 183 boolean isUnknown = true; 184 if (isSource(gameObject)) { 185 sourceGameObjects.add(gameObject); 186 isUnknown = false; 187 } 188 if (isSink(gameObject)) { 189 sinkGameObjects.add(gameObject); 190 isUnknown = false; 191 } 192 if (isUnknown) { 193 unknowns.add(gameObject); 194 } 195 } 196 197 /** 198 * Creates validation errors for this connection set. 199 * @param connection the connection value 200 * @param mapModel the map model 201 * @param errorCollector the error collector to add new errors to 202 */ 203 public void addErrors(final int connection, @NotNull final MapModel<G, A, R> mapModel, @NotNull final ErrorCollector<G, A, R> errorCollector) { 204 if (!unknowns.isEmpty()) { 205 for (final G gameObject : unknowns) { 206 errorCollector.collect(new ConnectionUnknownError<G, A, R>(gameObject)); 207 } 208 return; 209 } 210 if (sourceGameObjects.isEmpty()) { 211 errorCollector.collect(new ConnectionWithoutSourcesError<G, A, R>(mapModel, connection, sinkGameObjects)); 212 } 213 if (sinkGameObjects.isEmpty()) { 214 errorCollector.collect(new ConnectionWithoutSinksError<G, A, R>(mapModel, connection, sourceGameObjects)); 215 } 216 } 217 218 } 219 220}