package org.docear.plugin.pdfutilities.map; import java.io.File; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.docear.pdf.PdfDataExtractor; import org.docear.plugin.core.features.AnnotationID; import org.docear.plugin.core.util.HtmlUtils; import org.docear.plugin.core.util.Tools; import org.docear.plugin.pdfutilities.features.AnnotationModel; import org.docear.plugin.pdfutilities.features.AnnotationNodeModel; import org.docear.plugin.pdfutilities.features.AnnotationXmlBuilder; import org.docear.plugin.pdfutilities.features.IAnnotation; import org.docear.plugin.pdfutilities.features.IAnnotation.AnnotationType; import org.docear.plugin.pdfutilities.pdf.CachedHashItem; import org.freeplane.core.extension.IExtension; import org.freeplane.core.io.ReadManager; import org.freeplane.core.io.WriteManager; import org.freeplane.core.util.LogUtils; import org.freeplane.features.link.NodeLinks; import org.freeplane.features.map.MapController; import org.freeplane.features.map.NodeModel; import org.freeplane.features.mode.Controller; import org.freeplane.features.mode.ModeController; import org.freeplane.plugin.workspace.WorkspaceUtils; public class AnnotationController implements IExtension{ private final static Set<IAnnotationImporter> annotationImporters = new HashSet<IAnnotationImporter>(); private final static Map<String, CachedHashItem> documentHashMap = new HashMap<String, CachedHashItem>(); public static void addAnnotationImporter(IAnnotationImporter annotationImporter) { annotationImporters.add(annotationImporter); } public static Set<IAnnotationImporter> getAnnotationImporters() { return annotationImporters; } public static AnnotationController getController() { return getController(Controller.getCurrentModeController()); } public static AnnotationController getController(ModeController modeController) { return (AnnotationController) modeController.getExtension(AnnotationController.class); } public static void install( final AnnotationController annotationController) { Controller.getCurrentModeController().addExtension(AnnotationController.class, annotationController); } public AnnotationController(final ModeController modeController){ final MapController mapController = modeController.getMapController(); final ReadManager readManager = mapController.getReadManager(); final WriteManager writeManager = mapController.getWriteManager(); AnnotationXmlBuilder builder = new AnnotationXmlBuilder(); builder.registerBy(readManager, writeManager); } public static void markNewAnnotations(AnnotationModel importedAnnotation, Map<AnnotationID, Collection<AnnotationNodeModel>> oldAnnotations){ for(AnnotationModel child : importedAnnotation.getChildren()){ AnnotationController.markNewAnnotations(child, oldAnnotations); } if(oldAnnotations.containsKey(importedAnnotation.getAnnotationID())){ importedAnnotation.setNew(false); } else{ importedAnnotation.setNew(true); } } public static Collection<AnnotationModel> markNewAnnotations(Collection<AnnotationModel> importedAnnotations, Map<AnnotationID, Collection<AnnotationNodeModel>> oldAnnotations){ for(AnnotationModel annotation : importedAnnotations){ AnnotationController.markNewAnnotations(annotation, oldAnnotations); } return importedAnnotations; } public static Map<AnnotationID, Collection<IAnnotation>> getConflictedAnnotations(Collection<AnnotationModel> importedAnnotations, Map<AnnotationID, Collection<AnnotationNodeModel>> oldAnnotations) { Map<AnnotationID, Collection<IAnnotation>> result = new HashMap<AnnotationID, Collection<IAnnotation>>(); for(AnnotationModel annotation : importedAnnotations){ addConflictedAnnotations(getConflictedAnnotations(annotation, oldAnnotations), result); } return result; } public static Map<AnnotationID, Collection<IAnnotation>> getConflictedAnnotations(AnnotationModel importedAnnotation, Map<AnnotationID, Collection<AnnotationNodeModel>> oldAnnotations) { Map<AnnotationID, Collection<IAnnotation>> result = new HashMap<AnnotationID, Collection<IAnnotation>>(); if(oldAnnotations.containsKey(importedAnnotation.getAnnotationID())){ for(AnnotationNodeModel oldAnnotation : oldAnnotations.get(importedAnnotation.getAnnotationID())){ String oldAnnotationWithoutHTML = HtmlUtils.extractText(oldAnnotation.getTitle()); String importedAnnotationTitle = importedAnnotation.getTitle().replace("\r", "").replace("\n", "").replace("\t", "").replace(" ", ""); String oldAnnotationTitle = oldAnnotation.getTitle().replace("\r", "").replace("\n", "").replace("\t", "").replace(" ", ""); oldAnnotationWithoutHTML = oldAnnotationWithoutHTML.replace("\r", "").replace("\n", "").replace("\t", "").replace(" ", ""); if(!importedAnnotationTitle.equals(oldAnnotationTitle) && !importedAnnotationTitle.equals(oldAnnotationWithoutHTML) && !oldAnnotation.getAnnotationType().equals(AnnotationType.PDF_FILE)){ importedAnnotation.setConflicted(true); } } } if(importedAnnotation.isConflicted()){ addConflictedAnnotation(importedAnnotation, result); for(AnnotationNodeModel oldAnnotation : oldAnnotations.get(importedAnnotation.getAnnotationID())){ addConflictedAnnotation(oldAnnotation, result); } } for(AnnotationModel child : importedAnnotation.getChildren()){ addConflictedAnnotations(getConflictedAnnotations(child, oldAnnotations), result); } return result; } public static AnnotationModel getModel(final NodeModel node, boolean update) { AnnotationModel annotation = (AnnotationModel) node.getExtension(AnnotationModel.class); if(annotation == null && update){ AnnotationController.setModel(node); annotation = (AnnotationModel) node.getExtension(AnnotationModel.class); } return annotation; } public static int getAnnotationPosition(NodeModel node){ AnnotationModel annotation = AnnotationController.getModel(node, false); if(annotation != null && annotation.getParent() != null){ return annotation.getParent().getChildIndex(annotation); } return -1; } private static boolean isPdfFile(File file) { if (file == null) { return false; } return file.getName().toLowerCase().endsWith(".pdf"); } public static AnnotationNodeModel getAnnotationNodeModel(final NodeModel node){ IAnnotation annotation = AnnotationController.getModel(node, true); File file = WorkspaceUtils.resolveURI(NodeLinks.getValidLink(node), node.getMap()); if(annotation != null && file == null){ setModel(node, null); return null; } if(annotation != null && annotation.getAnnotationType() != null && !annotation.getAnnotationType().equals(AnnotationType.PDF_FILE)){ return new AnnotationNodeModel(node, new AnnotationID(Tools.getAbsoluteUri(node), annotation.getObjectNumber()), annotation.getAnnotationType()); } if(annotation != null && file != null && annotation.getAnnotationType().equals(AnnotationType.PDF_FILE)){ return new AnnotationNodeModel(node, new AnnotationID(Tools.getAbsoluteUri(node), 0), AnnotationType.PDF_FILE); } if(annotation == null && file != null && file.getName().equals(node.getText()) && isPdfFile(file)){ return new AnnotationNodeModel(node, new AnnotationID(Tools.getAbsoluteUri(node), 0), AnnotationType.PDF_FILE); } if(annotation == null && file != null && file.getName().equals(node.getText()) && !isPdfFile(file)){ return new AnnotationNodeModel(node, new AnnotationID(Tools.getAbsoluteUri(node), 0), AnnotationType.FILE); } return null; } public static IAnnotation createModel(final NodeModel node) { final IAnnotation extension = (IAnnotation) node.getExtension(IAnnotation.class); if (extension != null) { return extension; } final IAnnotation annotationModel = new AnnotationModel(); node.addExtension(annotationModel); return annotationModel; } public static void setModel(final NodeModel node, final IAnnotation annotationModel) { final IAnnotation oldAnnotationModel = (IAnnotation) node.getExtension(IAnnotation.class); if (annotationModel != null && oldAnnotationModel == null) { node.addExtension(annotationModel); } else if (annotationModel == null && oldAnnotationModel != null) { node.removeExtension(AnnotationModel.class); } else if(annotationModel == null && oldAnnotationModel == null){ node.removeExtension(AnnotationModel.class); } } private static void setModel(final NodeModel node){ File file = WorkspaceUtils.resolveURI(NodeLinks.getValidLink(node), node.getMap()); if(!isPdfFile(file)){ return; } URI uri = Tools.getAbsoluteUri(node); for(IAnnotationImporter importer : annotationImporters) { try { importer.searchAnnotation(uri, node); } catch (Exception e) { LogUtils.warn("org.docear.plugin.core.mindmap.AnnotationController.setModel: "+e.getMessage()); } } } public static void addConflictedAnnotation(IAnnotation annotation, Map<AnnotationID, Collection<IAnnotation>> result){ if(result.containsKey(annotation.getAnnotationID())){ boolean add = true; for(IAnnotation conflict: result.get(annotation.getAnnotationID())){ if(annotation instanceof AnnotationModel && !(annotation instanceof AnnotationNodeModel) && conflict instanceof AnnotationModel){ add = false; break; } if(annotation instanceof AnnotationNodeModel && conflict instanceof AnnotationNodeModel && ((AnnotationNodeModel) annotation).getNode().equals(((AnnotationNodeModel) conflict).getNode())){ add = false; break; } } if(add){ result.get(annotation.getAnnotationID()).add(annotation); } } else{ result.put(annotation.getAnnotationID(), new ArrayList<IAnnotation>()); result.get(annotation.getAnnotationID()).add(annotation); } } public static void addConflictedAnnotations(Map<AnnotationID, Collection<IAnnotation>> conflicts, Map<AnnotationID, Collection<IAnnotation>> result){ for(AnnotationID id :conflicts.keySet()){ if(result.containsKey(id)){ for(IAnnotation conflict : conflicts.get(id)){ addConflictedAnnotation(conflict, result); } //result.get(id).addAll(conflicts.get(id)); } else{ result.put(id, conflicts.get(id)); } } } public static void registerDocumentHash(final URI uri, final String hash) { if(uri == null || hash == null) { return; } final File file = new File(uri); final long lastModified = file.lastModified(); CachedHashItem hashItem = documentHashMap.get(file.toString()); if(hashItem == null) { documentHashMap.put(file.toString(), new CachedHashItem(hash, System.currentTimeMillis())); executeHashConfirmation(file, lastModified); } else { if(hashItem.getLastUpdate() < lastModified) { documentHashMap.put(file.toString(), new CachedHashItem(hashItem.getHashCode(), System.currentTimeMillis())); executeHashConfirmation(file, lastModified); } } } private static void executeHashConfirmation(final File file, final long lastModified) { new Thread() { public void run() { updateDocumentHashCache(file, lastModified); } }.start(); // ExecutorService executor = Executors.newSingleThreadExecutor(); // Future<?> future = executor.submit(new Runnable() { // public void run() { // updateDocumentHashCache(file, lastModified); // } // }); // try { // future.get(10, TimeUnit.SECONDS); // } catch (Exception e) { // //DOCEAR - log some info // } // executor.shutdown(); } public static String getDocumentHash(URI uri) { if(uri == null) { return null; } String hash = null; File file = new File(uri); long lastModified = file.lastModified(); CachedHashItem hashItem = documentHashMap.get(file.toString()); if(hashItem == null || (hashItem.getLastUpdate() < lastModified)) { hash = updateDocumentHashCache(file, lastModified); } else { hash = hashItem.getHashCode(); } return hash; } public static String getDocumentTitle(URI uri) { if(uri == null) { return null; } File file = new File(uri); try { PdfDataExtractor extractor = new PdfDataExtractor(file); String title = extractor.extractTitle(); return title; } catch (Exception e) { LogUtils.info("could not extract title from document: "+ e.getMessage()); } return null; } private static String updateDocumentHashCache(final File file, final long lastModified) { try { PdfDataExtractor extractor = new PdfDataExtractor(file); String hashCode = extractor.getUniqueHashCode(); if(hashCode != null ) { CachedHashItem newItem = new CachedHashItem(hashCode, lastModified); documentHashMap.put(file.toString(), newItem); } return hashCode; } catch (Exception e) { LogUtils.info("could not extract unique file hash: "+ e.getMessage()); } return null; } }