001    /*
002     * Copyright (c) 2013 by Oli B.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *   http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     *
016     * (c)reated 31.08.2013 by Oli B. (boehm@javatux.de)
017     */
018    
019    package patterntesting.runtime.log;
020    
021    import java.io.*;
022    import java.util.*;
023    import java.util.Map.Entry;
024    import java.util.zip.GZIPInputStream;
025    
026    import org.apache.commons.io.FilenameUtils;
027    import org.apache.commons.lang.ObjectUtils;
028    import org.aspectj.lang.JoinPoint;
029    import org.slf4j.*;
030    
031    import patterntesting.runtime.io.*;
032    import patterntesting.runtime.util.*;
033    
034    /**
035     * This is the counterpart to {@link ObjectRecorder} class. It can be used to
036     * replay recorded objects.
037     *
038     * @author oliver (boehm@javatux.de)
039     * @since 1.3.1 (31.08.2013)
040     */
041    public final class ObjectPlayer {
042    
043        private static final Logger log = LoggerFactory.getLogger(ObjectPlayer.class);
044        private static final AbstractSerializer serializer = AbstractSerializer.getInstance();
045        private final Map<String, List<Object>> loggedJoinpoints = new HashMap<String, List<Object>>();
046    
047        /**
048         * Instantiates a new object player. The logged objects will be loaded from
049         * the given log file.
050         *
051         * @param logFile the log file
052         * @throws IOException Signals that an I/O exception has occurred.
053         */
054        public ObjectPlayer(final File logFile) throws IOException {
055            load(logFile);
056        }
057    
058        /**
059         * Gets the return value for the given joinpoint. The return values are
060         * returned in the same order as the were recorded. I.e. if for the same
061         * joinpoint first the value "1" and then the value "2" is recorded you'll
062         * get first "1", then "2" as the return value.
063         *
064         * @param joinPoint the join point
065         * @return the return value
066         */
067        public Object getReturnValue(final JoinPoint joinPoint) {
068            String statement = JoinPointHelper.getAsLongString(joinPoint);
069            if (SignatureHelper.hasReturnType(joinPoint.getSignature())) {
070                return getReturnValue(statement);
071            }
072            log.debug("REPLAY: {}", statement);
073            return null;
074        }
075    
076        private Object getReturnValue(final String joinPoint) {
077            Object returnValue = null;
078            if (loggedJoinpoints.containsKey(joinPoint)) {
079                List<Object> loggedReturnValues = loggedJoinpoints.get(joinPoint);
080                returnValue = loggedReturnValues.get(0);
081                if (loggedReturnValues.size() > 1) {
082                    loggedReturnValues.remove(0);
083                }
084            } else {
085                log.trace("Not recorded: {}", joinPoint);
086            }
087            log.debug("REPLAY: {} = {}", joinPoint, returnValue);
088            return returnValue;
089        }
090    
091        /**
092         * If you want to use an always recored object log you can load it with
093         * this method. This allows you to use different files for logging and
094         * loading of recorded objects.
095         *
096         * @param logFile the log file
097         * @throws IOException Signals that an I/O exception has occurred.
098         */
099        public void load(final File logFile) throws IOException {
100            FileInputStream istream = new BetterFileInputStream(logFile);
101            try {
102                if (FilenameUtils.isExtension(logFile.getName(), "gz")) {
103                    log.debug("Loading \"{}\" as compressed file...", logFile);
104                    load(new GZIPInputStream(istream));
105                } else {
106                    log.debug("Loading \"{}\" as normal file...", logFile);
107                    load(istream);
108                }
109                log.debug("Loading \"{}\" sucessfully finished.", logFile);
110            } finally {
111                istream.close();
112            }
113        }
114    
115        /**
116         * If you want to use an always recored object log you can load it with
117         * this method. This allows you to use different files for logging and
118         * loading of recorded objects.
119         *
120         * @param istream the istream
121         * @throws IOException Signals that an I/O exception has occurred.
122         */
123        public void load(final InputStream istream) throws IOException {
124            ObjectInputStream oistream = serializer.createObjectInputStream(istream);
125            try {
126                while(true) {
127                    try {
128                        String jp = (String) oistream.readObject();
129                        Object retValue = oistream.readObject();
130                        logToMemory(jp, retValue);
131                    } catch (IOException ioe) {
132                        log.trace("Reading of {} stopped ({}).", istream, ioe);
133                        break;
134                    }
135                }
136                log.debug("{} joinpoint(s) are read from {}.", this.loggedJoinpoints.size(), istream);
137            } catch (ClassNotFoundException cnfe) {
138                throw new IOException("unknown object in " + istream, cnfe);
139            } finally {
140                oistream.close();
141            }
142        }
143    
144        private void logToMemory(final String joinPoint, final Object returnValue) {
145            List<Object> loggedReturnValues = new ArrayList<Object>();
146            if (loggedJoinpoints.containsKey(joinPoint)) {
147                loggedReturnValues = loggedJoinpoints.get(joinPoint);
148            }
149            loggedReturnValues.add(returnValue);
150            loggedJoinpoints.put(joinPoint, loggedReturnValues);
151            if (log.isTraceEnabled()) {
152                log.trace("logged (" + loggedReturnValues.size() + "): {} = {}",
153                        joinPoint, Converter.toString(returnValue));
154            }
155        }
156    
157        /**
158         * Hash code.
159         *
160         * @return the int
161         * @see java.lang.Object#hashCode()
162         */
163        @Override
164        public int hashCode() {
165            return loggedJoinpoints.hashCode();
166        }
167    
168        /**
169         * Equals.
170         *
171         * @param obj the obj
172         * @return true, if successful
173         * @see java.lang.Object#equals(java.lang.Object)
174         */
175        @Override
176        public boolean equals(final Object obj) {
177            if (!(obj instanceof ObjectPlayer)) {
178                return false;
179            }
180            ObjectPlayer other = (ObjectPlayer) obj;
181            return isEquals(this.loggedJoinpoints, other.loggedJoinpoints);
182        }
183    
184        private static boolean isEquals(final Map<String, List<Object>> m1,
185                final Map<String, List<Object>> m2) {
186            if (m1.size() != m2.size()) {
187                return false;
188            }
189            for (Entry<String, List<Object>> entry : m2.entrySet()) {
190                if (!ObjectUtils.equals(m1.get(entry.getKey()), entry.getValue())) {
191                    return false;
192                }
193            }
194            return true;
195        }
196    
197        /**
198         * To string.
199         *
200         * @return the string
201         * @see java.lang.Object#toString()
202         */
203        @Override
204        public String toString() {
205            return this.getClass().getSimpleName() + " with " + loggedJoinpoints.size()
206                    + " joinpoint(s).";
207        }
208    
209    }
210