1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.pdfbox.pdfviewer; 18 19 /** 20 * A tree model that uses a cos document. 21 * 22 * 23 * @author wurtz 24 * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a> 25 * @version $Revision: 1.9 $ 26 */ 27 import javax.swing.tree.TreePath; 28 import javax.swing.tree.TreeModel; 29 30 //import java.awt.event.*; 31 import javax.swing.event.TreeModelListener; 32 33 import org.apache.pdfbox.cos.COSArray; 34 import org.apache.pdfbox.cos.COSBase; 35 import org.apache.pdfbox.cos.COSDictionary; 36 import org.apache.pdfbox.cos.COSDocument; 37 import org.apache.pdfbox.cos.COSName; 38 import org.apache.pdfbox.cos.COSObject; 39 40 import org.apache.pdfbox.pdmodel.PDDocument; 41 42 import java.util.ArrayList; 43 import java.util.Collections; 44 import java.util.List; 45 46 /** 47 * A class to model a PDF document as a tree structure. 48 * 49 * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a> 50 * @version $Revision: 1.9 $ 51 */ 52 public class PDFTreeModel implements TreeModel 53 { 54 private PDDocument document; 55 56 /** 57 * constructor. 58 */ 59 public PDFTreeModel() 60 { 61 //default constructor 62 } 63 64 /** 65 * Constructor to take a document. 66 * 67 * @param doc The document to display in the tree. 68 */ 69 public PDFTreeModel(PDDocument doc) 70 { 71 setDocument(doc); 72 } 73 74 /** 75 * Set the document to display in the tree. 76 * 77 * @param doc The document to display in the tree. 78 */ 79 public void setDocument(PDDocument doc) 80 { 81 document = doc; 82 } 83 /** 84 * Adds a listener for the <code>TreeModelEvent</code> 85 * posted after the tree changes. 86 * 87 * @param l the listener to add 88 * @see #removeTreeModelListener 89 * 90 */ 91 public void addTreeModelListener(TreeModelListener l) 92 { 93 //required for interface 94 } 95 96 /** 97 * Returns the child of <code>parent</code> at index <code>index</code> 98 * in the parent's 99 * child array. <code>parent</code> must be a node previously obtained 100 * from this data source. This should not return <code>null</code> 101 * if <code>index</code> 102 * is a valid index for <code>parent</code> (that is <code>index >= 0 && 103 * index < getChildCount(parent</code>)). 104 * 105 * @param parent a node in the tree, obtained from this data source 106 * @param index The index into the parent object to location the child object. 107 * @return the child of <code>parent</code> at index <code>index</code> 108 * 109 */ 110 public Object getChild(Object parent, int index) 111 { 112 Object retval = null; 113 if( parent instanceof COSArray ) 114 { 115 ArrayEntry entry = new ArrayEntry(); 116 entry.setIndex( index ); 117 entry.setValue( ((COSArray)parent).getObject( index ) ); 118 retval = entry; 119 } 120 else if( parent instanceof COSDictionary ) 121 { 122 COSDictionary dict = ((COSDictionary)parent); 123 List<COSName> keys = new ArrayList<COSName>(dict.keySet()); 124 Collections.sort( keys ); 125 Object key = keys.get( index ); 126 Object value = dict.getDictionaryObject( (COSName)key ); 127 MapEntry entry = new MapEntry(); 128 entry.setKey( key ); 129 entry.setValue( value ); 130 retval = entry; 131 } 132 else if( parent instanceof MapEntry ) 133 { 134 retval = getChild( ((MapEntry)parent).getValue(), index ); 135 } 136 else if( parent instanceof ArrayEntry ) 137 { 138 retval = getChild( ((ArrayEntry)parent).getValue(), index ); 139 } 140 else if( parent instanceof COSDocument ) 141 { 142 retval = ((COSDocument)parent).getObjects().get( index ); 143 } 144 else if( parent instanceof COSObject ) 145 { 146 retval = ((COSObject)parent).getObject(); 147 } 148 else 149 { 150 throw new RuntimeException( "Unknown COS type " + parent.getClass().getName() ); 151 } 152 return retval; 153 } 154 155 /** Returns the number of children of <code>parent</code>. 156 * Returns 0 if the node 157 * is a leaf or if it has no children. <code>parent</code> must be a node 158 * previously obtained from this data source. 159 * 160 * @param parent a node in the tree, obtained from this data source 161 * @return the number of children of the node <code>parent</code> 162 * 163 */ 164 public int getChildCount(Object parent) 165 { 166 int retval = 0; 167 if( parent instanceof COSArray ) 168 { 169 retval = ((COSArray)parent).size(); 170 } 171 else if( parent instanceof COSDictionary ) 172 { 173 retval = ((COSDictionary)parent).size(); 174 } 175 else if( parent instanceof MapEntry ) 176 { 177 retval = getChildCount( ((MapEntry)parent).getValue() ); 178 } 179 else if( parent instanceof ArrayEntry ) 180 { 181 retval = getChildCount( ((ArrayEntry)parent).getValue() ); 182 } 183 else if( parent instanceof COSDocument ) 184 { 185 retval = ((COSDocument)parent).getObjects().size(); 186 } 187 else if( parent instanceof COSObject ) 188 { 189 retval = 1; 190 } 191 return retval; 192 } 193 194 /** Returns the index of child in parent. If <code>parent</code> 195 * is <code>null</code> or <code>child</code> is <code>null</code>, 196 * returns -1. 197 * 198 * @param parent a note in the tree, obtained from this data source 199 * @param child the node we are interested in 200 * @return the index of the child in the parent, or -1 if either 201 * <code>child</code> or <code>parent</code> are <code>null</code> 202 * 203 */ 204 public int getIndexOfChild(Object parent, Object child) 205 { 206 int retval = -1; 207 if( parent != null && child != null ) 208 { 209 if( parent instanceof COSArray ) 210 { 211 COSArray array = (COSArray)parent; 212 if( child instanceof ArrayEntry ) 213 { 214 ArrayEntry arrayEntry = (ArrayEntry)child; 215 retval = arrayEntry.getIndex(); 216 } 217 else 218 { 219 retval = array.indexOf( (COSBase)child ); 220 } 221 } 222 else if( parent instanceof COSDictionary ) 223 { 224 MapEntry entry = (MapEntry)child; 225 COSDictionary dict = (COSDictionary)parent; 226 List<COSName> keys = new ArrayList<COSName>(dict.keySet()); 227 Collections.sort( keys ); 228 for( int i=0; retval == -1 && i<keys.size(); i++ ) 229 { 230 if( keys.get( i ).equals( entry.getKey() ) ) 231 { 232 retval = i; 233 } 234 } 235 } 236 else if( parent instanceof MapEntry ) 237 { 238 retval = getIndexOfChild( ((MapEntry)parent).getValue(), child ); 239 } 240 else if( parent instanceof ArrayEntry ) 241 { 242 retval = getIndexOfChild( ((ArrayEntry)parent).getValue(), child ); 243 } 244 else if( parent instanceof COSDocument ) 245 { 246 retval = ((COSDocument)parent).getObjects().indexOf( child ); 247 } 248 else if( parent instanceof COSObject ) 249 { 250 retval = 0; 251 } 252 else 253 { 254 throw new RuntimeException( "Unknown COS type " + parent.getClass().getName() ); 255 } 256 } 257 return retval; 258 } 259 260 /** Returns the root of the tree. Returns <code>null</code> 261 * only if the tree has no nodes. 262 * 263 * @return the root of the tree 264 * 265 */ 266 public Object getRoot() 267 { 268 return document.getDocument().getTrailer(); 269 } 270 271 /** Returns <code>true</code> if <code>node</code> is a leaf. 272 * It is possible for this method to return <code>false</code> 273 * even if <code>node</code> has no children. 274 * A directory in a filesystem, for example, 275 * may contain no files; the node representing 276 * the directory is not a leaf, but it also has no children. 277 * 278 * @param node a node in the tree, obtained from this data source 279 * @return true if <code>node</code> is a leaf 280 * 281 */ 282 public boolean isLeaf(Object node) 283 { 284 boolean isLeaf = !(node instanceof COSDictionary || 285 node instanceof COSArray || 286 node instanceof COSDocument || 287 node instanceof COSObject || 288 (node instanceof MapEntry && !isLeaf(((MapEntry)node).getValue()) ) || 289 (node instanceof ArrayEntry && !isLeaf(((ArrayEntry)node).getValue()) )); 290 return isLeaf; 291 } 292 293 /** Removes a listener previously added with 294 * <code>addTreeModelListener</code>. 295 * 296 * @see #addTreeModelListener 297 * @param l the listener to remove 298 * 299 */ 300 301 public void removeTreeModelListener(TreeModelListener l) 302 { 303 //required for interface 304 } 305 306 /** Messaged when the user has altered the value for the item identified 307 * by <code>path</code> to <code>newValue</code>. 308 * If <code>newValue</code> signifies a truly new value 309 * the model should post a <code>treeNodesChanged</code> event. 310 * 311 * @param path path to the node that the user has altered 312 * @param newValue the new value from the TreeCellEditor 313 * 314 */ 315 public void valueForPathChanged(TreePath path, Object newValue) 316 { 317 //required for interface 318 } 319 }