001/* *********************************************************************** *
002 * project: org.matsim.*
003 *                                                                         *
004 * *********************************************************************** *
005 *                                                                         *
006 * copyright       : (C) 2010 by the members listed in the COPYING,        *
007 *                   LICENSE and WARRANTY file.                            *
008 * email           : info at matsim dot org                                *
009 *                                                                         *
010 * *********************************************************************** *
011 *                                                                         *
012 *   This program is free software; you can redistribute it and/or modify  *
013 *   it under the terms of the GNU General Public License as published by  *
014 *   the Free Software Foundation; either version 2 of the License, or     *
015 *   (at your option) any later version.                                   *
016 *   See also COPYING, LICENSE and WARRANTY file                           *
017 *                                                                         *
018 * *********************************************************************** */
019
020package org.matsim.utils.eventsfilecomparison;
021
022import java.util.Map;
023import java.util.Map.Entry;
024import java.util.concurrent.CyclicBarrier;
025
026import org.apache.log4j.Logger;
027import org.matsim.api.core.v01.events.Event;
028
029/**
030 * This class checks if two events files are semantic equivalent. The order of the events does not matter as long as
031 * they are chronologically sorted.
032 *
033 * @author mrieser
034 * @author laemmel
035 */
036public final class EventsFileComparator {
037        private static final Logger log = Logger.getLogger(EventsFileComparator.class);
038
039        @Deprecated // use Result enum
040        public static final int CODE_FILES_ARE_EQUAL = 0;
041        @Deprecated // use Result enum
042        public static final int CODE_DIFFERENT_NUMBER_OF_TIMESTEPS = -1;
043        @Deprecated // use Result enum
044        public static final int CODE_DIFFERENT_TIMESTEPS = -2;
045        @Deprecated // use Result enum
046        public static final int CODE_MISSING_EVENT = -3;
047        @Deprecated // use Result enum
048        public static final int CODE_WRONG_EVENT_COUNT = -4;
049        
050        public enum Result { FILES_ARE_EQUAL, DIFFERENT_NUMBER_OF_TIMESTEPS,
051                DIFFERENT_TIMESTEPS, MISSING_EVENT, WRONG_EVENT_COUNT }
052
053        private boolean ignoringCoordinates = false;
054        public EventsFileComparator setIgnoringCoordinates( boolean ignoringCoordinates ){
055                this.ignoringCoordinates = ignoringCoordinates;
056                return this;
057        }
058
059        public static void main(String[] args) {
060                if (args.length != 2) {
061                        System.out.println("Error: expected 2 events files as input arguments but found " + args.length);
062                        System.out.println("Syntax: EventsFileComparator eventsFile1 eventsFile2");
063                } else {
064                        String filename1 = args[0];
065                        String filename2 = args[1];
066                        
067                        EventsFileComparator.compare(filename1, filename2);
068                }
069        }
070
071
072        /**
073         * Compares two Events files. This method is thread-safe.
074         *
075         * @param filename1
076         * @param filename2
077         * @return <code>0</code> if the events files are equal, or some error code (see constants) if not.
078         */
079        @Deprecated // prefer the variant returning a Result enum.  kai, nov'17
080        public static int compareAndReturnInt(final String filename1, final String filename2) {
081                Result result = compare( filename1, filename2 ) ;
082                switch ( result ) {
083                case DIFFERENT_NUMBER_OF_TIMESTEPS:
084                        return -1 ; 
085                case DIFFERENT_TIMESTEPS:
086                        return -2 ; 
087                case FILES_ARE_EQUAL:
088                        return 0 ;
089                case MISSING_EVENT:
090                        return -3 ; 
091                case WRONG_EVENT_COUNT:
092                        return -4 ;
093                default:
094                        throw new RuntimeException("unknown Result code") ; 
095                }
096        }
097        public Result runComparison( final String filename1, final String filename2 ) {
098                // (need method name different from pre-existing static method.  kai, feb'20)
099
100                EventsComparator comparator = new EventsComparator( );
101                CyclicBarrier doComparison = new CyclicBarrier(2, comparator);
102                Worker w1 = new Worker(filename1, doComparison, ignoringCoordinates );
103                Worker w2 = new Worker(filename2, doComparison, ignoringCoordinates );
104                comparator.setWorkers(w1, w2);
105                w1.start();
106                w2.start();
107
108                try {
109                        w1.join();
110                } catch (InterruptedException e) {
111                        e.printStackTrace();
112                }
113                try {
114                        w2.join();
115                } catch (InterruptedException e) {
116                        e.printStackTrace();
117                }
118
119                Result retCode = comparator.retCode;
120                if (retCode == Result.FILES_ARE_EQUAL) {
121                        log.info("Event files are semantic equivalent.");
122                } else {
123                        log.warn("Event files differ.");
124                }
125                return retCode;
126        }
127        public static Result compare(final String filename1, final String filename2) {
128                return new EventsFileComparator().runComparison( filename1, filename2 );
129        }
130
131        private static class EventsComparator implements Runnable {
132
133                private Worker worker1 = null;
134                private Worker worker2 = null;
135                private volatile Result retCode = null ;
136
137                /*package*/ void setWorkers(final Worker w1, final Worker w2) {
138                        this.worker1 = w1;
139                        this.worker2 = w2;
140                }
141
142                @Override
143                public void run() {
144                        if (this.worker1.getCurrentTime() != this.worker2.getCurrentTime()) {
145                                log.warn("Differnt time steps in event files!");
146                                setExitCode(Result.DIFFERENT_TIMESTEPS);
147                                return;
148                        }
149
150                        if (this.worker1.isFinished() != this.worker2.isFinished()) {
151                                log.warn("Events files have different number of time steps!");
152                                setExitCode(Result.DIFFERENT_NUMBER_OF_TIMESTEPS);
153                                return;
154                        }
155
156                        Map<String, Counter> map1 = this.worker1.getEventsMap();
157                        Map<String, Counter> map2 = this.worker2.getEventsMap();
158
159                        boolean problem = false ;
160
161                        // check that map2 contains all keys of map1, with the same values
162                        for (Entry<String, Counter> entry : map1.entrySet()) {
163
164                                Counter counter = map2.get(entry.getKey());
165                                if (counter == null) {
166                                        log.warn("The event:" ) ;
167                                        log.warn( entry.getKey() );
168                                        log.warn("is missing in events file:" + worker2.getEventsFile());
169                                        setExitCode(Result.MISSING_EVENT);
170                                        problem = true ;
171                                } else{
172                                        if( counter.getCount() != entry.getValue().getCount() ){
173                                                log.warn(
174                                                          "Wrong event count for: " + entry.getKey() + "\n" + entry.getValue().getCount() + " times in file:" + worker1.getEventsFile()
175                                                                    + "\n" + counter.getCount() + " times in file:" + worker2.getEventsFile() );
176                                                setExitCode( Result.WRONG_EVENT_COUNT );
177                                                problem = true;
178                                        }
179                                }
180                        }
181
182                        // also check that map1 contains all keys of map2
183                        for (Entry<String, Counter> e : map2.entrySet()) {
184                                Counter counter = map1.get(e.getKey());
185                                if (counter == null) {
186                                        log.warn("The event:");
187                                        log.warn(e.getKey());
188                                        log.warn("is missing in events file:" + worker1.getEventsFile());
189                                        setExitCode(Result.MISSING_EVENT);
190                                        problem = true ;
191                                }
192                        }
193
194                        if ( problem ) {
195                                return ;
196                        }
197
198                        if (this.worker1.isFinished()) {
199                                setExitCode(Result.FILES_ARE_EQUAL);
200                        }
201                }
202
203                private void setExitCode(final Result errCode) {
204                        this.retCode= errCode;
205                        if (errCode != Result.FILES_ARE_EQUAL) {
206                                this.worker1.interrupt();
207                                this.worker2.interrupt();
208                        }
209                }
210        }
211
212}