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 01.09.2013 by Oli B. (boehm@javatux.de)
017 */
018
019 package patterntesting.runtime.log;
020
021 import java.io.*;
022 import java.util.*;
023
024 import org.apache.commons.lang.ObjectUtils;
025 import org.aspectj.lang.JoinPoint;
026 import org.slf4j.*;
027
028 import patterntesting.annotation.check.runtime.NullArgsAllowed;
029 import patterntesting.runtime.util.*;
030
031 /**
032 * In contradiction to {@link ObjectRecorder} this class only records joinpoints
033 * and return values if they are different from the last record. I.e. If a
034 * return value is always the same for the same joinpoint this pair is only
035 * recorded once.
036 *
037 * @author oliver (boehm@javatux.de)
038 * @since 1.3.1 (01.09.2013)
039 */
040 public class LazyObjectRecorder extends ObjectRecorder {
041
042 private static final Logger log = LoggerFactory.getLogger(LazyObjectRecorder.class);
043 private final Map<String, ValueContainer> cachedJoinpoints = new HashMap<String, LazyObjectRecorder.ValueContainer>();
044
045 /**
046 * Instantiates a new lazy object recorder.
047 */
048 public LazyObjectRecorder() {
049 super();
050 }
051
052 /**
053 * Instantiates a new lazy object recorder.
054 *
055 * @param logFile the log file
056 */
057 public LazyObjectRecorder(final File logFile) {
058 super(logFile);
059 }
060
061 /**
062 * Instantiates a new lazy object recorder.
063 *
064 * @param ostream the ostream
065 */
066 public LazyObjectRecorder(OutputStream ostream) {
067 super(ostream);
068 }
069
070 /**
071 * Both things are logged with this method: the call of a method (joinPoint)
072 * and the return value of this method. Constructors or method of type
073 * 'void' are not recorded because the have no return value.
074 * <p>
075 * Because the given joinPoint cannot be used as key for a map in
076 *
077 * @param joinPoint the joinpoint
078 * @param returnValue the return value
079 * {@link ObjectPlayer} it is saved as string. As a side effect this will
080 * speedup the serialization stuff and shorten the generated record file.
081 * </p>
082 * <p>
083 * The given return value will be only stored if it is not the same as the
084 * last time.
085 * </p>
086 */
087 @Override
088 @NullArgsAllowed
089 public void log(final JoinPoint joinPoint, final Object returnValue) {
090 String statement = JoinPointHelper.getAsLongString(joinPoint);
091 if ((returnValue != null) && (SignatureHelper.hasReturnType(joinPoint.getSignature()))) {
092 try {
093 saveLazy(statement, returnValue);
094 } catch (IOException ioe) {
095 log.debug("Logging failed because of {}.", ioe.getMessage());
096 log.info("{} = {}", statement, returnValue);
097 }
098 } else {
099 log.trace("Not recorded: {}", statement);
100 }
101 }
102
103 private void saveLazy(final String statement, final Object returnValue) throws IOException {
104 ValueContainer saved = this.cachedJoinpoints.get(statement);
105 if (saved == null) {
106 this.cachedJoinpoints.put(statement, new ValueContainer(returnValue));
107 save(statement, returnValue);
108 } else {
109 if (ObjectUtils.equals(saved.value, returnValue)) {
110 log.trace("cached: {} = {}", statement, returnValue);
111 saved.count++;
112 } else {
113 saveCache(statement, saved);
114 saved.setValue(returnValue);
115 save(statement, returnValue);
116 }
117 }
118 }
119
120 private void saveCache(final String statement, ValueContainer saved) throws IOException {
121 log.trace("saving: {} = {}", statement, saved);
122 for (int i = 0; i < saved.count; i++) {
123 save(statement, saved.value);
124 }
125 }
126
127 // ------------------------------------------------------------------------
128
129 /**
130 * This container acts as a kind of cache. The count attribute is used to
131 * store the number of (unsaved) values.
132 */
133 private static class ValueContainer {
134
135 protected Object value;
136 protected int count;
137
138 protected ValueContainer(final Object value) {
139 this.value = value;
140 }
141
142 protected void setValue(final Object newValue) {
143 this.value = newValue;
144 this.count = 0;
145 }
146
147 @Override
148 public String toString() {
149 return value + " (" + count + " x cached)";
150 }
151
152 }
153
154 }
155