| /* ==================================================================== |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| ==================================================================== */ |
| package org.apache.poi.poifs.filesystem; |
| |
| import java.io.EOFException; |
| import java.io.IOException; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.stream.Collectors; |
| import java.util.stream.StreamSupport; |
| |
| import org.apache.poi.hpsf.MarkUnsupportedException; |
| import org.apache.poi.hpsf.NoPropertySetStreamException; |
| import org.apache.poi.hpsf.PropertySet; |
| import org.apache.poi.hpsf.PropertySetFactory; |
| import org.apache.poi.util.Internal; |
| |
| @Internal |
| public final class EntryUtils { |
| private EntryUtils() {} |
| |
| /** |
| * Copies an Entry into a target POIFS directory, recursively |
| */ |
| @Internal |
| public static void copyNodeRecursively( Entry entry, DirectoryEntry target ) |
| throws IOException { |
| if ( entry.isDirectoryEntry() ) { |
| DirectoryEntry dirEntry = (DirectoryEntry)entry; |
| DirectoryEntry newTarget = target.createDirectory( entry.getName() ); |
| newTarget.setStorageClsid( dirEntry.getStorageClsid() ); |
| Iterator<Entry> entries = dirEntry.getEntries(); |
| |
| while ( entries.hasNext() ) { |
| copyNodeRecursively( entries.next(), newTarget ); |
| } |
| } else { |
| DocumentEntry dentry = (DocumentEntry) entry; |
| DocumentInputStream dstream = new DocumentInputStream( dentry ); |
| target.createDocument( dentry.getName(), dstream ); |
| dstream.close(); |
| } |
| } |
| |
| /** |
| * Copies all the nodes from one POIFS Directory to another |
| * |
| * @param sourceRoot |
| * is the source Directory to copy from |
| * @param targetRoot |
| * is the target Directory to copy to |
| */ |
| public static void copyNodes(DirectoryEntry sourceRoot, DirectoryEntry targetRoot) |
| throws IOException { |
| for (Entry entry : sourceRoot) { |
| copyNodeRecursively( entry, targetRoot ); |
| } |
| } |
| |
| /** |
| * Copies all nodes from one POIFS to the other |
| * |
| * @param source |
| * is the source POIFS to copy from |
| * @param target |
| * is the target POIFS to copy to |
| */ |
| public static void copyNodes(POIFSFileSystem source, POIFSFileSystem target ) |
| throws IOException { |
| copyNodes( source.getRoot(), target.getRoot() ); |
| } |
| |
| /** |
| * Copies nodes from one POIFS to the other, minus the excepts. |
| * This delegates the filtering work to {@link FilteringDirectoryNode}, |
| * so excepts can be of the form "NodeToExclude" or |
| * "FilteringDirectory/ExcludedChildNode" |
| * |
| * @param source is the source POIFS to copy from |
| * @param target is the target POIFS to copy to |
| * @param excepts is a list of Entry Names to be excluded from the copy |
| */ |
| public static void copyNodes(POIFSFileSystem source, POIFSFileSystem target, List<String> excepts ) |
| throws IOException { |
| copyNodes( |
| new FilteringDirectoryNode(source.getRoot(), excepts), |
| new FilteringDirectoryNode(target.getRoot(), excepts) |
| ); |
| } |
| |
| /** |
| * Checks to see if the two Directories hold the same contents. |
| * For this to be true, they must have entries with the same names, |
| * no entries in one but not the other, and the size+contents |
| * of each entry must match, and they must share names. |
| * To exclude certain parts of the Directory from being checked, |
| * use a {@link FilteringDirectoryNode} |
| */ |
| public static boolean areDirectoriesIdentical(DirectoryEntry dirA, DirectoryEntry dirB) { |
| return new DirectoryDelegate(dirA).equals(new DirectoryDelegate(dirB)); |
| } |
| |
| /** |
| * Compares two {@link DocumentEntry} instances of a POI file system. |
| * Documents that are not property set streams must be bitwise identical. |
| * Property set streams must be logically equal.<p> |
| * |
| * (Their parent directories are not checked) |
| */ |
| @SuppressWarnings("WeakerAccess") |
| public static boolean areDocumentsIdentical(DocumentEntry docA, DocumentEntry docB) |
| throws IOException { |
| try { |
| return new DocumentDelegate(docA).equals(new DocumentDelegate(docB)); |
| } catch (RuntimeException e) { |
| if (e.getCause() instanceof IOException) { |
| throw (IOException)e.getCause(); |
| } else { |
| throw e; |
| } |
| } |
| } |
| |
| private interface POIDelegate { |
| } |
| |
| private static class DirectoryDelegate implements POIDelegate { |
| final DirectoryEntry dir; |
| |
| DirectoryDelegate(DirectoryEntry dir) { |
| this.dir = dir; |
| } |
| |
| private Map<String,POIDelegate> entries() { |
| return StreamSupport.stream(dir.spliterator(), false) |
| .collect(Collectors.toMap(Entry::getName, DirectoryDelegate::toDelegate)); |
| } |
| |
| private static POIDelegate toDelegate(Entry entry) { |
| return (entry.isDirectoryEntry()) |
| ? new DirectoryDelegate((DirectoryEntry)entry) |
| : new DocumentDelegate((DocumentEntry)entry); |
| } |
| |
| @Override |
| public int hashCode() { |
| return dir.getName().hashCode(); |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (!(other instanceof DirectoryDelegate)) { |
| return false; |
| } |
| |
| DirectoryDelegate dd = (DirectoryDelegate)other; |
| |
| if (this == dd) { |
| return true; |
| } |
| |
| // First, check names |
| if (!Objects.equals(dir.getName(),dd.dir.getName())) { |
| return false; |
| } |
| |
| // Next up, check they have the same number of children |
| if (dir.getEntryCount() != dd.dir.getEntryCount()) { |
| return false; |
| } |
| |
| return entries().equals(dd.entries()); |
| } |
| } |
| |
| private static class DocumentDelegate implements POIDelegate { |
| final DocumentEntry doc; |
| |
| DocumentDelegate(DocumentEntry doc) { |
| this.doc = doc; |
| } |
| |
| @Override |
| public int hashCode() { |
| return doc.getName().hashCode(); |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (!(other instanceof DocumentDelegate)) { |
| return false; |
| } |
| |
| DocumentDelegate dd = (DocumentDelegate)other; |
| |
| if (this == dd) { |
| return true; |
| } |
| |
| |
| if (!Objects.equals(doc.getName(), dd.doc.getName())) { |
| // Names don't match, not the same |
| return false; |
| } |
| |
| try (DocumentInputStream inpA = new DocumentInputStream(doc); |
| DocumentInputStream inpB = new DocumentInputStream(dd.doc)) { |
| |
| if (PropertySet.isPropertySetStream(inpA) && |
| PropertySet.isPropertySetStream(inpB)) { |
| final PropertySet ps1 = PropertySetFactory.create(inpA); |
| final PropertySet ps2 = PropertySetFactory.create(inpB); |
| return ps1.equals(ps2); |
| } else { |
| return isEqual(inpA, inpB); |
| } |
| } catch (MarkUnsupportedException | NoPropertySetStreamException | IOException ex) { |
| throw new RuntimeException(ex); |
| } |
| } |
| |
| private static boolean isEqual(DocumentInputStream i1, DocumentInputStream i2) |
| throws IOException { |
| final byte[] buf1 = new byte[4*1024]; |
| final byte[] buf2 = new byte[4*1024]; |
| try { |
| int len; |
| while ((len = i1.read(buf1)) > 0) { |
| i2.readFully(buf2,0,len); |
| for(int i=0;i<len;i++) { |
| if (buf1[i] != buf2[i]) { |
| return false; |
| } |
| } |
| } |
| // is the end of the second file also. |
| return i2.read() < 0; |
| } catch(EOFException | RuntimeException ioe) { |
| return false; |
| } |
| } |
| } |
| } |