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