View Javadoc

1   /*
2    * This file is a part of CAST project.
3    * (c) Copyright 2007, AGH University of Science & Technology
4    * https://caribou.iisg.agh.edu.pl/trac/cast
5    *
6    * Licensed under the Eclipse Public License, Version 1.0 (the "License").
7    * You may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    * http://www.eclipse.org/legal/epl-v10.html
10   */
11  /*
12   * File: Mapper.java
13   * Created: 2008-07-12
14   * Author: apohllo
15   * $Id: XMLSaver.java 2232 2009-01-04 22:59:53Z apohllo $
16   */
17  
18  package pl.edu.agh.cast.model.mapper.internal;
19  
20  import java.io.BufferedOutputStream;
21  import java.io.BufferedWriter;
22  import java.io.File;
23  import java.io.FileOutputStream;
24  import java.io.FileWriter;
25  import java.io.IOException;
26  import java.io.OutputStream;
27  import java.io.UnsupportedEncodingException;
28  import java.io.Writer;
29  import java.text.DateFormat;
30  import java.text.SimpleDateFormat;
31  import java.util.Date;
32  
33  import org.eclipse.core.resources.IProject;
34  
35  import pl.edu.agh.cast.model.base.BasePlugin;
36  import pl.edu.agh.cast.model.base.IDataSet;
37  import pl.edu.agh.cast.model.base.IModel;
38  import pl.edu.agh.cast.model.base.IRelation;
39  import pl.edu.agh.cast.model.mapper.Mappable;
40  import pl.edu.agh.cast.model.mapper.Mapper;
41  import pl.edu.agh.cast.resources.BaseProjectUtil;
42  
43  /**
44   * Limited version of base model saver - allows only saving of DataSets. The DataSets are stored as separate XML files
45   * in their project's directory. The saved structure is limited - any links which go beyond DataSet - Relation - Entity
46   * relationships are discarded.
47   *
48   * The identifiers of mappable elements are unique only within the DataSet.
49   *
50   * @author AGH CAST Team
51   *
52   */
53  public class XMLSaver extends AbstractSaver {
54  
55  	/**
56       *
57       */
58  	private static final String QUOTE_W_SPACE = "' "; //$NON-NLS-1$
59  
60  	/**
61       *
62       */
63  	private static final String GT_W_NEW_LINE = ">\n"; //$NON-NLS-1$
64  
65  	/**
66  	 * Tag fragment: lower than with slash
67  	 */
68  	private static final String LT_W_SLASH = "</"; //$NON-NLS-1$
69  
70  	/**
71  	 * Tag fragment: quote + greater than + new line
72  	 */
73  	private static final String GT_W_QUOTE_AND_NEW_LINE = "'>\n"; //$NON-NLS-1$
74  
75  	/**
76  	 * Tag fragment: equals and quote
77  	 */
78  	private static final String EQUAL_W_QUOTE = "='"; //$NON-NLS-1$
79  
80  	/**
81  	 * Tag fragment: single space
82  	 */
83  	private static final String SINGLE_SPACE = " "; //$NON-NLS-1$
84  
85  	/**
86  	 * Tag fragment: lower than
87  	 */
88  	private static final String LT = "<"; //$NON-NLS-1$
89  
90  	/**
91  	 * The name of the file where model properties are stored.
92  	 */
93  	public static final String MODEL_PROPS = "project.props"; //$NON-NLS-1$
94  
95  	private static final String UTF_8 = "UTF-8"; //$NON-NLS-1$
96  
97  	private static final String ISO_8859_2 = "ISO-8859-2"; //$NON-NLS-1$
98  
99  	private static final String CP_1250 = "CP1250"; //$NON-NLS-1$
100 
101 	/**
102 	 * The extension of the model file.
103 	 */
104 	public static final String EXTENSION = ".xml"; //$NON-NLS-1$
105 
106 	/**
107 	 * The name of the directory where the project is stored.
108 	 */
109 	public static final String ROOT = "model"; //$NON-NLS-1$
110 
111 	/**
112 	 * The name of the field which holds the type of the node.
113 	 */
114 	public static final String TYPE = "type"; //$NON-NLS-1$
115 
116 	/**
117 	 * The name of the field which holds the id of the node.
118 	 */
119 	public static final String ID = "id"; //$NON-NLS-1$
120 
121 	/**
122 	 * The format of the date.
123 	 */
124 	public static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //$NON-NLS-1$
125 
126 	/**
127 	 * The name of the field used to indicate the source of the relation.
128 	 */
129 	public static final String SOURCE = "src"; //$NON-NLS-1$
130 
131 	/**
132 	 * The name of the field used to indicate the target of the relation.
133 	 */
134 	public static final String TARGET = "dst"; //$NON-NLS-1$
135 
136 	private StringBuilder buffer = new StringBuilder();
137 
138 	private long lastId = 1;
139 
140 	private IProject project;
141 
142 	private OutputStream file;
143 
144 	/**
145 	 * The parameterized constructor.
146 	 *
147 	 * @param object
148 	 *            The object to save.
149 	 * @param force
150 	 *            The force flag.
151 	 * @param project
152 	 *            The project which is owner of the object.
153 	 */
154 	public XMLSaver(Mappable object, boolean force, IProject project) {
155 		super(object, force);
156 		this.project = project;
157 	}
158 
159 	/**
160 	 *
161 	 * {@inheritDoc}
162 	 *
163 	 * @see pl.edu.agh.cast.model.mapper.Saver#save()
164 	 */
165 	public boolean save() {
166 		try {
167 			if (primaryObject instanceof IDataSet) {
168 				IDataSet dataSet = (IDataSet)primaryObject;
169 				saveModelProps(dataSet.getModel());
170 				save(dataSet);
171 			} else if (primaryObject instanceof IModel) {
172 				saveModelProps((IModel)primaryObject);
173 				for (IDataSet dataSet : ((IModel)primaryObject).getDataSets()) {
174 					save(dataSet);
175 				}
176 			}
177 			return true;
178 		} catch (Exception e) {
179 			e.printStackTrace();
180 			return false;
181 		}
182 	}
183 
184 	private void saveModelProps(IModel model) throws IOException {
185 		if (project != null) {
186 			BaseProjectUtil.setProperty(project, BaseProjectUtil.PROJECT_ID_PROPERTY, model.getName());
187 		} else {
188 			// this solution is used only in tests, when there is
189 			// no instance of Eclipse environment
190 			Writer projectProps = new BufferedWriter(new FileWriter(dirFor(project) + MODEL_PROPS));
191 			projectProps.write(model.getName());
192 			projectProps.close();
193 		}
194 	}
195 
196 	private void save(IDataSet dataSet) throws Exception {
197 		file = new BufferedOutputStream(new FileOutputStream(fileFor(project, dataSet.getId())));
198 		buffer.append("<?xml version='1.0' ?>\n"); //$NON-NLS-1$
199 		buffer.append(LT);
200 		buffer.append(ROOT);
201 		buffer.append(SINGLE_SPACE);
202 		buffer.append(ID);
203 		buffer.append(EQUAL_W_QUOTE);
204 		buffer.append(dataSet.getModel().getId());
205 		buffer.append(GT_W_QUOTE_AND_NEW_LINE);
206 		store(null);
207 		saveMappable(dataSet);
208 		for (IRelation relation : dataSet.getRelations()) {
209 			saveMappable(relation.getSource());
210 			saveMappable(relation.getTarget());
211 			saveRelation(relation);
212 		}
213 		buffer.append(LT_W_SLASH);
214 		buffer.append(ROOT);
215 		buffer.append(GT_W_NEW_LINE);
216 		store(null);
217 		file.close();
218 	}
219 
220 	/**
221 	 * For given project returns the directory where the model is stored.
222 	 *
223 	 * @param project
224 	 *            The project name.
225 	 * @return The directory object.
226 	 */
227 	public static String dirFor(IProject project) {
228 		String path = getProjectPath(project);
229 		File dir = new File(path);
230 		if (!dir.exists()) {
231 			dir.mkdir();
232 		}
233 		if (!path.substring(path.length() - 1).equals(File.separator)) {
234 			path += File.separator;
235 		}
236 		return path;
237 	}
238 
239 	/**
240 	 * Returns the path to the file which holds data set with given id.
241 	 *
242 	 * @param project
243 	 *            the project where the data set is stored.
244 	 * @param id
245 	 *            The id of the data set.
246 	 * @return The path to the file.
247 	 */
248 	public static String fileFor(IProject project, String id) {
249 		// seems stupid? thanks to ingenious Windows (r) path separator
250 		return dirFor(project) + id.replaceAll("[^a-zA-Z0-9_]", "_") + EXTENSION; //$NON-NLS-1$ //$NON-NLS-2$
251 	}
252 
253 	private static String getProjectPath(IProject project) {
254 		if (project == null) {
255 			return Mapper.DEFAULT_PATH;
256 		} else {
257 			return project.getLocation().toOSString() + File.separator + Mapper.DEFAULT_FOLDER;
258 		}
259 
260 	}
261 
262 	private void saveMappable(Mappable mappable) throws Exception {
263 		if (stored.contains(mappable)) {
264 			return;
265 		}
266 		buffer.append(LT);
267 		buffer.append(Helper.mappedTypeName(mappable.getClass()));
268 		buffer.append(SINGLE_SPACE);
269 		buffer.append(ID);
270 		buffer.append(EQUAL_W_QUOTE);
271 		mappable.setMid(lastId);
272 		buffer.append(lastId++);
273 		buffer.append(GT_W_QUOTE_AND_NEW_LINE);
274 		appendAttributesXML(mappable, mappable.getClass());
275 		buffer.append(LT_W_SLASH);
276 		buffer.append(Helper.mappedTypeName(mappable.getClass()));
277 		buffer.append(GT_W_NEW_LINE);
278 		store(mappable);
279 	}
280 
281 	private void saveRelation(IRelation relation) throws Exception {
282 		if (stored.contains(relation)) {
283 			return;
284 		}
285 		buffer.append(LT);
286 		buffer.append(Helper.mappedTypeName(relation.getClass()));
287 		buffer.append(SINGLE_SPACE);
288 		buffer.append(ID);
289 		buffer.append(EQUAL_W_QUOTE);
290 		relation.setMid(lastId);
291 		buffer.append(lastId++);
292 		buffer.append(QUOTE_W_SPACE);
293 		buffer.append(SOURCE);
294 		buffer.append(EQUAL_W_QUOTE);
295 		buffer.append(relation.getSource().getMid());
296 		buffer.append(QUOTE_W_SPACE);
297 		buffer.append(TARGET);
298 		buffer.append(EQUAL_W_QUOTE);
299 		buffer.append(relation.getTarget().getMid());
300 		buffer.append(GT_W_QUOTE_AND_NEW_LINE);
301 		appendAttributesXML(relation, relation.getClass());
302 		buffer.append(LT_W_SLASH);
303 		buffer.append(Helper.mappedTypeName(relation.getClass()));
304 		buffer.append(GT_W_NEW_LINE);
305 		store(relation);
306 	}
307 
308 	private void store(Mappable mappable) throws IOException {
309 		// System.out.println(_buffer.toString());
310 		String value = buffer.toString();
311 		buffer.setLength(0);
312 		value = convertToUTF(value);
313 		file.write(value.getBytes(UTF_8));
314 		if (mappable != null) {
315 			stored.add(mappable);
316 		}
317 	}
318 
319 	private String convertToUTF(String value) {
320 		String valueCopy = value + ""; //$NON-NLS-1$
321 		boolean shouldConvert = false;
322 		// the opposite is ISO-8859-2
323 		boolean windowsEncoding = false;
324 		for (byte byteValue : valueCopy.getBytes()) {
325 			if (byteValue < 0) {
326 				int code = 256 + byteValue;
327 				// UTF codes - if found, stop analysis
328 				// XXX && and || not in parenthesis seems to be bug
329 				if (code == 195 || code == 196 || code == 197 && !shouldConvert) {
330 					break;
331 				}
332 				// WIN and ISO common codes
333 				if (code == 191 || code == 243 || code == 179 || code == 230 || code == 234 || code == 241
334 				        || code == 175 || code == 211 || code == 163 || code == 198 || code == 202 || code == 209) {
335 					shouldConvert = true;
336 				}
337 				// strict ISO codes
338 				if (code == 182 || code == 177 || code == 188 || code == 166 || code == 161 || code == 172) {
339 					shouldConvert = true;
340 					break;
341 				}
342 				if (code == 156 || code == 185 || code == 159 || code == 140 || code == 165 || code == 143) {
343 					shouldConvert = true;
344 					windowsEncoding = true;
345 					break;
346 				}
347 			}
348 		}
349 		try {
350 			if (shouldConvert) {
351 				if (windowsEncoding) {
352 					valueCopy = new String(value.getBytes(), CP_1250);
353 				} else {
354 					valueCopy = new String(value.getBytes(), ISO_8859_2);
355 				}
356 			}
357 		} catch (UnsupportedEncodingException ex) {
358 			BasePlugin.getLogger().error("Cannot save file as UTF", ex); //$NON-NLS-1$
359 			return valueCopy;
360 		}
361 		return valueCopy;
362 	}
363 
364 	private void appendAttributesXML(Mappable mappable, Class klass) throws Exception {
365 		if (Helper.isSuperMapped(klass)) {
366 			appendAttributesXML(mappable, klass.getSuperclass());
367 		}
368 		for (AttributeEntry entry : Helper.getMappedAttributes(klass)) {
369 			Object value = entry.getMethod().invoke(mappable);
370 			String attributeName = entry.getAnnotation().name();
371 			if (attributeName.equals("")) { //$NON-NLS-1$
372 				attributeName = entry.getMethod().getName();
373 			}
374 			buffer.append(LT);
375 			buffer.append(attributeName);
376 			buffer.append(SINGLE_SPACE);
377 			buffer.append(TYPE);
378 			buffer.append(EQUAL_W_QUOTE);
379 			buffer.append(entry.getMethod().getReturnType().getCanonicalName());
380 			buffer.append("'>"); //$NON-NLS-1$
381 			if (value instanceof Date) {
382 				buffer.append(DATE_FORMAT.format((Date)value));
383 			} else {
384 				buffer.append(value);
385 			}
386 			buffer.append(LT_W_SLASH);
387 			buffer.append(attributeName);
388 			buffer.append(GT_W_NEW_LINE);
389 		}
390 	}
391 }