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}