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