/* ****************************************************************************** * Copyright (c) 2006-2012 XMind Ltd. and others. * * This file is a part of XMind 3. XMind releases 3 and * above are dual-licensed under the Eclipse Public License (EPL), * which is available at http://www.eclipse.org/legal/epl-v10.html * and the GNU Lesser General Public License (LGPL), * which is available at http://www.gnu.org/licenses/lgpl.html * See http://www.xmind.net/license.html for details. * * Contributors: * XMind Ltd. - initial API and implementation *******************************************************************************/ package org.xmind.core.internal.dom; import static org.xmind.core.internal.dom.DOMConstants.ATTR_FULL_PATH; import static org.xmind.core.internal.dom.DOMConstants.ATTR_MEDIA_TYPE; import static org.xmind.core.internal.dom.DOMConstants.PASSWORD_HINT; import static org.xmind.core.internal.dom.DOMConstants.TAG_FILE_ENTRY; import static org.xmind.core.internal.dom.DOMConstants.TAG_MANIFEST; import static org.xmind.core.internal.dom.InternalDOMUtils.getParentPath; import static org.xmind.core.internal.zip.ArchiveConstants.MANIFEST_XML; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xmind.core.Core; import org.xmind.core.IEncryptionData; import org.xmind.core.IEntryStreamNormalizer; import org.xmind.core.IFileEntry; import org.xmind.core.IFileEntryFilter; import org.xmind.core.IWorkbook; import org.xmind.core.event.ICoreEventListener; import org.xmind.core.event.ICoreEventRegistration; import org.xmind.core.event.ICoreEventSource; import org.xmind.core.internal.Manifest; import org.xmind.core.internal.event.CoreEventSupport; import org.xmind.core.io.ByteArrayStorage; import org.xmind.core.io.IStorage; import org.xmind.core.util.DOMUtils; import org.xmind.core.util.FileUtils; public class ManifestImpl extends Manifest implements ICoreEventSource { private Document implementation; private IStorage storage; private WorkbookImpl ownedWorkbook; private final Map<String, IFileEntry> entries; private IEntryStreamNormalizer normalizer; private CoreEventSupport coreEventSupport; public ManifestImpl(Document implementation, IStorage storage) { this.implementation = implementation; this.storage = storage == null ? new ByteArrayStorage() : storage; this.normalizer = IEntryStreamNormalizer.NULL; this.ownedWorkbook = null; this.entries = new HashMap<String, IFileEntry>(); init(); } private void init() { Element m = DOMUtils.ensureChildElement(implementation, TAG_MANIFEST); NS.setNS(NS.Manifest, m); // Prefetch all file entries, which makes getAllRegisteredEntries() // always returns correct value. for (Iterator<IFileEntry> it = iterFileEntries(); it.hasNext();) { it.next(); } IFileEntry metaEntry = createFileEntry(MANIFEST_XML, Core.MEDIA_TYPE_TEXT_XML); insertFileEntryImpl(metaEntry.getAdapter(Element.class)); } public Document getImplementation() { return implementation; } public Element getManifestElement() { return implementation.getDocumentElement(); } public <T> T getAdapter(Class<T> adapter) { if (adapter.isAssignableFrom(Document.class)) return adapter.cast(implementation); if (IWorkbook.class.equals(adapter)) return adapter.cast(ownedWorkbook); return super.getAdapter(adapter); } public IWorkbook getOwnedWorkbook() { return ownedWorkbook; } /* * (non-Javadoc) * @see org.xmind.core.IWorkbookComponent#isOrphan() */ public boolean isOrphan() { return ownedWorkbook == null; } /** * @param workbook */ protected void setWorkbook(WorkbookImpl workbook) { this.ownedWorkbook = workbook; if (workbook != null) { getCoreEventSupport().setParent(workbook.getCoreEventSupport()); } } protected void setStorage(IStorage storage) { this.storage = storage == null ? new ByteArrayStorage() : storage; } protected IStorage getStorage() { return storage; } protected void setStreamNormalizer(IEntryStreamNormalizer normalizer) { this.normalizer = normalizer == null ? IEntryStreamNormalizer.NULL : normalizer; } protected IEntryStreamNormalizer getStreamNormalizer() { return this.normalizer; } protected Collection<IFileEntry> getAllRegisteredEntries() { return entries.values(); } /* * (non-Javadoc) * @see org.xmind.core.IManifest#iterFileEntries() */ public Iterator<IFileEntry> iterFileEntries() { return iterFileEntries(null); } /* * (non-Javadoc) * @see org.xmind.core.IManifest#iterFileEntries(org.xmind.core.IManifest. * IFileEntryFilter) */ public Iterator<IFileEntry> iterFileEntries(final IFileEntryFilter filter) { final Iterator<Element> it = DOMUtils .childElementIterByTag(getManifestElement(), TAG_FILE_ENTRY); return new Iterator<IFileEntry>() { IFileEntry next = findNext(); private IFileEntry findNext() { while (it.hasNext()) { Element e = it.next(); if (e.hasAttribute(ATTR_FULL_PATH) && select(e)) { return getFileEntry(e); } } return null; } private boolean select(Element e) { if (filter == null) return true; String path = e.getAttribute(ATTR_FULL_PATH); String mediaType = e.getAttribute(ATTR_MEDIA_TYPE); boolean isDirectory = path.endsWith("/"); //$NON-NLS-1$ return filter.select(path, mediaType, isDirectory); } public void remove() { } public IFileEntry next() { IFileEntry n = next; next = findNext(); return n; } public boolean hasNext() { return next != null; } }; } public IFileEntry getFileEntry(String path) { if (path == null) return null; IFileEntry entry = findEntry(path); if (entry == null) { while (path.startsWith("/")) { //$NON-NLS-1$ path = path.substring(1, path.length()); entry = findEntry(path); if (entry != null) break; } } if (entry == null) { entry = findEntry("/" + path); //$NON-NLS-1$ } return entry; } private IFileEntry findEntry(String path) { IFileEntry entry = entries.get(path); if (entry == null) { Element e = findEntryElementByPath(path); if (e != null) entry = createFileEntry(path, e); } return entry; } private Element findEntryElementByPath(String path) { Iterator<Element> it = DOMUtils .childElementIterByTag(getManifestElement(), TAG_FILE_ENTRY); while (it.hasNext()) { Element e = it.next(); if (path.equals(e.getAttribute(ATTR_FULL_PATH))) return e; } return null; } public IFileEntry createFileEntry(String path) { return createFileEntry(path, ""); //$NON-NLS-1$ } public IFileEntry createFileEntry(String path, String mediaType) { IFileEntry entry = getFileEntry(path); if (entry != null) return entry; String parent = InternalDOMUtils.getParentPath(path); if (parent != null) { IFileEntry parentFileEntry = createFileEntry(parent); insertFileEntryImpl(parentFileEntry.getAdapter(Element.class)); } Element e = implementation.createElement(TAG_FILE_ENTRY); e.setAttribute(ATTR_FULL_PATH, path); e.setAttribute(ATTR_MEDIA_TYPE, mediaType); return createFileEntry(path, e); } private IFileEntry createFileEntry(String path, Element entryElement) { IFileEntry entry = new FileEntryImpl(entryElement, this); entries.put(path, entry); return entry; } private IFileEntry getFileEntry(Element element) { String path = element.getAttribute(ATTR_FULL_PATH); IFileEntry entry = entries.get(path); if (entry != null) return entry; return createFileEntry(path, element); } protected void insertFileEntry(IFileEntry entry) { Element e = (Element) entry.getAdapter(Element.class); if (e != null) { insertFileEntryImpl(e); } getCoreEventSupport().dispatchTargetChange(this, Core.FileEntryAdd, entry); } protected void removeFileEntry(IFileEntry entry) { Element e = (Element) entry.getAdapter(Element.class); if (e != null) { Element m = getManifestElement(); if (m == e.getParentNode()) m.removeChild(e); } getCoreEventSupport().dispatchTargetChange(this, Core.FileEntryRemove, entry); } private void insertFileEntryImpl(Element entryElement) { Element e = findInsertLocation(entryElement); if (e != null) { getManifestElement().insertBefore(entryElement, e); } else { getManifestElement().appendChild(entryElement); } } private Element findInsertLocation(Element entryElement) { if (entryElement.hasAttribute(ATTR_FULL_PATH)) return findInsertLocation(entryElement, entryElement.getAttribute(ATTR_FULL_PATH)); return null; } private Element findInsertLocation(Element entryElement, String path) { Iterator<Element> it = DOMUtils .childElementIterByTag(getManifestElement(), TAG_FILE_ENTRY); while (it.hasNext()) { Element e = it.next(); if (e != entryElement && e.hasAttribute(ATTR_FULL_PATH)) { String p = e.getAttribute(ATTR_FULL_PATH); if (p != null && path.compareToIgnoreCase(p) < 0) { return e; } } } return null; } public IFileEntry createAttachmentFromFilePath(String sourcePath) throws IOException { return createAttachmentFromFilePath(sourcePath, null); } public IFileEntry createAttachmentFromFilePath(String sourcePath, String mediaType) throws IOException { if (sourcePath == null) throw new IllegalArgumentException("Path is null!"); //$NON-NLS-1$ File file = new File(sourcePath); if (!file.exists()) throw new FileNotFoundException("Source path does not exists."); //$NON-NLS-1$ if (file.isFile()) { IFileEntry entry = createAttachmentFromStream( new FileInputStream(sourcePath), sourcePath, mediaType); if (entry != null) { entry.setTime(file.lastModified()); } return entry; } if (file.isDirectory()) { String fileName = file.getName(); String path = makeAttachmentPath(fileName, true); if (mediaType == null) mediaType = FileUtils.getMediaType(fileName); IFileEntry root = createFileEntry(path, mediaType); if (root != null) { importDirectory(path, file); } return root; } throw new IllegalArgumentException( "Unknown file type (neither a file nor a directory)"); //$NON-NLS-1$ } protected void importDirectory(String parentPath, File dir) throws IOException { for (String sub : dir.list()) { File f = new File(dir, sub); if (f.isFile()) { String path = parentPath == null ? sub : parentPath + sub; String mediaType = FileUtils.getMediaType(sub); IFileEntry e = createFileEntry(path, mediaType); if (e != null) { e.setTime(f.lastModified()); OutputStream os = e.openOutputStream(); try { FileInputStream is = new FileInputStream(f); try { FileUtils.transfer(is, os); } finally { is.close(); } } finally { os.close(); } } } else if (f.isDirectory()) { String path = parentPath == null ? sub + "/" //$NON-NLS-1$ : parentPath + sub + "/"; //$NON-NLS-1$ importDirectory(path, f); } } } public IFileEntry createAttachmentFromStream(InputStream stream, String sourceName) throws IOException { return createAttachmentFromStream(stream, sourceName, null); } public IFileEntry createAttachmentFromStream(InputStream stream, String sourceName, String mediaType) throws IOException { if (sourceName == null || stream == null) return null; String path = makeAttachmentPath(sourceName, false); if (mediaType == null) mediaType = FileUtils.getMediaType(sourceName); IFileEntry entry = createFileEntry(path, mediaType); if (entry != null) { OutputStream os = entry.openOutputStream(); try { FileUtils.transfer(stream, os); } finally { os.close(); } } return entry; } public IFileEntry cloneEntry(IFileEntry sourceEntry, String targetPath) throws IOException { if (sourceEntry == null || targetPath == null) return null; IFileEntry existingEntry = getFileEntry(targetPath); if (existingEntry != null) { return null; } if (sourceEntry.isDirectory()) { if (!targetPath.endsWith("/")) //$NON-NLS-1$ targetPath = targetPath + "/"; //$NON-NLS-1$ importDirectoryEntry(targetPath, sourceEntry); } else { importFileEntry(targetPath, sourceEntry); } return getFileEntry(targetPath); } private void importFileEntry(String path, IFileEntry sourceEntry) throws IOException { InputStream is = sourceEntry.openInputStream(); try { IFileEntry entry = createFileEntry(path, sourceEntry.getMediaType()); entry.setTime(sourceEntry.getTime()); OutputStream os = entry.openOutputStream(); try { FileUtils.transfer(is, os); } finally { os.close(); } } finally { is.close(); } } private void importDirectoryEntry(String parentPath, IFileEntry sourceEntry) throws IOException { String sourceParentPath = getParentPath(sourceEntry.getPath()); for (IFileEntry sourceSubEntry : sourceEntry.getSubEntries()) { String sourceSubPath = sourceSubEntry.getPath(); if (sourceSubPath != null) { String subPath = sourceSubPath .substring(sourceParentPath.length()); if (parentPath != null) { subPath = parentPath + subPath; } if (!sourceSubEntry.isDirectory()) { importFileEntry(subPath, sourceSubEntry); } } } if (getFileEntry(parentPath) == null) { createFileEntry(parentPath, sourceEntry.getMediaType()); } } public IFileEntry cloneEntryAsAttachment(IFileEntry sourceEntry) throws IOException { String sourcePath = sourceEntry.getPath(); String path = makeAttachmentPath(sourcePath, sourceEntry.isDirectory()); IFileEntry cloneEntry = cloneEntry(sourceEntry, path); return cloneEntry; } public String makeAttachmentPath(String source) { return makeAttachmentPath(source, false); } public String makeAttachmentPath(String source, boolean directory) { String path = "attachments/" + Core.getIdFactory().createId() //$NON-NLS-1$ + FileUtils.getExtension(source); if (directory) path += "/"; //$NON-NLS-1$ return path; } /* * (non-Javadoc) * @see org.xmind.core.IManifest#getEncryptionData(java.lang.String) */ public IEncryptionData getEncryptionData(String entryPath) { IFileEntry entry = getFileEntry(entryPath); if (entry != null) return entry.getEncryptionData(); return null; } public ICoreEventRegistration registerCoreEventListener(String type, ICoreEventListener listener) { return ownedWorkbook.getCoreEventSupport() .registerCoreEventListener(this, type, listener); } public CoreEventSupport getCoreEventSupport() { if (coreEventSupport != null) return coreEventSupport; coreEventSupport = new CoreEventSupport(); return coreEventSupport; } public String getPasswordHint() { Element m = DOMUtils.ensureChildElement(implementation, TAG_MANIFEST); return m.getAttribute(PASSWORD_HINT); } public void setPasswordHint(String hint) { Element m = DOMUtils.ensureChildElement(implementation, TAG_MANIFEST); m.setAttribute(PASSWORD_HINT, hint); } // protected void saveTemp(String newPassword) // throws IOException, CoreException { // String oldPassword = getPassword(); // SerializerImpl serializer = (SerializerImpl) Core.getSerializerFactory() // .newSerializer(); // if (oldPassword == newPassword // || (oldPassword != null && oldPassword.equals(newPassword))) { // serializer.serializeAll(ownedWorkbook, this, this, // FileEntrySelection.None, true); // } else { // Transformer transformer = DOMUtils.getDefaultTransformer(); // DOMResult newDocResult = new DOMResult(); // try { // transformer.transform(new DOMSource(implementation), // newDocResult); // } catch (TransformerException e) { // throw new CoreException(Core.ERROR_FAIL_SERIALIZING_XML, // MANIFEST_XML, e); // } // // ManifestImpl tempManifest = new ManifestImpl( // (Document) newDocResult.getNode(), storage); // this.password = newPassword; // for (IFileEntry entry : getAllRegisteredEntries()) { // if (entry.getEncryptionData() != null) { // entry.deleteEncryptionData(); // entry.createEncryptionData(); // } // } // serializer.serializeAll(ownedWorkbook, tempManifest, this, // FileEntrySelection.All, true); // } // } }