View Javadoc
1   /**
2    * 
3    * Copyright (c) 2014-2015, Openflexo
4    * 
5    * This file is part of Flexodiagram, a component of the software infrastructure 
6    * developed at Openflexo.
7    * 
8    * 
9    * Openflexo is dual-licensed under the European Union Public License (EUPL, either 
10   * version 1.1 of the License, or any later version ), which is available at 
11   * https://joinup.ec.europa.eu/software/page/eupl/licence-eupl
12   * and the GNU General Public License (GPL, either version 3 of the License, or any 
13   * later version), which is available at http://www.gnu.org/licenses/gpl.html .
14   * 
15   * You can redistribute it and/or modify under the terms of either of these licenses
16   * 
17   * If you choose to redistribute it and/or modify under the terms of the GNU GPL, you
18   * must include the following additional permission.
19   *
20   *          Additional permission under GNU GPL version 3 section 7
21   *
22   *          If you modify this Program, or any covered work, by linking or 
23   *          combining it with software containing parts covered by the terms 
24   *          of EPL 1.0, the licensors of this Program grant you additional permission
25   *          to convey the resulting work. * 
26   * 
27   * This software is distributed in the hope that it will be useful, but WITHOUT ANY 
28   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
29   * PARTICULAR PURPOSE. 
30   *
31   * See http://www.openflexo.org/license.html for details.
32   * 
33   * 
34   * Please contact Openflexo (openflexo-contacts@openflexo.org)
35   * or visit www.openflexo.org if you need additional information.
36   * 
37   */
38  
39  package org.openflexo.foundation.doc;
40  
41  import java.util.logging.Logger;
42  
43  import org.openflexo.foundation.FlexoObject;
44  import org.openflexo.foundation.doc.FlexoDocFragment.FragmentConsistencyException;
45  import org.openflexo.foundation.technologyadapter.TechnologyAdapter;
46  import org.openflexo.pamela.annotations.Getter;
47  import org.openflexo.pamela.annotations.ImplementationClass;
48  import org.openflexo.pamela.annotations.ModelEntity;
49  import org.openflexo.pamela.annotations.PropertyIdentifier;
50  import org.openflexo.pamela.annotations.Setter;
51  import org.openflexo.pamela.annotations.XMLAttribute;
52  import org.openflexo.pamela.annotations.XMLElement;
53  import org.openflexo.toolbox.StringUtils;
54  
55  /**
56   * This class represent a text selection in a document fragment.<br>
57   * 
58   * Because of the structure of a {@link FlexoDocument}, a selection might be seen as a consecutive list of runs to consider, with eventually
59   * a begin index for the first run and a end index for the last run.<br>
60   * A {@link TextSelection} might be defined in a single paragraph (start and end element are then the same), or in multiple consecutive
61   * paragraphs.
62   * 
63   * More formally, a {@link TextSelection} is identified by:
64   * <ul>
65   * <li>start document element</li>
66   * <li>start run index (in the first document element)</li>
67   * <li>start character index (in the start run)</li>
68   * <li>end document element</li>
69   * <li>end run index (in the last document element)</li>
70   * <li>end character index (in the start run)</li>
71   * </ul>
72   * A {@link TextSelection} MUST reference a valid {@link FlexoDocFragment} containing the text selection
73   * 
74   * @author sylvain
75   * 
76   */
77  @ModelEntity
78  @ImplementationClass(TextSelection.TextSelectionImpl.class)
79  @XMLElement
80  public interface TextSelection<D extends FlexoDocument<D, TA>, TA extends TechnologyAdapter<TA>> extends FlexoObject {
81  
82  	@PropertyIdentifier(type = FlexoDocFragment.class)
83  	public static final String FRAGMENT_KEY = "fragment";
84  
85  	@PropertyIdentifier(type = FlexoDocElement.class)
86  	public static final String START_ELEMENT_KEY = "startElement";
87  	@PropertyIdentifier(type = String.class)
88  	public static final String START_ELEMENT_IDENTIFIER_KEY = "startElementId";
89  	@PropertyIdentifier(type = FlexoDocElement.class)
90  	public static final String END_ELEMENT_KEY = "endElement";
91  	@PropertyIdentifier(type = String.class)
92  	public static final String END_ELEMENT_IDENTIFIER_KEY = "endElementId";
93  	@PropertyIdentifier(type = Integer.class)
94  	public static final String START_RUN_INDEX_KEY = "startRunId";
95  	@PropertyIdentifier(type = Integer.class)
96  	public static final String END_RUN_INDEX_KEY = "endRunId";
97  	@PropertyIdentifier(type = Integer.class)
98  	public static final String START_CHARACTER_INDEX_KEY = "startCharId";
99  	@PropertyIdentifier(type = Integer.class)
100 	public static final String END_CHARACTER_INDEX_KEY = "endCharId";
101 
102 	/**
103 	 * Return the fragment in which this {@link TextSelection} is valid
104 	 * 
105 	 * @return
106 	 */
107 	@Getter(FRAGMENT_KEY)
108 	public FlexoDocFragment<D, TA> getFragment();
109 
110 	@Setter(FRAGMENT_KEY)
111 	public void setFragment(FlexoDocFragment<D, TA> fragment);
112 
113 	@Getter(START_ELEMENT_KEY)
114 	public FlexoDocElement<D, TA> getStartElement();
115 
116 	@Setter(START_ELEMENT_KEY)
117 	public void setStartElement(FlexoDocElement<D, TA> startElement);
118 
119 	@Getter(START_ELEMENT_IDENTIFIER_KEY)
120 	@XMLAttribute
121 	public String getStartElementIdentifier();
122 
123 	@Setter(START_ELEMENT_IDENTIFIER_KEY)
124 	public void setStartElementIdentifier(String startElementIdentifier);
125 
126 	@Getter(END_ELEMENT_KEY)
127 	public FlexoDocElement<D, TA> getEndElement();
128 
129 	@Setter(END_ELEMENT_KEY)
130 	public void setEndElement(FlexoDocElement<D, TA> endElement);
131 
132 	@Getter(END_ELEMENT_IDENTIFIER_KEY)
133 	@XMLAttribute
134 	public String getEndElementIdentifier();
135 
136 	@Setter(END_ELEMENT_IDENTIFIER_KEY)
137 	public void setEndElementIdentifier(String endElementIdentifier);
138 
139 	@Getter(value = START_RUN_INDEX_KEY, defaultValue = "-1")
140 	@XMLAttribute
141 	public int getStartRunIndex();
142 
143 	@Setter(START_RUN_INDEX_KEY)
144 	public void setStartRunIndex(int startRunIndex);
145 
146 	@Getter(value = END_RUN_INDEX_KEY, defaultValue = "-1")
147 	@XMLAttribute
148 	public int getEndRunIndex();
149 
150 	@Setter(END_RUN_INDEX_KEY)
151 	public void setEndRunIndex(int endRunIndex);
152 
153 	@Getter(value = START_CHARACTER_INDEX_KEY, defaultValue = "-1")
154 	@XMLAttribute
155 	public int getStartCharacterIndex();
156 
157 	@Setter(START_CHARACTER_INDEX_KEY)
158 	public void setStartCharacterIndex(int startRunIndex);
159 
160 	@Getter(value = END_CHARACTER_INDEX_KEY, defaultValue = "-1")
161 	@XMLAttribute
162 	public int getEndCharacterIndex();
163 
164 	@Setter(END_CHARACTER_INDEX_KEY)
165 	public void setEndCharacterIndex(int endRunIndex);
166 
167 	/**
168 	 * Return {@link FlexoDocRun} where first character of this {@link TextSelection} is defined
169 	 * 
170 	 * @return
171 	 */
172 	public FlexoDocRun<D, TA> getStartRun();
173 
174 	/**
175 	 * Return {@link FlexoDocRun} where last character of this {@link TextSelection} is defined
176 	 * 
177 	 * @return
178 	 */
179 	public FlexoDocRun<D, TA> getEndRun();
180 
181 	/**
182 	 * Return string raw representation of text beeing selected<br>
183 	 * Returned text is build accross document structure reflected in the fragment
184 	 * 
185 	 * @return
186 	 */
187 	public String getRawText();
188 
189 	/**
190 	 * Indicates if this {@link TextSelection} concerns a single paragraph
191 	 * 
192 	 * @return
193 	 */
194 	public boolean isSingleParagraph();
195 
196 	/**
197 	 * Indicates if this {@link TextSelection} concerns a single run in a single paragraph
198 	 * 
199 	 * @return
200 	 */
201 	public boolean isSingleRun();
202 
203 	public static abstract class TextSelectionImpl<D extends FlexoDocument<D, TA>, TA extends TechnologyAdapter<TA>> extends FlexoObjectImpl
204 			implements TextSelection<D, TA> {
205 
206 		@SuppressWarnings("unused")
207 		private static final Logger logger = Logger.getLogger(TextSelection.class.getPackage().getName());
208 
209 		private FlexoDocElement<D, TA> startElement = null;
210 		private String startElementIdentifier = null;
211 		private FlexoDocElement<D, TA> endElement = null;
212 		private String endElementIdentifier = null;
213 
214 		@Override
215 		public String getStartElementIdentifier() {
216 			if (getStartElement() != null) {
217 				return getStartElement().getIdentifier();
218 			}
219 			return startElementIdentifier;
220 		}
221 
222 		@Override
223 		public void setStartElementIdentifier(String startElementIdentifier) {
224 
225 			if ((startElementIdentifier == null && this.startElementIdentifier != null)
226 					|| (startElementIdentifier != null && !startElementIdentifier.equals(this.startElementIdentifier))) {
227 				FlexoDocElement<D, TA> oldStartElement = getStartElement();
228 				String oldStartElementId = getStartElementIdentifier();
229 				this.startElementIdentifier = startElementIdentifier;
230 				this.startElement = null;
231 				getPropertyChangeSupport().firePropertyChange(START_ELEMENT_IDENTIFIER_KEY, oldStartElementId, startElementIdentifier);
232 				getPropertyChangeSupport().firePropertyChange(START_ELEMENT_KEY, oldStartElement, getStartElement());
233 			}
234 		}
235 
236 		@Override
237 		public FlexoDocElement<D, TA> getStartElement() {
238 			if (startElement == null && startElementIdentifier != null && getFragment() != null) {
239 				startElement = getFragment().getFlexoDocument().getElementWithIdentifier(startElementIdentifier);
240 			}
241 			return startElement;
242 		}
243 
244 		@Override
245 		public void setStartElement(FlexoDocElement<D, TA> startElement) {
246 			if (startElement != this.startElement) {
247 				FlexoDocElement<D, TA> oldValue = this.startElement;
248 				this.startElement = startElement;
249 				getPropertyChangeSupport().firePropertyChange(START_ELEMENT_KEY, oldValue, startElement);
250 				getPropertyChangeSupport().firePropertyChange(START_ELEMENT_IDENTIFIER_KEY,
251 						oldValue != null ? oldValue.getIdentifier() : null, getStartElementIdentifier());
252 			}
253 		}
254 
255 		@Override
256 		public String getEndElementIdentifier() {
257 			if (getEndElement() != null) {
258 				return getEndElement().getIdentifier();
259 			}
260 			return endElementIdentifier;
261 		}
262 
263 		@Override
264 		public void setEndElementIdentifier(String endElementIdentifier) {
265 
266 			if ((endElementIdentifier == null && this.endElementIdentifier != null)
267 					|| (endElementIdentifier != null && !endElementIdentifier.equals(this.endElementIdentifier))) {
268 				FlexoDocElement<D, TA> oldEndElement = getEndElement();
269 				String oldEndElementId = getEndElementIdentifier();
270 				this.endElementIdentifier = endElementIdentifier;
271 				this.endElement = null;
272 				getPropertyChangeSupport().firePropertyChange(END_ELEMENT_IDENTIFIER_KEY, oldEndElementId, endElementIdentifier);
273 				getPropertyChangeSupport().firePropertyChange(END_ELEMENT_KEY, oldEndElement, getEndElement());
274 			}
275 		}
276 
277 		@Override
278 		public FlexoDocElement<D, TA> getEndElement() {
279 			if (endElement == null && endElementIdentifier != null && getFragment() != null) {
280 				endElement = getFragment().getFlexoDocument().getElementWithIdentifier(endElementIdentifier);
281 			}
282 			return endElement;
283 		}
284 
285 		@Override
286 		public void setEndElement(FlexoDocElement<D, TA> endElement) {
287 			if (endElement != this.endElement) {
288 				FlexoDocElement<D, TA> oldValue = this.endElement;
289 				this.endElement = endElement;
290 				getPropertyChangeSupport().firePropertyChange(END_ELEMENT_KEY, oldValue, endElement);
291 				getPropertyChangeSupport().firePropertyChange(END_ELEMENT_IDENTIFIER_KEY,
292 						oldValue != null ? oldValue.getIdentifier() : null, getEndElementIdentifier());
293 			}
294 		}
295 
296 		/**
297 		 * Return {@link FlexoDocRun} where first character of this {@link TextSelection} is defined
298 		 * 
299 		 * @return
300 		 */
301 		@Override
302 		public FlexoTextRun<D, TA> getStartRun() {
303 			if (getStartElement() instanceof FlexoDocParagraph && (getStartRunIndex() == -1)) {
304 				FlexoDocRun<D, TA> returned = ((FlexoDocParagraph<D, TA>) getStartElement()).getRuns().get(0);
305 				if (returned instanceof FlexoTextRun) {
306 					return (FlexoTextRun<D, TA>) returned;
307 				}
308 			}
309 			if (getStartElement() instanceof FlexoDocParagraph && (getStartRunIndex() >= 0)
310 					&& (getStartRunIndex() < ((FlexoDocParagraph<D, TA>) getStartElement()).getRuns().size())) {
311 				FlexoDocRun<D, TA> returned = ((FlexoDocParagraph<D, TA>) getStartElement()).getRuns().get(getStartRunIndex());
312 				if (returned instanceof FlexoTextRun) {
313 					return (FlexoTextRun<D, TA>) returned;
314 				}
315 			}
316 			return null;
317 		}
318 
319 		/**
320 		 * Return {@link FlexoDocRun} where last character of this {@link TextSelection} is defined
321 		 * 
322 		 * @return
323 		 */
324 		@Override
325 		public FlexoTextRun<D, TA> getEndRun() {
326 			if (getEndElement() instanceof FlexoDocParagraph && (getEndRunIndex() == -1)
327 					&& ((FlexoDocParagraph<D, TA>) getEndElement()).getRuns().size() > 0) {
328 				FlexoDocRun<D, TA> returned = ((FlexoDocParagraph<D, TA>) getEndElement()).getRuns()
329 						.get(((FlexoDocParagraph<D, TA>) getEndElement()).getRuns().size() - 1);
330 				if (returned instanceof FlexoTextRun) {
331 					return (FlexoTextRun<D, TA>) returned;
332 				}
333 			}
334 			if (getEndElement() instanceof FlexoDocParagraph && (getEndRunIndex() >= 0)
335 					&& (getEndRunIndex() < ((FlexoDocParagraph<D, TA>) getEndElement()).getRuns().size())) {
336 				FlexoDocRun<D, TA> returned = ((FlexoDocParagraph<D, TA>) getEndElement()).getRuns().get(getEndRunIndex());
337 				if (returned instanceof FlexoTextRun) {
338 					return (FlexoTextRun<D, TA>) returned;
339 				}
340 			}
341 			return null;
342 		}
343 
344 		/**
345 		 * Indicates if this {@link TextSelection} concerns a single paragraph
346 		 * 
347 		 * @return
348 		 */
349 		@Override
350 		public boolean isSingleParagraph() {
351 			return getStartElement() == getEndElement();
352 		}
353 
354 		/**
355 		 * Indicates if this {@link TextSelection} concerns a single run in a single paragraph
356 		 * 
357 		 * @return
358 		 */
359 		@Override
360 		public boolean isSingleRun() {
361 			return isSingleParagraph() && (getStartRun() == getEndRun());
362 		}
363 
364 		/**
365 		 * Return string raw representation of text beeing selected<br>
366 		 * Returned text is build accross document structure reflected in the fragment
367 		 * 
368 		 * @return
369 		 */
370 		@Override
371 		public String getRawText() {
372 			if (getStartElement() != null && getEndElement() != null) {
373 				try {
374 					StringBuffer sb = new StringBuffer();
375 					FlexoDocFragment<D, TA> f = getStartElement().getFlexoDocument().getFragment(startElement, endElement);
376 					for (FlexoDocElement<D, TA> element : f.getElements()) {
377 						if (element instanceof FlexoDocParagraph) {
378 							FlexoDocParagraph<D, TA> paragraph = (FlexoDocParagraph<D, TA>) element;
379 							boolean isFirst = (paragraph == getStartElement());
380 							boolean isLast = (paragraph == getEndElement());
381 							boolean isUnique = isFirst && isLast;
382 							if (!isFirst) {
383 								sb.append(StringUtils.LINE_SEPARATOR);
384 							}
385 							if (isUnique) { // Unique paragraph
386 								if (getStartRunIndex() == -1 && getEndRunIndex() == -1) {
387 									// Full paragraph
388 									for (int i = 0; i < paragraph.getRuns().size(); i++) {
389 										if (paragraph.getRuns().get(i) instanceof FlexoTextRun) {
390 											FlexoTextRun<D, TA> run = (FlexoTextRun<D, TA>) paragraph.getRuns().get(i);
391 											sb.append(run.getText());
392 										}
393 									}
394 								}
395 								else if (getStartRun() != null && getStartRun().getText() != null
396 										&& getStartRunIndex() == getEndRunIndex()) {
397 									if (getStartCharacterIndex() > -1 && getEndCharacterIndex() > -1
398 											&& getStartCharacterIndex() <= getEndCharacterIndex()
399 											&& getEndCharacterIndex() <= getStartRun().getText().length()) {
400 										sb.append(getStartRun().getText().substring(getStartCharacterIndex(), getEndCharacterIndex()));
401 									}
402 									else if (getStartCharacterIndex() > -1 && getStartCharacterIndex() < getStartRun().getText().length()) {
403 										sb.append(getStartRun().getText().substring(getStartCharacterIndex()));
404 									}
405 									else if (getEndCharacterIndex() > -1 && getEndCharacterIndex() < getStartRun().getText().length()) {
406 										sb.append(getStartRun().getText().substring(0, getEndCharacterIndex()));
407 									}
408 									else {
409 										sb.append(getStartRun().getText());
410 									}
411 								}
412 								else {
413 									for (int i = getStartRunIndex(); i <= getEndRunIndex(); i++) {
414 										if (i >= 0 && i < paragraph.getRuns().size()
415 												&& paragraph.getRuns().get(i) instanceof FlexoTextRun) {
416 											FlexoTextRun<D, TA> run = (FlexoTextRun<D, TA>) paragraph.getRuns().get(i);
417 											if (run.getText() != null) {
418 												if (i == getStartRunIndex() && getStartCharacterIndex() > -1
419 														&& getStartCharacterIndex() < run.getText().length()) {
420 													sb.append(run.getText().substring(getStartCharacterIndex()));
421 												}
422 												else if (i == getEndRunIndex() && getEndCharacterIndex() > -1
423 														&& getEndCharacterIndex() <= run.getText().length()) {
424 													sb.append(run.getText().substring(0, getEndCharacterIndex()));
425 												}
426 												else {
427 													sb.append(run.getText());
428 												}
429 											}
430 										}
431 									}
432 								}
433 							}
434 							else if (isFirst) { // First paragraph
435 								for (int i = getStartRunIndex(); i < paragraph.getRuns().size(); i++) {
436 									if (i >= 0 && paragraph.getRuns().get(i) instanceof FlexoTextRun) {
437 										FlexoTextRun<D, TA> run = (FlexoTextRun<D, TA>) paragraph.getRuns().get(i);
438 										if (i == getStartRunIndex() && getStartCharacterIndex() > -1
439 												&& getStartCharacterIndex() < run.getText().length()) {
440 											sb.append(run.getText().substring(getStartCharacterIndex()));
441 										}
442 										else {
443 											sb.append(run.getText());
444 										}
445 									}
446 								}
447 
448 							}
449 							else if (isLast) { // Last paragraph
450 								for (int i = 0; i <= getEndRunIndex(); i++) {
451 									if (i < paragraph.getRuns().size() && paragraph.getRuns().get(i) instanceof FlexoTextRun) {
452 										FlexoTextRun<D, TA> run = (FlexoTextRun<D, TA>) paragraph.getRuns().get(i);
453 										if (i == getEndRunIndex() && getEndCharacterIndex() > -1
454 												&& getEndCharacterIndex() <= run.getText().length()) {
455 											sb.append(run.getText().substring(0, getEndCharacterIndex()));
456 										}
457 										else {
458 											sb.append(run.getText());
459 										}
460 									}
461 								}
462 							}
463 							else { // Normal paragraph, fully included in the selection
464 								for (int i = 0; i < paragraph.getRuns().size(); i++) {
465 									if (paragraph.getRuns().get(i) instanceof FlexoTextRun) {
466 										FlexoTextRun<D, TA> run = (FlexoTextRun<D, TA>) paragraph.getRuns().get(i);
467 										sb.append(run.getText());
468 									}
469 								}
470 							}
471 						}
472 					}
473 					return sb.toString();
474 				} catch (FragmentConsistencyException e) {
475 					logger.warning(e.getMessage());
476 					e.printStackTrace();
477 				}
478 
479 			}
480 			return null;
481 		}
482 
483 		@Override
484 		public String toString() {
485 			return getStartElementIdentifier()
486 					+ (getStartRunIndex() > -1
487 							? ":" + getStartRunIndex() + (getStartCharacterIndex() > -1 ? ":" + getStartCharacterIndex() : "")
488 							: "")
489 					+ "-" + getEndElementIdentifier()
490 					+ (getEndRunIndex() > -1 ? ":" + getEndRunIndex() + (getEndCharacterIndex() > -1 ? ":" + getEndCharacterIndex() : "")
491 							: "");
492 		}
493 	}
494 
495 	public static class TextMarker {
496 		public FlexoDocElement<?, ?> documentElement;
497 		public int runIndex = -1;
498 		public int characterIndex = -1;
499 		public boolean firstChar = false;
500 		public boolean lastChar = false;
501 		public boolean firstRun = false;
502 		public boolean lastRun = false;
503 
504 		@Override
505 		public String toString() {
506 			return (documentElement != null ? documentElement.getIdentifier() : "null") + ":" + runIndex + ":" + characterIndex
507 					+ (firstChar ? "[FIRST_CHAR]" : (lastChar ? "[LAST_CHAR]" : ""))
508 					+ (firstRun ? "[FIRST_RUN]" : (lastRun ? "[LAST_RUN]" : ""));
509 		}
510 	}
511 }