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 java.util.Arrays;
31  import java.util.ResourceBundle;
32  
33  import org.diyefi.openlogviewer.Keys;
34  import org.diyefi.openlogviewer.OpenLogViewer;
35  import org.diyefi.openlogviewer.Text;
36  import org.diyefi.openlogviewer.coloring.InitialLineColoring;
37  
38  public class GenericLog extends LinkedHashMap<String, GenericDataElement> {
39  	public static final String RECORD_COUNT_KEY = "OLV Record Count"; // Fixed references, not for translation
40  	public static final String tempResetKey = "OLV Temp Resets";      // Fixed references, not for translation
41  	public static final String elapsedTimeKey = "OLV Elapsed Time";   // Fixed references, not for translation
42  
43  	// 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
44  	// For streams, always show that as a way of saying "still streaming!"
45  	public static enum LogState { LOG_NOT_LOADED, LOG_LOADING, LOG_LOADED }
46  
47  	private static final long serialVersionUID = 1L;
48  
49  	// Info to populate built-in fields efficiently, likely to be done differently in future, but if not, put this in some structure.
50  	private static final int NUMBER_OF_BUILTIN_FIELDS = 3; // See below:
51  	private static final int RECORD_COUNT_OFFSET = 0;
52  	private static final int TEMP_RESET_OFFSET   = 1;
53  	private static final int ELAPSED_TIME_OFFSET = 2;
54  	private static final int SIZE_OF_DOUBLE = 8;
55  	private static final int NUMBER_OF_BYTES_IN_A_MEG = 1000000;
56  
57  	private final ResourceBundle labels;
58  	private final GenericDataElement recordCountElement;
59  	private final PropertyChangeSupport pcs;
60  	private final int ourLoadFactor;
61  	private final int numberOfInternalHeaders;
62  
63  	private LogState logStatus;
64  	private String logStatusMessage;
65  
66  	// Track the size of our children so that we can bump them up one by one where required
67  	private int currentCapacity;
68  	private int currentPosition = -1;
69  	// ^ TODO if we end up limiting memory usage by some configurable amount and recycling positions, for live streaming, then add count
70  
71  	private final PropertyChangeListener autoLoad = new PropertyChangeListener() {
72  		public void propertyChange(final PropertyChangeEvent propertyChangeEvent) {
73  			if ((LogState) propertyChangeEvent.getNewValue() == LogState.LOG_LOADING) {
74  				final GenericLog genLog = (GenericLog) propertyChangeEvent.getSource();
75  				genLog.setLogStatus(LogState.LOG_LOADING);
76  				OpenLogViewer.getInstance().setLog(genLog);
77  			} else if ((LogState) propertyChangeEvent.getNewValue() == LogState.LOG_LOADED) {
78  				final GenericLog genLog = (GenericLog) propertyChangeEvent.getSource();
79  				genLog.setLogStatus(LogState.LOG_LOADED);
80  				OpenLogViewer.getInstance().setLog(genLog);
81  				OpenLogViewer.getInstance().getOptionFrame().updateFromLog(genLog);
82  				InitialLineColoring.INSTANCE.giveBackAllColors();
83  			}
84  		}
85  	};
86  
87  	/**
88  	 * provide a <code>String</code> array of headers<br>
89  	 * each header will be used as a HashMap key, the data related to each header will be added to an <code>ArrayList</code>.
90  	 * @param headers - of the data to be converted
91  	 */
92  	public GenericLog(final String[] headers, final int initialCapacity, final int ourLoadFactor, final ResourceBundle labels) {
93  		super(1 + (headers.length + NUMBER_OF_BUILTIN_FIELDS), 1.0f); // refactor to use (capacityRequired+1, 1.0) for maximum performance (no rehashing)
94  
95  		this.labels = labels;
96  
97  		GenericDataElement.resetPosition(); // Kinda ugly, but...
98  		logStatus = LogState.LOG_NOT_LOADED;
99  		pcs = new PropertyChangeSupport(this);
100 		addPropertyChangeListener(Keys.LOG_LOADED, autoLoad);
101 
102 		this.ourLoadFactor = ourLoadFactor;
103 		currentCapacity = initialCapacity;
104 
105 		// A bit dirty, but not too bad.
106 		numberOfInternalHeaders = headers.length + NUMBER_OF_BUILTIN_FIELDS;
107 		final String[] internalHeaders = Arrays.copyOf(headers, numberOfInternalHeaders);
108 
109 		// If this stays like this, move it to a structure and small loop...
110 		internalHeaders[headers.length + RECORD_COUNT_OFFSET] = RECORD_COUNT_KEY;
111 		internalHeaders[headers.length + TEMP_RESET_OFFSET] = tempResetKey;
112 		internalHeaders[headers.length + ELAPSED_TIME_OFFSET] = elapsedTimeKey;
113 
114 		for (int x = 0; x < internalHeaders.length; x++) {
115 			final GenericDataElement gde = new GenericDataElement(initialCapacity);
116 			gde.setName(internalHeaders[x]);
117 			this.put(internalHeaders[x], gde);
118 		}
119 
120 		recordCountElement = this.get(RECORD_COUNT_KEY);
121 	}
122 
123 	/**
124 	 * Add a piece of data to the <code>ArrayList</code> associated with the <code>key</code>
125 	 * @param key - header
126 	 * @param value - data to be added
127 	 */
128 	public final void addValue(final String key, final double value) {
129 		get(key).add(value);
130 	}
131 
132 	public final void incrementPosition() {
133 		currentPosition++;
134 		GenericDataElement.incrementPosition(); // Kinda ugly but...
135 		if (currentPosition >= currentCapacity) {
136 			System.out.println(OpenLogViewer.NEWLINE + labels.getString(Text.MEMORY_RESIZE_WARNING));
137 			final long startResizes = System.currentTimeMillis();
138 			System.out.println(labels.getString(Text.MEMORY_OLD_CAPACITY) + currentCapacity);
139 			final Runtime ourRuntime = Runtime.getRuntime();
140 
141 			System.out.println(labels.getString(Text.MEMORY_BEFORE)
142 					+ labels.getString(Text.MEMORY_MAX) + ourRuntime.maxMemory()
143 					+ labels.getString(Text.MEMORY_FREE) + ourRuntime.freeMemory()
144 					+ labels.getString(Text.MEMORY_TOTAL) + ourRuntime.totalMemory());
145 
146 			int numberResized = 0;
147 			final Iterator<GenericDataElement> genLogIterator = this.values().iterator();
148 			while (genLogIterator.hasNext()) {
149 				// Take a stab at detecting impending doom and letting the user know
150 				final long overheadInMemory = currentCapacity * SIZE_OF_DOUBLE;
151 				final long increaseInMemory =  overheadInMemory * ourLoadFactor;
152 
153 				// In order to expand our array we need oldArray bytes + newArray bytes.
154 				// oldArray is already excluded from free memory, so we just need memory for newArray and a fudge factor
155 				final long requiredMemory = 2 * (increaseInMemory + overheadInMemory); // Magic to account for late GC
156 				final long availableMemory = ourRuntime.freeMemory();
157 
158 				if (availableMemory < requiredMemory) {
159 					currentPosition--; // Back out the change because we never achieved it for all fields!
160 					System.out.println(labels.getString(Text.DETECTED_OOME_MEMORY_DOOM));
161 					System.out.println(labels.getString(Text.MEMORY_TOTAL_AVAILABLE) + availableMemory
162 							+ labels.getString(Text.MEMORY_TOTAL_REQUIRED) + requiredMemory
163 							+ labels.getString(Text.MEMORY_TOTAL_INCREASE) + increaseInMemory
164 							+ labels.getString(Text.MEMORY_TOTAL_OVERHEAD) + overheadInMemory);
165 					System.out.println(labels.getString(Text.JVM_HELP_MESSAGE));
166 					final long allocatedMemory = (ourRuntime.maxMemory() / NUMBER_OF_BYTES_IN_A_MEG);
167 					throw new RuntimeException(allocatedMemory + labels.getString(Text.MEG_INSUFFICIENT_MEMORY_FOR_INCREASE)
168 							+ labels.getString(Text.JVM_HELP_MESSAGE) + OpenLogViewer.NEWLINE
169 							+ labels.getString(Text.RESIZE_COMPLETED_PART1) + numberResized
170 							+ labels.getString(Text.RESIZE_COMPLETED_PART2) + numberOfInternalHeaders
171 							+ labels.getString(Text.RESIZE_COMPLETED_PART3) + currentCapacity
172 							+ labels.getString(Text.RESIZE_COMPLETED_PART4) + (currentCapacity * ourLoadFactor)
173 							+ labels.getString(Text.RESIZE_COMPLETED_PART5));
174 				}
175 
176 				final GenericDataElement dataElement = genLogIterator.next();
177 				dataElement.increaseCapacity(ourLoadFactor);
178 				numberResized++;
179 			}
180 			currentCapacity *= ourLoadFactor;
181 
182 			System.out.println(labels.getString(Text.MEMORY_AFTER)
183 					+ labels.getString(Text.MEMORY_MAX) + ourRuntime.maxMemory()
184 					+ labels.getString(Text.MEMORY_FREE) + ourRuntime.freeMemory()
185 					+ labels.getString(Text.MEMORY_TOTAL) + ourRuntime.totalMemory());
186 
187 			final long finishResizes = System.currentTimeMillis();
188 			System.out.println(labels.getString(Text.MEMORY_NEW_CAPACITY) + currentCapacity);
189 			System.out.println(labels.getString(Text.MEMORY_RESIZES_TOOK)
190 					+ (finishResizes - startResizes)
191 					+ labels.getString(Text.MEMORY_RESIZES_UNIT));
192 		}
193 
194 		recordCountElement.add((double) currentPosition);
195 	}
196 
197 	/**
198 	 * Set the state of the log
199 	 * @param newLogStatus GenericLog.LOG_NOT_LOADED / GenericLog.LOG_LOADING / GenericLog.LOG_LOADED
200 	 */
201 	public final void setLogStatus(final LogState newLogStatus) {
202 		final LogState oldLogStatus = this.logStatus;
203 		this.logStatus = newLogStatus;
204 		pcs.firePropertyChange(Keys.LOG_LOADED, oldLogStatus, newLogStatus);
205 	}
206 
207 	/**
208 	 *
209 	 * @return -1 if log not loaded 0 if loading or 1 if log is loaded
210 	 */
211 	public final LogState getLogStatus() {
212 		return this.logStatus;
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 final void setLogStatusMessage(final 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 }