View Javadoc

1   /* OpenLogViewer
2    *
3    * Copyright 2011
4    *
5    * This file is part of the OpenLogViewer project.
6    *
7    * OpenLogViewer software is free software: you can redistribute it and/or modify
8    * it under the terms of the GNU General Public License as published by
9    * the Free Software Foundation, either version 3 of the License, or
10   * (at your option) any later version.
11   *
12   * OpenLogViewer software is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Public License for more details.
16   *
17   * You should have received a copy of the GNU General Public License
18   * along with any OpenLogViewer software.  If not, see http://www.gnu.org/licenses/
19   *
20   * I ask that if you make any changes to this file you fork the code on github.com!
21   *
22   */
23  package org.diyefi.openlogviewer.genericlog;
24  
25  import java.beans.PropertyChangeEvent;
26  import java.beans.PropertyChangeListener;
27  import java.beans.PropertyChangeSupport;
28  import java.util.LinkedHashMap;
29  import java.util.Iterator;
30  import org.diyefi.openlogviewer.OpenLogViewer;
31  
32  public class GenericLog extends LinkedHashMap<String, GenericDataElement> {
33  	private static final long serialVersionUID = 1L;
34  
35  	// TODO this is no good, get rid of it, show some sort of status indicator in the GUI showing that log loading is not complete
36  	// For streams, always show that as a way of saying "still streaming!"
37  	private static final String LOG_LOADED_TEXT = "LogLoaded";
38  	public static enum LogState { LOG_NOT_LOADED, LOG_LOADING, LOG_LOADED }
39  
40  	// Info to populate built-in fields efficiently, likely to be done differently in future, but if not, put this in some structure.
41  	public static final String recordCountKey = "OLV Record Count";
42  	public static final String tempResetKey = "OLV Temp Resets";
43  	public static final String elapsedTimeKey = "OLV Elapsed Time";
44  	private static final int NUMBER_OF_BUILTIN_FIELDS = 3; // See below:
45  	private static final int RECORD_COUNT_OFFSET = 0;
46  	private static final int TEMP_RESET_OFFSET   = 1;
47  	private static final int ELAPSED_TIME_OFFSET = 2;
48  	private static final int SIZE_OF_DOUBLE = 8;
49  	private static final int NUMBER_OF_BYTES_IN_A_MEG = 1000000;
50  	private GenericDataElement recordCountElement;
51  
52  	private String metaData;
53  	protected final PropertyChangeSupport PCS;
54  	private LogState logStatus;
55  	private String logStatusMessage = null;
56  
57  	// Track the size of our children so that we can bump them up one by one where required
58  	private int currentCapacity;
59  	private int currentPosition = -1;
60  	// ^ TODO if we end up limiting memory usage by some configurable amount and recycling positions, for live streaming, then add count
61  	private int ourLoadFactor;
62  	private int numberOfInternalHeaders;
63  
64  	private final PropertyChangeListener autoLoad = new PropertyChangeListener() {
65  		public void propertyChange(final PropertyChangeEvent propertyChangeEvent) {
66  			if ((LogState) propertyChangeEvent.getNewValue() == LogState.LOG_LOADING) {
67  				final GenericLog genLog = (GenericLog) propertyChangeEvent.getSource();
68  				genLog.setLogStatus(LogState.LOG_LOADING);
69  				OpenLogViewer.getInstance().setLog(genLog);
70  			} else if ((LogState) propertyChangeEvent.getNewValue() == LogState.LOG_LOADED) {
71  				final GenericLog genLog = (GenericLog) propertyChangeEvent.getSource();
72  				genLog.setLogStatus(LogState.LOG_LOADED);
73  				OpenLogViewer.getInstance().setLog(genLog);
74  				OpenLogViewer.getInstance().getOptionFrame().updateFromLog(genLog);
75  			}
76  		}
77  	};
78  
79  	/**
80  	 * provide a <code>String</code> array of headers<br>
81  	 * each header will be used as a HashMap key, the data related to each header will be added to an <code>ArrayList</code>.
82  	 * @param headers - of the data to be converted
83  	 */
84  	public GenericLog(final String[] headers, final int initialCapacity, final int ourLoadFactor) {
85  		super(1 + (headers.length + NUMBER_OF_BUILTIN_FIELDS), 1.0f); // refactor to use (capacityRequired+1, 1.0) for maximum performance (no rehashing)
86  
87  		GenericDataElement.resetPosition(); // Kinda ugly, but...
88  		logStatus = LogState.LOG_NOT_LOADED;
89  		PCS = new PropertyChangeSupport(this);
90  		addPropertyChangeListener(LOG_LOADED_TEXT, autoLoad);
91  		metaData = "";
92  
93  		this.ourLoadFactor = ourLoadFactor;
94  		currentCapacity = initialCapacity;
95  
96  		// A bit dirty, but not too bad.
97  		numberOfInternalHeaders = headers.length + NUMBER_OF_BUILTIN_FIELDS;
98  		final String[] internalHeaders = new String[numberOfInternalHeaders];
99  		for (int i = 0; i < headers.length; i++) {
100 			internalHeaders[i] = headers[i];
101 		}
102 
103 		// If this stays like this, move it to a structure and small loop...
104 		internalHeaders[headers.length + RECORD_COUNT_OFFSET] = recordCountKey;
105 		internalHeaders[headers.length + TEMP_RESET_OFFSET] = tempResetKey;
106 		internalHeaders[headers.length + ELAPSED_TIME_OFFSET] = elapsedTimeKey;
107 
108 		for (int x = 0; x < internalHeaders.length; x++) {
109 			GenericDataElement GDE = new GenericDataElement(initialCapacity);
110 			GDE.setName(internalHeaders[x]);
111 			this.put(internalHeaders[x], GDE);
112 		}
113 
114 		recordCountElement = this.get(recordCountKey);
115 	}
116 
117 	/**
118 	 * Add a piece of data to the <code>ArrayList</code> associated with the <code>key</code>
119 	 * @param key - header
120 	 * @param value - data to be added
121 	 * @return true or false if it was successfully added
122 	 */
123 	public final void addValue(final String key, final double value) {
124 		this.get(key).add(value);
125 	}
126 
127 	public final void incrementPosition() {
128 		currentPosition++;
129 		GenericDataElement.incrementPosition(); // Kinda ugly but...
130 		if (currentPosition >= currentCapacity) {
131 			System.out.println(OpenLogViewer.NEWLINE + "############## Memory about to be resized! ##############");
132 			final long startResizes = System.currentTimeMillis();
133 			System.out.println("Old capacity = " + currentCapacity);
134 			final Runtime ourRuntime = Runtime.getRuntime();
135 
136 			System.out.println("Memory Before = Max: " + ourRuntime.maxMemory() + ", Free: " + ourRuntime.freeMemory() + ", Total: " + ourRuntime.totalMemory());
137 
138 			int numberResized = 0;
139 			final Iterator<GenericDataElement> genLogIterator = this.values().iterator();
140 			while (genLogIterator.hasNext()) {
141 				// Take a stab at detecting impending doom and letting the user know
142 				final long overheadInMemory = currentCapacity * SIZE_OF_DOUBLE;
143 				final long increaseInMemory =  overheadInMemory * ourLoadFactor;
144 
145 				// In order to expand our array we need oldArray bytes + newArray bytes.
146 				// oldArray is already excluded from free memory, so we just need memory for newArray and a fudge factor
147 				final long requiredMemory = 2 * (increaseInMemory + overheadInMemory); // Magic to account for late GC
148 				final long availableMemory = ourRuntime.freeMemory();
149 
150 				if (availableMemory < requiredMemory) {
151 					currentPosition--; // Back out the change because we never achieved it for all fields!
152 					final String jvmHelp = "Get more with -Xms and -Xmx JVM options!";
153 					System.out.println("Detected impending out-of-memory doom! Details below! :-(");
154 					System.out.println("Total" + "Available: " + availableMemory + " Required: " + requiredMemory + " Increase: " + increaseInMemory + " Overhead: " + overheadInMemory);
155 					System.out.println(jvmHelp);
156 					final long allocatedMemory = (ourRuntime.maxMemory() / NUMBER_OF_BYTES_IN_A_MEG);
157 					throw new RuntimeException(allocatedMemory + "MB is insufficent memory to increase log size! " + jvmHelp + OpenLogViewer.NEWLINE
158 							+ "Completed " + numberResized + " of " + numberOfInternalHeaders
159 							+ " while increasing from " + currentCapacity + " records to " + (currentCapacity * ourLoadFactor) + " records!");
160 				}
161 
162 				final GenericDataElement dataElement = genLogIterator.next();
163 				dataElement.increaseCapacity(ourLoadFactor);
164 				numberResized++;
165 			}
166 			currentCapacity *= ourLoadFactor;
167 
168 			System.out.println("Memory After = Max: " + ourRuntime.maxMemory() + ", Free: " + ourRuntime.freeMemory() + ", Total: " + ourRuntime.totalMemory());
169 
170 			final long finishResizes = System.currentTimeMillis();
171 			System.out.println("New capacity = " + currentCapacity);
172 			System.out.println("Resizes took " + (finishResizes - startResizes) + " ms");
173 		}
174 
175 		recordCountElement.add((double) currentPosition);
176 	}
177 
178 	/**
179 	 * Set the state of the log
180 	 * @param newLogStatus GenericLog.LOG_NOT_LOADED / GenericLog.LOG_LOADING / GenericLog.LOG_LOADED
181 	 */
182 	public final void setLogStatus(final LogState newLogStatus) {
183 		final LogState oldLogStatus = this.logStatus;
184 		this.logStatus = newLogStatus;
185 		PCS.firePropertyChange(LOG_LOADED_TEXT, oldLogStatus, newLogStatus);
186 	}
187 
188 	/**
189 	 *
190 	 * @return -1 if log not loaded 0 if loading or 1 if log is loaded
191 	 */
192 	public final LogState getLogStatus() {
193 		return this.logStatus;
194 	}
195 
196 	/**
197 	 * Add metadata This is information about the log being converted such as the location it was from or the date<br>
198 	 * This method does not add to its self so in order to add more info you must VAR.addMetaData(VAR.getMetaData() + NEWINFO)
199 	 * @param md meta data to be added
200 	 */
201 	public final void setMetaData(final String md) {
202 		metaData = md;
203 	}
204 
205 	/**
206 	 *
207 	 * @return String containing the current meta data
208 	 */
209 	public final String getMetadata() {
210 		return metaData;
211 	}
212 
213 	/**
214 	 * Add a property change listener to the generic log, REQUIRED!!
215 	 * GenericLog.LOG_STATUS is the name of the status property
216 	 * @param name
217 	 * @param listener
218 	 * <code>new OBJECT.addPropertyChangeListener("LogLoaded", new PropertyChangeListener() {<br>
219 	 *       public void propertyChange( final PropertyChangeEvent propertyChangeEvent) {<br>
220 	 *           OpenLogViewerApp.getInstance().setLog((GenericLog) propertyChangeEvent.getSource());
221 	 *           ...Insert code here...<br>
222 	 *
223 	 *       }<br>
224 	 *   });</code>
225 	 */
226 	public final void addPropertyChangeListener(final String name, final PropertyChangeListener listener) {
227 		PCS.addPropertyChangeListener(name, listener);
228 	}
229 
230 	/**
231 	 * Remove a PropertyChangeListener
232 	 * @param propertyName name of listener
233 	 * @param listener listener
234 	 */
235 	public final void removePropertyChangeListener(final String propertyName, final PropertyChangeListener listener) {
236 		PCS.removePropertyChangeListener(propertyName, listener);
237 	}
238 
239 	public final String getLogStatusMessage() {
240 		return logStatusMessage;
241 	}
242 
243 	public final void setLogStatusMessage(final String message) {
244 		this.logStatusMessage = message;
245 	}
246 
247 	public final int getRecordCount() {
248 		return currentPosition;
249 	}
250 
251 	public final void clearOut() {
252 		final Iterator<GenericDataElement> lastRound = this.values().iterator();
253 		while (lastRound.hasNext()) {
254 			lastRound.next().clearOut();
255 		}
256 		this.clear();
257 	}
258 }