1 /*******************************************************************************
2  *  Copyright (c) 2000, 2016 IBM Corporation and others.
3  *
4  *  This program and the accompanying materials
5  *  are made available under the terms of the Eclipse Public License 2.0
6  *  which accompanies this distribution, and is available at
7  *  https://www.eclipse.org/legal/epl-2.0/
8  *
9  *  SPDX-License-Identifier: EPL-2.0
10  *
11  *  Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Alena Laskavaia - Bug 453392 - No debug options help
14  *******************************************************************************/
15 package org.eclipse.pde.internal.core;
16 
17 import java.io.BufferedReader;
18 import java.io.File;
19 import java.io.FileInputStream;
20 import java.io.FileOutputStream;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.InputStreamReader;
24 import java.nio.charset.Charset;
25 import java.util.Enumeration;
26 import java.util.HashSet;
27 import java.util.Hashtable;
28 import java.util.Iterator;
29 import java.util.Map;
30 import java.util.Properties;
31 import java.util.zip.ZipEntry;
32 import java.util.zip.ZipFile;
33 import org.eclipse.core.runtime.IPath;
34 import org.eclipse.core.runtime.Path;
35 import org.eclipse.pde.core.plugin.IPluginModelBase;
36 import org.eclipse.pde.core.plugin.PluginRegistry;
37 
38 public class TracingOptionsManager {
39 	private Properties template;
40 
TracingOptionsManager()41 	public TracingOptionsManager() {
42 		super();
43 	}
44 
createTemplate()45 	private Properties createTemplate() {
46 		Properties temp = new Properties();
47 		IPluginModelBase[] models = PluginRegistry.getAllModels();
48 		for (IPluginModelBase model : models) {
49 			addToTemplate(temp, model);
50 		}
51 		return temp;
52 	}
53 
addToTemplate(Properties temp, IPluginModelBase model)54 	private void addToTemplate(Properties temp, IPluginModelBase model) {
55 		Properties modelOptions = getOptions(model);
56 		if (modelOptions != null) {
57 			temp.putAll(modelOptions);
58 		}
59 	}
60 
getTemplateTable(String pluginId)61 	public synchronized Hashtable<String, Object> getTemplateTable(String pluginId) {
62 		if (template == null) {
63 			template = createTemplate();
64 		}
65 		Hashtable<String, Object> defaults = new Hashtable<>();
66 		for (Enumeration<Object> keys = template.keys(); keys.hasMoreElements();) {
67 			String key = keys.nextElement().toString();
68 			if (belongsTo(key, pluginId)) {
69 				defaults.put(key, template.get(key));
70 			}
71 		}
72 		return defaults;
73 	}
74 
belongsTo(String option, String pluginId)75 	private boolean belongsTo(String option, String pluginId) {
76 		IPath path = new Path(option);
77 		String firstSegment = path.segment(0);
78 		return pluginId.equalsIgnoreCase(firstSegment);
79 	}
80 
getTracingOptions(Map<String, String> storedOptions)81 	public Properties getTracingOptions(Map<String, String> storedOptions) {
82 		// Start with the fresh template from plugins
83 		Properties defaults = getTracingTemplateCopy();
84 		if (storedOptions != null) {
85 			// Load stored values, but only for existing keys
86 			Iterator<?> iter = storedOptions.keySet().iterator();
87 			while (iter.hasNext()) {
88 				String key = iter.next().toString();
89 				if (defaults.containsKey(key)) {
90 					defaults.setProperty(key, storedOptions.get(key));
91 				}
92 			}
93 		}
94 		return defaults;
95 	}
96 
getTracingTemplateCopy()97 	public synchronized Properties getTracingTemplateCopy() {
98 		if (template == null) {
99 			template = createTemplate();
100 		}
101 		return (Properties) createTemplate().clone();
102 	}
103 
isTraceable(IPluginModelBase model)104 	public static boolean isTraceable(IPluginModelBase model) {
105 		String location = model.getInstallLocation();
106 		if (location == null) {
107 			return false;
108 		}
109 
110 		File pluginLocation = new File(location);
111 		if (pluginLocation.isDirectory()) {
112 			return new File(pluginLocation, ICoreConstants.OPTIONS_FILENAME).exists();
113 		}
114 		try (ZipFile jarFile = new ZipFile(pluginLocation, ZipFile.OPEN_READ)) {
115 			ZipEntry manifestEntry = jarFile.getEntry(ICoreConstants.OPTIONS_FILENAME);
116 			if (manifestEntry != null) {
117 				try (InputStream stream = jarFile.getInputStream(manifestEntry)) {
118 					return stream != null;
119 				}
120 			}
121 		} catch (IOException e) {
122 		}
123 		return false;
124 	}
125 
reset()126 	public synchronized void reset() {
127 		template = null;
128 	}
129 
save(String fileName, Properties properties)130 	private void save(String fileName, Properties properties) {
131 		try (FileOutputStream stream = new FileOutputStream(fileName);) {
132 			properties.store(stream, "Master Tracing Options"); //$NON-NLS-1$
133 			stream.flush();
134 		} catch (IOException e) {
135 			PDECore.logException(e);
136 		}
137 	}
138 
save(String filename, Map<String, String> map, HashSet<?> selected)139 	public void save(String filename, Map<String, String> map, HashSet<?> selected) {
140 		Properties properties = getTracingOptions(map);
141 		for (Enumeration<?> keys = properties.keys(); keys.hasMoreElements();) {
142 			String key = keys.nextElement().toString();
143 			Path path = new Path(key);
144 			if (path.segmentCount() < 1 || !selected.contains(path.segment(0))) {
145 				properties.remove(key);
146 			}
147 		}
148 		save(filename, properties);
149 	}
150 
save(String filename, Map<String, String> map)151 	public void save(String filename, Map<String, String> map) {
152 		save(filename, getTracingOptions(map));
153 	}
154 
getOptions(IPluginModelBase model)155 	private Properties getOptions(IPluginModelBase model) {
156 		String location = model.getInstallLocation();
157 		if (location == null) {
158 			return null;
159 		}
160 		try {
161 			File pluginLocation = new File(location);
162 			Properties modelOptions = new Properties();
163 			if (pluginLocation.isDirectory()) {
164 				File file = new File(pluginLocation, ICoreConstants.OPTIONS_FILENAME);
165 				if (file.exists()) {
166 					try (InputStream stream = new FileInputStream(file)) {
167 						modelOptions.load(stream);
168 					}
169 					try (InputStream stream = new FileInputStream(file)) {
170 						loadComments(stream, modelOptions);
171 					}
172 					return modelOptions;
173 				}
174 			} else {
175 				try (ZipFile jarFile = new ZipFile(pluginLocation, ZipFile.OPEN_READ)) {
176 					ZipEntry manifestEntry = jarFile.getEntry(ICoreConstants.OPTIONS_FILENAME);
177 					if (manifestEntry != null) {
178 						try (InputStream stream = jarFile.getInputStream(manifestEntry)) {
179 							modelOptions.load(stream);
180 						}
181 						try (InputStream stream = jarFile.getInputStream(manifestEntry)) {
182 							loadComments(stream, modelOptions);
183 						}
184 						return modelOptions;
185 					}
186 				}
187 			}
188 		} catch (IOException e) {
189 			PDECore.logException(e);
190 		}
191 		return null;
192 	}
193 
194 	/**
195 	 * Loads the comments from the properties files. This is simple version
196 	 * which won't cover 100% of the cases but hopefully cover 99%. It will find
197 	 * single or multiline comments starting with # (not !) and attach to the
198 	 * following property key by creating fake property with the key #key and
199 	 * value of the comment. Properties object which will receive these comments
200 	 * cannot be saved back properly without some special handling. It won't
201 	 * support cases when: key is split in multiple lines; key use escape
202 	 * characters; comment uses ! start char
203 	 */
loadComments(InputStream stream, Properties modelOptions)204 	private void loadComments(InputStream stream, Properties modelOptions) throws IOException {
205 		String prevComment = ""; //$NON-NLS-1$
206 		try (BufferedReader bufferedReader = new BufferedReader(
207 				new InputStreamReader(stream, Charset.defaultCharset()))) {
208 			String line;
209 			while ((line = bufferedReader.readLine()) != null) {
210 				if (line.startsWith("#") || line.trim().isEmpty()) { //$NON-NLS-1$
211 					prevComment += "\n" + line.trim(); //$NON-NLS-1$
212 				} else {
213 					if (prevComment.trim().isEmpty()) {
214 						continue;
215 					}
216 
217 					int eq = line.indexOf('=');
218 					if (eq >= 0) {
219 						String key = line.substring(0, eq).trim();
220 						modelOptions.put("#" + key, prevComment); //$NON-NLS-1$
221 					}
222 					prevComment = ""; //$NON-NLS-1$
223 				}
224 			}
225 		}
226 	}
227 }
228