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