All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fr.vergne.translation.impl.PatternFileMap Maven / Gradle / Ivy

The newest version!
package fr.vergne.translation.impl;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.TreeSet;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.FileUtils;

import fr.vergne.translation.TranslationEntry;
import fr.vergne.translation.TranslationMap;
import fr.vergne.translation.TranslationMetadata;
import fr.vergne.translation.TranslationMetadata.Field;
import fr.vergne.translation.TranslationMetadata.FieldListener;
import fr.vergne.translation.impl.PatternFileMap.PatternEntry;
import fr.vergne.translation.util.Switcher;
import fr.vergne.translation.util.impl.IdentitySwitcher;

public class PatternFileMap implements TranslationMap {

	private final File file;
	private final Pattern entryPattern;
	private final Pattern originalPattern;
	private final Pattern translationPattern;
	private final Map, Pattern> fieldPatterns = new HashMap<>();
	private final Map, Switcher> fieldSwitchers = new HashMap<>();
	private final Collection> editableFields = new HashSet<>();
	@SuppressWarnings("serial")
	private final List referenceStore = new ArrayList() {
		public boolean add(String content) {
			throw new UnsupportedOperationException();
		};

		public void add(int index, String content) {
			throw new UnsupportedOperationException();
		};

		public String set(int index, String content) {
			if (index == size()) {
				super.add(content);
				return null;
			} else {
				return super.set(index, content);
			}
		};
	};
	private final Map modifiedStore = new HashMap<>();
	private final Runnable saver = new Runnable() {

		@Override
		public void run() {
			logger.info("Saving " + file + "...");
			Charset charset = Charset.forName("UTF-8");
			try {
				FileOutputStream out = new FileOutputStream(file);
				for (String content : referenceStore) {
					out.write(content.getBytes(charset));
				}
				out.close();
			} catch (FileNotFoundException e) {
				throw new RuntimeException(e);
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
			logger.info("File saved.");
		}
	};

	public static final Logger logger = Logger.getLogger(PatternFileMap.class
			.getName());
	private static final Switcher STRING_SWITCHER = new IdentitySwitcher<>();

	public PatternFileMap(File file, String entryRegex, String originalRegex,
			String translationRegex) {
		this.file = file;
		this.entryPattern = Pattern.compile(entryRegex);
		this.originalPattern = Pattern.compile(originalRegex);
		this.translationPattern = Pattern.compile(translationRegex);
	}

	private List entries = null;

	private List getEntries() {
		if (this.entries != null) {
			// reuse
		} else {
			Iterator iterator = startIterator();
			List tempEntries = new LinkedList<>();
			while (iterator.hasNext()) {
				PatternEntry entry = iterator.next();
				tempEntries.add(entry);
			}
			this.entries = new ArrayList<>(tempEntries);
		}
		return this.entries;
	}

	public  void addFieldRegex(Field field, String regex,
			Switcher switcher, boolean editable) {
		fieldPatterns.put(field, Pattern.compile(regex));
		fieldSwitchers.put(field, switcher);
		if (editable) {
			editableFields.add(field);
		} else {
			editableFields.remove(field);
		}
	}

	public void addFieldRegex(Field field, String regex,
			boolean editable) {
		addFieldRegex(field, regex, STRING_SWITCHER, editable);
	}

	@Override
	public Iterator iterator() {
		return getEntries().iterator();
	}

	private Iterator startIterator() {
		logger.info("Reading " + file + "...");
		final String content;
		try {
			content = FileUtils
					.readFileToString(file, Charset.forName("UTF-8"));
		} catch (IOException e) {
			throw new RuntimeException();
		}
		return new Iterator() {

			private final Matcher entryMatcher = entryPattern.matcher(content);
			private String nextEntry = null;
			private int stored = 0;
			private int storeIndex = -1;

			@Override
			public boolean hasNext() {
				if (nextEntry != null) {
					// already found
				} else if (entryMatcher.find()) {
					logger.finer("Find new entry, processing...");
					nextEntry = entryMatcher.group();
					int nextStored = entryMatcher.start();
					if (stored < nextStored) {
						String extract = content.substring(stored, nextStored);
						stored = nextStored;
						storeIndex++;
						referenceStore.set(storeIndex, extract);
						logger.finer("Text before entry stored (" + stored
								+ " chars, " + (storeIndex + 1) + " pieces).");
					} else {
						// nothing between the two entries
					}
				} else {
					// no more
					if (stored < content.length()) {
						String extract = content.substring(stored);
						stored = content.length();
						storeIndex++;
						referenceStore.set(storeIndex, extract);
						logger.finer("End of map stored (" + stored
								+ " chars, " + (storeIndex + 1) + " pieces).");
					} else {
						// nothing after the entries
					}
				}
				return nextEntry != null;
			}

			@Override
			public PatternEntry next() {
				if (hasNext()) {
					try {
						/*
						 * EXTRACT USED SUBSTRINGS
						 */

						logger.finest("ENTRY: " + nextEntry);
						Map substore = new HashMap<>();
						@SuppressWarnings("serial")
						Map starts = new HashMap() {
							@Override
							public Object put(Integer key, Object value) {
								Object old = super.put(key, value);
								if (old != null) {
									throw new RuntimeException(value
											+ " overlaps with " + old);
								} else {
									return old;
								}
							}
						};

						Matcher original = originalPattern.matcher(nextEntry);
						if (original.find()) {
							substore.put(original, original.group());
							starts.put(original.start(), original);
							logger.finest("- O: " + original.group());
						} else {
							throw new ParsingException(
									"Impossible to find original version in entry: "
											+ nextEntry);
						}

						Matcher translation = translationPattern
								.matcher(nextEntry);
						if (translation.find()) {
							substore.put(translation, translation.group());
							starts.put(translation.start(), translation);
							logger.finest("- T: " + translation.group());
						} else {
							throw new ParsingException(
									"Impossible to find translated version in entry: "
											+ nextEntry);
						}

						for (Field field : fieldSwitchers.keySet()) {
							Pattern pattern = fieldPatterns.get(field);
							Matcher matcher = pattern.matcher(nextEntry);
							if (matcher.find()) {
								String group = matcher.group();
								substore.put(field, group);
								starts.put(matcher.start(), field);
								Switcher switcher = fieldSwitchers
										.get(field);
								logger.finest("- " + field + ": \"" + group
										+ "\" -> "
										+ switcher.switchForth(group));
							} else {
								throw new ParsingException(
										"Impossible to find field " + field
												+ " in entry: " + nextEntry);
							}
						}

						/*
						 * EXTRACT ALL ORDERED SUBSTRINGS
						 */

						Map storeIndexes = new HashMap<>();
						TreeSet sortedStarts = new TreeSet<>(
								starts.keySet());
						int substored = 0;
						String previous = null;
						final int firstIndex = storeIndex + 1;
						for (Integer start : sortedStarts) {
							if (substored < start) {
								storeIndex++;
								referenceStore.set(storeIndex,
										nextEntry.substring(substored, start));
								substored = start;
							} else if (substored > start) {
								String current = substore
										.get(starts.get(start));
								throw new RuntimeException(current
										+ " overlaps with " + previous);
							} else {
								// nothing unused before
							}

							Object object = starts.get(start);
							String current = substore.get(object);
							storeIndex++;
							referenceStore.set(storeIndex, current);
							storeIndexes.put(object, storeIndex);
							substored += current.length();

							previous = current;
						}
						if (substored < stored + nextEntry.length()) {
							storeIndex++;
							referenceStore.set(storeIndex,
									nextEntry.substring(substored));
						} else {
							// nothing unused after
						}
						stored += nextEntry.length();
						final int lastIndex = storeIndex;

						/*
						 * INSTANTIATE ENTRY
						 */

						Map, StoreAccessor> fieldAccessors = new HashMap<>();
						for (Field field : fieldSwitchers.keySet()) {
							Integer index = storeIndexes.get(field);
							Switcher switcher = fieldSwitchers
									.get(field);
							fieldAccessors.put(field, new StoreAccessor<>(
									referenceStore, modifiedStore, index,
									switcher));
						}
						PatternMetadata metadata = new PatternMetadata(
								fieldAccessors, editableFields, saver);
						Integer index = storeIndexes.get(translation);
						StoreAccessor translationAccessor = new StoreAccessor<>(
								referenceStore, modifiedStore, index,
								STRING_SWITCHER);
						StringRebuilder rebuilder = new StringRebuilder(
								referenceStore, firstIndex, lastIndex);
						PatternEntry entry = new PatternEntry(original.group(),
								translationAccessor, metadata, saver, rebuilder);
						logger.finer("Entry stored (" + stored + " chars, "
								+ (storeIndex + 1) + " pieces).");

						return entry;
					} finally {
						nextEntry = null;
					}
				} else {
					throw new NoSuchElementException();
				}
			}

			@Override
			public void remove() {
				throw new UnsupportedOperationException(
						"You cannot remove an entry from this map.");
			}
		};
	}

	@Override
	public PatternEntry getEntry(int index) {
		return getEntries().get(index);
	}

	public String getBeforeEntry(int index) {
		return getBetweenEntries(index - 1, index);
	}

	public String getAfterEntry(int index) {
		return getBetweenEntries(index, index + 1);
	}

	public String getBetweenEntries(int indexFrom, int indexTo) {
		int storeStart;
		if (indexFrom < 0) {
			storeStart = 0;
		} else if (indexFrom < size()) {
			storeStart = getEntry(indexFrom).getLastIndex() + 1;
		} else {
			throw new IndexOutOfBoundsException("The first index (" + indexFrom
					+ ") cannot be higher then the last entry (" + (size() - 1)
					+ ")");
		}

		int storeEnd;
		if (indexTo >= size()) {
			storeEnd = referenceStore.size() - 1;
		} else if (indexTo > indexFrom) {
			storeEnd = getEntry(indexTo).getFirstIndex() - 1;
		} else {
			throw new IndexOutOfBoundsException("The first index (" + indexFrom
					+ ") should be inferior to the second (" + indexTo + ")");
		}

		return new StringRebuilder(referenceStore, storeStart, storeEnd)
				.toString();
	}

	@Override
	public int size() {
		return getEntries().size();
	}

	@Override
	public void saveAll() {
		for (PatternEntry entry : this) {
			entry.saveAllBackgroundOnly();
		}
		saver.run();
	}

	@Override
	public void resetAll() {
		for (PatternEntry entry : this) {
			entry.resetAll();
		}
	}

	public static class PatternMetadata implements TranslationMetadata {

		private final Map, StoreAccessor> accessors;
		private final Collection> editableFields;
		private final Collection listeners = new HashSet<>();
		private final Runnable saver;

		public PatternMetadata(Map, StoreAccessor> fieldAccessors,
				Collection> editableFields, Runnable saver) {
			this.accessors = fieldAccessors;
			this.editableFields = editableFields;
			this.saver = saver;
		}

		@Override
		public Iterator> iterator() {
			return accessors.keySet().iterator();
		}

		@SuppressWarnings("unchecked")
		private  StoreAccessor getAccessor(Field field) {
			StoreAccessor accessor = accessors.get(field);
			if (accessor == null) {
				throw new IllegalArgumentException("Unknown field: " + field);
			} else {
				return (StoreAccessor) accessor;
			}
		}

		@Override
		public  T getStored(Field field) {
			return getAccessor(field).getStored();
		}

		@Override
		public  T get(Field field) {
			return getAccessor(field).get();
		}

		@Override
		public  boolean isEditable(Field field) {
			return editableFields.contains(field);
		}

		@Override
		public  void set(Field field, T value)
				throws UneditableFieldException {
			if (isEditable(field)) {
				getAccessor(field).set(value);
				for (FieldListener listener : listeners) {
					listener.fieldUpdated(field, value);
				}
			} else {
				throw new UneditableFieldException(field);
			}
		}

		@Override
		public void addFieldListener(FieldListener listener) {
			listeners.add(listener);
		}

		@Override
		public void removeFieldListener(FieldListener listener) {
			listeners.remove(listener);
		}

		@Override
		public  void save(Field field) {
			accessors.get(field).save();
			saver.run();
			for (FieldListener listener : listeners) {
				listener.fieldStored(field);
			}
		}

		@Override
		public  void reset(Field field) {
			accessors.get(field).reset();
			for (FieldListener listener : listeners) {
				listener.fieldUpdated(field, getStored(field));
			}
		}

		@Override
		public void saveAll() {
			saveAllBackgroundOnly();
			saver.run();
			for (FieldListener listener : listeners) {
				for (Field field : this) {
					listener.fieldStored(field);
				}
			}
		}

		public void saveAllBackgroundOnly() {
			for (Field field : this) {
				accessors.get(field).save();
			}
		}

		@Override
		public void resetAll() {
			for (Field field : this) {
				reset(field);
			}
		}

		@Override
		public String toString() {
			Map, Object> map = new HashMap<>();
			for (Field field : this) {
				map.put(field, get(field));
			}
			return map.toString();
		}

	}

	public static class PatternEntry implements
			TranslationEntry {

		private final String original;
		private final StoreAccessor translationAccessor;
		private final PatternMetadata metadata;
		private final Collection listeners = new HashSet();
		private final Runnable saver;
		private final StringRebuilder rebuilder;

		public PatternEntry(String original,
				StoreAccessor translationAccessor,
				PatternMetadata metadata, Runnable saver,
				StringRebuilder rebuilder) {
			this.original = original;
			this.translationAccessor = translationAccessor;
			this.metadata = metadata;
			this.saver = saver;
			this.rebuilder = rebuilder;
		}

		private int getFirstIndex() {
			return rebuilder.firstIndex;
		}

		private int getLastIndex() {
			return rebuilder.lastIndex;
		}

		@Override
		public String getOriginalContent() {
			return original;
		}

		@Override
		public String getStoredTranslation() {
			return translationAccessor.getStored();
		}

		@Override
		public String getCurrentTranslation() {
			return translationAccessor.get();
		}

		@Override
		public void setCurrentTranslation(String translation) {
			if (translation == null) {
				throw new IllegalArgumentException("No translation provided: "
						+ translation);
			} else {
				translationAccessor.set(translation);
				for (TranslationListener listener : listeners) {
					listener.translationUpdated(translation);
				}
			}
		}

		@Override
		public void saveTranslation() {
			translationAccessor.save();
			saver.run();
		}

		@Override
		public void resetTranslation() {
			translationAccessor.reset();
			for (TranslationListener listener : listeners) {
				listener.translationUpdated(getStoredTranslation());
			}
		}

		@Override
		public void saveAll() {
			saveAllBackgroundOnly();
			saver.run();
			for (TranslationListener listener : listeners) {
				listener.translationStored();
			}
			for (FieldListener listener : metadata.listeners) {
				for (Field field : metadata) {
					listener.fieldStored(field);
				}
			}
		}

		public void saveAllBackgroundOnly() {
			translationAccessor.save();
			metadata.saveAllBackgroundOnly();
		}

		@Override
		public void resetAll() {
			resetTranslation();
			metadata.resetAll();
		}

		@Override
		public PatternMetadata getMetadata() {
			return metadata;
		}

		@Override
		public void addTranslationListener(TranslationListener listener) {
			listeners.add(listener);
		}

		@Override
		public void removeTranslationListener(TranslationListener listener) {
			listeners.remove(listener);
		}

		@Override
		public boolean equals(Object obj) {
			if (obj == this) {
				return true;
			} else if (obj instanceof PatternEntry) {
				PatternEntry e = (PatternEntry) obj;
				return translationAccessor == e.translationAccessor;
			} else {
				return false;
			}
		}

		@Override
		public int hashCode() {
			return translationAccessor.hashCode();
		}

		public String rebuildString() {
			return rebuilder.toString();
		}

		@Override
		public String toString() {
			return getOriginalContent() + "=" + getCurrentTranslation();
		}
	}

	private static class StoreAccessor {

		private final List referenceStore;
		private final int storeIndex;
		private final Switcher switcher;
		private final Map modifiedStore;

		public StoreAccessor(List referenceStore,
				Map modifiedStore, int storeIndex,
				Switcher switcher) {
			this.referenceStore = referenceStore;
			this.modifiedStore = modifiedStore;
			this.storeIndex = storeIndex;
			this.switcher = switcher;
		}

		@SuppressWarnings("unchecked")
		public Value get() {
			Object value = modifiedStore.get(storeIndex);
			if (value == null) {
				return getStored();
			} else {
				return (Value) value;
			}
		}

		public void set(Value value) {
			logger.info("Value update of " + this + ": \"" + get() + "\" -> \""
					+ value + "\"");
			modifiedStore.put(storeIndex, value);
		}

		private Value getStored() {
			return switcher.switchForth(referenceStore.get(storeIndex));
		}

		public void save() {
			Object value = modifiedStore.get(storeIndex);
			if (value == null) {
				logger.finest("Store update of " + this + ": ");
			} else {
				String newValue = switcher.switchBack(get());
				logger.finest("Retrieve string of " + this + ": \"" + get()
						+ "\" -> \"" + newValue + "\"");
				logger.finest("Store update of " + this + ": \""
						+ referenceStore.get(storeIndex) + "\" -> \""
						+ newValue + "\"");
				referenceStore.set(storeIndex, newValue);
			}
		}

		public void reset() {
			logger.finest("Restore value of " + this + ": \"" + get()
					+ "\" -> \"" + getStored() + "\"");
			modifiedStore.remove(storeIndex);
		}

		@Override
		public boolean equals(Object obj) {
			if (obj == this) {
				return true;
			} else if (obj instanceof StoreAccessor) {
				StoreAccessor e = (StoreAccessor) obj;
				return referenceStore == e.referenceStore
						&& storeIndex == e.storeIndex;
			} else {
				return false;
			}
		}

		@Override
		public int hashCode() {
			return referenceStore.hashCode() + storeIndex;
		}

		@Override
		public String toString() {
			return "SA" + storeIndex;
		}
	}

	public static class StringRebuilder {
		private final List store;
		private final int firstIndex;
		private final int lastIndex;

		public StringRebuilder(List store, int firstIndex, int lastIndex) {
			this.store = store;
			this.firstIndex = firstIndex;
			this.lastIndex = lastIndex;
		}

		@Override
		public String toString() {
			StringBuilder builder = new StringBuilder();
			for (String content : store.subList(firstIndex, lastIndex + 1)) {
				builder.append(content);
			}
			return builder.toString();
		}
	}

	@SuppressWarnings("serial")
	public static class ParsingException extends RuntimeException {
		public ParsingException(String message) {
			super(message);
		}

		public ParsingException(String message, Throwable cause) {
			super(message, cause);
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy